Tag: API

  • Building an IDIRA (formerly CyberArk) CPM Plugin for a REST API: What I Learned from a Real-World Integration

    Recently, I had the opportunity to build an IDIRA (formerly CyberArk) Central Policy Manager (CPM) plugin for an application that exposes a REST API.

    The requirement sounded simple:

    Allow the CPM to verify an account password and automatically change it through the application’s REST API.

    But, as usually happens with integrations, the interesting part starts when you move from:

    “I can change the password with Postman, Bruno, or PowerShell.”

    to:

    “Now I need the CPM to understand this entire workflow.”

    While researching how to approach this, I found an excellent article by Tim Schindler about creating a CPM plugin using the REST API Framework. His article helped me understand the structure of the framework and gave me the starting point I needed to build my own implementation.

    So, first of all, credit to Tim for sharing his work with the community.

    His original article can be found here:

    https://timschindler.blog/creating-a-cyberark-central-policy-manager-plugin-for-an-api-using-the-new-rest-api-framework

    In this article, I want to take a slightly different approach.

    Instead of simply explaining the framework, I will walk through a real-world plugin I developed, with customer-specific information removed or generalized, and focus on the reasoning behind the implementation.

    The goal is not to give you something to copy and paste.

    The goal is to understand how to think about a REST API CPM plugin.


    The Scenario

    Imagine an enterprise application that manages its own user accounts and exposes REST API endpoints to:

    • authenticate a user;
    • create an authenticated session;
    • search for a user;
    • retrieve the user’s internal ID;
    • change the user’s password;
    • close the authenticated session.

    The customer wants IDIRA PAM to manage these accounts automatically.

    There is one important detail: the application allows a privileged administrative account to change another user’s password.

    This gives us two types of credentials:

    Target account — The account whose password is stored and managed by IDIRA.

    Logon account — A privileged account that can authenticate to the API and modify the target account.

    And that gives us two CPM workflows:

    VERIFY
    Target Account
    |
    v
    Authenticate
    |
    +---- Success ---> Password is valid
    |
    +---- Failure ---> Verification fails

    For Change:

    CHANGE
    Logon Account
    |
    v
    Authenticate
    |
    v
    SessionId
    |
    v
    Find Target User
    |
    v
    UserId
    |
    v
    Change Password
    |
    v
    Logout

    Once I understood and drew these workflows, designing the XML became much easier.

    Don’t start by writing XML. Start by understanding the API workflow.


    Thinking About the REST API Framework

    If you have worked with traditional CPM plugins, especially TPC plugins, you may be used to thinking about:

    Process.ini
    Prompts.ini
    PowerShell
    State Machine

    The REST API Framework changes that mental model.

    Instead of controlling an interactive process, we describe a sequence of API operations:

    Parameters
    API Calls
    Responses
    Parsed Values
    Next API Call
    Chains

    The XML becomes a description of the conversation between CPM and the target API.

    A simplified plugin structure looks like this:

    <RestPlugin>
    <Parameters>
    ...
    </Parameters>
    <ErrorMessages>
    ...
    </ErrorMessages>
    <APICalls>
    ...
    </APICalls>
    <Chains>
    ...
    </Chains>
    </RestPlugin>

    Let’s go through the important parts.


    Step 1 — Creating Reusable Parameters

    The first thing I wanted to avoid was repeating URLs throughout the plugin.

    For example:

    <Parameters>
    <Parameter
    name="AddressWithValidation"
    validationPattern="{{AddressValidationPattern}}">
    {{Address}}
    </Parameter>
    <Parameter name="BaseURL">
    {{AddressWithValidation}}
    </Parameter>
    <Parameter name="AuthURL">
    {{BaseURL}}auth/session
    </Parameter>
    <Parameter name="LogoutURL">
    {{BaseURL}}auth/logout
    </Parameter>
    </Parameters>

    Instead of repeating:

    https://application.example.com/api/...

    everywhere, requests can reuse:

    {{BaseURL}}

    This makes the plugin easier to read and maintain.

    However, while implementing this, I discovered an important limitation.

    Be Careful with Runtime-Derived Parameters

    The top-level <Parameters> section should contain values that can already be resolved when the plugin is initialized.

    For example:

    <Parameter name="BaseURL">
    {{AddressWithValidation}}
    </Parameter>

    works because AddressWithValidation is already available.

    The problem appears when a parameter depends on information that will only be obtained from a previous API call.

    During my first implementation, I tried something conceptually similar to this:

    <Parameter name="ChangePasswordURL">
    {{BaseURL}}api/entity/User/{{UserId}}
    </Parameter>

    At first glance, this looks reasonable.

    But UserId does not exist yet.

    It will only become available after another API call:

    Authenticate
    Get User
    Parse UserId
    Change Password

    Because the top-level parameter depended on a runtime value that had not yet been populated, the plugin failed during initialization.

    The solution was simple: build the URL inside the API call that actually consumes the runtime value.

    <Request name="ChangePassword" method="PUT">
    <URL>
    {{BaseURL}}api/entity/User/{{UserId}}
    </URL>
    ...
    </Request>

    By the time ChangePassword executes, the previous API call has already populated UserId.

    The rule I now follow is:

    Do not define a top-level reusable parameter that depends on a value that does not exist yet.

    Think of the values in two groups:

    Available at initialization
    ---------------------------
    Address
    BaseURL
    AuthenticationURL
    LogoutURL
    Platform parameters
    Runtime-derived
    ---------------
    SessionId
    UserId
    AccountId
    Token
    ObjectId
    ResourceId

    Runtime-derived values normally come from previous API responses and should be consumed only after the call that creates them has completed.

    Keeping these dependencies close to the API call that uses them also makes the XML much easier to troubleshoot.


    Step 2 — Implementing Verify

    Verify is the simplest operation.

    We don’t need to modify anything. We only need to prove that the username and current password stored in IDIRA can successfully authenticate to the target system.

    Conceptually:

    <Request name="LogonAsTargetAccount" method="POST">
    <URL>{{AuthURL}}</URL>
    <Headers>
    <Header key="Content-Type">application/json</Header>
    </Headers>
    <Body>
    {
    "username": "{{targetaccount\username}}",
    "password": "{{targetaccount\password|JsonEscape}}"
    }
    </Body>
    </Request>

    The important part here is:

    targetaccount\username
    targetaccount\password

    We are explicitly telling the framework to use the credentials belonging to the account being managed.

    There is another important detail:

    |JsonEscape

    Passwords are unpredictable and may contain characters that have special meaning inside JSON.

    Using:

    {{targetaccount\password|JsonEscape}}

    instead of directly inserting the password helps ensure that the value is correctly escaped when placed inside the JSON payload.

    This is particularly important to me because I have previously encountered CPM plugin problems caused by special characters being passed incorrectly between components.

    Whenever credentials move between systems, encoding and escaping deserve attention.


    Step 3 — Parsing the API Response

    Sending the request is only half of the job.

    The CPM also needs to understand what comes back.

    Imagine authentication returns:

    {
    "sessionId": "ABC123456"
    }

    We need that session for the next API calls.

    The response can therefore parse it:

    <Response
    name="LogonAsTargetAccountSuccess"
    type="valid"
    format="json"
    statusCode="200">
    <Parse>
    <ParseBody>
    <Parameter
    name="SessionId"
    path="sessionId"/>
    </ParseBody>
    </Parse>
    </Response>

    Now:

    sessionId

    from the API response becomes:

    {{SessionId}}

    inside the plugin.

    This is an important concept in multi-step REST integrations:

    The output of one API call can become the input of the next one.

    We will use exactly the same principle later with the target user’s internal ID.


    Step 4 — Error Handling Is Part of the Design

    A production plugin needs to understand failure just as well as success.

    For example:

    400 → Invalid or malformed request
    401 → Authentication problem
    403 → Authenticated, but not authorized
    404 → Target object not found
    500 → Target API/server problem
    503 → Service unavailable

    These situations should not all become:

    Plugin failed

    For example:

    <Response
    name="AuthenticationFailed"
    type="error"
    statusCode="401">
    <ErrorMessage
    name="AuthenticationFailedMessage"
    returnCode="2001"
    description="Target account authentication failed."/>
    </Response>

    In my implementation, I separated authentication failures, authorization problems, account-not-found conditions, password-change failures, server errors, and service-unavailable conditions.

    That gives the engineer troubleshooting the CPM something useful:

    Target account authentication failed.

    instead of simply:

    Operation failed.

    Good error handling isn’t just a developer feature.

    It makes the plugin supportable in production.


    Step 5 — Building the Change Workflow

    Change is more interesting because the target API does not allow us to simply say:

    Change password for service-account

    The password-change endpoint requires the application’s internal user identifier.

    Something similar to:

    ABC-123-XYZ

    Therefore, Change becomes a multi-step operation:

    1. Authenticate using the privileged Logon Account.
    2. Receive a SessionId.
    3. Search for the Target Account.
    4. Extract its internal UserId.
    5. Change the password.
    6. Logout.

    Let’s translate that into API calls.


    Step 6 — Authenticating and Finding the Target User

    For Verify, we authenticated with:

    targetaccount\username
    targetaccount\password

    For Change, we authenticate with:

    logonaccount\username
    logonaccount\password

    For example:

    "username": "{{logonaccount\username}}",
    "password": "{{logonaccount\password|JsonEscape}}"

    The successful response gives us the SessionId.

    Now we can search for the target account:

    <Request name="GetUser" method="GET">
    <URL>
    {{BaseURL}}api/users?username={{targetaccount\username|urlEncode}}
    </URL>
    <Headers>
    <Header key="Content-Type">application/json</Header>
    <Header key="Cookie">
    session={{SessionId}}
    </Header>
    </Headers>
    </Request>

    Notice another modifier:

    |urlEncode

    The username becomes part of a URL, so it should be properly encoded instead of being blindly concatenated into the request.

    Imagine the API returns:

    [
    {
    "values": {
    "UserId": "ABC-123-XYZ",
    "Username": "service-account"
    }
    }
    ]

    We can extract the ID:

    <Parse>
    <ParseBody>
    <Parameter
    name="UserId"
    path="[0].values.UserId"/>
    </ParseBody>
    </Parse>

    At this point, the plugin has everything required for the password change:

    SessionId
    UserId
    NewPassword

    Step 7 — Changing the Password

    Now we can call the password-change endpoint:

    <Request name="ChangePassword" method="PUT">
    <URL>
    {{BaseURL}}api/users/{{UserId}}
    </URL>
    <Headers>
    <Header key="Content-Type">application/json</Header>
    <Header key="Cookie">
    session={{SessionId}}
    </Header>
    </Headers>
    <Body>
    {
    "values": {
    "Password": "{{newpassword|JsonEscape}}"
    }
    }
    </Body>
    </Request>

    There are three important pieces here:

    {{UserId}}
    {{SessionId}}
    {{newpassword|JsonEscape}}

    UserId came from the previous user-search request.

    SessionId came from authentication.

    newpassword is the new password generated for the account and is safely escaped for the JSON payload.

    This is the core of the Change operation.


    Step 8 — Don’t Forget the Logout

    If the target API creates a server-side session, I don’t want the CPM repeatedly authenticating and leaving unnecessary sessions behind.

    So the workflow also includes a logout operation:

    <Request name="Logoff" method="POST">
    <URL>{{LogoutURL}}</URL>
    <Headers>
    <Header key="Content-Type">application/json</Header>
    <Header key="Cookie">
    session={{SessionId}}
    </Header>
    </Headers>
    </Request>

    Session cleanup may look like a small detail, but it should be considered part of the integration design.


    Step 9 — Chains: Putting Everything Together

    We now have the individual API calls.

    The Chains section defines which calls belong to each CPM operation and the order in which they should execute.

    For Verify:

    <Chain name="verifypass">
    <Request name="LogonAsTargetAccount"/>
    <Request name="Logoff"/>
    </Chain>

    For Change:

    <Chain name="changepass">
    <Request name="LogonAsLogonAccount"/>
    <Request name="GetUser"/>
    <Request name="ChangePassword"/>
    <Request name="Logoff"/>
    </Chain>

    And this is where the entire implementation becomes easy to visualize:

                     IDIRA CPM
                         |
              +----------+----------+
              |                     |
           VERIFY                 CHANGE
              |                     |
              v                     v
     Target Credentials       Logon Credentials
              |                     |
              v                     v
         Authenticate           Authenticate
              |                     |
              v                     v
          SessionId              SessionId
              |                     |
              |                     v
              |                 Find User
              |                     |
              |                     v
              |                   UserId
              |                     |
              |                     v
              |               Change Password
              |                     |
              +----------+----------+
                         |
                         v
                       Logout
                         |
                         v
                     CPM Result

    The XML is essentially the implementation of this workflow.


    Practical Lessons I Learned

    Beyond the XML itself, this project taught me a few things that I will reuse in future CPM integrations.

    1. Design the API workflow before writing the plugin

    Before touching the XML, I want to answer:

    • How does authentication work?
    • Which account performs each operation?
    • Does authentication return a token or session?
    • How do I identify the target account?
    • What does the password-change endpoint require?
    • Which values must move between API calls?
    • How is the session terminated?
    • Which failures should CPM understand?

    If I cannot clearly draw the workflow, I’m probably not ready to build the plugin.

    2. Test the API before testing the CPM plugin

    This is probably my favorite troubleshooting rule from this project:

    Don’t troubleshoot the API and the CPM plugin at the same time.

    Before building the XML, validate the individual operations with a tool such as:

    Bruno
    Postman
    PowerShell
    curl

    Make sure authentication works.

    Make sure you can find the user.

    Make sure the password-change request works.

    Make sure logout works.

    Otherwise, you may end up troubleshooting:

    CPM + XML + Authentication + API + JSON + Permissions

    all at once.

    If the API calls have already been independently validated, the troubleshooting scope becomes much smaller.

    3. Pay attention to when values become available

    This was one of the most useful lessons from the implementation.

    A value such as:

    BaseURL

    can exist before the chain starts.

    A value such as:

    UserId

    may only exist after a specific API response has been parsed.

    Understanding that difference prevents initialization problems and makes the relationship between API calls much clearer.

    4. Treat error handling as part of the plugin architecture

    Don’t design only for HTTP 200.

    Think about what the engineer supporting the platform needs to know when something goes wrong.

    A good plugin should help distinguish between:

    Wrong credentials
    Unauthorized operation
    Target account not found
    Password rejected
    Target API failure
    Service unavailable

    That information can save a lot of troubleshooting time later.


    Something I Would Improve Further

    One area I would continue improving is API error reporting.

    Several API responses provide information similar to:

    ErrorCode
    ErrorMessage

    which can be parsed:

    <Parse>
    <ParseBody>
    <Parameter
    name="ErrorCode"
    path="ErrorCode"/>
    <Parameter
    name="ErrorMessage"
    path="ErrorMessage"/>
    </ParseBody>
    </Parse>

    A future improvement could be to expose carefully selected information from those API responses in CPM errors where appropriate.

    However, I would do this carefully.

    API responses may contain internal or sensitive information that should not automatically appear in operational logs.

    The goal should be:

    Useful troubleshooting information
    +
    No credentials or secrets
    +
    No unnecessary sensitive API details

    Logging should help troubleshoot the plugin without creating another security problem.


    What About Reconcile?

    You may have noticed that this implementation focuses on:

    verifypass
    changepass

    The plugin used for this article does not implement a reconcilepass chain.

    A complete CPM platform may also require reconciliation depending on the use case, but I don’t want to present something as implemented when it wasn’t part of this project.

    That leads to another rule I try to follow:

    Document what you actually built and tested, and clearly separate that from possible future designs.


    REST API Framework vs. TPC + PowerShell

    I’ve also worked with CPM integrations where a TPC plugin launches PowerShell and the script handles the REST API communication.

    That architecture may look like:

    CPM
    |
    v
    TPC
    |
    +--> Process.ini
    |
    +--> Prompts.ini
    |
    +--> PowerShell
    |
    v
    REST API

    With the REST API Framework, the architecture can be more direct:

    CPM
    |
    v
    REST API Plugin Framework
    |
    v
    Target REST API

    That does not mean PowerShell-based plugins are wrong.

    Sometimes the integration requires complex logic or transformations that justify additional scripting.

    But if the requirement is fundamentally:

    HTTP Request
    Parse Response
    HTTP Request
    Parse Response

    a framework designed specifically for REST APIs can provide a cleaner implementation.


    Final Thoughts

    Building this plugin changed the way I think about API-based CPM integrations.

    At first, the XML can look intimidating:

    Requests
    Responses
    Parameters
    Parsing
    ErrorMessages
    Chains

    But the XML is not really the starting point.

    The workflow is.

    My approach now is:

    1. Understand the target API.
    2. Test every API operation independently.
    3. Draw the authentication and password-management workflow.
    4. Identify which values are available at initialization and which are created at runtime.
    5. Translate each HTTP operation into an API call.
    6. Parse only the values required by later requests.
    7. Handle expected API failures explicitly.
    8. Connect the requests using CPM chains.
    9. Test Verify and Change independently.
    10. Only then move toward production hardening.

    If I had to summarize the biggest lesson from this project in one sentence, it would be:

    A good REST API CPM plugin starts as a good API workflow, not as a good XML file.

    Once the workflow is clear, the XML becomes a way of describing it.


    Credits and References

    A big thank you to Tim Schindler, whose article gave me the initial direction for working with the REST API Framework.

    https://timschindler.blog/creating-a-cyberark-central-policy-manager-plugin-for-an-api-using-the-new-rest-api-framework

    His article walks through building a CPM plugin using the REST API Framework and was an important reference while I was learning how to structure my own implementation.

    The example presented in this article is based on a real-world integration I developed, but customer-specific names, endpoints, identifiers, and other implementation details have intentionally been removed or generalized.

    IDIRA was formerly known as CyberArk. Some product names, components, documentation, binaries, and configuration parameters may still use the CyberArk name.