<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Tim Schindler's Blog]]></title><description><![CDATA[A CyberArk Community Most Valuable Professional (MVP), CyberArk Guardian, CDE-PAM, CDE-CPC, CDE-Access, CDE-EPM, and CDE-CERT-SAAS.]]></description><link>https://timschindler.blog</link><generator>RSS for Node</generator><lastBuildDate>Mon, 20 Apr 2026 07:09:16 GMT</lastBuildDate><atom:link href="https://timschindler.blog/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Creating a CyberArk Central Policy Manager plugin for an API using the new REST API framework]]></title><description><![CDATA[CyberArk has released their new framework for creating Central Policy Manager (CPM) plugins that interact with REST APIs. Unlike WSChains, this framework is documented and supported by CyberArk, making it worthwhile to become familiar with it. CyberA...]]></description><link>https://timschindler.blog/creating-a-cyberark-central-policy-manager-plugin-for-an-api-using-the-new-rest-api-framework</link><guid isPermaLink="true">https://timschindler.blog/creating-a-cyberark-central-policy-manager-plugin-for-an-api-using-the-new-rest-api-framework</guid><category><![CDATA[cyberark]]></category><category><![CDATA[#cybersecurity]]></category><category><![CDATA[xml]]></category><category><![CDATA[central-policy-manager]]></category><category><![CDATA[REST API]]></category><dc:creator><![CDATA[Tim Schindler]]></dc:creator><pubDate>Mon, 17 Mar 2025 17:55:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/BfrQnKBulYQ/upload/4fa5585b00e0aa7ab24fdebc08a39e12.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>CyberArk has released their <a target="_blank" href="https://community.cyberark.com/marketplace/s/#a35Vy000000Lj3lIAC-a39Vy000002WA1VIAW">new framework for creating Central Policy Manager (CPM) plugins that interact with REST APIs</a>. Unlike <a target="_blank" href="https://timschindler.blog/exploring-cyberark-central-policy-managers-wschains">WSChains</a>, this framework is documented and supported by CyberArk, making it worthwhile to become familiar with it. CyberArk is already using it, as it is the framework on which the <a target="_blank" href="https://community.cyberark.com/marketplace/s/#a35Ht0000018s2cIAA-a39Ht000004GLKPIA4">new CyberArk Digital Vault platform</a> is built, among others.</p>
<p>Additionally, it offers an alternative to the Credential Management .NET SDK for storing credentials generated by target systems (or “access keys”), which is becoming increasingly common.</p>
<p>To complete our <a target="_blank" href="https://timschindler.blog/series/shopizer-admin-ext">CyberArk Shopizer Administrator extensions series</a>, we will use it to build a CPM plugin that manages Shopizer users through the Shopizer API.</p>
<h1 id="heading-developing-the-rest-api-plugin">Developing the REST API plugin</h1>
<p>The <a target="_blank" href="https://docs.cyberark.com/pam-self-hosted/latest/en/content/plugins/cpm-plugins-rest-api.htm">documentation for the REST API framework</a> is quite comprehensive so there is no need to repeat it. After giving it a read-through and ensuring we’ve met the <a target="_blank" href="https://docs.cyberark.com/pam-self-hosted/latest/en/content/plugins/cpm-plugins-rest-api.htm?tocpath=Developer%7CCreate%20extensions%7CCreate%20CPM%20plugins%7CCreate%20CPM%20plugins%20for%20REST%20API%20applications%7C_____0#Prerequisites">prerequisites for the framework</a>, we can jump in.</p>
<h2 id="heading-creating-a-new-platform">Creating a new platform</h2>
<p>After we <a target="_blank" href="https://docs.cyberark.com/pam-self-hosted/latest/en/content/plugins/cpm-plugins-rest-api.htm?tocpath=Developer%7CCreate%20extensions%7CCreate%20CPM%20plugins%7CCreate%20CPM%20plugins%20for%20REST%20API%20applications%7C_____0#CreateyournewplatformbasedontheRESTAPICPMPluginFramework">import the REST API framework CPM plugin</a> all that needs to be done is cloning it for use with our Shopizer platform and under <code>Additional Policy Settings</code> updating two parameters:</p>
<ol>
<li><p>Defining <code>8080</code> as the value for <code>Port</code></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742231581187/e869e7a8-b8ba-4982-8f41-405c362fbad0.png" alt="Screenshot of a software interface showing menu options on the left, including &quot;Automatic Password Management&quot; and &quot;Additional Policy Settings.&quot; On the right, a &quot;Properties&quot; table lists different settings with values, such as &quot;Port&quot; set to &quot;8080&quot; and &quot;UseSSL&quot; set to &quot;Yes.&quot;" class="image--center mx-auto" /></p>
</li>
</ol>
<ol start="2">
<li>Changing the value for <code>ConfigurationFilePath</code> under to the filename that will contain the Shopizer configuration: <code>ShopizerAdministratorConfiguration.xml</code></li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741625148660/18670746-09ce-4a4b-8225-e5f314dffef5.png" alt="Screenshot of a software interface showing the &quot;TimSchindler Shopizer Administrator Accounts via REST API.&quot; The left panel lists various account management options, including parameters like &quot;ConfigurationFilePath.&quot; The right panel displays properties with the &quot;Name&quot; and &quot;Value&quot; set as &quot;ConfigurationFilePath&quot; and &quot;ShopizerAdministratorConfiguration.xml.&quot;" class="image--center mx-auto" /></p>
<h2 id="heading-creating-the-parameters-file-for-testing">Creating the parameters file for testing</h2>
<p>When developing the CPM plugin, we will test it by invoking it manually. Our <code>.ini</code> will be similar to <a target="_blank" href="https://timschindler.blog/creating-a-cyberark-central-policy-manager-plugin-for-an-api-using-wschains#heading-preparing-our-parameters-file-for-testing">what we used for WSChains</a> with slight modifications:</p>
<pre><code class="lang-ini"><span class="hljs-section">[targetaccount]</span>
<span class="hljs-attr">username</span>=shopizeradministrator@timschindler.dev
<span class="hljs-attr">newpassword</span>=Password2
<span class="hljs-attr">password</span>=Password2
<span class="hljs-attr">safename</span>=Safename
<span class="hljs-attr">foldername</span>=Foldername
<span class="hljs-attr">objectname</span>=Objectname
<span class="hljs-attr">PolicyID</span>=PolicyID
<span class="hljs-comment">; The machine where the Shopizer instance is running.</span>
<span class="hljs-attr">Address</span>=<span class="hljs-number">192.168</span>.<span class="hljs-number">178.61</span>
<span class="hljs-comment">; The port the Shopizer REST API is running on.</span>
<span class="hljs-attr">Port</span>=<span class="hljs-number">8080</span>

<span class="hljs-section">[extrapass3]</span>
<span class="hljs-attr">username</span>=reconcile@timschindler.dev
<span class="hljs-attr">password</span>=Password1

<span class="hljs-section">[extrainfo]</span>
<span class="hljs-comment">; Path to the configuration file.</span>
<span class="hljs-attr">ConfigurationFilePath</span>=ShopizerAdministratorConfiguration.xml
<span class="hljs-attr">AddressValidationPattern</span>=.*
<span class="hljs-attr">Debug</span>=<span class="hljs-literal">Yes</span>
<span class="hljs-attr">ManagementType</span>=password
</code></pre>
<p>After adding <code>ConfigurationFilePath</code> and <code>AddressValidationPattern</code> we can invoke it from the root of the Central Policy Manager’s installation folder with <code>.\bin\CANetPluginInvoker.exe shopizerrestplugin.ini &lt;verifypass/changepass/reconcilepass&gt; CyberArk.Extensions.Generic.Plugin.RestAPI.dll True</code></p>
<h1 id="heading-writing-the-configuration-file">Writing the configuration file</h1>
<p>Luckily, we've already done most of the hard work when developing the WSChains plug-in. We know the necessary API calls for each operation, their URLs, and the request and possible response bodies. We just need to format the data to match what the REST API framework expects. We can refer to the <a target="_blank" href="https://app.swaggerhub.com/apis/shopizer/shopizer-rest-api">Shopizer API documentation</a> as needed.</p>
<p>To begin, we copy the sample configuration file provided with the framework, <code>SampleRestAPIPlatformConfiguration.xml</code>, and rename it to <code>ShopizerAdministratorConfiguration.xml</code>.</p>
<h2 id="heading-verify">Verify</h2>
<p>We’ll leverage the Parameters section for the sake of cleaner code, removing almost everything but opting to leave <code>AddressWithValidation</code> as it is.</p>
<p>We add the parameter <code>AddressWithValidationAndPort</code> that combines the <code>address</code> (host) and <code>port</code> properties along with the scheme, which is hard-coded to be <code>http</code> as the Shopizer instance we are developing against does not have a HTTPS listener.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Parameters</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"AddressWithValidation"</span> <span class="hljs-attr">validationPattern</span>=<span class="hljs-string">"{{AddressValidationPattern}}"</span>&gt;</span>{{Address}}<span class="hljs-tag">&lt;/<span class="hljs-name">Parameter</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"AddressWithValidationAndPort"</span>&gt;</span>http://{{AddressWithValidation}}:{{Port}}<span class="hljs-tag">&lt;/<span class="hljs-name">Parameter</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Parameters</span>&gt;</span>
</code></pre>
<p>We only need a single <code>APICall</code> in the <code>APICalls</code> section:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">APICall</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Request</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"LogonAsTargetAccount"</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"POST"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">URL</span>&gt;</span>{{AddressWithValidationAndPort}}/api/v1/private/login<span class="hljs-tag">&lt;/<span class="hljs-name">URL</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Headers</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Header</span> <span class="hljs-attr">key</span>=<span class="hljs-string">"Content-Type"</span>&gt;</span>application/json<span class="hljs-tag">&lt;/<span class="hljs-name">Header</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Headers</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Body</span>&gt;</span>
                    {
                    "username": "{{Username}}",
                    "password": "{{Password|JsonEscape}}"
                    }
        <span class="hljs-tag">&lt;/<span class="hljs-name">Body</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Request</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Responses</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Response</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"SuccessfulLogonResponse"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"valid"</span> <span class="hljs-attr">format</span>=<span class="hljs-string">"json"</span> <span class="hljs-attr">statusCode</span>=<span class="hljs-string">"200"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Parse</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">ParseBody</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"id"</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"id"</span> /&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"token"</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"token"</span> <span class="hljs-attr">secure</span>=<span class="hljs-string">"true"</span>/&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">ParseBody</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">Parse</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Response</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Response</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"IncorrectPassword"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"error"</span> <span class="hljs-attr">statusCode</span>=<span class="hljs-string">"401"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Parse</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">ParseBody</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"ErrorMessage"</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"message"</span>/&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">ParseBody</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">Parse</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">ErrorMessage</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"IncorrectPasswordMsg"</span> <span class="hljs-attr">returnCode</span>=<span class="hljs-string">"2001"</span> <span class="hljs-attr">description</span>=<span class="hljs-string">"{{ErrorMessage}}"</span>/&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Response</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Responses</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">APICall</span>&gt;</span>
</code></pre>
<p>The <code>Request</code> element does not have any magic to it. It is exactly what we would see if we were making the call using a tool such as cURL or PowerShell’s <code>Invoke-RestMethod</code>. The only part that is specific to the REST API framework is how we can JSON escape placeholders using <a target="_blank" href="https://docs.cyberark.com/pam-self-hosted/latest/en/content/plugins/cpm-plugins-rest-api-placeholders.htm#Placeholdermodifiers">placeholder modifiers</a>.</p>
<p>We have two <code>Response</code> child elements in the <code>Responses</code> element: one representing a successful logon response and a failed logon due to an incorrect password. In the successful logon response, we parse out the returned <code>id</code> and <code>token</code> as we can reuse the <code>Response</code> in other <code>APICall</code> elements. In the case of a failed login, we parse out the message so it can be displayed in the PVWA.</p>
<p>The last piece is defining the <code>Request</code> name in the <code>verifypass</code> chain in the <code>Chains</code> element. With that, our <code>ShopizerAdministratorConfiguration.xml</code> reads as below:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">RestPlugin</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">Parameters</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"AddressWithValidation"</span> <span class="hljs-attr">validationPattern</span>=<span class="hljs-string">"{{AddressValidationPattern}}"</span>&gt;</span>{{Address}}<span class="hljs-tag">&lt;/<span class="hljs-name">Parameter</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"AddressWithValidationAndPort"</span>&gt;</span>http://{{AddressWithValidation}}:{{Port}}<span class="hljs-tag">&lt;/<span class="hljs-name">Parameter</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Parameters</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">ErrorMessages</span>/&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">APICalls</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">APICall</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Request</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"LogonAsTargetAccount"</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"POST"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">URL</span>&gt;</span>{{AddressWithValidationAndPort}}/api/v1/private/login<span class="hljs-tag">&lt;/<span class="hljs-name">URL</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Headers</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">Header</span> <span class="hljs-attr">key</span>=<span class="hljs-string">"Content-Type"</span>&gt;</span>application/json<span class="hljs-tag">&lt;/<span class="hljs-name">Header</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">Headers</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Body</span>&gt;</span>
                    {
                    "username": "{{Username}}",
                    "password": "{{Password|JsonEscape}}"
                    }
                <span class="hljs-tag">&lt;/<span class="hljs-name">Body</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">Request</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Responses</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Response</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"SuccessfulLogonResponse"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"valid"</span> <span class="hljs-attr">format</span>=<span class="hljs-string">"json"</span> <span class="hljs-attr">statusCode</span>=<span class="hljs-string">"200"</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">Parse</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">ParseBody</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"id"</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"id"</span> /&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"token"</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"token"</span> <span class="hljs-attr">secure</span>=<span class="hljs-string">"true"</span>/&gt;</span>
                        <span class="hljs-tag">&lt;/<span class="hljs-name">ParseBody</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">Parse</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">Response</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Response</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"IncorrectPassword"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"error"</span> <span class="hljs-attr">statusCode</span>=<span class="hljs-string">"401"</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">Parse</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">ParseBody</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"ErrorMessage"</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"message"</span>/&gt;</span>
                        <span class="hljs-tag">&lt;/<span class="hljs-name">ParseBody</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">Parse</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">ErrorMessage</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"IncorrectPasswordMsg"</span> <span class="hljs-attr">returnCode</span>=<span class="hljs-string">"2001"</span> <span class="hljs-attr">description</span>=<span class="hljs-string">"{{ErrorMessage}}"</span>/&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">Response</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">Responses</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">APICall</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">APICalls</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">Chains</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Chain</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"verifypass"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Request</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"LogonAsTargetAccount"</span> /&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Chain</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Chains</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">RestPlugin</span>&gt;</span>
</code></pre>
<p>Even though we are not using <code>ErrorMessages</code>, we need to at least define <code>&lt;ErrorMessages/&gt;</code> as the REST API framework expects the element to be there.</p>
<p>Testing the plugin, we see positive progress:</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">PS</span> &gt; .\bin\CANetPluginInvoker.exe shopizerrestplugin.ini verifypass CyberArk.Extensions.Generic.Plugin.RestAPI.dll True
The plugin ended successfully (RC = <span class="hljs-number">0</span>)
<span class="hljs-built_in">PS</span> &gt;
</code></pre>
<h2 id="heading-change">Change</h2>
<p>We need one more <code>APICall</code> that is used to change the password.</p>
<p>The additional <code>APICall</code> in the <code>APICalls</code> element for our change operation is:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">APICall</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Request</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"ChangeAsTargetAccount"</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"PATCH"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">URL</span>&gt;</span>{{AddressWithValidationAndPort}}/api/v1/private/user/{{id}}/password<span class="hljs-tag">&lt;/<span class="hljs-name">URL</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Headers</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Header</span> <span class="hljs-attr">key</span>=<span class="hljs-string">"Content-Type"</span>&gt;</span>application/json<span class="hljs-tag">&lt;/<span class="hljs-name">Header</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Header</span> <span class="hljs-attr">key</span>=<span class="hljs-string">"Authorization"</span>&gt;</span>Bearer {{token}}<span class="hljs-tag">&lt;/<span class="hljs-name">Header</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Headers</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Body</span>&gt;</span>
                    {
                    "password": "{{password|JsonEscape}}",
                    "changePassword":"{{newpassword|JsonEscape}}"
                    }
        <span class="hljs-tag">&lt;/<span class="hljs-name">Body</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Request</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Responses</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Response</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"ValidChangeResponse"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"valid"</span> <span class="hljs-attr">statusCode</span>=<span class="hljs-string">"200"</span>/&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Response</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"InternalErrorResponse"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"error"</span> <span class="hljs-attr">statusCode</span>=<span class="hljs-string">"500"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Parse</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">ParseBody</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"ErrorMessage"</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"error"</span>/&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">ParseBody</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">Parse</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">ErrorMessage</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"InternalErrorResponseMsg"</span> <span class="hljs-attr">returnCode</span>=<span class="hljs-string">"2002"</span> <span class="hljs-attr">description</span>=<span class="hljs-string">"{{ErrorMessage}}"</span>/&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Response</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Responses</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">APICall</span>&gt;</span>
</code></pre>
<p>The <code>Request</code> element is simple. We include the Bearer token from the <code>LogonAsTargetAccount</code> Request as an <code>Authorization</code> header.</p>
<p>A successful password change gives a status code <code>200</code> with no extra details to process. We have a response for when the API returns a <code>500</code> — this is the only code the Shopizer API seems to give when the new password doesn't meet complexity requirements.</p>
<p>Adding the below <code>Chain</code> element to <code>Chains</code> and a successful test has us moving on to the reconcile operation.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Chain</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"changepass"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Request</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"LogonAsTargetAccount"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Request</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"ChangeAsTargetAccount"</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Chain</span>&gt;</span>
</code></pre>
<pre><code class="lang-powershell"><span class="hljs-built_in">PS</span> &gt; .\bin\CANetPluginInvoker.exe shopizerrestplugin.ini changepass CyberArk.Extensions.Generic.Plugin.RestAPI.dll True
The plugin ended successfully (RC = <span class="hljs-number">0</span>)
<span class="hljs-built_in">PS</span> &gt;
</code></pre>
<h2 id="heading-reconcile">Reconcile</h2>
<p>Compared to the verify and change operation, reconcile is more complicated. Our reconcile operation needs to:</p>
<ol>
<li><p>Log in as the reconciliation account.</p>
</li>
<li><p>Get the details of the target account.</p>
</li>
<li><p>Set the new password for the target account, using the details from step 2.</p>
</li>
</ol>
<p>We can create a new <code>APICall</code> element just for logging in as the reconciliation account, but since the request and responses would be the same as our current <code>APICall</code> for logging in as the target account during the verify operation, this would lead to a lot of repetition.</p>
<p>By using <a target="_blank" href="https://docs.cyberark.com/pam-self-hosted/latest/en/content/plugins/cpm-plugins-rest-api-placeholders.htm#Supportedimplementations">dynamic account types for placeholders</a>, we can modify the existing <code>APICall</code> to work for logging in as either the target account or the reconciliation account.</p>
<p>The <code>APICall</code> element is updated to:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">APICall</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Request</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"Logon"</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"POST"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">URL</span>&gt;</span>{{AddressWithValidationAndPort}}/api/v1/private/login<span class="hljs-tag">&lt;/<span class="hljs-name">URL</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Headers</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Header</span> <span class="hljs-attr">key</span>=<span class="hljs-string">"Content-Type"</span>&gt;</span>application/json<span class="hljs-tag">&lt;/<span class="hljs-name">Header</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Headers</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Body</span>&gt;</span>
                    {
                    "username": "{{reconcileaccountORtargetaccount\username}}",
                    "password": "{{reconcileaccountORtargetaccount\password|JsonEscape}}"
                    }
                <span class="hljs-tag">&lt;/<span class="hljs-name">Body</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Request</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Responses</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Response</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"SuccessfulLogonResponse"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"valid"</span> <span class="hljs-attr">format</span>=<span class="hljs-string">"json"</span> <span class="hljs-attr">statusCode</span>=<span class="hljs-string">"200"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Parse</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">ParseBody</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"id"</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"id"</span> /&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"token"</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"token"</span> <span class="hljs-attr">secure</span>=<span class="hljs-string">"true"</span>/&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">ParseBody</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">Parse</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Response</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Response</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"IncorrectPassword"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"error"</span> <span class="hljs-attr">statusCode</span>=<span class="hljs-string">"401"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Parse</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">ParseBody</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"ErrorMessage"</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"message"</span>/&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">ParseBody</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">Parse</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">ErrorMessage</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"IncorrectPasswordMsg"</span> <span class="hljs-attr">returnCode</span>=<span class="hljs-string">"2001"</span> <span class="hljs-attr">description</span>=<span class="hljs-string">"{{ErrorMessage}}"</span>/&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Response</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Responses</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">APICall</span>&gt;</span>
</code></pre>
<p>Besides updating the <code>Body</code> element of the <code>Request</code>, we change the <code>name</code> attribute to <code>Logon</code> to show that it's not used for only logging in as the target account anymore. <mark>Be sure to update the </mark> <code>verifypass</code> <mark> and </mark> <code>changepass</code> <mark> chains with the new name of the </mark> <code>Request</code> <mark> element!</mark></p>
<p>How do dynamic account type placeholders work? The documentation states:</p>
<blockquote>
<p>The plugin searches for the ParameterName in the accounts declared from left to right and takes the first occurrence it finds. If the parameter is not found in any account, the replacement fails. For example, {{logonaccountORextrapass2ORtargetaccount\username}} tries to retrieve the username in the logon account. If the plugin cannot find it, it tries to retrieve the username from the extrapass2 account. If not found there as well, the plugin tries to retrieve the username from the target account. If the username parameter is not found in any of the declared accounts, the plugin fails.</p>
</blockquote>
<p>The CPM only gives the reconciliation account's details during a reconcile operation. Since we list <code>reconcileaccount</code> first in the dynamic type placeholder, the plugin uses the reconcile account details for the Logon <code>Request</code> during the reconcile operation and uses the target account details for verify and change operations.</p>
<p>We still need the <code>APICall</code> elements for retrieving the target account’s details and the actual password change.</p>
<p>To retrieve the target account’s details needed as part of the password change process:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">APICall</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Request</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"GetUsersInformation"</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"GET"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">URL</span>&gt;</span>{{AddressWithValidationAndPort}}/api/v1/private/users<span class="hljs-tag">&lt;/<span class="hljs-name">URL</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Headers</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Header</span> <span class="hljs-attr">key</span>=<span class="hljs-string">"Content-Type"</span>&gt;</span>application/json<span class="hljs-tag">&lt;/<span class="hljs-name">Header</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Header</span> <span class="hljs-attr">key</span>=<span class="hljs-string">"Authorization"</span>&gt;</span>Bearer {{token}}<span class="hljs-tag">&lt;/<span class="hljs-name">Header</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Headers</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Body</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">Body</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Request</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Responses</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Response</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"ValidGetUsersInformationResponse"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"valid"</span> <span class="hljs-attr">format</span>=<span class="hljs-string">"json"</span> <span class="hljs-attr">statusCode</span>=<span class="hljs-string">"200"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Parse</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">ParseBody</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"targetaccount\id"</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"..data[?(@.userName=='{{username}}')].id"</span>/&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"targetaccount\active"</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"..data[?(@.userName=='{{username}}')].active"</span>/&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"targetaccount\defaultLanguage"</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"..data[?(@.userName=='{{username}}')].defaultLanguage"</span>/&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"targetaccount\emailAddress"</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"..data[?(@.userName=='{{username}}')].emailAddress"</span>/&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"targetaccount\firstName"</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"..data[?(@.userName=='{{username}}')].firstName"</span>/&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"targetaccount\groups"</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"..data[?(@.userName=='{{username}}')].groups"</span>/&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"targetaccount\lastName"</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"..data[?(@.userName=='{{username}}')].lastName"</span>/&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"targetaccount\merchant"</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"..data[?(@.userName=='{{username}}')].merchant"</span>/&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"targetaccount\username"</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"..data[?(@.userName=='{{username}}')].userName"</span>/&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">ParseBody</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">Parse</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Response</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Response</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"InternalErrorResponse"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Responses</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">APICall</span>&gt;</span>
</code></pre>
<p>The only thing we are doing for the first time is parsing the response using <a target="_blank" href="https://en.wikipedia.org/wiki/JSONPath">JSONPath</a>.</p>
<p>We used XPath before with the <a target="_blank" href="https://timschindler.blog/creating-a-cyberark-privileged-session-manager-connection-component-for-a-web-application#heading-identifying-elements-in-the-dom">Shopizer PSM connection component</a>, and JSONPath works in a similar way. We use it to extract user details from the response. Since the <code>APICall</code> element calls a URL that returns all Shopizer users, we use JSONPath to find the information we need for the target account, using the <code>username</code> placeholder in the expression.</p>
<p>In the final <code>APICall</code> element, we include the new password along with the details extracted from the previous <code>APICall</code>:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">APICall</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Request</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"ChangeAsReconcileAccount"</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"PUT"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">URL</span>&gt;</span>{{AddressWithValidationAndPort}}/api/v1/private/user/{{targetaccount\id}}<span class="hljs-tag">&lt;/<span class="hljs-name">URL</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Headers</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Header</span> <span class="hljs-attr">key</span>=<span class="hljs-string">"Content-Type"</span>&gt;</span>application/json<span class="hljs-tag">&lt;/<span class="hljs-name">Header</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Header</span> <span class="hljs-attr">key</span>=<span class="hljs-string">"Authorization"</span>&gt;</span>Bearer {{token}}<span class="hljs-tag">&lt;/<span class="hljs-name">Header</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Headers</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Body</span>&gt;</span>
                    {
                    "active": {{targetaccount\active}},
                    "defaultLanguage": "{{targetaccount\defaultLanguage}}",
                    "firstName": "{{targetaccount\firstName}}",
                    "groups:": {{targetaccount\groups}},
                    "lastName": "{{targetaccount\lastName}}",
                    "password": "{{newpassword}}",
                    "repeatPassword": "{{newpassword}}",
                    "store": "{{targetaccount\merchant}}",
                    "emailAddress": "{{targetaccount\emailAddress}}",
                    "userName": "{{targetaccount\username}}"            
                    }
                <span class="hljs-tag">&lt;/<span class="hljs-name">Body</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Request</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Responses</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Response</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"ValidReconcileChangePasswordResponse"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"valid"</span> <span class="hljs-attr">statusCode</span>=<span class="hljs-string">"200"</span>/&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Response</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"InternalErrorResponse"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Responses</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">APICall</span>&gt;</span>
</code></pre>
<p>Finally, our <code>Chains</code> element looks like:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Chains</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Chain</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"verifypass"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Request</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"Logon"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Chain</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Chain</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"changepass"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Request</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"Logon"</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Request</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"ChangeAsTargetAccount"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Chain</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Chain</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"reconcilepass"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Request</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"Logon"</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Request</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"GetUsersInformation"</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Request</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"ChangeAsReconcileAccount"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Chain</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Chains</span>&gt;</span>
</code></pre>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">You can find the complete platform, including the configuration constructed above, in <a target="_self" href="https://github.com/aaearon/cyberark-shopizer-admin-extensions/blob/main/restapi/ShopizerAdministratorConfiguration.xml">GitHub</a>.</div>
</div>

<p>In the end, what matters is that the plugin works when invoked by the CPM so we would be remiss not to ensure it works as we expect. Invoking the reconcile in the PVWA versus invoking it manually we see success:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742231701332/3c3eccab-cffe-48d0-9dbe-5774fa06c03a.png" alt="The image shows an account overview interface for a user at &quot;shopizeradministrator@timschindler.dev&quot; on IP address 192.168.0.4. It indicates compliance status as compliant, with a reconciliation done by PasswordManager. The status was checked 0 days ago, and there are buttons for reconciling and changing settings. The latest activity is a password reconcile action by PasswordManager performed on March 17 at 6:12:21 PM." class="image--center mx-auto" /></p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>Creating a CyberArk Central Policy Manager plugin using the new REST API framework offers a robust and supported method for managing credentials through REST APIs. This approach not only aligns with CyberArk's latest platform developments but also provides a documented alternative to older methods like WSChains.</p>
<p>By following the documentation and leveraging the flexibility of the REST API framework, we can efficiently build plugins tailored to specific applications, such as managing Shopizer administrator users.</p>
<p>See the below GitHub repository for all the platform files.</p>
<p><a target="_blank" href="https://github.com/aaearon/cyberark-shopizer-admin-extensions/tree/main/restapi">https://github.com/aaearon/cyberark-shopizer-admin-extensions</a></p>
]]></content:encoded></item><item><title><![CDATA[Creating a CyberArk Central Policy Manager plugin for an API using WSChains]]></title><description><![CDATA[In a previous article, we created a CyberArk Central Policy Manager plugin for the web application Shopizer using the Web Application CPM Plugin Framework and while it works, we had to employ an awkward workaround to get reconcile functioning. As Sho...]]></description><link>https://timschindler.blog/creating-a-cyberark-central-policy-manager-plugin-for-an-api-using-wschains</link><guid isPermaLink="true">https://timschindler.blog/creating-a-cyberark-central-policy-manager-plugin-for-an-api-using-wschains</guid><category><![CDATA[cyberark]]></category><category><![CDATA[wschains]]></category><category><![CDATA[central-policy-manager]]></category><category><![CDATA[Privileged access management]]></category><category><![CDATA[#cybersecurity]]></category><category><![CDATA[cybersecurity]]></category><dc:creator><![CDATA[Tim Schindler]]></dc:creator><pubDate>Fri, 02 Aug 2024 15:39:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1722612647614/7b319541-46d1-4b37-ac78-6d6b3b8c2526.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://timschindler.blog/creating-a-cyberark-central-policy-manager-plugin-for-a-web-application">In a previous article</a>, we created a CyberArk Central Policy Manager plugin for the web application Shopizer using the <a target="_blank" href="https://cyberark-customers.force.com/mplace/s/#a352J000000WU3aQAG-a392J0000013WwCQAU">Web Application CPM Plugin Framework</a> and while it works, we had to employ an awkward workaround to get reconcile functioning. As Shopizer's web interfaces leverage its APIs, we should skip the middleman and have our CPM plugin interface those.</p>
<p><a target="_blank" href="https://timschindler.blog/exploring-cyberark-central-policy-managers-wschains">WSChains is an undocumented, unsupported, used-by-CyberArk-interally framework to build CPM plugins that interact with APIs.</a> Using our existing knowledge of Shopizer, examples of WSChains-based plugins from the CyberArk Marketplace, we will build a WSChains-based CPM plugin to manage Shopizer users.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">This is article is one in a <a target="_blank" href="https://timschindler.blog/series/shopizer-admin-ext">series of articles around creating PSM connectors and CPM plugins</a>.</div>
</div>

<div data-node-type="callout">
<div data-node-type="callout-emoji">❗</div>
<div data-node-type="callout-text">Interested in seeing WSChains becoming a documented and supported option? <a target="_blank" href="https://community.cyberark.com/s/article/Document-and-support-the-WSChains-CPM-plugin-84e7-e8d">Vote on this enhancement request</a>!</div>
</div>

<h1 id="heading-developing-the-wschains-plugin">Developing the WSChains plugin</h1>
<p>Armed with <a target="_blank" href="https://app.swaggerhub.com/apis/shopizer/shopizer-rest-api">Shopizer REST API documentation</a> and a bit of knowledge about WSChains, we begin. We will re-use the Shopizer administrator users <a target="_blank" href="https://timschindler.blog/creating-a-cyberark-central-policy-manager-plugin-for-a-web-application#heading-setting-the-stage">we previously created</a>.</p>
<h2 id="heading-creating-a-new-web-application-platform">Creating a new web application platform</h2>
<p>We can clone the <a target="_blank" href="https://timschindler.blog/creating-a-cyberark-central-policy-manager-plugin-for-a-web-application#heading-creating-a-new-web-application-platform">web application framework-based platform</a> and make some changes:</p>
<ul>
<li><p>Under Automatic Password Management -&gt; CPM Plug-in, change <code>DLLName</code> to <code>Cyberark.Extensions.Plugin.WSChains.dll</code>.</p>
</li>
<li><p>Under Automatic Password Management -&gt; Generate Password, update <code>PasswordForbiddenChars</code> to include <code>&lt;&gt;&amp;\"'</code>, which are characters that need to be escaped inside of XML documents.</p>
</li>
<li><p>Under Automatic Password Management -&gt; Additional Policy Settings, set <code>Port</code> to <code>8080</code> as this is the default port the Shopizer REST API listens on. <code>Debug</code> can be set to <code>Yes</code> but a majority of testing will be done outside of CyberArk.</p>
</li>
<li><p>Under Automatic Password Management -&gt; Additional Policy Settings -&gt; Parameters, we only need a single, additional parameter named <code>ChainsFilePath</code> with the value of <code>bin\ShopizerAdministratorChains.xml</code> -- the relative path to the XML <code>Chains</code> file that holds the operations of the CPM plugin.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722603063695/d3d2a04e-8f7e-4e68-8987-942e8f14fd3e.png" alt class="image--center mx-auto" /></p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">❗</div>
<div data-node-type="callout-text">The WSChains DLL <code>Cyberark.Extensions.Plugin.WSChains.dll</code> is not available as part of the CPM's base installation. You need to acquire the DLL as part of a platform package from the CyberArk Marketplace or another source.</div>
</div>

<h2 id="heading-preparing-our-parameters-file-for-testing">Preparing our parameters file for testing</h2>
<p>We can test a WSChains plugin the same way we can test any other and we will do this to facilitate faster and easier development of our plugin. The <a target="_blank" href="https://docs.cyberark.com/pam-self-hosted/latest/en/Content/PASIMP/Plug-in-NetInvoker_Test.htm?tocpath=Developer%7CCreate%20extensions%7CCreate%20CPM%20plugins%7CCredentials%20Management%20.NET%20SDK%7C_____5#Parameterfile">Parameters file</a> looks like the following:</p>
<pre><code class="lang-ini"><span class="hljs-section">[targetaccount]</span>
<span class="hljs-attr">username</span>=shopizeradministrator@timschindler.dev
<span class="hljs-attr">newpassword</span>=Password2
<span class="hljs-attr">password</span>=Password2
<span class="hljs-attr">safename</span>=Safename
<span class="hljs-attr">foldername</span>=Foldername
<span class="hljs-attr">objectname</span>=Objectname
<span class="hljs-attr">PolicyID</span>=PolicyID
<span class="hljs-comment">; The machine where the Shopizer instance is running.</span>
<span class="hljs-attr">Address</span>=<span class="hljs-number">192.168</span>.<span class="hljs-number">178.61</span>
<span class="hljs-comment">; The port the Shopizer REST API is running on.</span>
<span class="hljs-attr">Port</span>=<span class="hljs-number">8080</span>

<span class="hljs-section">[extrapass3]</span>
<span class="hljs-attr">username</span>=reconcile@timschindler.dev
<span class="hljs-attr">password</span>=Password1

<span class="hljs-section">[extrainfo]</span>
<span class="hljs-comment">; Path to the chains file.</span>
<span class="hljs-attr">ChainsFilePath</span>=bin\ShopizerAdministratorChains.xml
<span class="hljs-attr">Debug</span>=<span class="hljs-literal">Yes</span>
</code></pre>
<p>This is saved as an <code>.ini</code> file in the root of the CPM folder.</p>
<h1 id="heading-crafting-shopizeradministratorchainsxml">Crafting <code>ShopizerAdministratorChains.xml</code></h1>
<p>Like a <code>WebFormFieldsFile</code>, the Chains file will hold the details for all three operations of our CPM plugin. For each operation, we will need to define the necessary requests that will be used by the links in the chains that represent each CPM operation.</p>
<p>We start with the bare minimum and some Fails.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">WSChains</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"ShopizerAdministrator"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">GlobalSettings</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">GlobalSettings</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Requests</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Requests</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Fails</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Fail</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"FAILBadRequest"</span> <span class="hljs-attr">rc</span>=<span class="hljs-string">"8101"</span> <span class="hljs-attr">message</span>=<span class="hljs-string">"Status Code = 400. Bad Request"</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Fail</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"FAILUnauthorized"</span> <span class="hljs-attr">rc</span>=<span class="hljs-string">"8102"</span> <span class="hljs-attr">message</span>=<span class="hljs-string">"Status Code = 401. The request sent by the CPM could not be authenticated"</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Fail</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"FAILForbidden"</span> <span class="hljs-attr">rc</span>=<span class="hljs-string">"8103"</span> <span class="hljs-attr">message</span>=<span class="hljs-string">"Status Code = 403. Access to the requested (valid) URL by the client is forbidden"</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Fail</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"FAILNotFound"</span> <span class="hljs-attr">rc</span>=<span class="hljs-string">"8104"</span> <span class="hljs-attr">message</span>=<span class="hljs-string">"Status Code = 404. Resource not found."</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Fail</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"FAILMethodNotFound"</span> <span class="hljs-attr">rc</span>=<span class="hljs-string">"8105"</span> <span class="hljs-attr">message</span>=<span class="hljs-string">"Status Code = 405. Method not allowed."</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Fail</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"FAILRequestConflict"</span> <span class="hljs-attr">rc</span>=<span class="hljs-string">"8106"</span> <span class="hljs-attr">message</span>=<span class="hljs-string">"Status Code = 409. Request conflict with current state of the server"</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Fail</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"FAILInternalServerError"</span> <span class="hljs-attr">rc</span>=<span class="hljs-string">"8107"</span> <span class="hljs-attr">message</span>=<span class="hljs-string">"Status Code = 500. The server cannot process the request for an unknown reason"</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Fail</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"FAILWebServer"</span> <span class="hljs-attr">rc</span>=<span class="hljs-string">"8108"</span> <span class="hljs-attr">message</span>=<span class="hljs-string">"Status Code = 501. The server cannot process the request for an unknown reason"</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Fail</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"FAILGeneral"</span> <span class="hljs-attr">rc</span>=<span class="hljs-string">"8109"</span> <span class="hljs-attr">message</span>=<span class="hljs-string">"General Error. Check the logs for more information."</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Fail</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"FAILUnauthorizedCurrPass"</span> <span class="hljs-attr">rc</span>=<span class="hljs-string">"8110"</span> <span class="hljs-attr">message</span>= <span class="hljs-string">"Failed to authenticate to the API using the target account username and current password. Status code 401."</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Fail</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"FAILUnauthorizedNewPass"</span> <span class="hljs-attr">rc</span>=<span class="hljs-string">"8111"</span> <span class="hljs-attr">message</span>= <span class="hljs-string">"Failed to authenticate to the API using the target account username and new password. Status code 401."</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Fail</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"FAILToParseRecUsername"</span> <span class="hljs-attr">rc</span>=<span class="hljs-string">"8112"</span> <span class="hljs-attr">message</span>= <span class="hljs-string">"Failed to parse the target account ID to perform a reconciliation"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Fails</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">Chains</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Chains</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">WSChains</span>&gt;</span>
</code></pre>
<p>Save the file as <code>ShopizerAdministratorChains.xml</code> in the <code>bin</code> folder of the CPM.</p>
<h2 id="heading-verify">Verify</h2>
<p>Like the web application framework plugin, we will simply authenticate to the Shopizer REST API. Authenticating with a correct username and password will result in an authentication token.</p>
<p>In the <code>Requests</code> element, the following <code>RequestWrapper</code> needs to be added based on the <a target="_blank" href="https://app.swaggerhub.com/apis/shopizer/shopizer-rest-api/3.0.1#/User%20authentication%20Api/authenticateUsingPOST_1">Shopizer REST API documentation</a>:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Requests</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">RequestWrapper</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">request</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"GetToken"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>2<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">general</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">endpoint</span>&gt;</span>/api/v1/private/login<span class="hljs-tag">&lt;/<span class="hljs-name">endpoint</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">method</span>&gt;</span>POST<span class="hljs-tag">&lt;/<span class="hljs-name">method</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">general</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">header</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Content-Type</span>&gt;</span>application/json<span class="hljs-tag">&lt;/<span class="hljs-name">Content-Type</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Accept</span>&gt;</span>application/json<span class="hljs-tag">&lt;/<span class="hljs-name">Accept</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">User-Agent</span>&gt;</span>Integration/1.0 (CyberArk, CPM, 1)<span class="hljs-tag">&lt;/<span class="hljs-name">User-Agent</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">json</span>&gt;</span>
                    {
                        "username":"{{username}}",
                        "password":"{{pmpass}}"
                    }
                <span class="hljs-tag">&lt;/<span class="hljs-name">json</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">request</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">RequestWrapper</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Requests</span>&gt;</span>
</code></pre>
<p>Inside the <code>request</code> element, the <code>name</code> attribute is used to refer to the Request in our Chain's links. <code>{{username}}</code> and <code>{{pmpass}}</code> will be replaced with the username and password of the account when the request is executed.</p>
<p>Under the <code>Chains</code> element, we create a <code>Chain</code> with the name of <code>verifypass</code>. It contains only a single <code>Link</code>:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Chain</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"verifypass"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"VerifyPasswordLink"</span> <span class="hljs-attr">request</span>=<span class="hljs-string">"GetToken"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"200"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"END"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Parse</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"parsed/token"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"json"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"token"</span> <span class="hljs-attr">secure</span>=<span class="hljs-string">"true"</span>/&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">StatusCode</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"400"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILBadRequest"</span>/&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"401"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILUnauthorized"</span>/&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"403"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILForbidden"</span>/&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"404"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILNotFound"</span>/&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"405"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILMethodNotFound"</span>/&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"409"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILRequestConflict"</span>/&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"500"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILInternalServerError"</span>/&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"501"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILWebServer"</span>/&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"*"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILGeneral"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Chain</span>&gt;</span>
</code></pre>
<p>The <code>Link</code> object is mostly self-explanatory: the <code>request</code> attribute defines what <code>Request</code> is invoked and the different <code>StatusCode</code> elements determine how the response to the request is handled.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">❗</div>
<div data-node-type="callout-text">There is not a hard requirement to having a child <code>Parse</code> element under the <code>StatusCode</code> that represents the end of the CPM operation however not parsing the returned authentication token will result in it being shown in plaintext in the debug logs!</div>
</div>

<p>When testing, we quickly get positive feedback:</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">PS</span> C:\Program Files (x86)\CyberArk\Password Manager&gt; .\bin\CANetPluginInvoker.exe shopizerwschains.ini verifypass Cyberark.Extensions.Plugin.WSChains.dll True
The plugin ended successfully (RC = <span class="hljs-number">0</span>)
</code></pre>
<p>Looking in the debug logs, we get a nice picture of what the plugin is doing.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722607018599/5414fb2c-406a-4725-b372-48d2531c51a9.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-change">Change</h2>
<p>Change is made simple for us as in the same response that provides the authentication token, it provides the <code>id</code> of the user the token is for. This is important <a target="_blank" href="https://app.swaggerhub.com/apis/shopizer/shopizer-rest-api/3.0.1#/User%20management%20resource%20(User%20Management%20Api)/passwordUsingPATCH">as we need to specify the <code>id</code> of the user whose password we are changing as part of the request URL</a>.</p>
<p>As part of the change operation, at the end, we are going to authenticate with the new password to be sure of the change so we need three <code>requests</code> in total. We can re-use the <code>GetToken</code> request but we need one represent the change operation itself as well as logging in with the new password:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Requests</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">RequestWrapper</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">request</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"ChangePassword"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>2<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">general</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">endpoint</span>&gt;</span>/api/v1/private/user/{{parsed/id}}/password<span class="hljs-tag">&lt;/<span class="hljs-name">endpoint</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">method</span>&gt;</span>PATCH<span class="hljs-tag">&lt;/<span class="hljs-name">method</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">general</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">header</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Content-Type</span>&gt;</span>application/json<span class="hljs-tag">&lt;/<span class="hljs-name">Content-Type</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Accept</span>&gt;</span>application/json<span class="hljs-tag">&lt;/<span class="hljs-name">Accept</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">User-Agent</span>&gt;</span>Integration/1.0 (CyberArk, CPM, 1)<span class="hljs-tag">&lt;/<span class="hljs-name">User-Agent</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Authorization</span>&gt;</span>Bearer {{parsed/token}}<span class="hljs-tag">&lt;/<span class="hljs-name">Authorization</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">json</span>&gt;</span>
                  {
                  "password": "{{pmpass}}",
                  "changePassword": "{{pmnewpass}}"
                  }
                <span class="hljs-tag">&lt;/<span class="hljs-name">json</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">request</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">RequestWrapper</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">RequestWrapper</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">request</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"GetTokenWithNewPass"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>2<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">general</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">endpoint</span>&gt;</span>/api/v1/private/login<span class="hljs-tag">&lt;/<span class="hljs-name">endpoint</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">method</span>&gt;</span>POST<span class="hljs-tag">&lt;/<span class="hljs-name">method</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">general</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">header</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Content-Type</span>&gt;</span>application/json<span class="hljs-tag">&lt;/<span class="hljs-name">Content-Type</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Accept</span>&gt;</span>application/json<span class="hljs-tag">&lt;/<span class="hljs-name">Accept</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">User-Agent</span>&gt;</span>Integration/1.0 (CyberArk, CPM, 1)<span class="hljs-tag">&lt;/<span class="hljs-name">User-Agent</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">json</span>&gt;</span>
                    {
                        "username":"{{username}}",
                        "password":"{{pmnewpass}}"
                    }
                <span class="hljs-tag">&lt;/<span class="hljs-name">json</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">request</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">RequestWrapper</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Requests</span>&gt;</span>
</code></pre>
<p>We add the <code>Authorization</code> header with the Bearer token we received from the <code>GetToken</code> request to the <code>ChangePassword</code> request. <code>GetTokenWithNewPass</code> looks exactly like <code>GetToken</code> but we use <code>{{pmnewpass}}</code> as the password.</p>
<p>Under the <code>Chains</code> element, we create a <code>Chain</code> with the name of <code>changepass</code> with three <code>Links</code>:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Chains</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Chain</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"changepass"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"ChangePasswordGetTokenLink"</span> <span class="hljs-attr">request</span>=<span class="hljs-string">"GetToken"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"200"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"ChangePasswordLink"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Parse</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"parsed/token"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"json"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"token"</span> <span class="hljs-attr">secure</span>=<span class="hljs-string">"true"</span>/&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Parse</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"parsed/id"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"json"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"id"</span> <span class="hljs-attr">secure</span>=<span class="hljs-string">"false"</span>/&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">StatusCode</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"400"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILBadRequest"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"401"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILUnauthorizedCurrPass"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"403"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILForbidden"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"404"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILNotFound"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"405"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILMethodNotFound"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"409"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILRequestConflict"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"500"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILInternalServerError"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"501"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILWebServer"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"*"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILGeneral"</span>/&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"ChangePasswordLink"</span> <span class="hljs-attr">request</span>=<span class="hljs-string">"ChangePassword"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"200"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"VerifyNewPassLink"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"400"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILBadRequest"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"401"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILUnauthorized"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"403"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILForbidden"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"404"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILNotFound"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"405"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILMethodNotFound"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"409"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILRequestConflict"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"500"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILInternalServerError"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"501"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILWebServer"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"*"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILGeneral"</span>/&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"VerifyNewPassLink"</span> <span class="hljs-attr">request</span>=<span class="hljs-string">"GetTokenWithNewPass"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"200"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"END"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Parse</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"parsed/tokennew"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"json"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"token"</span> <span class="hljs-attr">secure</span>=<span class="hljs-string">"true"</span> /&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">StatusCode</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"400"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILBadRequest"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"401"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILUnauthorizedNewPass"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"403"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILForbidden"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"404"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILNotFound"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"405"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILMethodNotFound"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"409"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILRequestConflict"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"500"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILInternalServerError"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"501"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILWebServer"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"*"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILGeneral"</span>/&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Chain</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Chains</span>&gt;</span>
</code></pre>
<p>The first <code>Link</code> is copied from our verify operation's <code>Chain</code>. The only difference is the addition of the <code>Parse</code> element that captures the <code>id</code> of the account so we can use it in the next Link. The <code>next</code> attribute of the Link tells the plugin the name of the Link in the Chain to execute.</p>
<p><code>ChangePasswordLink</code> invokes the <code>ChangePassword</code> request and when executed successfully (the response's status code is <code>200</code>), <code>VerifyNewPassLink</code> is called to login with the password just set. Again we parse the result so that we can mask the authentication token from the response in the logs.</p>
<p>Testing shows success:</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">PS</span> C:\Program Files (x86)\CyberArk\Password Manager&gt; .\bin\CANetPluginInvoker.exe shopizerwschains.ini changepass Cyberark.Extensions.Plugin.WSChains.dll True
The plugin ended successfully (RC = <span class="hljs-number">0</span>)
</code></pre>
<h2 id="heading-reconcile">Reconcile</h2>
<p>Reconcile is complex. We need to use <a target="_blank" href="https://app.swaggerhub.com/apis/shopizer/shopizer-rest-api/3.0.1#/User%20management%20resource%20(User%20Management%20Api)/updateUsingPUT_16">a different endpoint</a> when changing the password of another user. The request URL includes the <code>id</code> of the user to be changed -- requiring us to either store it as an account property or look this up as part of the CPM operation -- and because it is a PUT operation, we need to provide all the existing details of the user along with the new password, otherwise they will be deleted from the user's profile.</p>
<p>There is <a target="_blank" href="https://app.swaggerhub.com/apis/shopizer/shopizer-rest-api/3.0.1#/User%20management%20resource%20(User%20Management%20Api)/listUsingGET_18">an endpoint that will list all users and their <code>id</code></a> so we will need to call this as a link in our reconcile operation's chain and parse the response. We will then <a target="_blank" href="https://app.swaggerhub.com/apis/shopizer/shopizer-rest-api/3.0.1#/User%20management%20resource%20(User%20Management%20Api)/getUsingGET_14">retrieve the user's profile</a> in order to get the information we need to pass along as part of the change password request.</p>
<p>We need the following, additional requests:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Requests</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">RequestWrapper</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">request</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"RecGetToken"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>2<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">general</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">endpoint</span>&gt;</span>/api/v1/private/login<span class="hljs-tag">&lt;/<span class="hljs-name">endpoint</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">method</span>&gt;</span>POST<span class="hljs-tag">&lt;/<span class="hljs-name">method</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">general</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">header</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Content-Type</span>&gt;</span>application/json<span class="hljs-tag">&lt;/<span class="hljs-name">Content-Type</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Accept</span>&gt;</span>application/json<span class="hljs-tag">&lt;/<span class="hljs-name">Accept</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">User-Agent</span>&gt;</span>Integration/1.0 (CyberArk, CPM, 1)<span class="hljs-tag">&lt;/<span class="hljs-name">User-Agent</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">json</span>&gt;</span>
                    {
                        "username":"{{extrapass3\username}}",
                        "password":"{{pmextrapass3}}"
                    }
                <span class="hljs-tag">&lt;/<span class="hljs-name">json</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">request</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">RequestWrapper</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">RequestWrapper</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">request</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"GetUserByID"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>2<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">general</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">endpoint</span>&gt;</span>/api/v1/private/users<span class="hljs-tag">&lt;/<span class="hljs-name">endpoint</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">method</span>&gt;</span>GET<span class="hljs-tag">&lt;/<span class="hljs-name">method</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">general</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">header</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Content-Type</span>&gt;</span>application/json<span class="hljs-tag">&lt;/<span class="hljs-name">Content-Type</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Accept</span>&gt;</span>application/json<span class="hljs-tag">&lt;/<span class="hljs-name">Accept</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">User-Agent</span>&gt;</span>Integration/1.0 (CyberArk, CPM, 1)<span class="hljs-tag">&lt;/<span class="hljs-name">User-Agent</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Authorization</span>&gt;</span>Bearer {{parsed/token}}<span class="hljs-tag">&lt;/<span class="hljs-name">Authorization</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">json</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">json</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">request</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">RequestWrapper</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">RequestWrapper</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">request</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"GetUserProfileByID"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>2<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">general</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">endpoint</span>&gt;</span>/api/v1/private/users/{{parsed/id}}<span class="hljs-tag">&lt;/<span class="hljs-name">endpoint</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">method</span>&gt;</span>GET<span class="hljs-tag">&lt;/<span class="hljs-name">method</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">general</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">header</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Content-Type</span>&gt;</span>application/json<span class="hljs-tag">&lt;/<span class="hljs-name">Content-Type</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Accept</span>&gt;</span>application/json<span class="hljs-tag">&lt;/<span class="hljs-name">Accept</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">User-Agent</span>&gt;</span>Integration/1.0 (CyberArk, CPM, 1)<span class="hljs-tag">&lt;/<span class="hljs-name">User-Agent</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Authorization</span>&gt;</span>Bearer {{parsed/token}}<span class="hljs-tag">&lt;/<span class="hljs-name">Authorization</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">json</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">json</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">request</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">RequestWrapper</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">RequestWrapper</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">request</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"RecChangePassword"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>2<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">general</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">endpoint</span>&gt;</span>/api/v1/private/user/{{parsed/id}}<span class="hljs-tag">&lt;/<span class="hljs-name">endpoint</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">method</span>&gt;</span>PUT<span class="hljs-tag">&lt;/<span class="hljs-name">method</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">general</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">header</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Content-Type</span>&gt;</span>application/json<span class="hljs-tag">&lt;/<span class="hljs-name">Content-Type</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Accept</span>&gt;</span>application/json<span class="hljs-tag">&lt;/<span class="hljs-name">Accept</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">User-Agent</span>&gt;</span>Integration/1.0 (CyberArk, CPM, 1)<span class="hljs-tag">&lt;/<span class="hljs-name">User-Agent</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Authorization</span>&gt;</span>Bearer {{parsed/token}}<span class="hljs-tag">&lt;/<span class="hljs-name">Authorization</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">json</span>&gt;</span>
                {    
                    <span class="hljs-comment">&lt;!-- No quotes because it needs to be treated as a bool --&gt;</span>
                    "active": {{parsed/active}},
                    "defaultLanguage": "{{parsed/defaultLanguage}}",
                    "firstName": "{{parsed/firstName}}",            
                    <span class="hljs-comment">&lt;!-- The value for the groups key will be a string representation of the groups array. --&gt;</span>
                    "groups:": {{parsed/groups}},
                    "id": "{{parsed/id}}",
                    "lastName": "{{parsed/lastName}}",
                    "password": "{{pmnewpass}}",
                    "repeatPassword": "{{pmnewpass}}",
                    "store": "{{parsed/merchant}}",
                    "emailAddress": "{{parsed/emailAddress}}",
                    "userName": "{{parsed/userName}}"
                }            
                <span class="hljs-tag">&lt;/<span class="hljs-name">json</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">request</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">RequestWrapper</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Requests</span>&gt;</span>
</code></pre>
<p>The <code>RecGetToken</code> request is like <code>GetToken</code> but uses the linked reconcile account's username and password. The <code>GetUserByID</code> and <code>GetUserProfileByID</code> requests facilitate retrieving the needed details from the user's profile while <code>RecChangePassword</code> is used to change the password, including the parsed details.</p>
<p>The <code>reconcilepass</code> Chain will have five links:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Chains</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Chain</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"reconcilepass"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"ReconcilePasswordGetTokenLink"</span> <span class="hljs-attr">request</span>=<span class="hljs-string">"RecGetToken"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"200"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"GetUserByIDLink"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Parse</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"parsed/token"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"json"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"token"</span> <span class="hljs-attr">secure</span>=<span class="hljs-string">"true"</span> /&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">StatusCode</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"400"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILBadRequest"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"401"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILUnauthorizedCurrPass"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"403"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILForbidden"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"404"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILNotFound"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"405"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILMethodNotFound"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"409"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILRequestConflict"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"500"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILInternalServerError"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"501"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILWebServer"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"*"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILGeneral"</span>/&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"GetUserByIDLink"</span> <span class="hljs-attr">request</span>=<span class="hljs-string">"GetUserByID"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"200"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"ByParse"</span> <span class="hljs-attr">condition</span>=<span class="hljs-string">"OR"</span> <span class="hljs-attr">nomatch</span>=<span class="hljs-string">"FAILToParseRecUsername"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Parse</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"parsed/id"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"<span class="hljs-symbol">&amp;quot;</span>id<span class="hljs-symbol">&amp;quot;</span>:(\d+)(?=[^}]*<span class="hljs-symbol">&amp;quot;</span>userName<span class="hljs-symbol">&amp;quot;</span>:<span class="hljs-symbol">&amp;quot;</span>{{username}}<span class="hljs-symbol">&amp;quot;</span>)"</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">Equals</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"Success"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"END"</span>/&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">Equals</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"*"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"GetUserProfileByIDLink"</span>/&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">Parse</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">StatusCode</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"400"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILBadRequest"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"401"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILUnauthorized"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"403"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILForbidden"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"404"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILNotFound"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"405"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILMethodNotFound"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"409"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILRequestConflict"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"500"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILInternalServerError"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"501"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILWebServer"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"*"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILGeneral"</span>/&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"GetUserProfileByIDLink"</span> <span class="hljs-attr">request</span>=<span class="hljs-string">"GetUserProfileByID"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"200"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"ReconcilePasswordLink"</span>&gt;</span>
                <span class="hljs-comment">&lt;!-- Uses method=lowercase as we later treat it as a bool. Works with at least v12.5.7.3 of WSChains.dll--&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Parse</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"parsed/active"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"json"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"active"</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"lowercase"</span>/&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Parse</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"parsed/defaultLanguage"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"json"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"defaultLanguage"</span> /&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Parse</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"parsed/emailAddress"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"json"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"emailAddress"</span> /&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Parse</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"parsed/firstName"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"json"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"firstName"</span> /&gt;</span>
                <span class="hljs-comment">&lt;!-- Use regex to get the value of the groups key as when we parse it as type json, it cannot convert an Array to string. --&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Parse</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"parsed/groups"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"<span class="hljs-symbol">&amp;quot;</span>groups<span class="hljs-symbol">&amp;quot;</span>:(\[[^\]]+\])"</span> /&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Parse</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"parsed/lastName"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"json"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"lastName"</span> /&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Parse</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"parsed/merchant"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"json"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"merchant"</span> /&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Parse</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"parsed/username"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"json"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"userName"</span> /&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">StatusCode</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"400"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILBadRequest"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"401"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILUnauthorized"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"403"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILForbidden"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"404"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILNotFound"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"405"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILMethodNotFound"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"409"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILRequestConflict"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"500"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILInternalServerError"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"501"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILWebServer"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"*"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILGeneral"</span>/&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"ReconcilePasswordLink"</span> <span class="hljs-attr">request</span>=<span class="hljs-string">"RecChangePassword"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"200"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"RecVerifyNewPassLink"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"400"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILBadRequest"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"401"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILUnauthorized"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"403"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILForbidden"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"404"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILNotFound"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"405"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILMethodNotFound"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"409"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILRequestConflict"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"500"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILInternalServerError"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"501"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILWebServer"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"*"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILGeneral"</span>/&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"RecVerifyNewPassLink"</span> <span class="hljs-attr">request</span>=<span class="hljs-string">"GetTokenWithNewPass"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"200"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"END"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Parse</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"parsed/tokennew"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"json"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"token"</span> <span class="hljs-attr">secure</span>=<span class="hljs-string">"true"</span>/&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">StatusCode</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"400"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILBadRequest"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"401"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILUnauthorizedNewPass"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"403"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILForbidden"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"404"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILNotFound"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"405"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILMethodNotFound"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"409"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILRequestConflict"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"500"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILInternalServerError"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"501"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILWebServer"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StatusCode</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"*"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"FAILGeneral"</span>/&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Chain</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Chains</span>&gt;</span>
</code></pre>
<p>The heavy lifting is done in <code>GetUserByIDLink</code> and <code>GetUserProfileByIDLink</code>.</p>
<p>Regular expression is used to get the <code>id</code> of the user we are trying to reconcile in <code>GetUserByIDLink</code>. In <code>GetUserProfileByIDLink</code>, we can easily parse all the details except for the groups as this is a JSON array and again we use regular expression to grab the array as a string.</p>
<p>Again, our testing validates the chain.</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">PS</span> C:\Program Files (x86)\CyberArk\Password Manager&gt; .\bin\CANetPluginInvoker.exe shopizerwschains.ini reconcilepass Cyberark.Extensions.Plugin.WSChains.dll True
The plugin ended successfully (RC = <span class="hljs-number">0</span>)
</code></pre>
<h1 id="heading-conclusion">Conclusion</h1>
<p>WSChains is a great framework and our CPM plugin uses only a small amount of its features. I previously <a target="_blank" href="https://timschindler.blog/exploring-cyberark-central-policy-managers-wschains#heading-wrap-up">said it being unsupported by CyberArk makes it unsuitable for production use</a> but I no longer agree with that statement. The maturity of WSChains and the functionality of it makes it a no-brainer for developing CPM plugins that interact with REST APIs.</p>
<p>The platform files, Chain file, and parameter file can be found in my <a target="_blank" href="https://github.com/aaearon/cyberark-shopizer-admin-extensions">GitHub repository</a>. If you are interested in developing your own CPM plugin for Shopizer administrator users, you can use <a target="_blank" href="https://github.com/aaearon/cyberark-shopizer-admin-extensions/blob/main/shopizer/docker-compose.yml">this docker-compose.ymlto quickly stand up a c</a>ontainerized Shopizer environment. Check out the <a target="_blank" href="https://github.com/aaearon/cyberark-shopizer-admin-extensions/tree/main#standing-up-your-own-shopizer-environment-with-docker-compose">README</a> for more information. The same GitHub repository contains the platform files, Chain file, and a WSChains DLL.</p>
]]></content:encoded></item><item><title><![CDATA[Integrating CyberArk self-hosted PAM as a Secret Store in StrongDM]]></title><description><![CDATA[StrongDM bills itself as a Zero Trust PAM solution. It offers a pretty slick way to access resources through a Desktop client as well as a CLI. With either installed, you can easily connect to resources using native tooling. It is a nice alternative ...]]></description><link>https://timschindler.blog/integrating-cyberark-self-hosted-pam-as-a-secret-store-in-strongdm</link><guid isPermaLink="true">https://timschindler.blog/integrating-cyberark-self-hosted-pam-as-a-secret-store-in-strongdm</guid><category><![CDATA[strongdm]]></category><category><![CDATA[cyberark]]></category><category><![CDATA[#cybersecurity]]></category><category><![CDATA[secrets]]></category><category><![CDATA[privileged-session-manager]]></category><category><![CDATA[Privileged access management]]></category><dc:creator><![CDATA[Tim Schindler]]></dc:creator><pubDate>Sun, 26 May 2024 16:44:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1716741811680/1d40237d-704e-4977-a78f-574f3a7bf9e6.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://www.strongdm.com/">StrongDM</a> bills itself as a Zero Trust PAM solution. It offers a pretty slick way to access resources through a <a target="_blank" href="https://www.strongdm.com/docs/desktop/">Desktop client</a> as well as a <a target="_blank" href="https://www.strongdm.com/docs/cli/">CLI</a>. With either installed, you can easily connect to resources using native tooling. It is a nice alternative to the Privileged Session Manager (PSM).</p>
<p>The credentials for accessing resources can be stored in StrongDM's Strong Vault (a "simple" <a target="_blank" href="https://www.strongdm.com/docs/admin/secret-stores/">secret store</a>) or StrongDM can integrate with various third party stores such as CyberArk (self-hosted PAM and Conjur), AWS Secrets Manager, Azure Key Vault, and a few more.</p>
<p>This is a quick article on integrating CyberArk self-hosted PAM as a secret store in StrongDM.</p>
<h1 id="heading-reading-through-the-documentation">Reading through the documentation</h1>
<p>StrongDM provides <a target="_blank" href="https://www.strongdm.com/docs/admin/secret-stores/cyberark-pam/">documentation for integrating CyberArk self-hosted PAM as a secret store</a> but it is largely incomplete, and in one spot even incorrect, but depending on your familiarity with CyberArk, you can read between the lines as to what you need to do.</p>
<p>The first is <a target="_blank" href="https://www.strongdm.com/docs/admin/secret-stores/cyberark-pam/#configure-cyberark-pam">creating the necessary users in CyberArk and configuring the gateways and relays to use them</a>.</p>
<blockquote>
<p>First, any gateway(s) and relay(s) that you intend to use to access resources with via CyberArk PAM must be configured to authenticate with CyberArk. Due to the manner in which CyberArk identifies users and manages seats, each of those gateways and relays must be set up in CyberArk as a user with its own credentials. Once this is done, those gateways and relays are capable of authenticating to CyberArk in order to fetch the required credentials to connect a user to a protected resource.</p>
<p>On the gateway or relay, set the environment variables <code>PAM_USERNAME</code> and <code>PAM_PASSWORD</code> with the user’s corresponding credentials as the value. You can also set <code>PAM_TLS_SKIP_VERIFY=true</code> to skip certificate verification if the CyberArk instance doesn’t have a valid certificate.</p>
</blockquote>
<p>Without explicitly saying it, we can safely assume the integration uses the Password Vault Web Access (PVWA) REST API.</p>
<p>The justification for needing a CyberArk user per gateway or relay isn't clear to me but this requirement could also be in part due to not setting the <a target="_blank" href="https://docs.cyberark.com/pam-self-hosted/latest/en/Content/SDK/CyberArk%20Authentication%20-%20Logon_v10.htm?tocpath=Developer%7CREST%20APIs%7CAuthentication%7CLogon%7C_____1#Bodyparameters"><code>concurrentSession</code> parameter to <code>True</code> when calling the Logon endpoint.</a></p>
<p>The documentation doesn't mention how the CyberArk user authenticates. What is also missing are the needed safe memberships the users must have but they can already be assumed based on the nature of the integration.</p>
<p>Afterwards we <a target="_blank" href="https://www.strongdm.com/docs/admin/secret-stores/cyberark-pam/#set-up-the-secret-store-in-strongdm">set up the Secret Store in StrongDM</a>.</p>
<blockquote>
<ol>
<li><p>In the Admin UI, go to <strong>Network</strong> &gt; <strong>Secret Stores</strong>.</p>
</li>
<li><p>Click <strong>Add secret store</strong>.</p>
</li>
<li><p>Give the secret store a name that is recognizable within your organization, such as “CyberArk PAM.”</p>
</li>
<li><p>Choose <strong>CyberArk PAM</strong> as the secret store type.</p>
</li>
<li><p>For the application URL, enter the IP address of the Windows server that hosts your Central Credential Provider</p>
</li>
</ol>
</blockquote>
<p>Straight forward however step 5 looks to be incorrect. As we need to create CyberArk users for each gateway and relay and set the <code>PAM_USERNAME</code> and <code>PAM_PASSWORD</code> environmental variables, it must mean the PVWA and not the Central Credential Provider. <em>Note: I submitted a case to StrongDM support about CCP vs PVWA and they've already replied they are passing it on to the Docs team.</em></p>
<p>There is no option to choose how the gateways and relays should authenticate with their CyberArk user so it likely only works with the users doing Vault authentication.</p>
<p>The last part is <a target="_blank" href="https://www.strongdm.com/docs/admin/secret-stores/cyberark-pam/#connect-to-a-strongdm-resource">configuring a StrongDM resource to use an account in CyberArk</a>. The example below uses a database.</p>
<blockquote>
<ol>
<li><p>From the Admin UI, go to <strong>Infrastructure &gt; Datasources</strong>.</p>
</li>
<li><p>Click <strong>Add datasource</strong>.</p>
</li>
<li><p>Enter the properties for your database resource.</p>
</li>
<li><p>From the <strong>Secret Store</strong> dropdown menu, select the <strong>CyberArk PAM</strong> option.</p>
</li>
<li><p>In the <strong>Username (path)</strong> field, add the path to retrieve the username, in the format <code>&lt;ACCOUNT_ID&gt;?key=username</code>. Use the CyberArk account ID of your resource, followed by <code>?key=username</code>.</p>
</li>
<li><p>In the <strong>Private Key (path)</strong> field, add the path to retrieve the password, in the format <code>&lt;ACCOUNT_ID&gt;?key=password</code>. Use the CyberArk account ID of your resource, followed by <code>?key=password</code>.</p>
</li>
<li><p>When all required fields are complete, click <strong>Create</strong>.</p>
</li>
</ol>
</blockquote>
<p>The need to use an account ID cements the fact that the integration is using the PVWA REST API. An account ID is a construct only used in the PVWA.</p>
<h1 id="heading-integrating-cyberark-self-hosted-pam">Integrating CyberArk self-hosted PAM</h1>
<p>The documentation is enough to get us started and we can fill in the gaps along the way.</p>
<p>As my lab environment only has a single gateway, I create a single CyberArk Vault user named <code>strongdm</code>. Despite the documentation saying nothing about safe memberships, we know the user needs <em>something</em> and <code>List accounts</code> together with <code>Retrieve accounts</code> on the safes containing accounts we want to use with StrongDM resources makes the most sense to start with.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716726034459/02d60a6f-c66b-4f25-bab9-6429a251b29e.png" alt="Screenshot of the strongdm user's permissions on a safe" class="image--center mx-auto" /></p>
<p>I <a target="_blank" href="https://www.strongdm.com/docs/admin/nodes/linux/">created a gateway on an existing Linux server</a> in my lab and need to set the <code>PAM_USERNAME</code> and <code>PAM_PASSWORD</code> <a target="_blank" href="https://www.strongdm.com/docs/admin/deployment/environment-variables/">environmental variables</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716726362482/8de2d3f2-085e-4ce7-8932-c320508d8d96.png" alt="Screenshot of the sdm-proxy file where you set environmental variables" class="image--center mx-auto" /></p>
<p><code>PAM_TLS_SKIP_VERIFY</code> is set to true as my PVWA's certificate's issuer is not trusted by my Linux server.</p>
<p>Adding CyberArk as a Secret Store is effortless. We just need to keep in mind that, despite the instructions, we provide the URL of the PVWA and not a Central Credential Provider instance. We also do not need to add the <code>/PasswordVault/</code> path.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716726497971/298826f1-2fb9-49d6-be02-a8eb8cd27edd.png" alt="Picture of adding CyberArk PAM as a secret store" class="image--center mx-auto" /></p>
<p>Once the secret store is created, it is marked as healthy as it is reachable by at least one gateway.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716726559199/edd619ad-9ea0-4dd6-8ae1-ec0183fb34ff.png" alt="The secret store showing as healthy after adding it" class="image--center mx-auto" /></p>
<p>If we needed any more proof, we can check the IIS logs. <code>192.168.0.4</code> is the Linux server with the StrongDM gateway installed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716726706259/e39ba08c-4919-4bf4-912f-6a4f4e4bc7e0.png" alt class="image--center mx-auto" /></p>
<p>We can follow the steps for configuring a StrongDM resource to use an account stored in CyberArk self-hosted PAM quite literally. We just need the ID of the account we want to use.</p>
<p>A browser's developer tools is the quickest way to find an account's ID but if we needed to automate the adding of StrongDM resources using CyberArk accounts, the <a target="_blank" href="https://docs.cyberark.com/pam-self-hosted/Latest/en/Content/SDK/GetAccounts.htm?tocpath=Developer%7CREST%20APIs%7CAccounts%7C_____1">Get Accounts endpoint</a> would be more appropriate.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716727077534/413b13b7-4ea7-4263-8f7c-463a08dbfc77.png" alt="Developer tools in a browser with an account's ID shown as part of a network request" class="image--center mx-auto" /></p>
<p>With the account ID in hand, adding the resource looks like the following:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716727456225/0183754b-5f3c-47e8-bbc7-6d7ae332322c.png" alt="Adding a server in StrongDM. The secret store, username, and path are circled." class="image--center mx-auto" /></p>
<p>After resource creation, it is marked as healthy for our Linux gateway.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716728272719/9123272d-d34f-4e94-840a-430973e0c394.png" alt="The server/resource showing as healthy after adding it" class="image--center mx-auto" /></p>
<p>In activities for the account, we see the retrieval by the <code>strongdm</code> user with the reason "StrongDM Connection".</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716728374634/1263f975-e2f0-47f2-9428-fe448c593617.png" alt="A screenshot of the PVWA showing that the strongdm user retrieved the password for the account whose ID was specified when the server was added." class="image--center mx-auto" /></p>
<p>And for the ultimate test: a screenshot of the recording connecting through StrongDM.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716729501912/cb472518-0908-48b0-8a97-dea50dd887ba.png" alt="A screenshot of a StrongDM recording showing that strongdmadmin01 was used to authenticate to server01.timschindler.dev" class="image--center mx-auto" /></p>
<p>Even with the documentation for the integration not being complete, it was still pretty quick and easy.</p>
<h1 id="heading-closing-thoughts">Closing thoughts</h1>
<p>For what it is, StrongDM's CyberArk self-hosted PAM integration works nicely. A few thoughts:</p>
<ul>
<li><p>Being that it works with the PVWA REST API, it doesn't require any Central Credential Provider or Credential Provider licenses, which -- together with removing the need for any PSMs and their associated RDS CAL costs -- can be a nice cost savings.</p>
</li>
<li><p>Having to use the ID of an account can make the initial creation of a resource a bit of a pain but with how fast connecting to a resource with StrongDM is, even when StrongDM needs to retrieve the password from the PVWA, it makes up for it.</p>
</li>
<li><p>If the accounts you intend to use with StrongDM resources will also be used by users for password retrieval or PSM sessions in CyberArk, you need to be mindful of the platforms having exclusive usage active as StrongDM will not check-in an account after a StrongDM connection closes.</p>
</li>
<li><p>Don't forget to provide <code>Access Safe without confirmation</code> in addition to <code>List accounts</code> and <code>Retrieve accounts</code> to StrongDM's CyberArk users when accounts have dual control enforced.</p>
</li>
<li><p>Central Credential Provider integration would be a nice option. StrongDM does not seem to retrieve the password for an account for each connection so you could run the CP without <a target="_blank" href="https://docs.cyberark.com/credential-providers/latest/en/Content/CP%20and%20ASCP/cps_capacity-best-practices.htm#CredentialProvidercapacity">cache in order to not hit the 10,000 mark</a>. To avoid the CP having access to more than 50,000 accounts, a gateway or relay could use a CyberArk Application that has access to a certain scope of accounts.</p>
</li>
<li><p>Expanding support to CyberArk's Privilege Cloud would be a big plus as together with StrongDM it could greatly reduce the amount of infrastructure you need to manage for a PAM solution.</p>
</li>
</ul>
<p>Let me know your experience with integrating CyberArk self-hosted PAM and StrongDM.</p>
]]></content:encoded></item><item><title><![CDATA[Creating a CyberArk Central Policy Manager plugin for a web application]]></title><description><![CDATA[In a previous article, we developed a Microsoft Edge-based CyberArk Privileged Session Manager connection component for the web application Shopizer and with the knowledge we gained, we will create a Central Policy Manager plugin for a web applicatio...]]></description><link>https://timschindler.blog/creating-a-cyberark-central-policy-manager-plugin-for-a-web-application</link><guid isPermaLink="true">https://timschindler.blog/creating-a-cyberark-central-policy-manager-plugin-for-a-web-application</guid><category><![CDATA[central-policy-manager]]></category><category><![CDATA[cyberark]]></category><category><![CDATA[#cybersecurity]]></category><dc:creator><![CDATA[Tim Schindler]]></dc:creator><pubDate>Sat, 30 Dec 2023 16:46:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/FnA5pAzqhMM/upload/08e002a937737ad34fb2bdd0b4a96614.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In <a target="_blank" href="https://timschindler.blog/creating-a-cyberark-privileged-session-manager-connection-component-for-a-web-application">a previous article, we developed a Microsoft Edge-based CyberArk Privileged Session Manager connection component for the web application Shopizer</a> and with the knowledge we gained, we will create <a target="_blank" href="https://docs.cyberark.com/PAS/Latest/en/Content/Plugins/CPM_WebApplication.htm">a Central Policy Manager plugin for a web application</a> to manage the password of Shopizer administrator users.</p>
<p>Note: You can find the platform files for the Shopizer administrator user CPM plugin at <a target="_blank" href="https://github.com/aaearon/cyberark-shopizer-admin-extensions">https://github.com/aaearon/cyberark-shopizer-admin-extensions</a>, including a <a target="_blank" href="https://github.com/aaearon/cyberark-shopizer-admin-extensions/blob/main/shopizer/docker-compose.yml"><code>docker-compose.yml</code></a> file that can be used to quickly stand up your own Shopizer environment.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">This is article is one in a <a target="_blank" href="https://timschindler.blog/series/shopizer-admin-ext">series of articles around creating PSM connectors and CPM plug-ins</a>.</div>
</div>

<h1 id="heading-understanding-what-a-cpm-plugin-for-a-web-app-is-doing">Understanding what a CPM plugin for a web app is doing</h1>
<p>A CPM plugin for a web application works fundamentally the same as a PSM connection component for a web application.</p>
<p>The <a target="_blank" href="https://cyberark.my.site.com/mplace/s/#a352J000000WU3aQAG-a392J0000013WwCQAU">Web Application CPM Plugin Framework</a> uses a WebDriver to identify and interact with elements in a web page's DOM based on a set of commands that represent the verify, change, and reconcile operations. We <a target="_blank" href="https://timschindler.blog/creating-a-cyberark-privileged-session-manager-connection-component-for-a-web-application#heading-understanding-what-a-psm-connection-component-for-a-web-app-is-doing">already covered WebDrivers and the DOM in more depth</a> when we created a PSM connection component.</p>
<p>But instead of using the <code>WebFormFields</code> property, we use <code>WebFormFieldsFile</code> -- an <code>.ini</code> file -- that will hold the commands for each CPM operation. This file will live in the <code>bin</code> folder of the Central Policy Manager and the CPM plugin framework will parse and perform the commands contained.</p>
<h2 id="heading-a-closer-look-at-a-webformfieldsfile">A closer look at a <code>WebFormFieldsFile</code></h2>
<p>With an out of the box installation of the CPM there is no <code>WebFormFieldsFile</code> in the <code>bin</code> folder but <a target="_blank" href="https://docs.cyberark.com/PAS/Latest/en/Content/Plugins/CPM_WebApplication.htm?tocpath=Developer%7CCreate%20extensions%7CCreate%20CPM%20plugins%7C_____4#Configureplatformsforwebapplications">the documentation</a> tells us what it looks like (step 6): an <code>.ini</code> file with three sections -- verify, change, and reconcile.</p>
<pre><code class="lang-ini"><span class="hljs-section">[Verify]</span>
<span class="hljs-comment"># Verify commands go here, i.e. username &gt; {username} (searchby=name)</span>
<span class="hljs-section">[Change]</span>
<span class="hljs-comment"># Change commands go here</span>
<span class="hljs-section">[Reconcile]</span>
<span class="hljs-comment"># Reconcile commands go here</span>
</code></pre>
<p>The .<code>ini</code> file can be named anything so we want to make sure it is something unique and meaningful.</p>
<h1 id="heading-developing-our-shopizer-cpm-plugin">Developing our Shopizer CPM plugin</h1>
<p>We <a target="_blank" href="https://timschindler.blog/creating-a-cyberark-privileged-session-manager-connection-component-for-a-web-application#heading-identifying-elements-in-the-dom">already understand how to identify and select the best DOM elements</a> so we can jump right in with creating our CPM plugin. The <a target="_blank" href="https://docs.cyberark.com/PAS/Latest/en/Content/Plugins/CPM_WebApplication.htm?tocpath=Developer%7CCreate%20extensions%7CCreate%20CPM%20plugins%7C_____4#Configureplatformsforwebapplications">Create CPM plugins for Web applications documentation</a> has pretty explicit instructions for creating the platform.</p>
<h2 id="heading-setting-the-stage">Setting the stage</h2>
<p>Before creating the platform, we will create two administrator users in Shopizer for the purpose of development: <code>shopizeradministrator@timschindler.dev</code> and <code>reconcile@timschindler.dev</code> -- both with full administrator rights. The <code>shopizeradministrator</code> user will be the shared user and <code>reconcile</code> will serve as the reconciliation account for it.</p>
<h2 id="heading-creating-a-new-web-application-platform">Creating a new web application platform</h2>
<p>Picking up at step 5 of <a target="_blank" href="https://docs.cyberark.com/PAS/Latest/en/Content/Plugins/CPM_WebApplication.htm?tocpath=Developer%7CCreate%20extensions%7CCreate%20CPM%20plugins%7C_____4#Configureplatformsforwebapplications">the documentation for configuring platforms for web applications</a>, we need to define <code>VerifyURL</code>, <code>ChangeURL</code>, <code>ReconcileURL</code>, and <code>WebFormFieldsFile</code> for our newly created platform.</p>
<p>The <code>VerifyURL</code>, <code>ChangeURL</code>, and <code>ReconcileURL</code> parameters function the same as to that of <code>LogonURL</code> for PSM connection components for web apps: before executing the commands in the <code>WebFormFieldsFile</code>, the framework navigates to the URL for the relevant section. We can use dynamic parameters corresponding to account properties as part of the parameters, too (<code>{Address}</code>, <code>{Port}</code>, etc.)</p>
<p>The <code>WebFormFieldsFile</code> property will hold the name of the file with all the operations' commands. For our CPM plugin used to manage Shopizer administrators, we will name the file <code>ShopizerAdministrator.ini</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1703686127363/3128e9be-b07a-411f-8945-4aceef83678e.png" alt="A screenshot of the platform settings in the PVWA showing the WebFormFieldsFile and it's value of ShopizerAdministrator.ini" class="image--center mx-auto" /></p>
<p>We will keep the default values for the parameters, changing only <code>BrowserPath</code> and adding the parameter <code>Browser</code> with a value of <code>Edge</code> to reflect the fact we will use Microsoft Edge. We set <code>EnforceCertificate</code> to <code>No</code> as our Shopizer development environment does not have a valid TLS certificate (but we would want to change this to <code>Yes</code> for production.) We will leave the <code>VerifyURL</code>, <code>ChangeURL</code>, and <code>ReconcileURL</code> parameters for the time being as we will set them at same time we define the commands for the CPM operations in <code>ShopizerAdministrator.ini</code>. Our last change will be defining the value for the <code>Port</code> parameter as <code>4200</code> -- the port our Shopizer administrator portal is running as -- under <code>Additional Policy Settings</code>.</p>
<p>With our (incomplete) platform created, we can onboard our two Shopizer accounts into CyberArk.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1703754826283/a65d4fa2-c15e-4007-b58f-b6350dca50ab.png" alt="A screenshot of the PVWA with the shopizeradministrator@timschindler.dev and reconcile@timschindler.dev accounts onboarded" class="image--center mx-auto" /></p>
<h1 id="heading-crafting-shopizeradministratorini">Crafting <code>ShopizerAdministrator.ini</code></h1>
<p>For each operation, we need to determine:</p>
<ol>
<li><p>All the DOM elements that need to be interacted with to perform the operation. We need these for the commands.</p>
</li>
<li><p>The appropriate URL to start the commands at for the particular operation (<code>VerifyURL</code>, <code>ChangeURL</code>, and <code>ReconcileURL</code>.)</p>
</li>
</ol>
<p>Important to note: Unlike a PSM connection component where we can disable validations, CPM plugins for web applications require at least one validation per operation so that the CPM knows if it was successful or not.</p>
<h2 id="heading-verify-section">Verify section</h2>
<p>As a verify operation is simply logging into the website with the vaulted credentials, we can simply <a target="_blank" href="https://timschindler.blog/creating-a-cyberark-privileged-session-manager-connection-component-for-a-web-application#heading-defining-our-webformfields">re-use what we already have from our Shopizer PSM connection component</a> plus add the required validation.</p>
<p>This results in our <code>ShopizerAdministrator.ini</code> file looking like:</p>
<pre><code class="lang-ini"><span class="hljs-section">[Verify]</span>
username&gt;{Username} (<span class="hljs-attr">SearchBy</span>=name)
password&gt;{Password} (<span class="hljs-attr">SearchBy</span>=name)
//button<span class="hljs-section">[@type="submit"]</span>&gt;(Button) (<span class="hljs-attr">SearchBy</span>=XPath)
Informations sur le magasin&gt;(Validation) (<span class="hljs-attr">SearchBy</span>=text)
Store information&gt;(Validation) (<span class="hljs-attr">SearchBy</span>=text)
</code></pre>
<p>Note that, unlike the <code>WebFormFields</code> for a PSM connection component, we must not escape any brackets. And because the default Shopizer language is French, we search for the text 'Store information' or 'Informations sur le magasin' to validate we have logged in successfully.</p>
<p>We can also use the same value for <code>LogonURL</code> that we used in our PSM connection component for <code>VerifyURL</code> in our platform however we will not hard code the port and instead use a dynamic parameter, resulting in the final value being <code>http://{Address}:{Port}/#/auth</code> .</p>
<p>Clicking Verify in the PVWA, we see a successful verify operation.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1703758494289/aa5f62ed-f5b1-47fb-81a8-943a1765ec8b.png" alt="A screenshot of the shopizeradministrator@timschindler.dev account details in the PVWA showing Verify was successful" class="image--center mx-auto" /></p>
<h2 id="heading-change-section">Change section</h2>
<p>For a Shopizer administrator user to change their own password, they need to:</p>
<ol>
<li><p>Login to the Shopizer administrator portal.</p>
</li>
<li><p>Navigate to their profile.</p>
</li>
<li><p>Click Change Password from the pulldown.</p>
</li>
<li><p>Enter the current password, the new password, the new password again, and hit save.</p>
</li>
</ol>
<p>To simplify the commands in our Change section, we will use <code>http://{Address}:{Port}/#/auth</code> as <code>ChangeURL</code> and then use the <code>Navigate</code> command to have the browser go directly to where we can change the password, <code>http://{Address}:{Port}/#/pages/user-management/change-password</code>.</p>
<p>This results in our Change section looking like:</p>
<pre><code class="lang-ini"><span class="hljs-section">[Change]</span>
username&gt;{Username} (<span class="hljs-attr">SearchBy</span>=name)
password&gt;{Password} (<span class="hljs-attr">SearchBy</span>=name)
//button<span class="hljs-section">[@type="submit"]</span>&gt;(Button) (<span class="hljs-attr">SearchBy</span>=XPath)
Informations sur le magasin&gt;(Validation) (<span class="hljs-attr">SearchBy</span>=text)
Store information&gt;(Validation) (<span class="hljs-attr">SearchBy</span>=text)
(<span class="hljs-attr">Wait</span>=<span class="hljs-number">1</span>)
(<span class="hljs-attr">Navigate</span>=http://{Address}:{Port}/<span class="hljs-comment">#/pages/user-management/change-password)</span>
password&gt;{password} (<span class="hljs-attr">SearchBy</span>=id)
newPassword&gt;{newpassword} (<span class="hljs-attr">SearchBy</span>=id)
confirmNewPassword&gt;{newpassword} (<span class="hljs-attr">SearchBy</span>=id)
//button<span class="hljs-section">[text()=' Save ' or text()=' Sauvegarder ']</span>&gt;(Click) (<span class="hljs-attr">SearchBy</span>=xpath)
correctement&gt;(Validation) (<span class="hljs-attr">SearchBy</span>=text)
Password successfully changed&gt;(Validation) (<span class="hljs-attr">SearchBy</span>=text)
</code></pre>
<p>There is a lot going on so lets break it apart.</p>
<pre><code class="lang-ini">username&gt;{Username} (<span class="hljs-attr">SearchBy</span>=name)
password&gt;{Password} (<span class="hljs-attr">SearchBy</span>=name)
//button<span class="hljs-section">[@type="submit"]</span>&gt;(Button) (<span class="hljs-attr">SearchBy</span>=XPath)
Informations sur le magasin&gt;(Validation) (<span class="hljs-attr">SearchBy</span>=text)
Store information&gt;(Validation) (<span class="hljs-attr">SearchBy</span>=text)
</code></pre>
<p>The first lines are a copy and paste of our Verify section as we need to login first.</p>
<pre><code class="lang-ini">(<span class="hljs-attr">Wait</span>=<span class="hljs-number">1</span>)
(<span class="hljs-attr">Navigate</span>=http://{Address}:{Port}/<span class="hljs-comment">#/pages/user-management/change-password)</span>
</code></pre>
<p>We use a <code>Wait</code> to break the validation context and then use <code>Navigate</code> to go directly to the profile page where we can change the password.</p>
<pre><code class="lang-ini">password&gt;{password} (<span class="hljs-attr">SearchBy</span>=id)
newPassword&gt;{newpassword} (<span class="hljs-attr">SearchBy</span>=id)
confirmNewPassword&gt;{newpassword} (<span class="hljs-attr">SearchBy</span>=id)
//button<span class="hljs-section">[text()=' Save ' or text()=' Sauvegarder ']</span>&gt;(Click) (<span class="hljs-attr">SearchBy</span>=xpath)
</code></pre>
<p>After entering the current password and the new password twice we search for a button that has either the text <code>Save</code> or <code>Sauvegarder</code> , which is the Save button for changing the password, because the site may load in either English or French.</p>
<pre><code class="lang-ini">correctement&gt;(Validation) (<span class="hljs-attr">SearchBy</span>=text)
Password successfully changed&gt;(Validation) (<span class="hljs-attr">SearchBy</span>=text)
</code></pre>
<p>Our validations cover the <a target="_blank" href="https://en.wikipedia.org/wiki/Pop-up_notification">Toast notification</a> that briefly pops up when the password is changed successfully. What isn't visible in our <code>WebFormFieldsFile</code> is a necessary tweak to the <code>ActionTimeout</code> value.</p>
<p><code>ActionTimeout</code> determines how long to wait for a command or an action to complete. When the command is clicking on a button, the framework expects the button to disappear from the page as part of the action completing. The framework will wait up to the value of <code>ActionTimeout</code> before proceeding with the next command.</p>
<p>Because a successful change does not result in the Save button disappearing and the Toast disappears after 3 or 4 seconds, we have to adjust <code>ActionTimeout</code> in our platform to <code>2</code> so that the validations can catch the Toasts before they are gone.</p>
<p>The account's password is rotated after clicking Change in the PVWA.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1703843020210/a4354486-ab8a-4e09-84cc-7392f70c7e63.png" alt="A screenshot of the shopizeradministrator@timschindler.dev account details in the PVWA showing Change was successful" class="image--center mx-auto" /></p>
<h2 id="heading-reconcile-section">Reconcile section</h2>
<p>Reconcile will be similar to change in that for Shopizer administrators to change the password for another user, they need to:</p>
<ol>
<li><p>Login to the Shopizer administrator portal.</p>
</li>
<li><p>Navigate to the Users list.</p>
</li>
<li><p>Find the user in the list and edit their details.</p>
</li>
<li><p>Enter the new password, repeat the new password again, and hit Save.</p>
</li>
</ol>
<p>Like the Change section, our Reconcile section will use <code>http://{Address}:{Port}/#/auth</code> as <code>ReconcileURL</code> and then use <code>Navigate</code> to navigate directly to the Users list, <code>http://{Address}:{Port}/#/pages/user-management/users</code>.</p>
<p>Our Reconcile section results in:</p>
<pre><code class="lang-ini"><span class="hljs-section">[Reconcile]</span>
username&gt;{reconcileaccount\username} (<span class="hljs-attr">SearchBy</span>=name)
password&gt;{reconcileaccount\password} (<span class="hljs-attr">SearchBy</span>=name)
//button<span class="hljs-section">[@type="submit"]</span>&gt;(Button) (<span class="hljs-attr">SearchBy</span>=XPath)
Informations sur le magasin&gt;(Validation) (<span class="hljs-attr">SearchBy</span>=text)
Store information&gt;(Validation) (<span class="hljs-attr">SearchBy</span>=text)
(<span class="hljs-attr">Wait</span>=<span class="hljs-number">1</span>)
(<span class="hljs-attr">Navigate</span>=http://{Address}:{Port}/<span class="hljs-comment">#/pages/user-management/users)</span>
//tbody//tr//td<span class="hljs-section">[3]</span>//div<span class="hljs-section">[text()="{username}"]</span>/ancestor::tr/td<span class="hljs-section">[5]</span>//i&gt;(Click) (<span class="hljs-attr">Searchby</span>=XPath)
password&gt;{newpassword} (<span class="hljs-attr">SearchBy</span>=id)
repeatPassword&gt;{newpassword} (<span class="hljs-attr">SearchBy</span>=id)
//button<span class="hljs-section">[text()=' Save ' or text()=' Sauvegarder ']</span>&gt;(Click) (<span class="hljs-attr">SearchBy</span>=xpath)
utilisateur&gt;(Validation) (<span class="hljs-attr">SearchBy</span>=text)
User updated&gt;(Validation) (<span class="hljs-attr">SearchBy</span>=text)
</code></pre>
<p>Like for Change, lets break it down into manageable pieces:</p>
<pre><code class="lang-ini">username&gt;{reconcileaccount\username} (<span class="hljs-attr">SearchBy</span>=name)
password&gt;{reconcileaccount\password} (<span class="hljs-attr">SearchBy</span>=name)
//button<span class="hljs-section">[@type="submit"]</span>&gt;(Button) (<span class="hljs-attr">SearchBy</span>=XPath)
Informations sur le magasin&gt;(Validation) (<span class="hljs-attr">SearchBy</span>=text)
Store information&gt;(Validation) (<span class="hljs-attr">SearchBy</span>=text)
</code></pre>
<p>The first lines are a copy and paste of our Verify and Change sections as however we need to login with the reconcile account so we use <code>{reconcileaccount\username}</code> and <code>{reconcileaccount\password}</code> instead of <code>{username}</code> and <code>{password}</code>.</p>
<pre><code class="lang-ini">(<span class="hljs-attr">Wait</span>=<span class="hljs-number">1</span>)
(<span class="hljs-attr">Navigate</span>=http://{Address}:{Port}/<span class="hljs-comment">#/pages/user-management/users)</span>
</code></pre>
<p>Again, we use a <code>Wait</code> to break the validation context and then use <code>Navigate</code> to go directly to the Users list.</p>
<pre><code class="lang-ini">//tbody//tr//td<span class="hljs-section">[3]</span>//div<span class="hljs-section">[text()="{username}"]</span>/ancestor::tr/td<span class="hljs-section">[5]</span>//i&gt;(Click) (<span class="hljs-attr">Searchby</span>=XPath)
</code></pre>
<p>This is the most complex part of our CPM plugin.</p>
<p>The XPath expression identifies the Edit user "button" (in actuality it is an image) used to navigate to a user's details by looking for a <code>&lt;div&gt;</code> element with text content that matches the account's username within the third column (<code>&lt;td&gt;)</code> of a table row in the User's list table. Then, it goes up to the parent row (<code>&lt;tr&gt;</code>) and selects the fifth column (<code>&lt;td&gt;</code>), and finally, it searches for an <code>&lt;i&gt;</code> element anywhere within that fifth column (<code>&lt;td&gt;</code>).</p>
<p>Once found, it clicks on the image to navigate to that user's details page where the password can be updated.</p>
<pre><code class="lang-ini">password&gt;{newpassword} (<span class="hljs-attr">SearchBy</span>=id)
repeatPassword&gt;{newpassword} (<span class="hljs-attr">SearchBy</span>=id)
//button<span class="hljs-section">[text()=' Save ' or text()=' Sauvegarder ']</span>&gt;(Click) (<span class="hljs-attr">SearchBy</span>=xpath)
</code></pre>
<p>The new password is entered twice and the Save button is clicked.</p>
<pre><code class="lang-ini">utilisateur&gt;(Validation) (<span class="hljs-attr">SearchBy</span>=text)
User updated&gt;(Validation) (<span class="hljs-attr">SearchBy</span>=text)
</code></pre>
<p>Similar to changing a password, once we click Save, a toast pops up informing us that the user is updated. As <code>ActionTimeout</code> is already set to <code>2</code>, the framework spots the notification and the reconciliation should be considered successful.</p>
<p>Let's give it a shot.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1703930882385/ccfdab27-7274-4f68-9d49-0ec128787c3d.png" alt="A screenshot of the error message in the PVWA received when trying to Reconcile shopizeradministrator@timschindler.dev" class="image--center mx-auto" /></p>
<p>Unlike our Verify and Change operations, Reconcile isn't working on the first attempt.</p>
<p>The error message says it cannot press on the Save button, which is different from being unable to find the button. Digging in the Debug logs, we see the following error:</p>
<p><code>30/12/2023 11:06:47.438 | ERROR -&gt; WebElementWrapper :: Click -&gt; Failed to press on button. Exception Details: OpenQA.Selenium.ElementClickInterceptedException: element click intercepted: Element &lt;button _ngcontent-dex-c30="" class="success_button appearance-filled size-medium status-basic shape-rectangle nb-transition" nbbutton="" nbspinnersize="large" nbspinnerstatus="control" type="button" _nghost-dex-c4="" aria-disabled="false" tabindex="0"&gt;...&lt;/button&gt; is not clickable at point (1036, 20). Other element would receive the click: &lt;div _ngcontent-dex-c16="" class="user-container"&gt;...&lt;/div&gt; (Session info: MicrosoftEdge=120.0.2210.91)</code></p>
<p>The significant part is at the end: <code>Other element would receive the click: &lt;div _ngcontent-dex-c16="" class="user-container"&gt;...&lt;/div&gt;</code> .</p>
<p>The framework is telling us that it could not click the Save button that it found at an earlier position because now another element is on top of it.</p>
<p>At this point, we need to see how the framework is interacting with the page and unlike with PSM connection components, we cannot easily flip on switch to see the operations (<code>EnableTrace=yes</code>) but we can test the plugin manually without having to invoke an operation in CyberArk.</p>
<h1 id="heading-troubleshooting-reconcile-by-testing-the-cpm-plugin">Troubleshooting Reconcile by testing the CPM plugin</h1>
<p>Testing a CPM plugin is already <a target="_blank" href="https://docs.cyberark.com/PAS/Latest/en/Content/Plugins/CPM_WebApplication.htm#Step3Testtheplugin">well documented by CyberArk</a>. As we already have our <code>WebFormFieldsFile</code>, we just need to create a <code>user.ini</code> file that will hold the parameters sent to the CPM plugin as part of our testing.</p>
<p>Our <code>user.ini</code> file -- which I've named <code>shopizeruser.ini</code> as there is no requirement around the filename -- looks like:</p>
<pre><code class="lang-ini"><span class="hljs-section">[targetaccount]</span>
<span class="hljs-attr">username</span>=shopizeradministrator@timschindler.dev
<span class="hljs-attr">newpassword</span>=Password2
<span class="hljs-attr">password</span>=Password1
<span class="hljs-attr">safename</span>=Safename
<span class="hljs-attr">foldername</span>=Foldername
<span class="hljs-attr">objectname</span>=Objectname
<span class="hljs-attr">PolicyID</span>=Policyid
<span class="hljs-attr">Address</span>=<span class="hljs-number">192.168</span>.<span class="hljs-number">178.61</span>
<span class="hljs-attr">Port</span>=<span class="hljs-number">4200</span>

<span class="hljs-section">[extrapass3]</span>
<span class="hljs-attr">username</span>=reconcile@timschindler.dev
<span class="hljs-attr">password</span>=Password1

<span class="hljs-section">[extrainfo]</span>
<span class="hljs-attr">VerifyURL</span>=http://{Address}:{Port}/<span class="hljs-comment">#/auth</span>
<span class="hljs-attr">ChangeURL</span>=http://{Address}:{Port}/<span class="hljs-comment">#/auth</span>
<span class="hljs-attr">ReconcileURL</span>=http://{Address}:{Port}/<span class="hljs-comment">#/auth</span>
<span class="hljs-attr">WebFormFieldsFile</span>=ShopizerAdministrator.ini
<span class="hljs-attr">RunVerifyAfterChange</span>=<span class="hljs-literal">No</span>
<span class="hljs-attr">RunVerifyAfterReconcile</span>=<span class="hljs-literal">No</span>
<span class="hljs-attr">ActionTimout</span>=<span class="hljs-number">2</span>
<span class="hljs-attr">PageLoadTimeout</span>=<span class="hljs-number">30</span>
<span class="hljs-attr">EnforceCertificate</span>=<span class="hljs-literal">No</span>
<span class="hljs-attr">Debug</span>=<span class="hljs-literal">Yes</span>
<span class="hljs-attr">BrowserPath</span>=C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe
<span class="hljs-attr">Browser</span>=Edge
</code></pre>
<p>The <code>targetaccount</code> section holds parameters related to the account we are testing with, <code>extrapass3</code> has parameters relating to a reconcile account, and <code>extrainfo</code> has the same parameters and values as defined in our platform.</p>
<p>Note: The values for the <code>safename</code>, <code>foldername</code>, <code>objectname</code>, and <code>policyid</code> parameters are irrelevant for our testing but are required as they are used to form the name of the log file found in the <code>Logs\ThirdParty</code> folder.</p>
<p>With <code>shopizeruser.ini</code> saved to the root of the <code>Password Manager</code> folder, we can test the CPM plugin. Let's start with an operation we know works -- verify:</p>
<p><code>.\bin\CANetPluginInvoker.exe shopizeruser.ini verifypass CyberArk.Extensions.Plugin.WebApp.dll True</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1703934800377/e56df251-0f97-40c5-9146-6f04762d1cb9.gif" alt="A video showing that the Verify operation was successful when manually tested" class="image--center mx-auto" /></p>
<p>The plugin ends with error code 0, which is the error code returned when the operation is successful.</p>
<p>We know we have a working test setup so let's proceed with reconcile, with the expectation that it will fail:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1703935299131/40dd08e7-e8f9-4983-a4ce-04e392a9825b.gif" alt="A video showing that the Reconcile operation was successful when manually tested" class="image--center mx-auto" /></p>
<p>Reconcile works when we test but not when the CPM invokes the plugin.</p>
<p>It's a nice confirmation that our commands for the CPM plugin are correct but we need to understand why we get different results.</p>
<p>Because the error message we received mentions that another element would receive the click, it's worth opening the User details page in a normal Edge browser and experiencing the page when navigating around it. It is also important to change the browser window size as we are not sure what the window size is when the CPM invokes the plugin.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1703936447052/08b40760-493e-4dc0-8468-4d3ce045030d.gif" alt="A video of interacting with the Shopizer administrator portal" class="image--center mx-auto" /></p>
<p>At the resolution we are connected to the CPM with and the browser maximized, at no point is the Save button not visible. It's only when we put the browser in windowed mode is the Save button not visible after we focus on the Password and Repeat Password fields.</p>
<p>Resizing our RDP window to a smaller resolution and testing the plugin once more, we can reproduce the error that we originally received:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1703936731734/42fd05c5-8f79-4d77-8e11-92770087f1df.gif" alt="A video showing that the Reconcile operation failed when manually tested" class="image--center mx-auto" /></p>
<p>Checking the Debug log, we see:</p>
<p><code>30/12/2023 12:44:47.066 | ERROR -&gt; WebElementWrapper :: Click -&gt; Failed to press on button. Exception Details: OpenQA.Selenium.ElementClickInterceptedException: element click intercepted: Element &lt;button _ngcontent-bkl-c30="" class="success_button appearance-filled size-medium status-basic shape-rectangle nb-transition" nbbutton="" nbspinnersize="large" nbspinnerstatus="control" type="button" _nghost-bkl-c4="" aria-disabled="false" tabindex="0"&gt;...&lt;/button&gt; is not clickable at point (792, 20). Other element would receive the click: &lt;div _ngcontent-bkl-c16="" class="user-container"&gt;...&lt;/div&gt;</code></p>
<p>The message details differ slightly but it still complains about another element receiving the click. We can reasonably assume that when the framework focuses on the Password and New Password fields to enter text, the browser window no longer shows the Save button because the floating header is covering it.</p>
<p>How can we move the browser window to the top of the page?</p>
<p>With the framework, there is no command we can define in our <code>WebFormFieldsFile</code> to have the browser scroll to the top of the page. Furthermore, we cannot send keys outside of an input field so we could not simply instruct the framework to hit the Home key.</p>
<h1 id="heading-fixing-the-reconcile-operation">Fixing the Reconcile operation</h1>
<p>The solution to fixing the Reconcile operation isn't elegant.</p>
<p>We know the browser scrolls to an input field when text is being entered so we need to enter text in the <code>First name</code> field. This will result in the browser 'scrolling up' and the header not covering the Save button.</p>
<p>Because we use <code>Shopizer</code> as the <code>First name</code> for our Shopizer users and the value of this user property is irrelevant, we can have commands to send <code>Shopizer</code> to the <code>First name</code> field before trying to click the Save button. The relevant part of our Reconcile section transforms to:</p>
<pre><code class="lang-ini">password&gt;{newpassword} (<span class="hljs-attr">SearchBy</span>=id)
repeatPassword&gt;{newpassword} (<span class="hljs-attr">SearchBy</span>=id)
firstname&gt;Shopizer (<span class="hljs-attr">SearchBy</span>=id)
//button<span class="hljs-section">[text()=' Save ' or text()=' Sauvegarder ']</span>&gt;(Click) (<span class="hljs-attr">SearchBy</span>=xpath)
</code></pre>
<p>Testing our reconcile operation, we see a success:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1703937722677/ce41d640-4aee-4059-a1e6-4bcccbe4505f.gif" alt="A video showing that the Reconcile operation was successful when manually tested" class="image--center mx-auto" /></p>
<p>But what matters is if it is successful when the CPM invokes the plugin.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1703937906294/7a174f2e-e31d-4e8a-ab70-97af1a35db94.png" alt="A screenshot of the shopizeradministrator@timschindler.dev account details in the PVWA showing Reconcile was successful" class="image--center mx-auto" /></p>
<p>Adding the command to populate the <code>First name</code> field so that the browser would have the Save button in view worked!</p>
<p>Our final Reconcile section is:</p>
<pre><code class="lang-ini"><span class="hljs-section">[Reconcile]</span>
username&gt;{reconcileaccount\username} (<span class="hljs-attr">SearchBy</span>=name)
password&gt;{reconcileaccount\password} (<span class="hljs-attr">SearchBy</span>=name)
//button<span class="hljs-section">[@type="submit"]</span>&gt;(Button) (<span class="hljs-attr">SearchBy</span>=XPath)
Informations sur le magasin&gt;(Validation) (<span class="hljs-attr">SearchBy</span>=text)
Store information&gt;(Validation) (<span class="hljs-attr">SearchBy</span>=text)
(<span class="hljs-attr">Wait</span>=<span class="hljs-number">1</span>)
(<span class="hljs-attr">Navigate</span>=http://{Address}:{Port}/<span class="hljs-comment">#/pages/user-management/users)</span>
//tbody//tr//td<span class="hljs-section">[3]</span>//div<span class="hljs-section">[text()="{username}"]</span>/ancestor::tr/td<span class="hljs-section">[5]</span>//i&gt;(Click) (<span class="hljs-attr">Searchby</span>=XPath)
password&gt;{newpassword} (<span class="hljs-attr">SearchBy</span>=id)
repeatPassword&gt;{newpassword} (<span class="hljs-attr">SearchBy</span>=id)
firstname&gt;Shopizer (<span class="hljs-attr">SearchBy</span>=id)
//button<span class="hljs-section">[text()=' Save ' or text()=' Sauvegarder ']</span>&gt;(Click) (<span class="hljs-attr">SearchBy</span>=xpath)
utilisateur&gt;(Validation) (<span class="hljs-attr">SearchBy</span>=text)
User updated&gt;(Validation) (<span class="hljs-attr">SearchBy</span>=text)
</code></pre>
<h1 id="heading-next-steps">Next steps</h1>
<p>Our CPM plugin for Shopizer administrator users works but there are some improvements that can be made before rolling out to production:</p>
<ol>
<li><p>Each operation should include Failure commands so that meaningful error messages can be shown in the PVWA.</p>
</li>
<li><p>We should change <code>EnforceCertificate</code> to <code>Yes</code> after securing all our Shopizer administrator portals with trusted TLS certificates.</p>
</li>
<li><p>Evaluate if using the Web App framework is the best solution. The Shopizer administrator portal leverages REST APIs and our CPM plugin would be less prone to breaking if we call the endpoints directly.</p>
</li>
</ol>
<p>If you are interested in developing your own CPM plugin for Shopizer administrator users, you can use <a target="_blank" href="https://github.com/aaearon/cyberark-shopizer-admin-extensions/blob/main/shopizer/docker-compose.yml">this docker-compose.yml</a> to quickly stand up a containerized Shopizer environment. Check out the <a target="_blank" href="https://github.com/aaearon/cyberark-shopizer-admin-extensions/tree/main#standing-up-your-own-shopizer-environment-with-docker-compose">README</a> for more information. The same GitHub repository contains the platform files and <code>WebFormFieldsFile</code>.</p>
]]></content:encoded></item><item><title><![CDATA[Generating TOTPs for CyberArk Privileged Session Manager Webapp Connectors using a PreConnect DLL]]></title><description><![CDATA[With version 13.0 of CyberArk's Privileged Session Manager, as part of a Webapp PSM connector, you can inject a time-based one-time password. You can even store the secret for the TOTP in a separate account and link it as a logon account to secure it...]]></description><link>https://timschindler.blog/generating-totps-for-cyberark-privileged-session-manager-webapp-connectors-using-a-preconnect-dll</link><guid isPermaLink="true">https://timschindler.blog/generating-totps-for-cyberark-privileged-session-manager-webapp-connectors-using-a-preconnect-dll</guid><category><![CDATA[cyberark]]></category><category><![CDATA[#cybersecurity]]></category><category><![CDATA[MFA]]></category><category><![CDATA[privileged-session-manager]]></category><category><![CDATA[Privileged access management]]></category><dc:creator><![CDATA[Tim Schindler]]></dc:creator><pubDate>Thu, 12 Oct 2023 15:59:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/BXOXnQ26B7o/upload/06bd070120b8677ef6395e0235ea2fff.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>With version 13.0 of CyberArk's Privileged Session Manager, as part of a Webapp PSM connector, you can inject a time-based one-time password. You can even <a target="_blank" href="https://timschindler.blog/generating-time-based-one-time-passwords-in-privileged-session-manager-webapp-connectors">store the secret for the TOTP in a separate account and link it as a logon account to secure it</a>.</p>
<p>As the latest <a target="_blank" href="https://docs.cyberark.com/EOL/en/Content/EOL-Self-hosted-products.htm#CyberArkPAMSelfHostedVaultPVWACPMPSMPSMforSSHPTA">LTS version is 12.6</a>, many customers are 'stuck' without TOTP injection support but by using a custom <a target="_blank" href="https://docs.cyberark.com/PAS/12.2/en/Content/PASIMP/psm_WebApplication_Preconnect.htm?tocpath=Developer%7CCreate%20extensions%7CPSM%20Connectors%7CWeb%20applications%20for%20PSM%7C_____1">PreConnect DLL</a> for Webapp PSM connectors -- a feature introduced in PSM version 12.2-- we can achieve the same functionality and have TOTP injection with PSMs as early as version 12.2</p>
<h1 id="heading-what-is-a-preconnect-dll">What is a PreConnect DLL?</h1>
<p>A PreConnect DLL is a piece of code that is run before a Webapp PSM connector's <code>WebFormFields</code> is executed. <a target="_blank" href="https://docs.cyberark.com/PAS/12.2/en/Content/PASIMP/psm_WebApplication_Preconnect.htm?tocpath=Developer%7CCreate%20extensions%7CPSM%20Connectors%7CWeb%20applications%20for%20PSM%7C_____1#Configurethewebapplication">Account properties can be passed to the DLL as parameters and return values can be used in the connector's <code>WebFormFields</code>' input fields.</a> CyberArk provides <a target="_blank" href="https://docs.cyberark.com/PAS/12.2/en/Content/PASIMP/psm_WebApplication_Preconnect.htm?tocpath=Developer%7CCreate%20extensions%7CPSM%20Connectors%7CWeb%20applications%20for%20PSM%7C_____1#ImplementthePreConnectDLLfile">straightforward documentation</a> on how to develop and configure a PreConnect DLL.</p>
<h1 id="heading-creating-the-preconnect-dll">Creating the PreConnect DLL</h1>
<p>We will keep the PreConnect DLL simple.</p>
<p>It will take a single parameter -- <code>logonaccount_password</code> -- being the TOTP secret, generate a six-digit TOTP using it, and return the TOTP. We will use <code>&amp;MfaCode&amp;</code> to represent the TOTP for use in the <code>WebFormFields</code>.</p>
<p>As version 12.2 of the PSM comes with <a target="_blank" href="https://github.com/kspearrin/Otp.NET">Otp.NET</a> out of the box in the <code>Components</code> folder, we will re-use the library to do the OTP generating and leave our PreConnect DLL to handle just the receiving of the parameter and the returning of the TOTP. With this, we have only a single file we need to distribute to each PSM.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> CyberArk.PSM.WebAppDispatcher.PreconnectUtils;
<span class="hljs-keyword">using</span> OtpNet;
<span class="hljs-keyword">using</span> System;
<span class="hljs-keyword">using</span> System.Collections.Generic;
<span class="hljs-keyword">using</span> System.Net;
<span class="hljs-keyword">using</span> System.Security;
<span class="hljs-keyword">using</span> System.Text;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">CyberArk.PSM.GenerateTOTPPreconnect</span>
{   

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">GenerateTOTP</span> : <span class="hljs-title">IPreconnectContract</span>
    {
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> <span class="hljs-keyword">string</span> TOTP_OUTPUT_PARAMETER = <span class="hljs-string">"MFACode"</span>;

        <span class="hljs-function"><span class="hljs-keyword">public</span> Dictionary&lt;<span class="hljs-keyword">string</span>, SecureString&gt; <span class="hljs-title">GetParameters</span>(<span class="hljs-params">Dictionary&lt;<span class="hljs-keyword">string</span>, SecureString&gt; parameters, LogUtils.WriteToLogHandler writeToLogMethod</span>)</span>
        {
            <span class="hljs-keyword">string</span> totpCode;

            <span class="hljs-keyword">try</span>
            {
                <span class="hljs-keyword">byte</span>[] totpSecretBytes = Encoding.UTF8.GetBytes(<span class="hljs-keyword">new</span> NetworkCredential(<span class="hljs-keyword">string</span>.Empty, parameters[<span class="hljs-string">"logonaccount_password"</span>]).Password);

                writeToLogMethod(<span class="hljs-string">"Generating TOTP."</span>, Consts.LOG_LEVEL_INFO);

                <span class="hljs-keyword">var</span> totp = <span class="hljs-keyword">new</span> Totp(totpSecretBytes);
                totpCode = totp.ComputeTotp();
            }
            <span class="hljs-keyword">catch</span> (KeyNotFoundException ex)
            {
                writeToLogMethod(<span class="hljs-keyword">string</span>.Format(<span class="hljs-string">"Key not found in parameters dictionary: {0}"</span>, ex.ToString()), Consts.LOG_LEVEL_ERROR);
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> PreconnectException(<span class="hljs-string">"Key not found in parameters dictionary"</span>);
            }
            <span class="hljs-keyword">catch</span> (Exception ex)
            {
                writeToLogMethod(<span class="hljs-keyword">string</span>.Format(<span class="hljs-string">"Error while generating TOTP: {0}"</span>, ex.ToString()), Consts.LOG_LEVEL_ERROR);
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> PreconnectException(<span class="hljs-string">"Error while generating TOTP"</span>);
            }

            writeToLogMethod(<span class="hljs-string">"Successfully generated TOTP."</span>, Consts.LOG_LEVEL_INFO);

            <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Dictionary&lt;<span class="hljs-keyword">string</span>, SecureString&gt; {
                {
                    TOTP_OUTPUT_PARAMETER,
                    <span class="hljs-keyword">new</span> NetworkCredential(<span class="hljs-keyword">string</span>.Empty, totpCode).SecurePassword
                }
            };
        }
    }
}
</code></pre>
<center><i>Note: I am not a C# developer so there is room for improvement!</i></center>

<p>After compiling the DLL and moving it to the <code>Components</code> folder of our PSM, we are ready to leverage it with the Webapp PSM connector.</p>
<p>You can find the code and Visual Studio project, including <a target="_blank" href="https://github.com/aaearon/CyberArk.PSM.GenerateTOTPPreconnect/releases/">a compiled DLL</a>, in my GitHub repository below:</p>
<p><a target="_blank" href="https://github.com/aaearon/CyberArk.PSM.GenerateTOTPPreconnect">https://github.com/aaearon/CyberArk.PSM.GenerateTOTPPreconnect</a></p>
<h1 id="heading-configuring-the-webapp-psm-connector">Configuring the Webapp PSM Connector</h1>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">This was tested in a PAM self-hosted environment with Vault version 13.0 and PSM 12.2. The 12.2 PSM is the <strong>second PSM</strong> installed, after one running version 13.0.</div>
</div>

<p>With the compiled DLL copied to all PSMs,</p>
<ol>
<li><p>Ensure that the Webapp PSM connector has <code>LogonAccount</code> added under Component Parameters &gt; Target Settings &gt; Supported Capabilities.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697112511393/ecdea847-3a62-459d-8758-27d026757569.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Add the required parameters <code>PreConnectDllName</code> and <code>PreConnectParameters</code> as described in <a target="_blank" href="https://docs.cyberark.com/PAS/12.2/en/Content/PASIMP/psm_WebApplication_Preconnect.htm?tocpath=Developer%7CCreate%20extensions%7CPSM%20Connectors%7CWeb%20applications%20for%20PSM%7C_____1#Configurethewebapplication">step three</a>. For <code>PreConnectDllName</code>, specify the name of the DLL (<code>CyberArk.PSM.GenerateTOTPPreconnect.dll</code> if downloaded from my GitHub repository) and for <code>PreConnectParameters</code> provide just <code>logonaccount_password</code>.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697112557290/cf71ddcf-f383-4f28-b323-e748abe5614e.png" alt="a screenshot of the PreConnectDllName parameter" class="image--center mx-auto" /></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697112580674/f51f78b5-26d2-4841-9dc7-47473d84a600.png" alt="a screenshot of the PreConnectParameters parameter" class="image--center mx-auto" /></p>
</li>
<li><p>In <code>WebFormFields</code>, use <code>&amp;MfaCode&amp;</code> to inject the TOTP in the desired input field.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697112762884/229028a2-6838-4883-88f6-5c77be306695.png" alt class="image--center mx-auto" /></p>
</li>
</ol>
<p>Connecting with our Webapp PSM connector, the TOTP is generated based on a secret that is stored as the password for the account's linked logon account.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697112416490/ea46cec0-f74c-4159-abd7-deb0f3d124d9.png" alt="a screenshot showing the generated TOTP being injected" class="image--center mx-auto" /></p>
<p>The code and a compiled DLL can be found at <a target="_blank" href="https://github.com/aaearon/CyberArk.PSM.GenerateTOTPPreconnect">https://github.com/aaearon/CyberArk.PSM.GenerateTOTPPreconnect</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Exploring CyberArk Central Policy Manager's WSChains]]></title><description><![CDATA[If you have downloaded newer CPM plug-ins from the CyberArk Marketplace, you may have noticed that some deviate from the typical TPC or C#-based implementations and instead leverage a new WSChains DLL with an accompanying XML file. From the plug-in d...]]></description><link>https://timschindler.blog/exploring-cyberark-central-policy-managers-wschains</link><guid isPermaLink="true">https://timschindler.blog/exploring-cyberark-central-policy-managers-wschains</guid><category><![CDATA[cyberark]]></category><category><![CDATA[#cybersecurity]]></category><category><![CDATA[wschains]]></category><dc:creator><![CDATA[Tim Schindler]]></dc:creator><pubDate>Tue, 29 Aug 2023 19:36:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/K8iHtzoIKQ4/upload/f692feb7eba8c32e71c3ddf7a30a8d07.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you have downloaded newer CPM plug-ins from the CyberArk Marketplace, you may have noticed that some deviate from the typical TPC or C#-based implementations and instead leverage a new WSChains DLL with an accompanying XML file. From the plug-in descriptions and the XML file content, WSChains is used when communicating with a REST API.</p>
<p>Scouring the CyberArk technical community and official documentation results in practically zero information about WSChains but studying the XML files of the Marketplace plug-ins utilizing it provides a fair amount of information about this extremely powerful and flexible plug-in type.</p>
<h1 id="heading-first-look-at-wschains">First look at WSChains</h1>
<p>Like the Secure Web App Framework, WSChains is declarative in nature. It consists of two files: an XML file and the WSChains DLL that acts on it and the contents within.</p>
<p>In the XML, there are four top elements:</p>
<ul>
<li><p>GlobalSettings</p>
</li>
<li><p>Requests</p>
</li>
<li><p>Fails</p>
</li>
<li><p>Chains</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693337186551/40b587a9-9d66-44f4-99ed-7f268e1703c8.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-globalsettings">GlobalSettings</h2>
<p>Of the three plug-ins I looked at, only one had a child <code>GlobalSetting</code> elements in <code>GlobalSettings</code>.</p>
<p>In the <a target="_blank" href="https://cyberark-customers.force.com/mplace/s/#a352J0000003MQpQAM-a392J000002KqxrQAC">Cisco ISE Internal Users via API</a> plug-in, the <code>GlobalSetting</code> element and the comment imply that the <code>Address</code> property of the account is used as the base URL for all the subsequently defined <code>Request</code> elements.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693331980239/9f5fbbef-6f7e-48f9-aeb6-064505587e71.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-requests">Requests</h2>
<p>The <code>Requests</code> section is made up of <code>RequestWrapper</code> child elements with each <code>RequestWrapper</code> element representing an API call that can be made as part of a later-defined chain.</p>
<p>Within each <code>RequestWrapper</code> there is a <code>Request</code> element where the relative URL, HTTP method, headers, and request body can be defined as child elements. Seemingly, the base URL and the relative URL are automatically concatenated to create the full URL.</p>
<p>Account properties -- including those of the linked logon and reconcile accounts -- are available to use inside the elements of the <code>Request</code> element.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693332383760/aa201e1f-6211-4632-9372-066ce035fe22.png" alt class="image--center mx-auto" /></p>
<p>The <a target="_blank" href="https://cyberark-customers.force.com/mplace/s/#a352J000000WUKqQAO-a392J0000013eWLQAY">Tenable.io - CPM</a> plug-in shows that the body can contain JSON when inside a <code>json</code> element.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693334512457/c22b6a37-dc74-47f1-91b0-d52241461b43.png" alt class="image--center mx-auto" /></p>
<p>When the <code>HTTP POST</code> request sends form data and not <code>json</code>, the <code>Request</code> element can look like:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">request</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"PasswordChange"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>2<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">general</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">endpoint</span>&gt;</span>/authentication/users/{{username}}<span class="hljs-tag">&lt;/<span class="hljs-name">endpoint</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">method</span>&gt;</span>POST<span class="hljs-tag">&lt;/<span class="hljs-name">method</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">general</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">header</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Content-Type</span>&gt;</span>application/x-www-form-urlencoded<span class="hljs-tag">&lt;/<span class="hljs-name">Content-Type</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Authorization</span>&gt;</span>Basic {{username}}:{{pmpass}}<span class="hljs-tag">&lt;/<span class="hljs-name">Authorization</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">text</span>&gt;</span>
          oldpassword={{pmpass}}<span class="hljs-symbol">&amp;amp;</span>password={{pmnewpass}}
        <span class="hljs-tag">&lt;/<span class="hljs-name">text</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">request</span>&gt;</span>
</code></pre>
<p>It is important to remember when sending multiple key-value paris in the <code>body</code> element, that the ampersand must be escaped as <code>&amp;amp;</code>!</p>
<h2 id="heading-fails">Fails</h2>
<p>The <code>Fails</code> section is made up of one or more child <code>Fail</code> elements, which are similar to what you would find in a TPC-based plug-in's Process file.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693332738718/1c8d2820-d78c-4c07-831b-d2772bed635e.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-chains">Chains</h2>
<p>The <code>Chains</code> section is where it all comes together.</p>
<p>Each CPM operation has its own <code>Chain</code> element consisting of one or more child <code>Link</code> elements. Each <code>Link</code> element has <code>name</code> and <code>request</code> attributes, where the <code>request</code> attribute's value is the name of a <code>Request</code> element to be executed.</p>
<p><code>Link</code> elements contain child <code>StatusCode</code> elements, each with a <code>value</code> attribute whose value corresponds to a HTTP status code and a <code>next</code> attribute's value that corresponds to the name of a <code>Fail</code> element or a sibling <code>Link</code> element in the same <code>Chain</code> to be executed when the HTTP status code is matched.</p>
<p>In the case that the value for <code>next</code> is <code>END</code>, similar to a Process file, the operation is considered to have been completed successfully.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693335436466/e991657a-fdf0-4936-858c-037e640b8c2e.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-statuscode">StatusCode</h3>
<p>The <code>StatusCode</code> elements are particularly interesting.</p>
<p>The content returned in a response's body can be parsed and stored as a variable to be used in <code>Request</code> elements, such as in the case where a session token must first be generated and used as part of subsequent requests.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693335606201/d5d70579-8f0a-4546-9873-e32d73bd5763.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693335693321/60315a9a-df24-489f-a581-828f9c26b96c.png" alt /></p>
<p>In the case that the next <code>Link</code> depends not just on the HTTP status code returned but also on the content of a response's body, <code>StatusCode</code> elements can have child <code>Parse</code> elements that in turn have child <code>Equals</code> elements. The <code>Equals</code> elements have <code>next</code> attributes that operate the same as they do with <code>StatusCode</code> elements.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693335976356/9491decc-c7d1-4591-a5df-2773eed07977.png" alt class="image--center mx-auto" /></p>
<p><code>Equals</code> elements are also powerful as the <a target="_blank" href="https://cyberark-customers.force.com/mplace/s/#a35Ht000001VhyWIAS-a39Ht0000032Gw5IAE">Hitachi Administrator Link CS</a> plug-in shows that they can be used to query an asynchronous job's status.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Equals</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"*"</span> <span class="hljs-attr">incrementcounter</span>=<span class="hljs-string">"PollJobCounter"</span> <span class="hljs-attr">maxcounter</span>=<span class="hljs-string">"{{maxretries}}"</span>  <span class="hljs-attr">maxcounterreached</span>=<span class="hljs-string">"FAILStorageOnboardingFailedChange"</span> <span class="hljs-attr">sleep</span>=<span class="hljs-string">"{{sleepseconds}}"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"CheckJobIdStatusLink"</span> /&gt;</span>
</code></pre>
<h1 id="heading-wrap-up">Wrap-up</h1>
<p>With an understanding of CPM plug-ins and REST APIs, it is relatively easy to create your own WSChains-based plug-ins using existing plug-ins as references, even without official documentation. However, since it appears to be an internal-use-only framework for CyberArk, it is not suitable for use in a production setting.</p>
<p>Hopefully, CyberArk will choose to support WSChains in the same way they do TPC and the Web App Frameworks, and provide official documentation. WSChains is powerful in its current state and could potentially replace the need for C# or TPC-based plug-ins developed to interact with REST APIs.</p>
]]></content:encoded></item><item><title><![CDATA[Creating a Universal iDRAC CyberArk PSM Connector with if-else Conditional Statements]]></title><description><![CDATA[With the version 13.2 release of the Secure Web Application Connectors Framework for the CyberArk Privileged Session Manager, if-else conditional statements can be used in WebForm fields which can be used to build more resilient connectors and allow ...]]></description><link>https://timschindler.blog/creating-a-universal-idrac-cyberark-psm-connector-with-if-else-conditional-statements</link><guid isPermaLink="true">https://timschindler.blog/creating-a-universal-idrac-cyberark-psm-connector-with-if-else-conditional-statements</guid><category><![CDATA[cyberark]]></category><category><![CDATA[privileged-session-manager]]></category><category><![CDATA[#cybersecurity]]></category><category><![CDATA[dell]]></category><category><![CDATA[idrac]]></category><dc:creator><![CDATA[Tim Schindler]]></dc:creator><pubDate>Sun, 09 Jul 2023 20:38:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/1kvdplxe_pA/upload/f467456a5fb7a32a253dc22524ac6719.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>With the version 13.2 release of the <a target="_blank" href="https://cyberark-customers.force.com/mplace/s/#a3550000000EiCMAA0-a3950000000jjUwAAI">Secure Web Application Connectors Framework</a> for the CyberArk Privileged Session Manager, <code>if-else</code> conditional statements can be used in WebForm fields which can be used to build more resilient connectors and allow a single PSM connector to be used for different versions of an application that have different web elements.</p>
<p>Using <code>if-else</code> conditional statements, we will build a PSM connector that will cover both iDRAC versions 8 and 9.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Version 13.2 of the <a target="_blank" href="https://cyberark-customers.force.com/mplace/s/#a352J000000WU3aQAG-a392J0000013WwCQAU">Web Application CPM Plugin Framework</a> offers the same functionality.</div>
</div>

<h1 id="heading-the-frameworks-conditional-statements">The Framework's Conditional Statements</h1>
<p>The <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/Latest/en/Content/PASIMP/psm_WebApplication.htm?tocpath=Developer%7CCreate%20extensions%7CPSM%20Connectors%7C_____2#Webformfields">documentation for conditional statements in the Secure Web Application Connectors Framework</a> is pretty comprehensive, if not a bit overwhelming in parts.</p>
<p>It offers the standard conditional statements found in other programming languages (<code>if</code>, <code>else-if</code>, <code>else</code>) as well as conditional operators (<code>or</code>, <code>and</code>) that can be used between conditions.</p>
<p>It allows the following actions:</p>
<ul>
<li><p>Exists - verifies if an element exists on the page with operators <code>true</code> and <code>false</code>.</p>
</li>
<li><p>Count - counts how many elements exist on the page with operators <code>eq</code>, <code>ne</code>, <code>gt</code>,<code>ge</code>, <code>lt</code>, and <code>le</code>.</p>
</li>
<li><p>Placeholder - allows for usage of account properties or <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/Latest/en/Content/PASIMP/psm_WebApplication.htm?tocpath=Developer%7CCreate%20extensions%7CPSM%20Connectors%7C_____2#Preconnectcustomcode">Preconnect</a> return values with the single operator <code>eq</code>.</p>
</li>
</ul>
<p>Using a conditional statement in <code>WebFormFields</code> would look something like:</p>
<pre><code class="lang-ini">if ( (Login &gt; (Condition) (<span class="hljs-attr">searchby</span>=text) (exists eq <span class="hljs-literal">true</span>)) )
    username &gt; {Username}
    password &gt; {password}
    submit &gt; (Click)
if-end
</code></pre>
<p><code>(Condition)</code> is similar to <code>(Click)</code>, <code>(Validation)</code>, and <code>{accountProperty}</code> in that it informs the Framework on how to interpret the statement.</p>
<h1 id="heading-creating-a-universal-idrac-psm-connector">Creating a Universal iDRAC PSM Connector</h1>
<p>A Universal iDRAC PSM connector makes for a simple example of using conditional statements but is also practical as iDRACs can be upgraded without CyberArk administrators being informed and while an account can be assigned a platform with PSM connectors for all possible iDRAC versions, it may not be known to the end user which one to use.</p>
<p>Our Universal iDRAC PSM Connector will work for versions 8 and 9 and due to the nature of iDRACs (being a part of enterprise-grade, physical hardware), you may not have all or any of the versions available so we will use publically exposed (!) iDRAC interfaces:</p>
<ul>
<li><p>iDRAC8 -&gt; <a target="_blank" href="https://103.215.176.114/">https://103.215.176.114/</a></p>
</li>
<li><p>iDRAC9 -&gt; <a target="_blank" href="https://www.deos.lr.tudelft.nl/restgui/start.html">https://</a><a target="_blank" href="http://www.deos.lr.tudelft.nl">www.deos.lr.tudelft.nl</a></p>
</li>
</ul>
<p>These are not iDRACs we have access to and therefore we do not have valid credentials for but it is enough for us to build our PSM connector. We will know if our Universal iDRAC connector works or not because we will not receive any messages about elements not being found but rather error messages that the login or password is invalid.</p>
<h2 id="heading-connectors-for-each-version-idrac-version">Connectors for each version iDRAC version</h2>
<p>As the point is to use conditional statements, we will assume we already have a functioning PSM connector for both iDRAC versions and that all we need to do is to combine them into a single one.</p>
<p>If you don't have working PSM connectors for each version, you can <a target="_blank" href="https://cyberark-customers.force.com/mplace/s/#--Dell-CyberArk_Solution__c-PrivilegedSessionManagement">import from the Marketplace</a> or use the following WebFormFields (taken from the Marketplace connectors minus validations) when creating your own:</p>
<h3 id="heading-idrac8">iDRAC8</h3>
<pre><code class="lang-ini">user &gt; {username}(<span class="hljs-attr">searchby</span>=id)
password &gt; {password}(<span class="hljs-attr">searchby</span>=id)
submit_lbl &gt; (Button)(<span class="hljs-attr">searchby</span>=id)
</code></pre>
<h3 id="heading-idrac9">iDRAC9</h3>
<pre><code class="lang-ini">username &gt; {username}(<span class="hljs-attr">searchby</span>=name)
(<span class="hljs-attr">wait</span>=<span class="hljs-number">2</span>)
password &gt; (Button)(<span class="hljs-attr">searchby</span>=name)
password &gt; {password}(<span class="hljs-attr">searchby</span>=name)
cux-button &gt; (Button)(<span class="hljs-attr">searchby</span>=class)
</code></pre>
<h1 id="heading-creating-the-universal-idrac-connector">Creating the Universal iDRAC Connector</h1>
<p>Before defining our WebFormFields using the <code>if-else</code> statements, we need to determine the logic we will use.</p>
<p>As we are only considering two versions, we need only a single <code>if</code> statement to identify a specific iDRAC version and an <code>else</code> statement to handle the other. We only need to develop a condition for the <code>if</code> statement as <code>else</code> does not take one.</p>
<p>Version 8 of iDRAC uses a lot of JavaScript that updates the DOM and elements may not be found by the Framework so we will build a condition for our <code>if</code> statement that identifies version 9 of iDRAC and <code>if</code> the condition is not met, assume version 8 in our <code>else</code> section.</p>
<p>In addition, versions 8 and 9 of iDRAC have different paths for their login forms (8: <code>/login.html</code>, 9: <code>/restgui/start.html</code> ) so when the <code>if</code> condition is not met and the version is 8, we need to first navigate to the correct path in our <code>else</code> section.</p>
<p>With all the above considered, the WebFormFields for our Universal iDRAC connector looks like:</p>
<pre><code class="lang-ini">if ( (Integrated Dell Remote Access Controller 9 &gt; (Condition) (<span class="hljs-attr">searchby</span>=text) (exists eq <span class="hljs-literal">true</span>)) )
    username &gt; {username}(<span class="hljs-attr">searchby</span>=name)
    (<span class="hljs-attr">wait</span>=<span class="hljs-number">2</span>)
    password &gt; (Button)(<span class="hljs-attr">searchby</span>=name)
    password &gt; {password}(<span class="hljs-attr">searchby</span>=name)
    cux-button &gt; (Button)(<span class="hljs-attr">searchby</span>=class)
end-if
else
    (<span class="hljs-attr">Navigate</span>=https://{address}/login.html)
    user &gt; {username}(<span class="hljs-attr">searchby</span>=id)
    password &gt; {password}(<span class="hljs-attr">searchby</span>=id)
    submit_lbl &gt; (Button)(<span class="hljs-attr">searchby</span>=id)
end-else
</code></pre>
<p>The condition for our <code>if</code> statement is simple: we search for the text <code>Integrated Dell Remote Access Controller 9</code> and if found, treat it as iDRAC version 9. If not, treat it as version 8 but navigate to the version 8-specific login form first.</p>
<p>After cloning the <code>PSM-DellDRAC9</code> PSM connector, setting a unique PSM connector ID and appropriate connector display name, <code>RunValidations</code> to <code>No</code> under Client Specific Target Settings and <code>EnforceCertificateValidation</code> to <code>No</code> in Web Form Settings, we can set the above as the <code>WebFormFields</code> value.</p>
<h1 id="heading-testing-the-universal-idrac-connector">Testing the Universal iDRAC Connector</h1>
<p>I've onboarded two accounts both with the username and password <code>test</code> but with different addresses: <code>103.215.176.114</code> (iDRAC8) and <code>www.deos.lr.tudelft.nl</code> (iDRAC9). Both have the same platform with only our Universal iDRAC Connector.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688933716096/03be101e-274d-4aff-ab59-8031cf059e7a.png" alt class="image--center mx-auto" /></p>
<p>Connecting to <code>103.215.176.114</code>, the result is an iDRAC error message indicating the username and password are invalid -- a success in our case!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688933971153/c9cfd83c-7580-443b-97ef-240c486c5192.png" alt class="image--center mx-auto" /></p>
<p>And with <code>www.deos.lr.tudelft.nl</code> is the same situation.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688933985669/78f15b61-ee26-44de-9d76-cc08e0e1cc3d.png" alt class="image--center mx-auto" /></p>
<p>The conditional statements introduced as part of version 13.2 PSM and CPM web application Frameworks are a nice addition. They provide flexibility that is both beneficial to CyberArk administrators and end users.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Looking for more content around CyberArk PSM Web application connectors? Check out my other post <a target="_blank" href="https://timschindler.blog/creating-a-cyberark-privileged-session-manager-connection-component-for-a-web-application">Creating a CyberArk Privileged Session Manager connection component for a web application</a>.</div>
</div>]]></content:encoded></item><item><title><![CDATA[Generating time-based one-time passwords in Privileged Session Manager WebApp Connectors]]></title><description><![CDATA[Edit: After conversing with a contact, this requires version 13.0 of the PSM no matter the version of the Secure Web Application Connectors Framework!
Edit 2: This is not officially supported but there exists an enhancement request you can vote on.
L...]]></description><link>https://timschindler.blog/generating-time-based-one-time-passwords-in-privileged-session-manager-webapp-connectors</link><guid isPermaLink="true">https://timschindler.blog/generating-time-based-one-time-passwords-in-privileged-session-manager-webapp-connectors</guid><category><![CDATA[cyberark]]></category><category><![CDATA[MFA]]></category><category><![CDATA[privileged-session-manager]]></category><category><![CDATA[#cybersecurity]]></category><dc:creator><![CDATA[Tim Schindler]]></dc:creator><pubDate>Sat, 10 Jun 2023 20:40:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/UAvYasdkzq8/upload/09d5cd67c210cf9d7a3a6d4998de787b.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Edit</strong>: After conversing with a contact, this requires version 13.0 of the PSM no matter the version of the Secure Web Application Connectors Framework!</p>
<p><strong>Edit 2</strong>: This is not officially supported but there exists <a target="_blank" href="https://cyberark-customers.force.com/s/article/Support-automatic-TOTP-injection-through-PSM-3ee5-a70">an enhancement request you can vote on</a>.</p>
<p>Like possible with CyberArk Central Policy Manager plugins for Web applications, you can generate <a target="_blank" href="https://en.wikipedia.org/wiki/Time-based_one-time_password">time-based one-time passwords</a> (TOTP) as part of a Privileged Session Manager (PSM) connector.</p>
<p>The <a target="_blank" href="https://cyberark-customers.force.com/mplace/s/#a352J000000WU3aQAG-a392J0000013WwCQAU">Web Application CPM Plugin Framework</a> enables you to <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/13.0/en/Content/Plugins/CPM_WebApplication.htm?tocpath=Developer%7CCreate%20extensions%7CCreate%20CPM%20plugins%7C_____4&amp;highlight=MFA%20code#WebFormFields">generate a TOTP as part of the WebFormFields</a> but this functionality is not advertised for PSM connectors as part of its <a target="_blank" href="https://cyberark.my.site.com/mplace/s/#a3550000000EiCMAA0-a3950000000jjUwAAI">Secure Web Application Connectors Framework</a>. Despite this, it is still possible.</p>
<p>Furthermore, we can generate a TOTP with a secret stored as a Logon Account instead of storing the secret as part of the PSM connector's WebFormFields.</p>
<h1 id="heading-generating-a-totp-in-webformfields">Generating a TOTP in WebFormFields</h1>
<p>Generating a TOTP for a Webapp PSM connector is done the same way as with a CPM plugin for a Webapp. You can copy and paste right out of Example 3 in the Web Form Fields section of the <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/13.0/en/Content/Plugins/CPM_WebApplication.htm?tocpath=Developer%7CCreate%20extensions%7CCreate%20CPM%20plugins%7C_____4#WebFormFields">CPM plugin for Web applications documentation</a>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1686426520121/c459b6c4-2aaa-408a-8b08-380d6f925045.png" alt class="image--center mx-auto" /></p>
<p>Launching the PSM connector, the OTP is generated and injected successfully:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1686426633892/b7bef5e4-90cd-4f3f-8599-57b20e53fa1a.png" alt class="image--center mx-auto" /></p>
<p>But hardcoding the TOTP secret as shown in the example doesn't make sense as the secret will be different for each account and the secret itself should be kept as secure as the password.</p>
<h1 id="heading-the-totp-secret-as-a-logon-account">The TOTP secret as a Logon Account</h1>
<p>The best way to securely store the TOTP secret is to onboard it as an account. Afterward, we will use that account as the Logon Account from which we will retrieve the TOTP secret.</p>
<p>The account the TOTP secret is onboarded with can have any platform, address, username, etc. CPM management can be disabled unless you have a CPM plugin that can rotate the TOTP secret itself.</p>
<p>After linking the TOTP secret as a Logon Account, we can tweak our <code>WebFormFields</code> to generate the TOTP using the Logon Account's password using the <code>{logonaccount\password}</code> property instead of a hardcoded value:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1686427678662/33cd5519-95b4-4578-b173-056c4acb8961.png" alt class="image--center mx-auto" /></p>
<p>Before we test our new PSM connector, we need to add <code>LogonAccount</code> to the Supported Capabilities under the Target Settings of the PSM connector:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1686427978870/689fc6a0-192a-4fb1-abdc-b18ec397f9a3.png" alt class="image--center mx-auto" /></p>
<p>If we neglect to add <code>LogonAccount</code>, then the Logon Account's properties and password will not be available to the PSM connector and we receive an error:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1686428135483/5d18727a-5a97-4651-b67e-1f67139bc22f.png" alt class="image--center mx-auto" /></p>
<p>With the <code>WebFormFields</code> updated and the capability added, the PSM connector launches, generates a TOTP based on the secret stored in the password of the Logon Account, and injects it successfully:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1686428358229/387c19a0-bc09-421e-b919-cb937139f5ef.png" alt class="image--center mx-auto" /></p>
<p>We see the username of the Logon Account in the details of the PSM Connect activity:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1686428656605/522b3393-7632-46b7-bf9d-0384653d915c.png" alt class="image--center mx-auto" /></p>
<p>And we see a Connect in the activities of the Logon Account:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1686428692063/d19f1549-8ad4-492e-9d6a-aa3cc09fb810.png" alt class="image--center mx-auto" /></p>
<p>It's not clear why this PSM connector functionality is undocumented by CyberArk. Maybe it is not supported or the documentation was simply overlooked?</p>
<p>Either way, just like generating a TOTP as part of a CPM plugin may be needed in some scenarios, the same goes for PSM connectors.</p>
]]></content:encoded></item><item><title><![CDATA[Solution to Network Connectivity Issues after the Hardening of the CyberArk PAM self-hosted's Vault on Virtualized Hardware]]></title><description><![CDATA[The CyberArk PAM self-hosted Vault is not typically installed on virtualized hardware but some cases make the scenario justified -- one of them being when standing up a lab environment.
When using Hyper-V (and potentially other hypervisors), after ha...]]></description><link>https://timschindler.blog/solution-to-network-connectivity-issues-after-the-hardening-of-the-cyberark-pam-self-hosteds-vault-on-virtualized-hardware</link><guid isPermaLink="true">https://timschindler.blog/solution-to-network-connectivity-issues-after-the-hardening-of-the-cyberark-pam-self-hosteds-vault-on-virtualized-hardware</guid><category><![CDATA[cyberark]]></category><category><![CDATA[Vault]]></category><category><![CDATA[firewall]]></category><category><![CDATA[windows defender firewall]]></category><category><![CDATA[Security]]></category><dc:creator><![CDATA[Tim Schindler]]></dc:creator><pubDate>Fri, 05 May 2023 17:12:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/40XgDxBfYXM/upload/91b006ea54c39b1597d0426e5555995c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The CyberArk PAM self-hosted Vault is not typically installed on virtualized hardware but some cases make the scenario justified -- <a target="_blank" href="https://timschindler.blog/effortlessly-setting-up-a-cyberark-pam-self-hosted-lab-with-automatedlab">one of them being when standing up a lab environment</a>.</p>
<p>When using Hyper-V (and potentially other hypervisors), after hardening the Vault during the installation process, the Vault may lose network connectivity upon subsequent reboot. This results in the inability to establish inbound or outbound connections for all services.</p>
<p>This issue arises because the Windows Firewall rules created by the CyberArk Vault servers do not take effect, necessitating minor adjustments to two registry keys to resolve the problem.</p>
<h1 id="heading-determining-if-your-vault-is-impacted">Determining if your Vault is impacted</h1>
<p>The easiest way to tell if you are being impacted by this is by checking the Vault machine's network connectivity. This can be done using <a target="_blank" href="https://learn.microsoft.com/en-us/powershell/module/nettcpip/test-netconnection?view=windowsserver2019-ps"><code>Test-NetConnection</code></a> and seeing that outbound connections to all services such as Active Directory and the NTP server fail.</p>
<p>Another way is checking the Firewall pane under the Monitoring section in the Windows Defender Firewall snap-in.</p>
<p>This pane shows the rules in effect and when the Vault services are running, four rules representing the allowing of outbound and inbound Vault-related traffic should be showing. If it is empty, then there are no effective rules and no inbound or outbound traffic is allowed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683305255552/e3eee1d8-bc48-4aae-8227-acba449ee291.png" alt="A screenshot of an empty Windows Firewall Defender Firewall pane" class="image--center mx-auto" /></p>
<center>A screenshot of the Firewall pane when there is no network connectivity.</center>

<h1 id="heading-the-fix">The Fix</h1>
<p>The fix is straightforward. You simply need to change the two following Windows Defender Firewall-related registry keys from <code>0</code> to <code>1</code>:</p>
<ul>
<li><p><code>HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\WindowsFirewall\PublicProfile\AllowLocalIPsecPolicyMerge</code></p>
</li>
<li><p><code>HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\WindowsFirewall\PublicProfile\AllowLocalPolicyMerge</code></p>
</li>
</ul>
<p>Afterward, reboot the Vault machine. Rules will show up in the Firewall pane and network connectivity will be restored.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683304713006/9e90581f-75b4-4f57-a0ca-42ea146fec7b.png" alt="A screenshot of a populated Windows Firewall Defender Firewall pane" class="image--center mx-auto" /></p>
<center>A screenshot of the Firewall pane when network connectivity is working.</center>

<h1 id="heading-what-is-happening-as-part-of-the-hardening">What is happening as part of the hardening?</h1>
<p>These keys control if locally defined Windows Defender Firewall rules -- such as the ones created by the Vault when the service starts -- should take effect or not and as part of the Vault hardening, these registry keys are explicitly set to <code>0</code> for the Public profile and <code>1</code> for the Private profile. It is likely that the intention is for the Vault to take on the Private profile but is for an unknown reason assuming the Public profile.</p>
<p>Even CyberArk is uncertain about why this occurs, as when the Vault hardening process identifies that the Vaults are in AWS, CyberArk explicitly sets both keys to <code>1</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683305824007/2d8e1ebb-855d-41b5-8086-916ec15c27b2.png" alt="An exerpt from the HardenWindowsFireWall.ps1 script found in Server-Rls-x.x/Hardening/Scripts" class="image--center mx-auto" /></p>
<center>An exerpt from the HardenWindowsFireWall.ps1 script found in Server-Rls-x.x/Hardening/Scripts</center>]]></content:encoded></item><item><title><![CDATA[Effortlessly Setting Up A CyberArk PAM Self-Hosted Lab with AutomatedLab]]></title><description><![CDATA[If you want to learn CyberArk PAM self-hosted without risking your production environment or experiment with different configurations and test scenarios, setting up a personal lab environment is a great option. However, installing the Vault and its c...]]></description><link>https://timschindler.blog/effortlessly-setting-up-a-cyberark-pam-self-hosted-lab-with-automatedlab</link><guid isPermaLink="true">https://timschindler.blog/effortlessly-setting-up-a-cyberark-pam-self-hosted-lab-with-automatedlab</guid><category><![CDATA[cyberark]]></category><category><![CDATA[Security]]></category><category><![CDATA[#cybersecurity]]></category><category><![CDATA[Powershell]]></category><category><![CDATA[automatedlab]]></category><dc:creator><![CDATA[Tim Schindler]]></dc:creator><pubDate>Mon, 24 Apr 2023 06:09:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/qwtCeJ5cLYs/upload/7d3b14233a83ad753ec4dc80e4a4d451.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you want to learn CyberArk PAM self-hosted without risking your production environment or experiment with different configurations and test scenarios, setting up a personal lab environment is a great option. However, installing the Vault and its components can take up valuable time. Fortunately, with <a target="_blank" href="https://automatedlab.org/">AutomatedLab</a> and <a target="_blank" href="https://github.com/aaearon/AutomatedLab.CyberArk">custom CyberArk roles</a>, you can automate the entire process and focus on more important tasks. In this article, you will see how to deploy a CyberArk PAM self-hosted lab environment using AutomatedLab with Active Directory Domain Services.</p>
<h1 id="heading-what-is-automatedlab">What is AutomatedLab?</h1>
<p>"AutomatedLab is a provisioning solution and framework that lets you deploy complex labs on HyperV and Azure with simple PowerShell scripts. It supports all Windows operating systems from 2008 R2 to 2022, some Linux distributions and various products like AD, Exchange, PKI, IIS, etc." [0]</p>
<p>AutomatedLab will provision virtual machines as well as install and configure the operating system. It provides PowerShell cmdlets by which you can easily install your software.</p>
<p>You can assign roles to machines which will result in services like Active Directory Domain Services, Exchange, or IIS being installed. <a target="_blank" href="https://automatedlab.org/en/latest/Wiki/Roles/roles/">AutomatedLab also provides methods for creating your own custom roles</a>.</p>
<p>[0] <a target="_blank" href="https://github.com/AutomatedLab/AutomatedLab">https://github.com/AutomatedLab/AutomatedLab</a></p>
<h1 id="heading-deploying-cyberark-pam-self-hosted-with-automatedlab">Deploying CyberArk PAM self-hosted with AutomatedLab</h1>
<p>AutomatedLab has a bit of a learning curve but provides <a target="_blank" href="https://automatedlab.org/en/latest/Wiki/Basic/gettingstarted/">documentation to help you get started</a>. The remainder of this article assumes you have successfully installed your first lab as described in the documentation.</p>
<p>In addition, you need the installation archives from the <a target="_blank" href="https://cyberark-customers.force.com/mplace/s/#software">CyberArk Marketplace</a> as well as Operator/Master keys and a license file. These can be placed anywhere on your local machine but I have stored them under <code>$LabSources\CyberArkInstallFiles</code>.</p>
<h2 id="heading-installing-the-custom-cyberark-roles">Installing the custom CyberArk roles</h2>
<p>Installation is nothing more than copying the folders representing the roles to <code>$LabSources\CustomRoles</code> .</p>
<ol>
<li><p>Check out the repository at <a target="_blank" href="https://github.com/aaearon/AutomatedLab.CyberArk">https://github.com/aaearon/AutomatedLab.CyberArk</a></p>
<p> %[https://github.com/aaearon/AutomatedLab.CyberArk] </p>
</li>
<li><p>Copy all the folders starting with <code>PAM</code> to <code>$LabSources\CustomRoles</code> . On Windows, unless you changed it during the installation of AutomatedLab, <code>C:\LabSources</code> is used for <code>$LabSources</code>.</p>
</li>
</ol>
<p>The Vault and each component have their folder. <code>PAMCommon</code> contains common functions used among all the roles.</p>
<h2 id="heading-creating-the-lab-definition">Creating the lab definition</h2>
<p>We will use Hyper-V running locally and our lab will consist of four machines:</p>
<ul>
<li><p>DC01 - the domain controller with Active Directory Domain Services.</p>
</li>
<li><p>VAULT01 - the CyberArk Vault.</p>
</li>
<li><p>COMP01 - the CyberArk Password Vault Web Access (PVWA) and CyberArk Central Policy Manager (CPM.)</p>
</li>
<li><p>PSM01 - the CyberArk Privileged Session Manager (PSM.)</p>
</li>
</ul>
<p>We will have a switch with a defined address space to segregate our lab from other Hyper-V machines.</p>
<p>First, we create the lab definition, add our switch, and set some defaults so we do not need to repeat ourselves when defining the machines:</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">New-LabDefinition</span> <span class="hljs-literal">-Name</span> PAM <span class="hljs-literal">-DefaultVirtualizationEngine</span> HyperV <span class="hljs-literal">-VmPath</span> C:\AutomatedLab<span class="hljs-literal">-VMs</span>

<span class="hljs-built_in">Add-LabVirtualNetworkDefinition</span> <span class="hljs-literal">-Name</span> PAM <span class="hljs-literal">-AddressSpace</span> <span class="hljs-number">192.168</span>.<span class="hljs-number">0.0</span>/<span class="hljs-number">24</span>

<span class="hljs-variable">$PSDefaultParameterValues</span> = <span class="hljs-selector-tag">@</span>{
    <span class="hljs-string">'Add-LabMachineDefinition:OperatingSystem'</span>      = <span class="hljs-string">'Windows Server 2019 Datacenter Evaluation (Desktop Experience)'</span>
    <span class="hljs-string">'Add-LabMachineDefinition:Memory'</span>               = <span class="hljs-number">4</span>GB
    <span class="hljs-string">'Add-LabMachineDefinition:Network'</span>              = <span class="hljs-string">'PAM'</span>
}
</code></pre>
<p>Then define our domain controller with the 'RootDC' role and set the <code>DomainName</code> parameter to a domain name of our choice:</p>
<pre><code class="lang-powershell"><span class="hljs-variable">$DC01MachineProperties</span> = <span class="hljs-selector-tag">@</span>{
    Name = <span class="hljs-string">'DC01'</span>
    Roles = <span class="hljs-string">'RootDC'</span>
    DomainName = <span class="hljs-string">'acme.corp'</span>
}

<span class="hljs-built_in">Add-LabMachineDefinition</span> @DC01MachineProperties
</code></pre>
<h2 id="heading-defining-the-pam-self-hosted-machines">Defining the PAM-self hosted machines</h2>
<p>For each of our CyberArk PAM self-hosted lab machines, we need to define role properties that will be used when installing the CyberArk software and the properties for the machine itself -- where we will specify the custom role as a <code>PostInstallationActivity</code>.</p>
<p>All the roles require the installation archive path to be defined. The Vault necessitates two folders to be specified – one for each of the Master and Operator key sets. The license in XML format is also needed. Furthermore, we can designate the Master and Administrator account passwords, but if we do not, they will both default to <code>Cyberark1</code>.</p>
<p>The other components require at least the Vault's IP address to be defined, and optionally, the username and password of the user for installation. If neither is specified, the default values of <code>Administrator</code> and <code>Cyberark1</code> will be used.</p>
<p>All component servers will have <code>DomainName</code> defined, directing AutomatedLab to join them to our domain.</p>
<p>Our Vault is defined with:</p>
<pre><code class="lang-powershell"><span class="hljs-variable">$PAMVaultRoleProperties</span> = <span class="hljs-selector-tag">@</span>{
    InstallationArchivePath = <span class="hljs-string">'C:\LabSources\CyberArkInstallFiles\Server-Rls-v13.0.zip'</span>
    OperatorKeysFolder      = <span class="hljs-string">'C:\LabSources\CyberArkInstallFiles\DemoOperatorKeys'</span>
    MasterKeysFolder        = <span class="hljs-string">'C:\LabSources\CyberArkInstallFiles\DemoMasterKeys'</span>
    LicensePath             = <span class="hljs-string">'C:\LabSources\CyberArkInstallFiles\nfr_license.xml'</span>
}
<span class="hljs-variable">$PAMVaultRole</span> = <span class="hljs-built_in">Get-LabPostInstallationActivity</span> <span class="hljs-literal">-CustomRole</span> PAMVault <span class="hljs-literal">-Properties</span> <span class="hljs-variable">$PAMVaultRoleProperties</span>

<span class="hljs-variable">$PAMVaultMachineProperties</span> = <span class="hljs-selector-tag">@</span>{
    Name = <span class="hljs-string">'VAULT01'</span>
    IpAddress = <span class="hljs-string">'192.168.0.100'</span>
    PostInstallationActivity = <span class="hljs-variable">$PAMVaultRole</span>
}

<span class="hljs-built_in">Add-LabMachineDefinition</span> @PAMVaultMachineProperties
</code></pre>
<p>COMP01 hosts both the PVWA and CPM and is defined by:</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">Add-LabMachineDefinition</span> @PAMVaultMachineProperties

<span class="hljs-variable">$PAMPvwaRoleProperties</span> = <span class="hljs-selector-tag">@</span>{
    InstallationArchivePath = <span class="hljs-string">'C:\LabSources\CyberArkInstallFiles\Password Vault Web Access-Rls-v13.0.zip'</span>
    VaultIpAddress          = <span class="hljs-string">'192.168.0.100'</span>
}
<span class="hljs-variable">$PAMPvwaRole</span> = <span class="hljs-built_in">Get-LabPostInstallationActivity</span> <span class="hljs-literal">-CustomRole</span> PAMPvwa <span class="hljs-literal">-Properties</span> <span class="hljs-variable">$PAMPvwaRoleProperties</span>

<span class="hljs-variable">$PAMCpmRoleProperties</span> = <span class="hljs-selector-tag">@</span>{
    InstallationArchivePath = <span class="hljs-string">'C:\LabSources\CyberArkInstallFiles\Central Policy Manager-Rls-v13.0.zip'</span>
    VaultIpAddress          = <span class="hljs-string">'192.168.0.100'</span>
}
<span class="hljs-variable">$PAMCpmRole</span> = <span class="hljs-built_in">Get-LabPostInstallationActivity</span> <span class="hljs-literal">-CustomRole</span> PAMCpm <span class="hljs-literal">-Properties</span> <span class="hljs-variable">$PAMCpmRoleProperties</span>

<span class="hljs-variable">$COMP01MachineParameters</span> = <span class="hljs-selector-tag">@</span>{
    Name = <span class="hljs-string">'COMP01'</span>
    DomainName = <span class="hljs-string">'acme.corp'</span>
    PostInstallationActivity = <span class="hljs-variable">$PAMPvwaRole</span>,<span class="hljs-variable">$PAMCpmRole</span>
}

<span class="hljs-built_in">Add-LabMachineDefinition</span> @COMP01MachineParameters
</code></pre>
<p>Finally, our PSM is:</p>
<pre><code class="lang-powershell"><span class="hljs-variable">$PAMPsmRoleProperties</span> = <span class="hljs-selector-tag">@</span>{
    InstallationArchivePath = <span class="hljs-string">'C:\LabSources\CyberArkInstallFiles\Privileged Session Manager-Rls-v13.0.1.zip'</span>
    VaultIpAddress          = <span class="hljs-string">'192.168.0.100'</span>
}
<span class="hljs-variable">$PAMPsmRole</span> = <span class="hljs-built_in">Get-LabPostInstallationActivity</span> <span class="hljs-literal">-CustomRole</span> PAMPsm <span class="hljs-literal">-Properties</span> <span class="hljs-variable">$PAMPsmRoleProperties</span>

<span class="hljs-variable">$PSM01MachineParameters</span> = <span class="hljs-selector-tag">@</span>{
    Name = <span class="hljs-string">'PSM01'</span>
    DomainName = <span class="hljs-string">'acme.corp'</span>
    PostInstallationActivity = <span class="hljs-variable">$PAMPsmRole</span>
}

<span class="hljs-built_in">Add-LabMachineDefinition</span> @PSM01MachineParameters
</code></pre>
<h2 id="heading-installing-the-lab">Installing the lab</h2>
<p>As the Vault hardening removes the ability for AutomatedLab to communicate with the machine over WinRM, we cannot simply invoke <code>Install-Lab</code> rather we need to install in stages.</p>
<p>First, we do everything but the post-installation activities:</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">Install-Lab</span> <span class="hljs-literal">-BaseImages</span> <span class="hljs-literal">-NetworkSwitches</span> <span class="hljs-literal">-VMs</span> <span class="hljs-literal">-Domains</span> <span class="hljs-literal">-NoValidation</span>
</code></pre>
<p>After, we have use <code>Invoke-LabCommand</code> to install CyberArk PAM-self hosted installed.</p>
<pre><code class="lang-powershell"><span class="hljs-comment"># Perform the Vault installation. The hardening part of the installation will kill the ability to use WinRM so it will timeout and throw an error. We want to ignore that error.</span>
<span class="hljs-built_in">Invoke-LabCommand</span> <span class="hljs-literal">-ComputerName</span> VAULT01 <span class="hljs-literal">-PostInstallationActivity</span> <span class="hljs-literal">-ActivityName</span> <span class="hljs-string">'CyberArk Vault installation'</span> <span class="hljs-literal">-ErrorAction</span> SilentlyContinue
<span class="hljs-built_in">Invoke-LabCommand</span> <span class="hljs-literal">-ComputerName</span> COMP01 <span class="hljs-literal">-PostInstallationActivity</span> <span class="hljs-literal">-ActivityName</span> <span class="hljs-string">'CyberArk Pvwa and CPM installation'</span>
<span class="hljs-built_in">Invoke-LabCommand</span> <span class="hljs-literal">-ComputerName</span> PSM01 <span class="hljs-literal">-PostInstallationActivity</span> <span class="hljs-literal">-ActivityName</span> <span class="hljs-string">'CyberArk PSM installation'</span>
</code></pre>
<p>The entire lab definition can be found below:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="d584a11f09efa21091785b1bf129c6a9"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/aaearon/d584a11f09efa21091785b1bf129c6a9" class="embed-card">https://gist.github.com/aaearon/d584a11f09efa21091785b1bf129c6a9</a></div><p> </p>
<p>A little less than two hours later (depending on the resources allocated to each machine), a complete CyberArk PAM self-hosted lab environment is fully operational and ready for use.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682237396652/8598098d-437a-485f-adb8-eccd23576835.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682237332822/26018cbb-a0ae-4f5d-ad40-c7334713ca1e.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-elevating-our-lab">Elevating our lab</h1>
<p>We did little more than the bare minimum to get our lab environment off the ground however with all the different functionality AutomatedLab provides plus the ability to write custom roles, we can take it much further.</p>
<p>We could have our domain controller serve as <a target="_blank" href="https://github.com/AutomatedLab/AutomatedLab/blob/a30d1de5247b94fb6cda4c56b53e1bb2c8c559c7/Help/Wiki/SampleScripts/Introduction/en-us/09%20Web%20Servers%20with%20SSL%20certs%2C%20Root%20CA%2C%20domain%20joined.md">a certificate authority and then issue a certificate and configure the HTTPS binding in IIS.</a> We could also execute a script that creates safes and onboard accounts after the installation of the components.</p>
]]></content:encoded></item><item><title><![CDATA[Installing and configuring Utimaco's SecurityServer Simulator]]></title><description><![CDATA[In a previous blog where I integrated a HSM with the CyberArk Vault using Utimaco's SecurityServer simulator, I did not touch on at all how to install and configure it but this is arguably the toughest part of the integration.
Though the documentatio...]]></description><link>https://timschindler.blog/installing-and-configuring-utimacos-securityserver-simulator</link><guid isPermaLink="true">https://timschindler.blog/installing-and-configuring-utimacos-securityserver-simulator</guid><category><![CDATA[pkcs11]]></category><category><![CDATA[utimaco]]></category><category><![CDATA[cyberark]]></category><category><![CDATA[hsm]]></category><category><![CDATA[#cybersecurity]]></category><dc:creator><![CDATA[Tim Schindler]]></dc:creator><pubDate>Thu, 03 Nov 2022 20:06:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/y5N2HDwagVw/upload/v1667506052168/N3229-iO9.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In a <a target="_blank" href="https://timschindler.blog/using-a-hardware-security-module-to-secure-the-cyberark-vaults-server-key">previous blog where I integrated a HSM with the CyberArk Vault using Utimaco's SecurityServer simulator</a>, I did not touch on at all how to install and configure it but this is arguably the toughest part of the integration.</p>
<p>Though the documentation from <a target="_blank" href="https://utimaco.com/">Utimaco</a> is comprehensive, the sheer amount can be overwhelming so in this post I want to show how the Utimaco SecurityServer simulator can installed and configured. We will then configure Utimaco PKCS#11 provider DLL on a client, using a CyberArk Vault as an example.</p>
<h1 id="heading-installation-and-configuration-of-the-securityserver-simulator">Installation and configuration of the SecurityServer simulator</h1>
<h2 id="heading-requirements">Requirements</h2>
<p>You will need:</p>
<ul>
<li>The <a target="_blank" href="https://utimaco.com/downloads/free-simulators-and-sdks/securityserver-simulator">Utimaco SecurityServer simulator</a>. You can only download it after you register for an account in the Utimaco portal. As the simulator is meant for commercial evaluation, you should sign up with your business contact details. You may need to wait up to 48 hours until the account is activated.</li>
<li>A Windows server with a Java Development Kit installed with the Zip archive downloaded from the Utimaco portal.</li>
</ul>
<h2 id="heading-installing-the-simulator">Installing the simulator</h2>
<p>Installing the simulator is as simple as using the .msi provided in the Zip archive but be sure to choose a <strong>Complete</strong> setup type otherwise the tool needed to initialize the HSM slots will not be installed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667495470220/yD1m7TVHw.png" alt="image.png" /></p>
<p>After installation, you will have three new icons on your desktop:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667495564508/oxp9M8KIZ.png" alt="image.png" /></p>
<p>We only need the CryptoServer Simulator and the PKCS#11 CryptoServer Administration applications. <strong>If you do not see the PKCS#11 CryptoServer Administration then you did not install the Complete setup type as part of the installation.</strong></p>
<h2 id="heading-configuration-the-provider-and-initializing-the-slots">Configuration the provider and initializing the slots</h2>
<p>The simulator is our HSM. Double clicking the CryptoServer Simulator icon starts the simulator and this needs to run continuously. The PKCS#11 CryptoServer Administration tool is only used to initialize the token and slots.</p>
<p>Just like a client uses the PKCS#11 provider DLL to communicate with the HSM, so does the PKCS#11 CryptoServer Administration tool. The PKCS#11 provider DLL uses a configuration file -- <code>cs_pkcs11_R3.cfg</code> -- to know the location of the HSM. We need to change the <code>cs_pkcs11_R3.cfg</code> to point to the simulator running on the same server plus enable some logging for easier troubleshooting. </p>
<p>The provider DLL knows where to find <code>cs_pkcs11_R3.cfg</code> based on the value of the system environmental variable <code>CS_PKCS11_R3_CFG</code>. This is automatically created as part of the installation and the default location is <code>C:\ProgramData\Utimaco\PKCS11_R3\cs_pkcs11_R3.cfg</code>.</p>
<p>Opening <code>C:\ProgramData\Utimaco\PKCS11_R3\cs_pkcs11_R3.cfg</code> we want to change three things: enable the PKCS#11 provider to write logs in the same directory as the configuration file, change the log level to <code>INFO</code>, and define the address of the ‘device’ the provider connects to -- in our case, the locally-running simulator.</p>
<p>Change the following settings to have the following values, uncommenting the settings as necessary:</p>
<ol>
<li><code>Logpath = C:/ProgramData/Utimaco/PKCS11_R3</code></li>
<li><code>Logging = 3</code></li>
<li>Under a <code>[CryptoServer]</code> section that is uncommented, set <code>Device = 3001@127.0.0.1</code></li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667500299430/aRPhhaphm.png" alt="image.png" /></p>
<p>Save the changes.</p>
<p>With the the CryptoServer Simulator running, open the PKCS#11 CryptoServer Administration tool. Ten slots should be shown on the left.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667500462545/K5jHydGjv.png" alt="image.png" /></p>
<p>Click on the first slot, Login/Logout at the top, and click Login Generic. Type in <code>ADMIN</code> as the username, select <code>Keyfile</code> as the authentication token, and define <code>C:\Program Files\Utimaco\SecurityServer\Administration\ADMIN.key</code> as the Keyfile. Do not define a password.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667502244073/GSK_MH5HR.png" alt="image.png" /></p>
<p><strong>NOTE: In the latest versions of the SecurityServer, complexity requirements are being enforced for the SO and User PIN. Use strong passwords as the PINs for both the SO and User.</strong></p>
<p>Once logged in, the token needs to be initialized by setting the SO PIN. Click Slot Management at the top, expand <code>Init Token</code>, and set the SO PIN and Confirm SO PIN and click Init Token. In the Status pane, you will see the message <code>Slot 0000 0000: Token initialized.</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667502413917/DtbfmLNU0.png" alt="image.png" /></p>
<p>With the slot's token initialized, we then login with the SO PIN and initialize (another, separate PIN.)</p>
<p>As you can only be logged in with a single user at a time, hit Login/Logout, click the Logout All button, click on Login/Logout once more, and login with the SO PIN. After successful login with the SO PIN, click on Slot Management at the top and expand <code>Init PIN</code>. Define both <code>Normal User PIN</code> and <code>Confirm Normal User PIN</code>. <strong>This is the PIN that will be secured with <code>CAVaultManager SecureSecretFiles</code> when defining <code>HSMPinCode</code> in the <code>dbparm.ini</code> and used clients to access the Server key.</strong> Non-CyberArk Vault clients will also use a PIN when needing to access keys.</p>
<p>After initializing the PIN, log out once more by clicking Login/Logout and then clicking Logout All. Login as <code>User</code> by clicking on the Login/Logout button, expanding Login User, and logging in with the <code>Normal User PIN</code> you defined previously.</p>
<p>After successful authentication with the <code>Normal User PIN</code>, the Object Management button at top is clickable. Any cryptographic information (symmetric/asymmetric keys) will show up as an object in this pane but as we just initialized the PIN, it is empty.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667502914386/T4hUxsm3R.png" alt="image.png" /></p>
<p>We can generate our own keys by clicking Generate. In the case of integration with the CyberArk Vault, we will do it as part of <code>CAVaultManger GenerateKeyOnHSM /ServerKey</code>.</p>
<h1 id="heading-setting-up-the-cs-pkcs11-provider-on-the-cyberark-vault">Setting up the CS PKCS#11 Provider on the CyberArk Vault</h1>
<p>In order to use the HSM from another client, we need the PKCS#11 provider DLL and the configuration file -- just like we have it on the server running the simulator.</p>
<p>With the CyberArk Vault as our client, in order to use the SecurityServer simulator as our HSM, we need to:</p>
<ul>
<li>On the server running the SecurityServer simulator, allow inbound traffic on port <code>3001/tcp</code>.</li>
<li>Copy <code>cs_pkcs11_R3.dll</code> to the Vault. This can be found in the Zip archive in the <code>Software\Windows\x86-64\Crypto_APIs\PKCS11_R3\lib\</code> folder.</li>
<li>Copy <code>cs_pkcs11_R3.cfg</code> to the Vault.</li>
<li>Update <code>cs_pkcs11_R3.cfg</code> to reflect the proper paths on the Vault.</li>
<li>Define the system environmental variable <code>CS_PKCS11_R3_CFG</code>.</li>
</ul>
<h2 id="heading-configuring-the-cyberark-vault">Configuring the CyberArk Vault</h2>
<p>On the server running the SecurityServer simulator, add a rule that allows inbound traffic over <code>3001/tcp</code>. In a PowerShell opened as Administrator, run <code>New-NetFirewallRule -DisplayName "SecurityServerSimulator" -Direction Inbound -LocalPort 3001 -Protocol TCP -Action Allow</code>.</p>
<p>On the Vault server, create the <code>HSM</code> folder under <code>C:\Program Files (x86)\PrivateArk\Server</code>. Copy <code>cs_pkcs11_R3.dll</code>, and <code>cs_pkcs11_R3.cfg</code> into <code>C:\Program Files (x86)\PrivateArk\Server\HSM</code>.</p>
<p>Open <code>cs_pkcs11_R3.cfg</code> and set the following:</p>
<ol>
<li><code>Logpath = C:\Program Files (x86)\PrivateArk\Server\HSM</code></li>
<li>Under a <code>[CryptoServer]</code> section that is uncommented, set <code>Device = 3001@&lt;ip of the server running the SecurityServer simulator&gt;</code></li>
<li><code>SlotCount = 1</code></li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667503811990/8Hgdq6_YC.png" alt="image.png" /></p>
<p>Now edit the system environmental variables and add <code>CS_PKCS11_R3_CFG</code> with a value of <code>C:\Program Files (x86)\PrivateArk\Server\HSM\cs_pkcs11_R3.cfg</code></p>
<p>At this point the PKCS#11 provider is configured. The easiest way to test connectivity is <a target="_blank" href="https://timschindler.blog/using-a-hardware-security-module-to-secure-the-cyberark-vaults-server-key#heading-generate-the-new-server-key">trying to generate a Server key with <code>CAVaultManger GenerateKeyOnHSM /ServerKey</code></a> after <a target="_blank" href="https://timschindler.blog/using-a-hardware-security-module-to-secure-the-cyberark-vaults-server-key#heading-initial-vault-configurations">performing the initial Vault configurations for HSM integration</a>.</p>
<h1 id="heading-closing-notes">Closing notes</h1>
<ul>
<li>If you are having trouble, do not forget to look in the <code>cs_pkcs11_R3.log</code> file. Together with error messages returned by the client using the PKCS#11 provider DLL, the log contains useful information.</li>
<li>Utimaco is the only vendor I have seen provide something like the SecurityServer simulator to the public. If you are in search of a HSM solution, consider checking out their offerings as a way to say 'thanks.'</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Creating a CyberArk Privileged Session Manager connection component for a web application]]></title><description><![CDATA[After helping someone in a CyberArk Discord that I frequent with correctly identifying a button as part of a Privileged Session Manager connection component for a web application, I realized that I've never made a connection component or a CPM plugin...]]></description><link>https://timschindler.blog/creating-a-cyberark-privileged-session-manager-connection-component-for-a-web-application</link><guid isPermaLink="true">https://timschindler.blog/creating-a-cyberark-privileged-session-manager-connection-component-for-a-web-application</guid><category><![CDATA[privileged-session-manager]]></category><category><![CDATA[cyberark]]></category><category><![CDATA[Xpath]]></category><category><![CDATA[DOM]]></category><category><![CDATA[#cybersecurity]]></category><dc:creator><![CDATA[Tim Schindler]]></dc:creator><pubDate>Tue, 01 Nov 2022 18:14:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/OqtafYT5kTw/upload/v1667326410011/eTu8NHNv2.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>After helping someone in a <a target="_blank" href="https://discord.gg/daScMcyWkZ">CyberArk Discord that I frequent</a> with correctly identifying a button as part of a <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/12.6/en/Content/PASIMP/psm_WebApplication.htm">Privileged Session Manager connection component for a web application</a>, I realized that I've never made a connection component or a CPM plugin for a web application myself.</p>
<p>From experience I have from 'previous lives', I have already worked with the Document Object Model (DOM) and Selenium -- both used by the PSM and CPM web application frameworks -- so the fundamentals are already familiar but I've never done it in the context of CyberArk.</p>
<p>In this post, we will develop a Microsoft Edge-based PSM connection component for the web application <a target="_blank" href="https://www.shopizer.com/">Shopizer</a> -- an open-source eCommerce platform that is available as a container so we can focus our time on developing the connection component.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">This is article is one in a <a target="_blank" href="https://timschindler.blog/series/shopizer-admin-ext">series of articles around creating PSM connectors and CPM plug-ins</a>.</div>
</div>

<h1 id="heading-understanding-what-a-psm-connection-component-for-a-web-app-is-doing">Understanding what a PSM connection component for a web app is doing</h1>
<p>Before we start thinking about creating a PSM connection component for a web app, it makes sense to understand how they work.</p>
<p>The CyberArk <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/12.6/en/Content/PASIMP/psm_WebApplication.htm?tocpath=Developer%7CCreate%20extensions%7CPSM%20Connectors">Web applications for PSM documentation</a> does not explicitly explain how they function but reviewing the page gives us important hints: <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/12.6/en/Content/PASIMP/psm_WebApplication.htm?tocpath=Developer%7CCreate%20extensions%7CPSM%20Connectors#Supportedbrowsers">the need to have the proper (Web)Driver for the browser</a> and the <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/12.6/en/Content/PASIMP/psm_WebApplication.htm?tocpath=Developer%7CCreate%20extensions%7CPSM%20Connectors#Syntax">syntax</a> and <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/12.6/en/Content/PASIMP/psm_WebApplication.htm?tocpath=Developer%7CCreate%20extensions%7CPSM%20Connectors#WebFormFields">examples</a> for defining Web Form Fields.</p>
<p>What is a driver and what does it do? A (Web)Driver <a target="_blank" href="https://www.w3.org/TR/webdriver1/">".. is a remote control interface that enables introspection and control of user agents. It provides a platform- and language-neutral wire protocol as a way for out-of-process programs to remotely instruct the behavior of web browsers. Provided is a set of interfaces to discover and manipulate DOM elements in web documents and to control the behavior of a user agent."</a></p>
<p>We can extract another hint from the description of a WebDriver: the DOM or Document Object Model. The DOM <a target="_blank" href="https://www.w3.org/TR/WD-DOM/introduction.html">"... is a programming API for HTML and XML documents. It defines the logical structure of documents and the way a document is accessed and manipulated. ... With the Document Object Model, programmers can create and build documents, navigate their structure, and add, modify, or delete elements and content."</a></p>
<p>From the information we gleaned, we can assume that the PSM uses a WebDriver to identify and interact with elements in the DOM. It will inject usernames and passwords into input fields, click buttons to submit forms, and read resulting DOMs to perform validations to determine if the login was successful or not. The connection component's WebFormFields property is how we instruct the PSM to identify elements and what to do with them.</p>
<h1 id="heading-identifying-elements-in-the-dom">Identifying elements in the DOM</h1>
<p>We can tell CyberArk to identify elements in the DOM several ways: using the name, class, text, or XPath. <a target="_blank" href="https://www.w3schools.com/xml/xpath_intro.asp">XPath can be used to navigate through elements and attributes in an XML document.</a> XPath can also be used with an HTML document.</p>
<p>Explaining HTML elements and XPath is outside the scope of this post however let us quickly see the multiple ways we can <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/12.6/en/Content/PASIMP/psm_WebApplication.htm?tocpath=Developer%7CCreate%20extensions%7CPSM%20Connectors#Syntax">identify elements in a connection component's WebFormsField using its syntax</a> with a simplified DOM:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">form</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"i_username"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"username"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"input_field"</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Enter your username"</span>/&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">br</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"i_password"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"password"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"input_field"</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Enter your password"</span>/&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">br</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"s_login"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"submit_button"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"Login"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<h2 id="heading-identifying-elements-with-id-name-class-or-text-in-webformsfield">Identifying elements with id, name, class, or text in <code>WebFormsField</code></h2>
<p>With the above DOM as an example, we can identify the elements in our <code>WebFormsField</code> effortlessly. Identifying by the elements' <code>id</code> and <code>name</code>, it would look something like:</p>
<pre><code class="lang-powershell">i_username &gt; {Username} (SearchBy=id)
password &gt; {Password} (SearchBy=name)
s_login &gt; (Click) (SearchBy=id)
</code></pre>
<p>We can mix and match what we search by. Depending on the elements, it may lend itself to one way or the other.</p>
<h2 id="heading-identifying-elements-with-xpath-in-webformsfield">Identifying elements with XPath in <code>WebFormsField</code></h2>
<p>We can accomplish the same as we did purely with XPath:</p>
<pre><code class="lang-xml">//*^[@id="i_username"^] &gt; {Username} (SearchBy=XPath)
//*/form/input^[2^] &gt; {Password} (SearchBy=XPath)
//*^[@class="submit_button"^] &gt; (Click) (SearchBy=XPath)
</code></pre>
<p>With XPath we can use the same identifiers as we previously had and do not need to specify element attributes at all like in the case of the password field -- which could be useful if an element we are working at has attributes with dynamic values or cannot be defined uniquely by it's attributes.</p>
<p><strong>Note:</strong> The way we defined the XPath for the password field is not recommended. It is simply selecting the second <code>input</code> element on the page. If the page is later updated and an additional <code>input</code> is added before the two existing ones (for example: a search field in a header of the page) then our password field is no longer the second <code>input</code> but rather the third.</p>
<h1 id="heading-creating-our-shopizer-connection-component">Creating our Shopizer connection component</h1>
<p>With a better understanding of how PSM connection components for web apps work and how we can identify DOM elements in the <code>WebFormsField</code>, the creation of the connection component should be pretty straightforward.</p>
<p>Shopizer offers two portals: a customer-facing one and one meant for administrators. We will create a connection component for the Shopizer administrator portal.</p>
<p>With our local Shopizer containers running, a local test Shopizer administrator user account created, and our PSM equipped with Edge, the Edge Driver and the <a target="_blank" href="https://cyberark-customers.force.com/mplace/s/#a3550000000EiCMAA0-a3950000000jjUwAAI">latest Secure Web Application Connectors Framework</a>, let's create our first connection component for a web application.</p>
<h2 id="heading-creating-a-new-web-connection-component">Creating a new web connection component</h2>
<p>As instructed in the <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/12.6/en/Content/PASIMP/psm_WebApplication.htm?tocpath=Developer%7CCreate%20extensions%7CPSM%20Connectors#Configuration">Configuration part of the documentation</a>, we first need to clone the <code>PSM-WebAppSample</code> and change the <code>Id</code> of it. We will set <code>PSM-ShopizerAdministrator</code> as the new <code>Id</code>.</p>
<p>Under <code>Target Settings</code>, we set the <code>ClientApp</code> to <code>Edge</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667305935155/B6zPmCemn.png" alt="image.png" /></p>
<p>Under <code>Client Specific</code>, we add new two new parameters: <code>BrowserPath</code> and <code>DriverPath</code>. As we are using Edge, the paths are <code>C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe</code> and <code>C:\Program Files (x86)\CyberArk\PSM\Components\msedgedriver.exe</code> respectively. While in the area, we set the value for <code>RunValidations</code> to <code>No</code> -- this is something we should do later.</p>
<p>Then under <code>Web Form Settings</code>, we adjust the <code>LogonURL</code> to <code>http://{address}:4200/#/auth</code>. The administrator web interface runs on port <code>4200</code> and it would be better to add <code>Port</code> as a connection parameter but as we are not creating this for production use, we hard-code the port.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667306129101/NvFm06Y_m.png" alt="image.png" /></p>
<h2 id="heading-defining-our-webformfields">Defining our <code>WebFormFields</code></h2>
<p>The <code>WebFormFields</code> is the key to this connection component working. As we did in the example above, we need to define the username, password, and submit button elements.</p>
<p>But unlike in our example, we do not have the DOM clearly laid out in front of us so how can we identify those elements? This is when your favorite browser's Developer Tools come into play. I am using Firefox but other browsers have similar functionality.</p>
<p>Assuming the Shopizer containers are running on your local machine, navigate to <code>http://localhost:4200/#/auth</code> and you should be greeted with the login page.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667306384752/NYv0svjKL.png" alt="image.png" /></p>
<p>From there, we can right-click the username field and click <code>Inspect</code> to be brought directly to the element in the DOM. When we mouse over the element in the Inspector tool, the element is highlighted above in the web page.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667306588176/Z5v62ip6R.png" alt="image.png" /></p>
<p>Now it is just about how we want to identify the <code>input</code> element that represents the Username field.</p>
<p>In addition to XPath, as the <code>input</code> element has an <code>id</code>, <code>class</code>, and <code>name</code> attributes defined, we can use whatever we want in our <code>WebFormFields</code> to identify the element. But which way is best?</p>
<p>Ultimately, it depends. As we are typically developing connection components for web applications we are not developing ourselves, we are at the will of the application's developers.</p>
<p>An element that has a <code>class</code> defined one day may not have it defined the next day if the site goes through a re-vamp. Some attributes may have values with random digits at the end giving us the clue that the attribute's values are not explicitly set and may be auto-generated with each deployment. XPath can be pretty resilient if our path expression does not include too many nodes.</p>
<p>For the username field of our Shopizer login page, we have a few options. The <code>id</code> and <code>name</code> values both look explicitly set and feel as if they have a relatively low risk of changing in the future. Let's go with <code>name.</code></p>
<p>Back in our <code>WebFormFields</code> we update it to identify the field to input the account username using the attribute <code>name</code> with the value of <code>username</code>. We use <code>Inspect</code> in Firefox to identify the password and submit buttons and define those too, resulting in:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667307635405/GL7D0Cb9i.png" alt="image.png" /></p>
<p>Now after adding our new connection component to the platform assigned to the onboarded test Shopizer administrator account we created, let's give it a whirl.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667320671257/ivQuKm5lQ.png" alt="image.png" /></p>
<p>It's in French -- this seems to be the default language for Shopizer -- but it worked! We are logged into the Shopizer administrator portal.</p>
<h1 id="heading-troubleshooting">Troubleshooting</h1>
<p>Depending on the complexity of the web app, you may run into problems. With complex DOMs -- especially ones that involve iFrames -- it may not be straightforward when identifying elements.</p>
<p>Thankfully, though not well documented at all, you can enable very verbose trace levels in <code>&lt;session id&gt;.ClientDispatcher.log</code> that are created in the <code>Logs\Components\</code> folder. To do so, set <code>TraceLevels=1,2</code> in the PVWA options under <code>Privileged Session Management &gt; General Settings &gt; Client Connection Settings</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667595495627/YyCpPbLgt.png" alt="image.png" /></p>
<p>After a restart of the PSM service or waiting for the PVWA configuration refresh on the PSM, you will see the log file with helpful content such as:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667595659656/UAV_OE6B0.png" alt="image.png" /></p>
<p>Another helpful debugging 'tool' is setting <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/13.0/en/Content/PASIMP/psm_cc_target_settings.htm?highlight=EnableTrace#Clientspecific"><code>EnableTrace</code> to <code>Yes</code></a>. This shows the entire login process that the PSM obscures.</p>
<h1 id="heading-improving-our-connection-component">Improving our connection component</h1>
<p>We developed our connection component 'quick and dirty' which is fine for a lab environment but nothing we would want to introduce into production. We have a lot of areas to improve on:</p>
<ol>
<li><p>We should not hardcode the Port in the <code>LogonURL</code>, rather allow this to be defined at the account level to handle the case where the Shopizer administrator portal is running on a port other than the default.</p>
</li>
<li><p>We have no validations. After we login, we should use an element unique to a successfully-authenticated Shopizer administrator portal to inform the PSM the login was successful.</p>
</li>
<li><p>Similar to a validation of a successful login, we should set a validation for login failures. Like many applications, Shopizer returns an error message when a login was unsuccessful and we can use that element to clue the PSM into the fact that it failed.</p>
</li>
</ol>
<p>With the above points addressed, the connection component moves closer to being production-ready and with our new knowledge of how a connection component for a web app works, we likely have a lot of the information we need to develop <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/Latest/en/Content/Plugins/CPM_WebApplication.htm?tocpath=Developer%7CCreate%20extensions%7CCreate%20CPM%20plugins%7C_____4">a CPM plugin for a web app</a>, too.</p>
]]></content:encoded></item><item><title><![CDATA[Using a hardware security module to secure the CyberArk Vault's Server Key]]></title><description><![CDATA[The CyberArk Vault allows for the Server key to be stored in a hardware security module (HSM). The Server key is used as a key-encryption-key so it is appropriate to use a HSM as they provide the highest level of protection for the Server key. 
HSM i...]]></description><link>https://timschindler.blog/using-a-hardware-security-module-to-secure-the-cyberark-vaults-server-key</link><guid isPermaLink="true">https://timschindler.blog/using-a-hardware-security-module-to-secure-the-cyberark-vaults-server-key</guid><category><![CDATA[hsm]]></category><category><![CDATA[cyberark]]></category><category><![CDATA[infosec]]></category><category><![CDATA[#cybersecurity]]></category><category><![CDATA[Cryptography]]></category><dc:creator><![CDATA[Tim Schindler]]></dc:creator><pubDate>Mon, 31 Oct 2022 10:42:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/lVZjvw-u9V8/upload/v1667212799892/lMqDai-yU.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The CyberArk Vault allows for the Server key to be stored in a <a target="_blank" href="https://en.wikipedia.org/wiki/Hardware_security_module">hardware security module (HSM)</a>. The Server key is used as a <a target="_blank" href="https://csrc.nist.gov/glossary/term/key_encryption_key">key-encryption-key</a> so it is appropriate to use a HSM as they provide the highest level of protection for the Server key. </p>
<p>HSM integration with CyberArk is actually well-documented. CyberArk provides information on <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/12.6/en/Content/PAS%20INST/configuring-HSM-Key-Management.htm?TocPath=Administrator|Components|Digital%20Vault|Operate%20the%20CyberArk%20Vault|Integrate%20the%20Digital%20Vault%20with%20Third-Party%20Components|_____3">loading an existing Server key into an HSM</a> as well as generating a new <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/12.6/en/Content/PAS%20INST/configuring-HSM-Key-Management.htm?TocPath=Administrator|Components|Digital%20Vault|Operate%20the%20CyberArk%20Vault|Integrate%20the%20Digital%20Vault%20with%20Third-Party%20Components|_____3#_Ref242158975">Server key directly in the HSM</a> but as with anything, hands-on experience goes a long way to demystifying a concept.</p>
<p>In a lab environment, we will generate a new Server key directly on the HSM then perform a re-key of the Vault using that key. The <a target="_blank" href="https://utimaco.com/downloads/free-simulators-and-sdks/securityserver-simulator">SecurityServer simulator from Utimaco</a> will be used as our HSM.</p>
<h1 id="heading-what-are-hardware-security-modules-and-what-do-they-offer">What are hardware security modules and what do they offer?</h1>
<p>Hardware security modules provide a secure location to store cryptographic information (typically keys) as well as provide a secure place to perform cryptographic operations (such as using the keys to encrypt or decrypt objects.) HSMs are commonly physical, hardware devices that have tamper proof protections. They can be in the form of a network appliance or a PCI device. </p>
<p>Applications interact with HSMs through different APIs -- the <a target="_blank" href="https://en.wikipedia.org/wiki/PKCS_11">PKCS#11 standard</a> being one of the most common and the one CyberArk uses to talk to HSMs.</p>
<p>As HSMs store cryptographic information and provide the place to perform operations involving that information, applications need a constant connection to the HSM. When the connection is broken, the ability to use the cryptographic information is lost. The same applies to CyberArk: when it cannot connect to the HSM, critical functionality such as retrieving credentials from the Vault is does not work!</p>
<h1 id="heading-the-vault-and-its-encryption-keys">The Vault and it's encryption keys</h1>
<p>Before doing any sort of HSM integration, it first makes sense to understand the encryption keys used by the Vault to secure Vault objects. The CyberArk Technical Community has <a target="_blank" href="https://cyberark-customers.force.com/s/article/What-are-the-Vault-Keys-for">a nice article on all the Vault keys and their purpose</a>. We will focus on the ones used to encrypt Vault objects.</p>
<p>There are three 'tiers' of keys in the Vault encryption hierarchy: Vault keys, safe keys, and object keys. Each object is encrypted by a unique key specific to the object, object keys are encrypted by a safe key, and Vault keys encrypt the safe keys.</p>
<p>There are two types of Vault keys when referring to the encryption of Vault objects: the Recovery key (really it is a pair as the Recovery 'key' is asymmetric) and the Server key (symmetric.) Safe keys are encrypted and decrypted by both leading to two copies of the encrypted safe key. The Server key must be accessible to the Vault in order for it to start.</p>
<p>Only the Server key can be stored on a HSM and the private key for the Recovery key pair should be kept securely offline.</p>
<p><em>The CyberArk Technical Community has an excellent knowledge article regarding <a target="_blank" href="https://cyberark-customers.force.com/s/article/Hierarchical-Key-Management">hierarchical key management</a>.</em></p>
<h1 id="heading-hsm-and-cyberark-integration">HSM and CyberArk integration</h1>
<p>To better understand HSM integration with CyberArk, in our CyberArk 12.6 Primary-DR lab environment running on Windows Server 2019, we will:</p>
<ol>
<li>Perform initial Vault configurations</li>
<li>Generate a new Server key directly on the HSM</li>
<li>Re-encrypt all the Vault data and metadata with the new Server key on the HSM </li>
</ol>
<p>The Server key will be generated on the HSM from the DR Vault. We will then re-encrypt the DR Vault before re-encrypting the Primary vault.</p>
<p><em>Note: In the lab environment, the HSM (Utimaco's SecurityServer simulator) is already configured with a slot initialized to store our new Server key. On both the Primary and DR Vaults, the PKCS#11 provider used to communicate with the HSM is configured and functional. See <a target="_blank" href="https://timschindler.blog/installing-and-configuring-utimacos-securityserver-simulator">this blog post dedicated to configuring Ultimaco's SecurityServer simulator</a>.</em></p>
<h2 id="heading-initial-vault-configurations">Initial Vault configurations</h2>
<p>On both Vaults, we need to directly edit the <code>dbparm.ini</code> to define the path to our HSM's PKCS11 provider DLL via the <code>PKCS11ProviderPath</code> as well as use <code>AllowNonStandardFWAddresses</code> to allow outbound communication to the HSM -- the last part being optional if your Vaults are <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/12.6/en/Content/Release%20Notes/RN-WhatsNew12-6.htm?tocpath=Get%20Started%7CWhat%E2%80%99s%20New%7CRelease%20Notes%7C_____2#WindowsServer2019HardeningrevisedtofollowCISstandards">version 12.6 and running Windows Server 2019 due to hardening changes</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666975248812/eD7XDcUbw.png" alt="image.png" /></p>
<p>Afterwards we need to define the PIN that the Vault will use to access the slot the Server key is located using <code>CAVaultManager.exe SecureSecretFiles /SecretType HSM /Secret 1111</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666975373287/1lqJhySzO.png" alt="image.png" /></p>
<p>Opening <code>dbparm.ini</code> we see <code>HSMPinCode</code> defined with the value of our encrypted PIN.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666975498748/73g5_MrYC.png" alt="image.png" /></p>
<p>Now we are in a position to generate the Server key.</p>
<h2 id="heading-generate-the-new-server-key">Generate the new Server key</h2>
<p>We will generate the Server key on the DR Vault. This only needs to be done once as the same Server key will be used for both the Primary and DR Vault. There is no requirement to generate it on one Vault versus the other -- we just do it on the DR Vault as we will re-encrypt the DR Vault with it first.</p>
<p>After stopping the CyberArk Vault Disaster Recovery service, we use <code>CAVaultManger GenerateKeyOnHSM /ServerKey</code> to generate the Server key. <code>CAVaultManager</code> uses the parameters defined in <code>dbparm.ini</code> to communicate with the HSM. Successful Server key generation lets you know the Vault will be able to as well.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666977073748/QkQrpgT94.png" alt="image.png" /></p>
<p>We need to note down the <code>KeyID</code> as we will use it later.</p>
<h2 id="heading-re-encrypt-the-dr-vault-with-our-new-server-key">Re-encrypt the DR Vault with our new Server key</h2>
<p>At this point it makes sense to ensure the Primary Vault's Vault services have been stopped as after the re-encryption on the DR Vault the two will be using different Server keys and replication between them will not be possible until both are using the same, new Server key.</p>
<p>With the Master private key at the location defined at <code>RecoveryPrvKey</code> in the <code>dbparm.ini</code>,  <code>ChangeServerKeys</code> is used to re-encrypt the Vault data.</p>
<p>We run <code>ChangeServerKeys.exe C:\Keys\DemoOperatorKeys\ C:\Keys\DemoOperatorKeys\VaultEmergency.pass HSM#1</code>. <code>C:\Keys\DemoOperatorKeys\</code> refers to the location of the keys that will be used to re-encrypt the Vault -- including our existing Master private key. The Server key used will be the one on the HSM, however.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667161189577/MvhKDrvDa.png" alt="image.png" /></p>
<p>The changing of keys may take awhile depending on the specifications of your Vault and the HSM as well as the amount of data in the Vault. As this process creates new safe and object keys based on the new Server key, this could take <strong>days</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667161140278/_P1nugqUr.png" alt="image.png" /></p>
<p>Like the output says, now we need to update the <code>ServerKey</code> parameter in the <code>dbparm.ini</code> to refer to the HSM.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667161408747/BnzkQGmNR.png" alt="image.png" /></p>
<p>And quickly start the Vault services.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667161507786/I67SS8Xvi.png" alt="image.png" /></p>
<p>The final verification is logging into PrivateArk.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667202466856/Z9vlemOBA.png" alt="image.png" /></p>
<p>After logging out of PrivateArk and stopping the Vault services on the DR Vault, we are ready to move on to the Primary.</p>
<h2 id="heading-re-encrypt-the-primary-vault-with-our-new-server-key">Re-encrypt the Primary Vault with our new Server key</h2>
<p>We run the exact same <code>ChangeServerKeys</code> command on the Primary Vault.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667202746063/mNiPmVH83.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667202777029/uOfzu2scr.png" alt="image.png" /></p>
<p>Like the DR Vault, we change the <code>dbparm.ini</code> on the Primary Vault to have the <code>ServerKey</code> parameter point to <code>HSM#1</code>, start the Vault services, and login.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667203040697/UqycOzCWW.png" alt="image.png" /></p>
<p>At this point, we can start the CyberArk Vault Disaster Recovery service on the DR Vault and observe successful replication.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667203450300/De5cw8QfZ.png" alt="image.png" /></p>
<h1 id="heading-vault-behavior-when-the-hsm-is-unavailable">Vault behavior when the HSM is unavailable</h1>
<p>Our Server key being stored on a HSM increases the security posture of our environment but also introduces complexity and another point of failure. We should ensure we have a good understanding on what the behavior of the Vault is when the HSM is unavailable and what we need to do.</p>
<p>We should consider the following:</p>
<ul>
<li>Vault behavior when the Vault tries to start and the HSM is unavailable.</li>
<li>Vault behavior when the Vault is already running and the HSM is unavailable.</li>
<li>What needs to be done on the Vault side when the HSM becomes available.</li>
</ul>
<h2 id="heading-starting-the-vault-with-the-hsm-unavailable">Starting the Vault with the HSM unavailable</h2>
<p>This is a pretty straightforward case. As the Server key is needed to start the Vault services and the Server key lives on the HSM, the HSM being unavailable prevents the Vault services from starting.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667205302261/K3-fvxwTj.png" alt="image.png" /></p>
<p>Looking in the log file for the PKCS#11 provider we are using, we see the device cannot be found which could give us a hint where to start our troubleshooting.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667205473151/tBHpooo38.png" alt="image.png" /></p>
<h2 id="heading-an-already-running-vault-with-the-hsm-unavailable">An already running Vault with the HSM unavailable</h2>
<p>If the Vault is already running and the HSM becomes unavailable, the Vault does not become completely unavailable. Some functionality -- user authentication, report generation, user management, changing of safe memberships, etc. -- may still work and give a false sense of security.</p>
<p>What is guaranteed not to work is the retrieval of Vault objects (credentials.) When attempting to retrieve a credential while the HSM is unavailable, there will be an error about the Safe key being incorrect.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667206182346/IHGlFSkNH.png" alt="image.png" /></p>
<p>Peeking in the trace logs, we can even see the decryption failing:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667207053584/Avo26CpFX.png" alt="image.png" /></p>
<p>Based on our understanding of the Vault's encryption hierarchy, this makes sense. We know each Vault object is encrypted by an individual key, which is then encrypted by a Safe key where the Safe key is then encrypted by the Server key. With the Server key being unavailable, the Safe key cannot be decrypted in order to be used to decrypt the object's key.</p>
<p>In our current setup, the HSM becoming available again is enough for the Vault to assume normal operations. The first retrieval of a credential takes awhile -- the Vault is probably doing whatever it needs to do to recover from an HSM outage -- while the subsequent ones go much quicker:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667208137185/RnJTtTgNl.png" alt="image.png" /></p>
<h2 id="heading-recovery-of-the-vault-with-reconnecthsmonerrorcodes">Recovery of the Vault with <code>ReconnectHSMOnErrorCodes</code></h2>
<p>In the case the Vault does not assume normal operations automatically after the HSM becoming unavailable again then we can use <a target="_blank" href="https://cyberark-customers.force.com/s/article/How-to-configure-the-Vault-to-reconnect-to-a-HSM"><code>ReconnectHSMOnErrorCodes</code> to have the Vault reconnect to the HSM</a>.</p>
<p><code>DBPARM.sample.ini</code> has an example of the values for <code>ReconnectHSMOnErrorCodes</code> (<code>ReconnectHSMOnErrorCodes=48,50,179</code>) but there does not seem to be a complete list of possible HSM error codes however we can see them in the trace logs with debug levels <code>CRYPT(1,2)</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667209974103/FNPIOdVhU.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667211013982/UZ9_HswtV.png" alt="image.png" /></p>
<h1 id="heading-wrapping-it-up">Wrapping it up</h1>
<p>HSMs provide the the best security for our Vaults' Server key by providing a secure location for them to reside and to perform cryptographic operations with. Generating a Server key and re-encrypting our Vaults' data is easy with the available documentation from CyberArk.  </p>
<p>Once the integration with an HSM is complete, together with an understanding of the Vault's encryption hierarchy, Vault debug levels and trace logs give us important insight into when and how the Vault interacts with it, demystifying both HSMs and HSM integration with CyberArk's Vault.</p>
<p>Are you planning to or have you already integrated your CyberArk environment with a HSM? What HSM vendor are you using? Provide some feedback in the comments.</p>
]]></content:encoded></item><item><title><![CDATA[Managing objects in CyberArk with PowerShell Desired State Configuration]]></title><description><![CDATA[PowerShell Desired State Configuration (DSC) is a tool similar to Ansible, Puppet, and Chef that enables declaratively setting how an environment is configured. PowerShell DSC can be used to ensure resources such as environmental variables, software,...]]></description><link>https://timschindler.blog/managing-objects-in-cyberark-with-powershell-desired-state-configuration</link><guid isPermaLink="true">https://timschindler.blog/managing-objects-in-cyberark-with-powershell-desired-state-configuration</guid><category><![CDATA[cyberark]]></category><category><![CDATA[Powershell]]></category><category><![CDATA[configuration]]></category><category><![CDATA[Security]]></category><category><![CDATA[#cybersecurity]]></category><dc:creator><![CDATA[Tim Schindler]]></dc:creator><pubDate>Mon, 19 Sep 2022 10:07:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/HfFoo4d061A/upload/v1663357698259/eSKclOiVL.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://docs.microsoft.com/de-de/powershell/scripting/dsc/overview?view=powershell-7.2">PowerShell Desired State Configuration (DSC)</a> is a tool similar to Ansible, Puppet, and Chef that enables declaratively setting how an environment is configured. PowerShell DSC can be used to ensure <a target="_blank" href="https://docs.microsoft.com/en-us/powershell/dsc/concepts/resources?view=dsc-2.0"><em>resources</em></a> such as environmental variables, software, files, Active Directory users, and application-specific settings are either present or absent through the use of <a target="_blank" href="https://docs.microsoft.com/en-us/powershell/dsc/concepts/configurations?view=dsc-2.0"><em>configurations</em></a>.</p>
<p>A few of the most obvious, CyberArk-related use cases for PowerShell DSC include making sure that the required software is installed after importing a <a target="_blank" href="https://timschindler.blog/creating-cyberark-connection-component-connector-packages">Universal Connector package</a> or after <a target="_blank" href="https://timschindler.blog/continuous-deployment-of-cyberark-platforms-using-github-actions">deploying a CPM plugin as part of a continuous deployment pipeline</a>. It can be used to manage the rules within an <code>PSMConfigureAppLocker.xml</code> file or invoke the automated installation or upgrade processes for components.</p>
<p>Though with the <a target="_blank" href="https://github.com/aaearon/CyberArkDsc"><code>CyberArkDsc</code> module</a>, PowerShell DSC can also be used to manage CyberArk Vault objects such as safes, safe memberships, and accounts.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/aaearon/CyberArkDsc">https://github.com/aaearon/CyberArkDsc</a></div>
<p> </p>
<h1 id="heading-why-use-powershell-dsc-to-manage-vault-objects">Why use PowerShell DSC to manage Vault objects?</h1>
<p>Using PowerShell DSC to manage Vault objects makes sense for the same reasons configuration management makes sense for infrastructure.</p>
<ul>
<li><p><strong>Combat configuration drift</strong> - Ensure accounts have the intended configurations long after they are created -- particularly useful in a test or lab environment where experimentation may lead to false settings and thus unintended consequences.</p>
</li>
<li><p><strong>Idempotency</strong> - Safes, safe memberships, and accounts can be created and configured using scripts, the Password Upload Utility, or PACLI but running executing these when the objects already exist may produce different results or fail entirely.</p>
</li>
<li><p><strong>Treat Vault objects as code</strong> - Like with infrastructure as code, Vault objects can be defined in configurations and these configurations can be stored in version control, be included in continuous deployment pipelines, or reviewed by other team members.</p>
</li>
<li><p><strong>Included in PowerShell 5.1 and above</strong> - PowerShell DSC is included in PowerShell 5.1 and above which absolves the need to install to install more software, such as Python when dealing with Ansible.</p>
</li>
</ul>
<h1 id="heading-using-powershell-dsc-and-cyberarkdsc-to-populate-an-empty-vault">Using PowerShell DSC and <code>CyberArkDsc</code> to populate an empty Vault</h1>
<p>One of the leading use cases for using PowerShell DSC together with CyberArk is that it can be used to quickly create accounts on target systems and onboard them into the Vault.</p>
<p>In the following section, a DSC configuration will be used to create Active Directory users, safes to store them in the Vault, assign the needed safe memberships to make the accounts accessible, and finally onboard the users into the safes with the needed reconcile accounts.</p>
<p><em>Note: For the sake of brevity, knowledge of PowerShell DSC concepts and</em> <a target="_blank" href="https://docs.microsoft.com/en-us/powershell/dsc/concepts/terminology?view=dsc-2.0"><em>terminology</em></a> <em>such as resources and configurations is assumed.</em></p>
<h2 id="heading-installing-the-cyberarkdsc-and-activedirectorydsc-modules">Installing the <code>CyberArkDsc</code> and <code>ActiveDirectoryDsc</code> modules</h2>
<p>Installing the <code>ActiveDirectoryDsc</code> module is straightforward as it can be installed from the PowerShell Gallery like any other PowerShell module. The <a target="_blank" href="https://github.com/dsccommunity/ActiveDirectoryDsc/wiki/#getting-started"><code>ActiveDirectoryDsc</code> wiki</a> has information on getting started.</p>
<p><code>CyberArkDsc</code> is not available in the PowerShell Gallery and needs to be manually installed. As by default configurations are executed under the <code>SYSTEM</code> account, <code>CyberArkDsc</code> needs to, along with it's dependency <a target="_blank" href="https://github.com/pspete/psPAS"><code>psPAS</code></a>, exist in a non-user-specific folder location defined in <code>$env:PSModulePath</code> -- for example: <code>$env:ProgramFiles\WindowsPowerShell\Modules</code>.</p>
<p>We can use <code>Get-DscResource</code> to ensure that both DSC modules are installed correctly.</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">PS</span> &gt; <span class="hljs-built_in">Get-DscResource</span> | <span class="hljs-built_in">Where-Object</span> {<span class="hljs-variable">$_</span>.Name <span class="hljs-operator">-eq</span> <span class="hljs-string">'CYA_Account'</span> <span class="hljs-operator">-or</span> <span class="hljs-variable">$_</span>.Name <span class="hljs-operator">-eq</span> <span class="hljs-string">'ADUser'</span>}

ImplementedAs   Name                      ModuleName                     Version    Properties
-------------   ----                      ----------                     -------    ----------
PowerShell      ADUser                    ActiveDirectoryDsc             <span class="hljs-number">6.2</span>.<span class="hljs-number">0</span>      {DomainName, UserName, AccountNotDelegated, All...
PowerShell      CYA_Account               CyberArkDsc                    <span class="hljs-number">0.0</span>.<span class="hljs-number">2</span>      {Address, AuthenticationType, Credential, Ensur...

<span class="hljs-built_in">PS</span> &gt;
</code></pre>
<h2 id="heading-writing-our-configuration">Writing our configuration</h2>
<p>We will keep our DSC configuration relatively short and sweet. We already have LDAP integration configured for the Vault, a user that is in both Vault Admins and Domain Admins groups, and platforms created in the Vault that the accounts will be onboarded with so this just leaves us with:</p>
<ol>
<li><p>Creating two users in Active Directory, one to be used as a reconcile account.</p>
</li>
<li><p>Adding the reconcile account as a member to the Domain Admins group in Active Directory so it can reconcile the other.</p>
</li>
<li><p>Creating two safes to store the accounts in: one for the reconcile account, the other.</p>
</li>
<li><p>Assigning a membership to each safe enabling an existing Active Directory group named <code>CyberArk Administrators</code> full permissions to the reconcile account safe and another, existing Active Directory group named <code>Windows Administrators</code> with list, retrieve, and use accounts to the other.</p>
</li>
<li><p>Onboarding both accounts: The reconcile account will be onboarded with the password defined when creating it in Active Directory and the other account will be onboarded with the reconcile account defined as a linked account.</p>
</li>
</ol>
<h2 id="heading-defining-the-base-of-our-configuration">Defining the base of our configuration</h2>
<p>Each DSC resource in <code>CyberArkDsc</code> takes at least four parameters: <code>PvwaUrl</code>, <code>AuthenticationType</code>, <code>Credential</code>, and <code>SkipCertificateCheck.</code> We define these as parameters our configuration accepts. In addition, we define an <code>InitialPassword</code> parameter that will be used when creating the reconcile account's Active Directory user and when onboarding into CyberArk.</p>
<p>We will have a single node -- <code>localhost</code> -- as we will run the DSC configuration on the same machine we compile it on. Our resources will go inside it.</p>
<p>Both <code>Credential</code> and <code>InitialPassword</code> will be passed <code>PSCredential</code> objects. As this is a lab environment I will not go through the <a target="_blank" href="https://docs.microsoft.com/en-us/powershell/dsc/configurations/configdatacredentials?view=dsc-1.1">effort to have the credentials be properly secured in the resulting, compiled configuration</a> so I set a switch to allow the passwords to be stored plaintext.</p>
<pre><code class="lang-powershell">Configuration CreateLab_Configuration {
    <span class="hljs-keyword">param</span>(
        <span class="hljs-variable">$PvwaUrl</span>,
        <span class="hljs-variable">$AuthenticationType</span>,
        <span class="hljs-variable">$Credential</span>,
        <span class="hljs-variable">$SkipCertificateCheck</span>,

        <span class="hljs-variable">$InitialPassword</span>
    )

    Node localhost {

    }

}

<span class="hljs-variable">$ConfigData</span> = <span class="hljs-selector-tag">@</span>{
    AllNodes = <span class="hljs-selector-tag">@</span>(
        <span class="hljs-selector-tag">@</span>{
            NodeName                    = <span class="hljs-string">'localhost'</span>
            PSDscAllowDomainUser        = <span class="hljs-variable">$true</span>
            PSDscAllowPlainTextPassword = <span class="hljs-variable">$true</span>
        }
    )
}

<span class="hljs-variable">$VaultCredential</span> = <span class="hljs-built_in">New-Object</span> System.Management.Automation.PSCredential(<span class="hljs-string">'allison'</span>, (<span class="hljs-built_in">ConvertTo-SecureString</span> <span class="hljs-string">'Password!'</span> <span class="hljs-literal">-AsPlainText</span> <span class="hljs-literal">-Force</span>))
<span class="hljs-variable">$Password</span> = <span class="hljs-built_in">New-Object</span> System.Management.Automation.PSCredential(<span class="hljs-string">'banana'</span>, (<span class="hljs-built_in">ConvertTo-SecureString</span> <span class="hljs-string">'P@$$W0RD12345'</span> <span class="hljs-literal">-AsPlainText</span> <span class="hljs-literal">-Force</span>))

<span class="hljs-variable">$ConfigurationParameters</span> = <span class="hljs-selector-tag">@</span>{
    ConfigurationData    = <span class="hljs-variable">$ConfigData</span>

    PvwaUrl              = <span class="hljs-string">'https://192.168.137.101'</span>
    AuthenticationType   = <span class="hljs-string">'LDAP'</span>
    SkipCertificateCheck = <span class="hljs-variable">$true</span>
    Credential           = <span class="hljs-variable">$VaultCredential</span>
    InitialPassword      = <span class="hljs-variable">$Password</span>
}

CreateLab_Configuration @ConfigurationParameters
</code></pre>
<h2 id="heading-defining-resources">Defining resources</h2>
<p>Our configuration will have nine resources. Using documentation, our familiarity with managing Vault objects via <code>psPAS</code> and <code>Get-DscResource -Name ADUser,ADGroup,CYA_Account,CYA_Safe,CYA_SafeMember -Syntax</code> to know how to define each resource, our complete configuration looks like:</p>
<pre><code class="lang-powershell">Configuration CreateLab_Configuration {
    <span class="hljs-keyword">param</span>(
        <span class="hljs-variable">$PvwaUrl</span>,
        <span class="hljs-variable">$AuthenticationType</span>,
        <span class="hljs-variable">$Credential</span>,
        <span class="hljs-variable">$SkipCertificateCheck</span>,

        <span class="hljs-variable">$InitialPassword</span>
    )

    <span class="hljs-built_in">Import-DscResource</span> <span class="hljs-literal">-ModuleName</span> <span class="hljs-string">'ActiveDirectoryDsc'</span> <span class="hljs-literal">-Name</span> <span class="hljs-string">'ADUser'</span>, <span class="hljs-string">'ADGroup'</span>
    <span class="hljs-built_in">Import-DscResource</span> <span class="hljs-literal">-ModuleName</span> <span class="hljs-string">'CyberArkDsc'</span>

    Node <span class="hljs-string">'localhost'</span> {

        ADUser <span class="hljs-string">'iosharp\windowsReconcile50'</span> {
            Ensure     = <span class="hljs-string">'Present'</span>
            UserName   = <span class="hljs-string">'windowsReconcile50'</span>
            DomainName = <span class="hljs-string">'iosharp.dev'</span>
            Password   = <span class="hljs-variable">$InitialPassword</span>
            PasswordNeverResets =  <span class="hljs-variable">$true</span>

            Credential = <span class="hljs-variable">$Credential</span>
        }

        ADUser <span class="hljs-string">'iosharp\windowsAdmin50'</span> {
            Ensure     = <span class="hljs-string">'Present'</span>
            UserName   = <span class="hljs-string">'windowsAdmin50'</span>
            DomainName = <span class="hljs-string">'iosharp.dev'</span>
            Password   = <span class="hljs-variable">$InitialPassword</span>
            PasswordNeverResets =  <span class="hljs-variable">$true</span>

            Credential = <span class="hljs-variable">$Credential</span>
        }

        ADGroup <span class="hljs-string">'Domain Admins'</span> {
            Ensure           = <span class="hljs-string">'Present'</span>
            GroupName        = <span class="hljs-string">'Domain Admins'</span>
            MembersToInclude = <span class="hljs-string">'windowsReconcile50'</span>
            Credential       = <span class="hljs-variable">$Credential</span>

            DependsOn        = <span class="hljs-string">'[ADUser]iosharp\windowsReconcile50'</span>
        }

        CYA_Safe <span class="hljs-string">'WinRec50'</span> {
            Ensure                = <span class="hljs-string">'Present'</span>

            SafeName              = <span class="hljs-string">'WinRec50'</span>
            Description           = <span class="hljs-string">'Windows Reconcile Safe'</span>
            ManagingCPM           = <span class="hljs-string">'PasswordManager'</span>
            NumberOfDaysRetention = <span class="hljs-string">'1'</span>

            PvwaUrl               = <span class="hljs-variable">$PvwaUrl</span>
            AuthenticationType    = <span class="hljs-variable">$AuthenticationType</span>
            SkipCertificateCheck  = <span class="hljs-variable">$SkipCertificateCheck</span>
            Credential            = <span class="hljs-variable">$Credential</span>
        }

        CYA_Safe <span class="hljs-string">'WinAdmins50'</span> {
            Ensure                = <span class="hljs-string">'Present'</span>

            SafeName              = <span class="hljs-string">'WinAdmins50'</span>
            Description           = <span class="hljs-string">'Windows Admins Safe'</span>
            ManagingCPM           = <span class="hljs-string">'PasswordManager'</span>
            NumberOfDaysRetention = <span class="hljs-string">'1'</span>

            PvwaUrl               = <span class="hljs-variable">$PvwaUrl</span>
            AuthenticationType    = <span class="hljs-variable">$AuthenticationType</span>
            SkipCertificateCheck  = <span class="hljs-variable">$SkipCertificateCheck</span>
            Credential            = <span class="hljs-variable">$Credential</span>
        }

        CYA_SafeMember <span class="hljs-string">'WinRec\CyberArk Administrators'</span> {
            Ensure                                 = <span class="hljs-string">'Present'</span>

            SafeName                               = <span class="hljs-string">'WinRec50'</span>
            MemberName                             = <span class="hljs-string">'CyberArk Administrators'</span>
            SearchIn                               = <span class="hljs-string">'iosharp.dev'</span>
            UseAccounts                            = <span class="hljs-variable">$true</span>
            RetrieveAccounts                       = <span class="hljs-variable">$true</span>
            ListAccounts                           = <span class="hljs-variable">$true</span>
            AddAccounts                            = <span class="hljs-variable">$true</span>
            UpdateAccountContent                   = <span class="hljs-variable">$true</span>
            UpdateAccountProperties                = <span class="hljs-variable">$true</span>
            InitiateCPMAccountManagementOperations = <span class="hljs-variable">$true</span>
            SpecifyNextAccountContent              = <span class="hljs-variable">$true</span>
            RenameAccounts                         = <span class="hljs-variable">$true</span>
            DeleteAccounts                         = <span class="hljs-variable">$true</span>
            UnlockAccounts                         = <span class="hljs-variable">$true</span>
            ManageSafe                             = <span class="hljs-variable">$true</span>
            ManageSafeMembers                      = <span class="hljs-variable">$true</span>
            BackupSafe                             = <span class="hljs-variable">$true</span>
            ViewAuditLog                           = <span class="hljs-variable">$true</span>
            ViewSafeMembers                        = <span class="hljs-variable">$true</span>
            AccessWithoutConfirmation              = <span class="hljs-variable">$true</span>
            CreateFolders                          = <span class="hljs-variable">$true</span>
            DeleteFolders                          = <span class="hljs-variable">$true</span>
            MoveAccountsAndFolders                 = <span class="hljs-variable">$true</span>
            RequestsAuthorizationLevel1            = <span class="hljs-variable">$true</span>
            RequestsAuthorizationLevel2            = <span class="hljs-variable">$false</span>

            PvwaUrl                                = <span class="hljs-variable">$PvwaUrl</span>
            AuthenticationType                     = <span class="hljs-variable">$AuthenticationType</span>
            SkipCertificateCheck                   = <span class="hljs-variable">$SkipCertificateCheck</span>
            Credential                             = <span class="hljs-variable">$Credential</span>

            DependsOn                              = <span class="hljs-string">'[CYA_Safe]WinRec50'</span>
        }

        CYA_SafeMember <span class="hljs-string">'WinAdmins\Windows Administrators'</span> {
            Ensure               = <span class="hljs-string">'Present'</span>

            SafeName             = <span class="hljs-string">'WinAdmins50'</span>
            MemberName           = <span class="hljs-string">'Windows Administrators'</span>
            SearchIn             = <span class="hljs-string">'iosharp.dev'</span>
            UseAccounts          = <span class="hljs-variable">$true</span>
            RetrieveAccounts     = <span class="hljs-variable">$true</span>
            ListAccounts         = <span class="hljs-variable">$true</span>
            ViewSafeMembers      = <span class="hljs-variable">$true</span>

            PvwaUrl              = <span class="hljs-variable">$PvwaUrl</span>
            AuthenticationType   = <span class="hljs-variable">$AuthenticationType</span>
            SkipCertificateCheck = <span class="hljs-variable">$SkipCertificateCheck</span>
            Credential           = <span class="hljs-variable">$Credential</span>

            DependsOn            = <span class="hljs-string">'[CYA_Safe]WinAdmins50'</span>
        }

        CYA_Account <span class="hljs-string">'windowsReconcile50'</span> {
            Ensure               = <span class="hljs-string">'Present'</span>
            UserName             = <span class="hljs-string">'windowsReconcile50'</span>
            Address              = <span class="hljs-string">'iosharp.dev'</span>
            PlatformId           = <span class="hljs-string">'ioSHARPWindowsDomainReconcileAccount'</span>
            SafeName             = <span class="hljs-string">'WinRec50'</span>
            Name                 = <span class="hljs-string">'windowsReconcile50@iosharp.dev'</span>
            Password             = <span class="hljs-variable">$InitialPassword</span>

            PvwaUrl              = <span class="hljs-string">'https://192.168.137.101'</span>
            AuthenticationType   = <span class="hljs-string">'LDAP'</span>
            SkipCertificateCheck = <span class="hljs-variable">$true</span>
            Credential           = <span class="hljs-variable">$VaultCredential</span>

            DependsOn            = <span class="hljs-string">'[CYA_Safe]WinRec50'</span>
        }

        CYA_Account <span class="hljs-string">'windowsAdmin50'</span> {
            Ensure               = <span class="hljs-string">'Present'</span>
            UserName             = <span class="hljs-string">'windowsAdmin50'</span>
            Address              = <span class="hljs-string">'iosharp.dev'</span>
            PlatformId           = <span class="hljs-string">'ioSHARPWindowsDomainAccount'</span>
            SafeName             = <span class="hljs-string">'WinAdmins50'</span>
            Name                 = <span class="hljs-string">'windowsAdmin50@iosharp.dev'</span>
            ReconcileAccount     = <span class="hljs-selector-tag">@</span>{
                Name   = <span class="hljs-string">'windowsReconcile50@iosharp.dev'</span>
                Safe   = <span class="hljs-string">'WinRec50'</span>
                Folder = <span class="hljs-string">'root'</span>
            }

            PvwaUrl              = <span class="hljs-string">'https://192.168.137.101'</span>
            AuthenticationType   = <span class="hljs-string">'LDAP'</span>
            SkipCertificateCheck = <span class="hljs-variable">$true</span>
            Credential           = <span class="hljs-variable">$VaultCredential</span>

            DependsOn            = <span class="hljs-string">'[CYA_Account]windowsReconcile50'</span>, <span class="hljs-string">'[CYA_Safe]WinAdmins50'</span>

        }

    }
}

<span class="hljs-variable">$ConfigData</span> = <span class="hljs-selector-tag">@</span>{
    AllNodes = <span class="hljs-selector-tag">@</span>(
        <span class="hljs-selector-tag">@</span>{
            NodeName                    = <span class="hljs-string">'localhost'</span>
            PSDscAllowDomainUser        = <span class="hljs-variable">$true</span>
            PSDscAllowPlainTextPassword = <span class="hljs-variable">$true</span>
        }
    )
}

<span class="hljs-variable">$VaultCredential</span> = <span class="hljs-built_in">New-Object</span> System.Management.Automation.PSCredential(<span class="hljs-string">'allison'</span>, (<span class="hljs-built_in">ConvertTo-SecureString</span> <span class="hljs-string">'Password!'</span> <span class="hljs-literal">-AsPlainText</span> <span class="hljs-literal">-Force</span>))
<span class="hljs-variable">$Password</span> = <span class="hljs-built_in">New-Object</span> System.Management.Automation.PSCredential(<span class="hljs-string">'banana'</span>, (<span class="hljs-built_in">ConvertTo-SecureString</span> <span class="hljs-string">'P@$$W0RD12345'</span> <span class="hljs-literal">-AsPlainText</span> <span class="hljs-literal">-Force</span>))

<span class="hljs-variable">$ConfigurationParameters</span> = <span class="hljs-selector-tag">@</span>{
    ConfigurationData    = <span class="hljs-variable">$ConfigData</span>

    PvwaUrl              = <span class="hljs-string">'https://192.168.137.101'</span>
    AuthenticationType   = <span class="hljs-string">'LDAP'</span>
    SkipCertificateCheck = <span class="hljs-variable">$true</span>
    Credential           = <span class="hljs-variable">$VaultCredential</span>
    InitialPassword      = <span class="hljs-variable">$Password</span>
}

CreateLab_Configuration @ConfigurationParameters
</code></pre>
<h2 id="heading-compiling-and-running-our-configuration">Compiling and running our configuration</h2>
<p>We dot source our saved configuration file to compile it:</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">PS</span> C:\Users\Tim.IOSHARP\Desktop\DSC&gt; . .\CreateLab.ps1


    Directory: C:\Users\Tim.IOSHARP\Desktop\DSC\CreateLab_Configuration


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
<span class="hljs-literal">-a</span>----        <span class="hljs-number">9</span>/<span class="hljs-number">16</span>/<span class="hljs-number">2022</span>   <span class="hljs-number">7</span>:<span class="hljs-number">43</span> PM          <span class="hljs-number">15494</span> localhost.mof


<span class="hljs-built_in">PS</span> C:\Users\Tim.IOSHARP\Desktop\DSC&gt;
</code></pre>
<p>We can use <code>Test-DscConfiguration</code> to see if our resources in their desired state.</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">PS</span> C:\Users\Tim.IOSHARP\Desktop\DSC&gt; <span class="hljs-built_in">Test-DscConfiguration</span> .\CreateLab_Configuration\ | <span class="hljs-built_in">Select-Object</span> <span class="hljs-literal">-ExpandProperty</span> ResourcesNotInDesiredState | <span class="hljs-built_in">Select-Object</span> ResourceId

ResourceId
----------
[<span class="hljs-type">ADUser</span>]iosharp\windowsReconcile50
[<span class="hljs-type">ADUser</span>]iosharp\windowsAdmin50
[<span class="hljs-type">ADGroup</span>]Domain Admins
[<span class="hljs-type">CYA_Safe</span>]WinRec50
[<span class="hljs-type">CYA_Safe</span>]WinAdmins50
[<span class="hljs-type">CYA_SafeMember</span>]WinRec\CyberArk Administrators
[<span class="hljs-type">CYA_SafeMember</span>]WinAdmins\Windows Administrators
[<span class="hljs-type">CYA_Account</span>]windowsReconcile50
[<span class="hljs-type">CYA_Account</span>]windowsAdmin50


<span class="hljs-built_in">PS</span> C:\Users\Tim.IOSHARP\Desktop\DSC&gt;
</code></pre>
<p>No surprise, none of our resources are as they do not even exist.</p>
<p>From there, we can start the configuration using <code>Start-DscConfiguration</code> and passing the <code>Wait</code> parameter.</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">PS</span> C:\Users\Tim.IOSHARP\Desktop\DSC&gt; <span class="hljs-built_in">Start-DscConfiguration</span> .\CreateLab_Configuration\ <span class="hljs-literal">-Wait</span>
<span class="hljs-built_in">PS</span> C:\Users\Tim.IOSHARP\Desktop\DSC&gt;
</code></pre>
<p>We do not get any output from <code>Start-DscConfiguration</code> which is exactly what we want. Running <code>Test-DscConfiguration</code> but this time looking at resources in the desired state shows us all nine of them.</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">PS</span> C:\Users\Tim.IOSHARP\Desktop\DSC&gt; <span class="hljs-built_in">Test-DscConfiguration</span> .\CreateLab_Configuration\ | <span class="hljs-built_in">Select-Object</span> <span class="hljs-literal">-ExpandProperty</span> ResourcesInDesiredState | <span class="hljs-built_in">Select-Object</span> ResourceId

ResourceId
----------
[<span class="hljs-type">ADUser</span>]iosharp\windowsReconcile50
[<span class="hljs-type">ADUser</span>]iosharp\windowsAdmin50
[<span class="hljs-type">ADGroup</span>]Domain Admins
[<span class="hljs-type">CYA_Safe</span>]WinRec50
[<span class="hljs-type">CYA_Safe</span>]WinAdmins50
[<span class="hljs-type">CYA_SafeMember</span>]WinRec\CyberArk Administrators
[<span class="hljs-type">CYA_SafeMember</span>]WinAdmins\Windows Administrators
[<span class="hljs-type">CYA_Account</span>]windowsReconcile50
[<span class="hljs-type">CYA_Account</span>]windowsAdmin50


<span class="hljs-built_in">PS</span> C:\Users\Tim.IOSHARP\Desktop\DSC&gt;
</code></pre>
<p>But what matters is what we see in CyberArk. Browsing to the PVWA, we see our two newly onboarded accounts in our newly created safes. <code>windowsAdmin50</code> has <code>windowsReconcile50</code> defined as it's reconcile just as we defined in our DSC configuration.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663355550955/wF-pLMLnp.png" alt="image.png" /></p>
<h1 id="heading-changing-existing-vault-objects-with-powershell-dsc">Changing existing Vault objects with PowerShell DSC</h1>
<p>Our configuration creates the objects when they are missing but what happens if they already exist in some form?</p>
<p>We make small changes to our accounts -- change their names, unlink the reconcile account, different platform -- with the goal that running <code>Start-DscConfiguration</code> will adjust them back to what is defined in our configuration.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663496265835/-w2pRHmqu.png" alt="image.png" /></p>
<p>A quick <code>Test-DscConfiguration</code> shows that DSC knows the accounts in the Vault are incorrect based on the configuration:</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">PS</span> C:\Users\Tim.IOSHARP\Desktop\DSC&gt; <span class="hljs-built_in">Test-DscConfiguration</span> .\CreateLab_Configuration\ | <span class="hljs-built_in">Select-Object</span> <span class="hljs-literal">-ExpandProperty</span> ResourcesNotInDesiredState | <span class="hljs-built_in">Select-Object</span> ResourceId

ResourceId
----------
[<span class="hljs-type">CYA_Account</span>]windowsReconcile50
[<span class="hljs-type">CYA_Account</span>]windowsAdmin50

<span class="hljs-built_in">PS</span> C:\Users\Tim.IOSHARP\Desktop\DSC&gt;
</code></pre>
<p>Simply running <code>Start-DscConfiguration</code> 'silently' adjusts the accounts in CyberArk to their desired configuration.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663581070966/J56czAoxV.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663581091383/wApMU8z6j.png" alt="image.png" /></p>
<p>There we have it: the same configuration we used to create the Vault objects was used to correct them without having to do any changes.</p>
<p>The <code>CyberArkDsc</code> module can be found on GitHub. Currently it only has resources for safes, safe memberships, and accounts. Pull requests are welcome!</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/aaearon/CyberArkDsc">https://github.com/aaearon/CyberArkDsc</a></div>
]]></content:encoded></item><item><title><![CDATA[SecretManagement.CyberArk: An extension for the  SecretManagement PowerShell module]]></title><description><![CDATA[The SecretManagement PowerShell module provides a common interface to interact with a wide array of secret vaults enabled through SecretManagement extensions. There are a handful of SecretManagement extensions, including for Azure KeyVault, KeePass, ...]]></description><link>https://timschindler.blog/secretmanagementcyberark-an-extension-for-the-secretmanagement-powershell-module</link><guid isPermaLink="true">https://timschindler.blog/secretmanagementcyberark-an-extension-for-the-secretmanagement-powershell-module</guid><category><![CDATA[cyberark]]></category><category><![CDATA[Powershell]]></category><category><![CDATA[Security]]></category><category><![CDATA[secrets]]></category><category><![CDATA[#cybersecurity]]></category><dc:creator><![CDATA[Tim Schindler]]></dc:creator><pubDate>Mon, 22 Aug 2022 13:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/-MMeeP7pjbE/upload/v1660995508913/0KTPc-jsN.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The <a target="_blank" href="https://docs.microsoft.com/en-us/powershell/utility-modules/secretmanagement/overview?view=ps-modules">SecretManagement</a> PowerShell module provides a common interface to interact with a wide array of secret vaults enabled through SecretManagement extensions. There are a handful of SecretManagement extensions, including for <a target="_blank" href="https://www.powershellgallery.com/packages/Az.KeyVault">Azure KeyVault</a>, <a target="_blank" href="https://www.powershellgallery.com/packages/SecretManagement.KeePass">KeePass</a>, and <a target="_blank" href="https://www.powershellgallery.com/packages/SecretManagement.LastPass">LastPass</a>.</p>
<p>The <code>SecretManagement</code> module supports having multiple secret vault types registered at the same time -- allowing you to access secrets in Azure KeyVault, KeePass, LastPass, etc. through the same cmdlets. The same secrets vault type can also be registered multiple times, allowing you to easily retrieve secrets from multiple KeePass databases.</p>
<p><a target="_blank" href="https://www.powershellgallery.com/packages/SecretManagement.CyberArk/">SecretManagement.CyberArk</a> is a SecretManagement extension that brings support to connecting to CyberArk using the Central Provider/Central Credential Provider (using <a target="_blank" href="https://github.com/pspete/CredentialRetriever/">CredentialRetriever</a>) or REST API (using <a target="_blank" href="https://github.com/pspete/psPAS">psPAS</a>). </p>
<h1 id="heading-using-secretmanagementcyberark-with-the-central-credential-provider">Using SecretManagement.CyberArk with the Central Credential Provider</h1>
<p>The <a target="_blank" href="https://github.com/aaearon/SecretManagement.CyberArk">SecretManagement.CyberArk README</a> provides the information we need to start working with the module.</p>
<p>The first step is to install and import both the <code>Microsoft.PowerShell.SecretManagement</code> and <code>SecretManagement.CyberArk</code> modules.</p>
<pre><code class="lang-powershell">PS &gt; Install-Module Microsoft.PowerShell.SecretManagement
PS &gt; Install-Module SecretManagement.CyberArk
PS &gt; Import-Module Microsoft.PowerShell.SecretManagement
PS &gt; Import-Module SecretManagement.CyberArk
</code></pre>
<p>Next is to register a secret vault with the module <code>SecretManagement.CyberArk</code>. We give it a name to refer to when working with a secret and specify that we want to use the Central Credential Provider and pass the relevant details in <code>VaultParameters</code>.</p>
<pre><code class="lang-powershell">PS &gt; $VaultParameters = @{
           ConnectionType       = 'CentralCredentialProvider'
           AppID                = 'windowsScript'
           URL                  = 'https://comp01'
           SkipCertificateCheck = $true
    }

PS &gt; Register-SecretVault -Name CyberArk -ModuleName SecretManagement.CyberArk -VaultParameters $VaultParameters
</code></pre>
<p><code>Get-SecretInfo</code> returns information about a secret: the name of the account, the data type of the secret when using <code>Get-Secret</code> and the <code>SecretManagement</code> Vault the secret is found in.</p>
<p>It also returns a <code>Metadata</code> property that shows information in CyberArk about the account.</p>
<pre><code class="lang-powershell">PS &gt; Get-SecretInfo -VaultName CyberArk -Filter windowsAdmin01
Name                                                                    Type         VaultName
----                                                                    ----         ---------
Operating System-ioSHARPWindowsDomainAccount-iosharp.lab-windowsAdmin01 SecureString CyberArk

PS &gt; Get-SecretInfo -VaultName CyberArk -Filter windowsAdmin01 | Select-Object -ExpandProperty Metadata

Key                       Value
---                       -----
PolicyID                  ioSHARPWindowsDomainAccount
Folder                    Root
LastTask                  ChangeTask
CreationMethod            PVWA
Safe                      Windows
CPMStatus                 success
UserName                  windowsAdmin01
PasswordChangeInProcess   False
Address                   192.168.0.10
Name                      Operating System-ioSHARPWindowsDomainAccount-iosharp.lab-windowsAdmin01
LastSuccessVerification   1660836856
SequenceID                116
LastSuccessReconciliation 1660834970
DeviceType                Operating System
LastSuccessChange         1660923260
RetriesCount              -1

PS &gt;
</code></pre>
<p>We retrieve the secret in a similar way through <code>Get-Secret</code>, returned as a <code>SecureString</code>.</p>
<pre><code class="lang-powershell">PS &gt; $Secret = Get-Secret -VaultName CyberArk -Name 'Operating System-ioSHARPWindowsDomainAccount-iosharp.lab-windowsAdmin01'
PS &gt; $Secret.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    SecureString                             System.Object

PS &gt; $Secret | ConvertFrom-SecureString -AsPlainText
UTyga8!ZOEDvPhQsrwlI(xjb2K$c
PS &gt;
</code></pre>
<p>The SecretManagement module supports adding and removing secrets through <code>Set-Secret</code> and <code>Remove-Secret</code> however as the Central Provider and Central Credential Provider do not support this functionality, these two cmdlets are only implemented for the <code>REST</code> ConnectionType.</p>
<h1 id="heading-working-with-multiple-secretmanagementcyberark-secret-vaults">Working with multiple <code>SecretManagement.CyberArk</code> secret vaults</h1>
<p>We can register multiple <code>SecretManagement.CyberArk</code> secret vaults. These secret vaults can use different connection types.</p>
<pre><code class="lang-powershell">PS &gt; $TestEnvironmentVaultParameters = @{
           ConnectionType = 'CredentialProvider'
           AppID          = 'linuxApp'
           ClientPath     = 'C:\Path\To\CLIPasswordSDK.exe'
      }

PS &gt; Register-SecretVault -Name CyberArkTest -ModuleName SecretManagement.CyberArk -VaultParameters $TestEnvironmentVaultParameters
PS &gt; $ProductionEnvironmentVaultParameters = @{
           ConnectionType       = 'CentralCredentialProvider'
           AppID                = 'windowsScript'
           URL                  = 'https://comp01'
     }

PS &gt; Register-SecretVault -Name CyberArkProduction -ModuleName SecretManagement.CyberArk -VaultParameters $ProductionEnvironmentVaultParameters
</code></pre>
<p>Executing <code>Get-SecretVault</code> shows us all our register secret vaults and their names so we can specify which Vault to get the secret from.</p>
<pre><code class="lang-powershell">PS &gt; Get-SecretVault

Name               ModuleName                IsDefaultVault
----               ----------                --------------
CyberArkProduction SecretManagement.CyberArk False
CyberArkTest       SecretManagement.CyberArk False

PS &gt;
</code></pre>
<h1 id="heading-conclusion">Conclusion</h1>
<p>The <code>SecretManagement.CyberArk</code> extension allows you to retrieve secrets from multiple CyberArk environments using different connection methods through the same cmdlets provided by the <code>SecretManagement</code> module.</p>
<p><code>SecretManagement.CyberArk</code> can be found in the <a target="_blank" href="https://www.powershellgallery.com/packages/SecretManagement.CyberArk/">PowerShell Gallery</a>. The code is available on <a target="_blank" href="https://github.com/aaearon/SecretManagement.CyberArk">GitHub</a>.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/aaearon/SecretManagement.CyberArk">https://github.com/aaearon/SecretManagement.CyberArk</a></div>
]]></content:encoded></item><item><title><![CDATA[Continuous Deployment of CyberArk Platforms using GitHub Actions]]></title><description><![CDATA[Management of CyberArk platforms can be difficult. They have numerous settings that are integral to how privileged accounts are used and their credentials managed. An incorrect configuration could leave an account in an unusable state.
Platforms are ...]]></description><link>https://timschindler.blog/continuous-deployment-of-cyberark-platforms-using-github-actions</link><guid isPermaLink="true">https://timschindler.blog/continuous-deployment-of-cyberark-platforms-using-github-actions</guid><category><![CDATA[cyberark]]></category><category><![CDATA[Powershell]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[continuous deployment]]></category><category><![CDATA[github-actions]]></category><dc:creator><![CDATA[Tim Schindler]]></dc:creator><pubDate>Wed, 13 Jul 2022 16:43:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/842ofHC6MaI/upload/v1657726852834/IH9ec4Kdw.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Management of CyberArk platforms can be difficult. They have numerous settings that are integral to how privileged accounts are used and their credentials managed. An incorrect configuration could leave an account in an unusable state.</p>
<p>Platforms are typically staged across multiple environments. Whereas the REST API allows for easy export and import of platforms from environments, it lacks endpoints to comprehensively manage their settings once they are created.</p>
<p>Furthermore, it is common to have platforms recorded outside of CyberArk -- the XML-based visual representation of them in the PVWA often shoehorned inside a table in Confluence or Word -- with the necessity to keep the documentation accurate, which can be challenging in an environment with a large number of platforms and people who can make edits.</p>
<p>Being that platforms are <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/12.6/en/Content/Platforms/Platform-Packages-Import-Introduction.htm">represented by both an XML and INI file</a> we can ditch the awkward tables and stash them in a Git repository while reaping the rewards version control offers. From there, when it is a new platform, we can create a platform package with the help of the <a target="_blank" href="https://github.com/aaearon/New-PASExtensions">New-PASExtensions</a> module and  import it into a CyberArk environment but how can we streamline this in the case where the platform already exists in the Vault and thus importing is not an option? </p>
<p>We could still keep manually updating platforms in CyberArk based on what is documented in our Git repository but this is still time-intensive and error prone. After understanding how the importing of platform packages works, we may be able to use part of or all of the functionality in order to develop a <a target="_blank" href="https://en.wikipedia.org/wiki/Continuous_deployment">continuous deployment</a> workflow.</p>
<h1 id="heading-platforms-and-platform-packages">Platforms and platform packages</h1>
<p>The first step to seeing how our continuous deployment could leverage CyberArk's platform deployment mechanism is understanding how the entire import platform package process works. And that starts with understanding a platform package.</p>
<p>According to the <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/12.2/en/Content/Platforms/Platform-Packages-Import-Introduction.htm?tocpath=End%20user%7CPrivileged%20Accounts%7CClassic%20Interface%7CManage%20platforms%20classic%20interface%7C_____5">documentation</a> a platform package is straightforward: it is just a Zip file that contains a CPM policy file (an INI file with the name <code>Policy-$PolicyId.ini</code>), a PVWA settings file (a XML file with the name <code>Policy-$PolicyId.xml</code>), and (optionally) the CPM plugin files (executables, PowerShell scripts, TPC prompts and process files, or a combination.)</p>
<p>Once created, the Zip file is <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/12.2/en/Content/Platforms/Platform-Packages-Import-Introduction.htm?tocpath=End%20user%7CPrivileged%20Accounts%7CClassic%20Interface%7CManage%20platforms%20classic%20interface%7C_____5#ImportaPlatformPackage">imported through the PVWA</a> and the CPM plugin files are copied automatically to the CPM.</p>
<p>Whereas the documentation does a good job telling us what to do, it does not provide enough details on how it is exactly done and this "how" is what we need to really understand if we want or can use this process in our continuous deployment.</p>
<h2 id="heading-understanding-the-import-process-of-a-platforms-package">Understanding the import process of a platform's package</h2>
<p>The best place to start is downloading a plugin from the <a target="_blank" href="https://cyberark-customers.force.com/mplace/s/">CyberArk Marketplace</a>, importing it, and observing the behavior. </p>
<p>I chose the <a target="_blank" href="https://cyberark-customers.force.com/mplace/s/#a352J000000pthhQAA-a392J000001h52sQAA">RealVNC Service Mode</a> as it is a TPC plugin using a PowerShell script with the classic prompts and process files, in addition to the required CPM policy and PVWA setting files, so we will see how those files are distributed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1655645497292/XmHYgYGkW.png" alt="image.png" /></p>
<p>Upon importing the platform package for the first time we see that it is done successfully and is added under Imported Platforms:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1655644697282/XQOL5xIyv.png" alt="image.png" /></p>
<p>With the knowledge we already have from <a target="_blank" href="https://timschindler.blog/changing-cyberark-platform-properties-with-powershell">programmatically changing CyberArk platform properties using PowerShell</a> we know that the PVWA settings file <code>Policy-RealVNC.xml</code> was likely merged into <code>Policies.xml</code> in the <code>PVWAConfig</code> safe and the CPM policy file <code>Policy-RealVNC.ini</code> was dropped into the <code>Policies</code> folder of the <code>PasswordManagerShared</code> safe but what happened to the CPM plugin files made up of the PowerShell script and the prompts and process files?</p>
<p>As with anything to do with platforms and their files, the first place to look should be the <code>PasswordManagerShared</code> safe. </p>
<p>First lets take a quick look to make sure the CPM policy file <code>Policy-RealVNC.ini</code> landed in the <code>Policies</code> folder like we expected:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1655645726671/r42Rz4ZN-.png" alt="image.png" /></p>
<p>What is interesting to note is that as part of the import process the CPM policy file was renamed from <code>Policy-RealVNC.ini</code> to <code>Policy-RealVNCServiceMode.ini</code>, which is the <code>PolicyID</code> of the plugin.</p>
<p>Moving on to finding the CPM plugins files, inside the same safe we see the <code>ImportedPlatforms</code> folder and a sub-folder named <code>Policy-RealVNCServiceMode</code> that contains our CPM plugin files. The sub-folder seems to be named based off the convention <code>Policy-$PolicyId</code>, which is something we should keep in mind for our CD workflow:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1655645427276/abfKj5y5M.png" alt="image.png" /></p>
<p>Step 3 of <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/12.2/en/Content/Platforms/Platform-Packages-Import-Introduction.htm?tocpath=End%20user%7CPrivileged%20Accounts%7CClassic%20Interface%7CManage%20platforms%20classic%20interface%7C_____5">creating a platform package</a> explains that the CPM plugin files will be dumped into the <code>bin</code> folder of the CPM. Checking the folder and <code>pm.log</code> we see that is the case:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1655646089559/u6UG2LGP-.png" alt="image.png" /></p>
<p>The distribution of CPM plugin files is similar to that of what we see with the <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/12.2/en/Content/PASIMP/ConfigurePSMUniversalConnector.htm">deployment of Universal Connectors on multiple PSMs</a>. This works for the first deployment of a new platform but what about consecutive deployments for updated platforms or CPM plugin files?</p>
<h1 id="heading-updating-platforms-and-cpm-plugin-files">Updating platforms and CPM plugin files</h1>
<p>Although we manually imported the platform through the PVWA we know that an <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/12.2/en/Content/WebServices/ImportPlatform.htm?tocpath=Developer%7CREST%20APIs%7CPlatforms%7C_____3">API endpoint to do the same exists</a>. The best idea when needing to update a platform's settings or it's CPM plugin files would be to make the needed updates, re-create the platform package, and use the import platform package API endpoint to deploy the platform.</p>
<p>Unfortunately trying to import a package for a platform that shares the <code>PolicyID</code> or <code>PolicyName</code> of an existing platform is not possible. As the platform of an account is defined through an account property you could first <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/12.2/en/Content/SDK/rest-api-delete-target-platform.htm?tocpath=Developer%7CREST%20APIs%7CPlatforms%7CTarget%20Platforms%7C_____5">delete the platform</a> and then import it but at the very least any Master Policy exceptions are also deleted and must be manually re-created.</p>
<p>We saw that as part of the import process our platform's CPM plugin files were stashed in their own sub-folder under <code>ImportedPlatforms</code> in the <code>PasswordManagerShared</code> safe where they then ended up in the <code>bin</code> folder of the CPM. What happens when we upload a new version of an existing file directly into the Vault?</p>
<h2 id="heading-uploading-new-cpm-plugin-files-into-the-vault">Uploading new CPM plugin files into the Vault</h2>
<p>For the CPM plugin files we should download one of the files out of PrivateArk, make an inconsequential change to it, and observe the results. </p>
<p>Inside of <code>RealVNCProcess.ini</code> we add a new comment to the file:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1655651635246/twRuL9VPF.png" alt="image.png" />
and then upload it back into the safe:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1655651704375/tmJX_31f9.png" alt="image.png" /></p>
<p>After waiting some minutes, searching the <code>pm.log</code> for any log entry comes up with nothing:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1655652070549/tE-YE1_sI.png" alt="image.png" /></p>
<p>and inside the <code>bin</code> folder <code>RealVNCProcess.ini</code> is still the same:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1655652156470/Yc1mxN-tW.png" alt="image.png" /></p>
<p>Out of curiosity, lets see what happens when we restart the CPM service:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1655652317839/035PS6dMR.png" alt="image.png" /></p>
<p>Kicking the CPM service worked but having to restart the CPM service every time we want to deploy new CPM plugin files -- which could be countless times per day with our continuous deployment workflow -- across all our CPMs probably isn't what we want.</p>
<p>Looking again in <code>pm.log</code> when the plugin files are downloaded successfully, we always seeing a proceeding log message about a new policy being found. Can we trigger the downloading of new CPM plugin files by changing a superfluous value in the CPM policy file <code>Policy-RealVNCServiceMode.ini</code> and uploading that?</p>
<h2 id="heading-manually-triggering-the-download-of-new-cpm-plugin-files-by-the-cpm">Manually triggering the download of new CPM plugin files by the CPM</h2>
<p>We make another change to <code>RealVNCProcess.ini</code>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1655652890586/yxmqzcMhl.png" alt="image.png" /></p>
<p>and upload it to the Vault. We then head to the <code>Policies</code> folder in the <code>PasswordManagerShared</code> safe and download a copy of <code>Policy-RealVNCServiceMode.ini</code>.</p>
<p>But before we make any change to <code>Policy-RealVNCServiceMode.ini</code> lets simply upload that same exact copy back into the safe. This is worth trying because more than likely CyberArk is not comparing the content of files to know if there is an update rather just looking at the <code>Last Modified</code> date of the file -- which changes when the file is uploaded again.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1655653308426/g_PI0qh5L.png" alt="image.png" /></p>
<p>Simply re-uploading <code>Policy-RealVNCServiceMode.ini</code> and thus it having a new <code>Last Modified</code> date was enough to trigger CyberArk to distribute our updated CPM plugin file.</p>
<h1 id="heading-putting-it-all-together-in-a-continuous-deployment-workflow">Putting it all together in a continuous deployment workflow</h1>
<p>I am using GitHub Actions with a self-hosted Windows runner as PACLI/PoShPACLI needs to communicate with the Vault. </p>
<p>FYI: All the code can be found in the below repos.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/aaearon/cyberark-extensions-continuous-deployment">https://github.com/aaearon/cyberark-extensions-continuous-deployment</a></div>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/aaearon/Deploy-PASExtensions">https://github.com/aaearon/Deploy-PASExtensions</a></div>
<h2 id="heading-setting-up-the-github-repository">Setting up the GitHub repository</h2>
<p>Still staying with the RealVNC CPM plugin, we create a repository on GitHub to store the platform files. All the plugin's files are stored in a folder that matches the platform's ID -- <code>RealVNCServiceMode</code> in our case -- in the <code>platforms</code> folder. </p>
<pre><code>PS C:\Users\Tim\Projects\cyberark<span class="hljs-operator">-</span>extensions<span class="hljs-operator">-</span>continuous<span class="hljs-operator">-</span>delivery<span class="hljs-operator">&gt;</span> Get<span class="hljs-operator">-</span>ChildItem

    Directory: C:\Users\Tim\Projects\cyberark<span class="hljs-operator">-</span>extensions<span class="hljs-operator">-</span>continuous<span class="hljs-operator">-</span>delivery

Mode                 LastWriteTime         Length Name
<span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>                 <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>         <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>
d<span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>           <span class="hljs-number">7</span><span class="hljs-operator">/</span><span class="hljs-number">13</span><span class="hljs-operator">/</span><span class="hljs-number">2022</span>  <span class="hljs-number">4</span>:<span class="hljs-number">27</span> PM                .git
d<span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>           <span class="hljs-number">6</span><span class="hljs-operator">/</span><span class="hljs-number">19</span><span class="hljs-operator">/</span><span class="hljs-number">2022</span>  <span class="hljs-number">1</span>:<span class="hljs-number">40</span> PM                .github
d<span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>           <span class="hljs-number">7</span><span class="hljs-operator">/</span><span class="hljs-number">10</span><span class="hljs-operator">/</span><span class="hljs-number">2022</span> <span class="hljs-number">11</span>:<span class="hljs-number">19</span> AM                platforms
<span class="hljs-operator">-</span>a<span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>           <span class="hljs-number">7</span><span class="hljs-operator">/</span><span class="hljs-number">13</span><span class="hljs-operator">/</span><span class="hljs-number">2022</span>  <span class="hljs-number">4</span>:<span class="hljs-number">27</span> PM            <span class="hljs-number">621</span> deploy.ps1
<span class="hljs-operator">-</span>a<span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>           <span class="hljs-number">6</span><span class="hljs-operator">/</span><span class="hljs-number">19</span><span class="hljs-operator">/</span><span class="hljs-number">2022</span> <span class="hljs-number">12</span>:<span class="hljs-number">16</span> PM           <span class="hljs-number">1070</span> LICENSE
<span class="hljs-operator">-</span>a<span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>           <span class="hljs-number">7</span><span class="hljs-operator">/</span><span class="hljs-number">13</span><span class="hljs-operator">/</span><span class="hljs-number">2022</span>  <span class="hljs-number">4</span>:<span class="hljs-number">27</span> PM           <span class="hljs-number">1950</span> README.md

PS C:\Users\Tim\Projects\cyberark<span class="hljs-operator">-</span>extensions<span class="hljs-operator">-</span>continuous<span class="hljs-operator">-</span>delivery<span class="hljs-operator">&gt;</span> Get<span class="hljs-operator">-</span>ChildItem .\platforms\RealVNCServiceMode\

    Directory: C:\Users\Tim\Projects\cyberark<span class="hljs-operator">-</span>extensions<span class="hljs-operator">-</span>continuous<span class="hljs-operator">-</span>delivery\platforms\RealVNCServiceMode

Mode                 LastWriteTime         Length Name
<span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>                 <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>         <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>
<span class="hljs-operator">-</span>a<span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>           <span class="hljs-number">7</span><span class="hljs-operator">/</span><span class="hljs-number">10</span><span class="hljs-operator">/</span><span class="hljs-number">2022</span> <span class="hljs-number">11</span>:<span class="hljs-number">19</span> AM            <span class="hljs-number">235</span> LICENSE
<span class="hljs-operator">-</span>a<span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>           <span class="hljs-number">7</span><span class="hljs-operator">/</span><span class="hljs-number">13</span><span class="hljs-operator">/</span><span class="hljs-number">2022</span>  <span class="hljs-number">4</span>:<span class="hljs-number">27</span> PM           <span class="hljs-number">6642</span> Policy<span class="hljs-operator">-</span>RealVNCServiceMode.ini
<span class="hljs-operator">-</span>a<span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>           <span class="hljs-number">7</span><span class="hljs-operator">/</span><span class="hljs-number">11</span><span class="hljs-operator">/</span><span class="hljs-number">2022</span>  <span class="hljs-number">2</span>:<span class="hljs-number">40</span> PM            <span class="hljs-number">673</span> Policy<span class="hljs-operator">-</span>RealVNCServiceMode.xml
<span class="hljs-operator">-</span>a<span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>           <span class="hljs-number">7</span><span class="hljs-operator">/</span><span class="hljs-number">10</span><span class="hljs-operator">/</span><span class="hljs-number">2022</span> <span class="hljs-number">11</span>:<span class="hljs-number">19</span> AM           <span class="hljs-number">2566</span> realvnc.ps1
<span class="hljs-operator">-</span>a<span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>           <span class="hljs-number">7</span><span class="hljs-operator">/</span><span class="hljs-number">10</span><span class="hljs-operator">/</span><span class="hljs-number">2022</span> <span class="hljs-number">11</span>:<span class="hljs-number">19</span> AM           <span class="hljs-number">4177</span> RealVNCProcess.ini
<span class="hljs-operator">-</span>a<span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>           <span class="hljs-number">7</span><span class="hljs-operator">/</span><span class="hljs-number">10</span><span class="hljs-operator">/</span><span class="hljs-number">2022</span> <span class="hljs-number">11</span>:<span class="hljs-number">19</span> AM           <span class="hljs-number">1432</span> RealVNCPrompts.ini
</code></pre><p><em>Note that <code>Policy-RealVNC.ini</code> was renamed to <code>Policy-RealVNCServiceMode.ini</code>!</em></p>
<p>Inside the root of the repository, I have created a small PowerShell script <a target="_blank" href="https://github.com/aaearon/cyberark-extensions-continuous-deployment/blob/main/deploy.ps1"><code>deploy.ps1</code></a> to be invoked by our GitHub Actions workflow that calls a custom function I wrote -- <code>Update-PASPlatformFiles.ps1</code>, part of the <a target="_blank" href="https://github.com/aaearon/Deploy-PASExtensions">Deploy-PASExtensions</a> PowerShell module -- that does the heavy lifting and uses PACLI/PoShPACLI to get the platform files -- regardless if they were changed -- in the Vault as part of the continuous deployment workflow:</p>
<pre><code class="lang-powershell">param (
    [string]$PACLIClientPath,
    [PSCredential]$VaultCredential,
    [string]$VaultAddress,
    [string]$BasePlatformFolder
)

Import-Module PoShPACLI
Import-Module .\deploy-pasextensions\Deploy-PASExtensions\Deploy-PASExtensions.psd1

Set-PVConfiguration -ClientPath $PACLIClientPath
Start-PVPacli
New-PVVaultDefinition -vault $VaultAddress -address $VaultAddress -ErrorAction SilentlyContinue
Connect-PVVault -user $($VaultCredential.UserName) -password $($VaultCredential.Password)

(Get-ChildItem $BasePlatformFolder).FullName | Update-PASPlatformFiles

Disconnect-PVVault
Stop-PVPacli
</code></pre>
<p>The script is pretty simple. It imports the needed modules, builds up the PACLI session, and for each platform in <code>$BasePlatformFolder</code> updates it's files and then tears down the PACLI session.</p>
<p>The last step is <a target="_blank" href="https://docs.github.com/en/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository">adding the password of the user you will connect to the Vault via PACLI with as a secret to the GitHub repository</a>. Use <code>VAULT_PASSWORD</code> as the secret name otherwise you will need to adapt the workflow below with the correct secret name.</p>
<h2 id="heading-installing-the-github-actions-self-hosted-runner">Installing the GitHub Actions self-hosted runner</h2>
<p>Using the GitHub Actions documentation we install a <a target="_blank" href="https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners">self-hosted runner</a> and <a target="_blank" href="https://docs.github.com/en/actions/hosting-your-own-runners/adding-self-hosted-runners#adding-a-self-hosted-runner-to-a-repository">add it to the repository we created above</a>.</p>
<p>It also makes sense to make sure PACLI and <a target="_blank" href="https://github.com/pspete/PoShPACLI">PoShPACLI</a> are installed and working on the same machine the self-hosted runner is on.</p>
<h2 id="heading-creating-the-github-actions-workflow">Creating the GitHub Actions workflow</h2>
<p>This is where the magic happens. In the workflow is where we define our jobs, the steps they consist of, and which actions trigger the workflow. Whereas GitHub Actions is extremely versatile and would allow us to trigger a workflow upon a merged pull request, for now we keep it simple and have the workflow trigger on each push.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">platform</span> <span class="hljs-string">files</span> <span class="hljs-string">to</span> <span class="hljs-string">CyberArk</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
  <span class="hljs-attr">workflow_dispatch:</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">deploy:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">platform</span> <span class="hljs-string">files</span> <span class="hljs-string">to</span> <span class="hljs-string">the</span> <span class="hljs-string">Vault</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">self-hosted</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span> <span class="hljs-string">Deploy-PASExtensions</span> <span class="hljs-string">repo</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">repository:</span> <span class="hljs-string">aaearon/deploy-pasextensions</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">deploy-pasextensions</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Uploads</span> <span class="hljs-string">platform</span> <span class="hljs-string">files</span> <span class="hljs-string">to</span> <span class="hljs-string">the</span> <span class="hljs-string">Vault</span>
        <span class="hljs-attr">shell:</span> <span class="hljs-string">powershell</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          $env:HOMEDRIVE = ""
          $env:HomePath = "${{ runner.temp }}"
          $VaultCredential = New-Object System.Management.Automation.PSCredential ("Administrator", (ConvertTo-SecureString -String ${{ secrets.VAULT_PASSWORD }} -AsPlainText -Force))
          .\deploy.ps1 -PACLIClientPath 'C:\PACLI\Pacli.exe' -VaultCredential $VaultCredential -VaultAddress 192.168.0.50 -BasePlatformFolder '.\platforms\'</span>
</code></pre>
<p>We need to overwrite <code>$env:HOMEDRIVE</code> and <code>$env:HomePath</code> as these do not seem to exist inside the runner and thus thrown an error. A <code>PSCredential</code> object is created with the <code>VAULT_PASSWORD</code> secret stored in the GitHub repository and is passed as an argument to <code>deploy.ps1</code> along with other arguments that match my target CyberArk environment.</p>
<p>At this point we can push everything to GitHub.</p>
<h1 id="heading-making-a-change-that-will-kick-off-the-continuous-deployment-workflow">Making a change that will kick off the continuous deployment workflow</h1>
<p>With the GitHub repository set up, self-hosted runner installed, and GitHub Actions workflow defined we are ready to see if it works.</p>
<p>We make two small changes to the <code>RealVNCServiceMode</code> platform, commit them, and then push: Change <code>ImmediateInterval</code> to 6 and add the <code>Banana</code> property as optional.</p>
<pre><code>PS C:\Users\Tim\Projects\cyberark<span class="hljs-operator">-</span>extensions<span class="hljs-operator">-</span>continuous<span class="hljs-operator">-</span>delivery<span class="hljs-operator">&gt;</span> git show 36aa8ad2ec2fb1afe12c6f34bcbb794551ab0ae4
commit 36aa8ad2ec2fb1afe12c6f34bcbb794551ab0ae4 (HEAD <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> main, origin<span class="hljs-operator">/</span>main, origin<span class="hljs-operator">/</span>HEAD)
Author: Tim Schindler <span class="hljs-operator">&lt;</span>tim@iosharp.com&gt;
Date:   Wed Jul <span class="hljs-number">13</span> <span class="hljs-number">17</span>:01:<span class="hljs-number">15</span> <span class="hljs-number">2022</span> <span class="hljs-operator">+</span>0200

    add banana property and change immediateinterval

diff <span class="hljs-operator">-</span><span class="hljs-operator">-</span>git a<span class="hljs-operator">/</span>platforms<span class="hljs-operator">/</span>RealVNCServiceMode<span class="hljs-operator">/</span>Policy<span class="hljs-operator">-</span>RealVNCServiceMode.ini b<span class="hljs-operator">/</span>platforms<span class="hljs-operator">/</span>RealVNCServiceMode<span class="hljs-operator">/</span>Policy<span class="hljs-operator">-</span>RealVNCServiceMode.ini
index 93fbcbf..b78d453 <span class="hljs-number">100644</span>
<span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span> a<span class="hljs-operator">/</span>platforms<span class="hljs-operator">/</span>RealVNCServiceMode<span class="hljs-operator">/</span>Policy<span class="hljs-operator">-</span>RealVNCServiceMode.ini
<span class="hljs-operator">+</span><span class="hljs-operator">+</span><span class="hljs-operator">+</span> b<span class="hljs-operator">/</span>platforms<span class="hljs-operator">/</span>RealVNCServiceMode<span class="hljs-operator">/</span>Policy<span class="hljs-operator">-</span>RealVNCServiceMode.ini
@@ <span class="hljs-number">-2</span>,<span class="hljs-number">7</span> <span class="hljs-operator">+</span><span class="hljs-number">2</span>,<span class="hljs-number">7</span> @@ PolicyID<span class="hljs-operator">=</span>RealVNCServiceMode                   ;Mandatory, unique identifier <span class="hljs-keyword">for</span>
 PolicyName<span class="hljs-operator">=</span>Real VNC    Service Mode via PowerShell             ;Short description
 SearchForUsages<span class="hljs-operator">=</span>No                             ;Expected values: yes<span class="hljs-operator">/</span>no
 PolicyType<span class="hljs-operator">=</span>Regular                     ;Expected values: regular, usage, group
<span class="hljs-operator">-</span>ImmediateInterval<span class="hljs-operator">=</span><span class="hljs-number">5</span>                    ;In <span class="hljs-literal">minutes</span>
<span class="hljs-operator">+</span>ImmediateInterval<span class="hljs-operator">=</span><span class="hljs-number">6</span>                    ;In <span class="hljs-literal">minutes</span>
 Interval<span class="hljs-operator">=</span><span class="hljs-number">1440</span>                          ;In <span class="hljs-literal">minutes</span>
 MaxConcurrentConnections<span class="hljs-operator">=</span><span class="hljs-number">3</span>                             ;Expected values: integer
 AllowedSafes<span class="hljs-operator">=</span>.*                                                ;Regular expression of Safes pattern. Must be set <span class="hljs-keyword">if</span> Reconciliation <span class="hljs-keyword">is</span> enabled.
diff <span class="hljs-operator">-</span><span class="hljs-operator">-</span>git a<span class="hljs-operator">/</span>platforms<span class="hljs-operator">/</span>RealVNCServiceMode<span class="hljs-operator">/</span>Policy<span class="hljs-operator">-</span>RealVNCServiceMode.xml b<span class="hljs-operator">/</span>platforms<span class="hljs-operator">/</span>RealVNCServiceMode<span class="hljs-operator">/</span>Policy<span class="hljs-operator">-</span>RealVNCServiceMode.xml
index 549e6ba..0097911 <span class="hljs-number">100644</span>
<span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span> a<span class="hljs-operator">/</span>platforms<span class="hljs-operator">/</span>RealVNCServiceMode<span class="hljs-operator">/</span>Policy<span class="hljs-operator">-</span>RealVNCServiceMode.xml
<span class="hljs-operator">+</span><span class="hljs-operator">+</span><span class="hljs-operator">+</span> b<span class="hljs-operator">/</span>platforms<span class="hljs-operator">/</span>RealVNCServiceMode<span class="hljs-operator">/</span>Policy<span class="hljs-operator">-</span>RealVNCServiceMode.xml
@@ <span class="hljs-number">-7</span>,<span class="hljs-number">7</span> <span class="hljs-operator">+</span><span class="hljs-number">7</span>,<span class="hljs-number">7</span> @@
                                        <span class="hljs-operator">&lt;</span>Property Name<span class="hljs-operator">=</span><span class="hljs-string">"Address"</span> <span class="hljs-operator">/</span><span class="hljs-operator">&gt;</span>
                                <span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>Required<span class="hljs-operator">&gt;</span>
                                <span class="hljs-operator">&lt;</span>Optional<span class="hljs-operator">&gt;</span>
<span class="hljs-operator">-</span>
<span class="hljs-operator">+</span>                                       <span class="hljs-operator">&lt;</span>Property Name<span class="hljs-operator">=</span><span class="hljs-string">"Banana"</span> <span class="hljs-operator">/</span><span class="hljs-operator">&gt;</span>
                                        <span class="hljs-operator">&lt;</span>Property Name<span class="hljs-operator">=</span><span class="hljs-string">"Description"</span> <span class="hljs-operator">/</span><span class="hljs-operator">&gt;</span>
                                <span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>Optional<span class="hljs-operator">&gt;</span>
                        <span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>Properties<span class="hljs-operator">&gt;</span>
PS C:\Users\Tim\Projects\cyberark<span class="hljs-operator">-</span>extensions<span class="hljs-operator">-</span>continuous<span class="hljs-operator">-</span>delivery<span class="hljs-operator">&gt;</span>
</code></pre><p>Upon seeing the <a target="_blank" href="https://github.com/aaearon/cyberark-extensions-continuous-deployment/actions/runs/2664355134">successful execution of the GitHub Actions workflow</a> lets look in the PVWA to see if our changes are reflected:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1657724945486/s56Kstvnw.png" alt="image.png" /></p>
<p>Without having to do anything -- no restart of a CPM, no <code>iisreset</code> on the PVWAs, no changes to any platform in an attempt to force a 'refresh' of <code>Policies.xml</code> -- the changes in our commit take effect.</p>
<h1 id="heading-going-forward">Going forward</h1>
<p>We can see that it is (relatively) straightforward to have continuous delivery for platforms, including their CPM plugin files. Leveraging how imported platforms' plugin files are distributed automatically to all CPMs means we only have a single target system for our workflow -- the Vault.</p>
<p>Improving the GitHub Actions workflow to be more flexible and only deploying those files that have changed or only deploy upon a closed pull request and not using the Vault Administrator account when connecting via PACLI can lead to mature continuous deployment for all of our platforms -- enabling the ability to treat them "as code."</p>
]]></content:encoded></item><item><title><![CDATA[Load balancing CyberArk Privileged Session Manager for SSH (PSMP) with HAProxy and Expect]]></title><description><![CDATA[This is one post in a series focusing on load balancing various CyberArk components using HAProxy with a focus on application/service-based health checking.
Load balancing CyberArk Privileged Session Manager for SSH (often referred to as PSMP) with a...]]></description><link>https://timschindler.blog/load-balancing-cyberark-privileged-session-manager-for-ssh-psmp-with-haproxy-and-expect</link><guid isPermaLink="true">https://timschindler.blog/load-balancing-cyberark-privileged-session-manager-for-ssh-psmp-with-haproxy-and-expect</guid><category><![CDATA[#cybersecurity]]></category><category><![CDATA[Docker]]></category><category><![CDATA[infrastructure]]></category><category><![CDATA[Security]]></category><category><![CDATA[cyberark]]></category><dc:creator><![CDATA[Tim Schindler]]></dc:creator><pubDate>Mon, 14 Feb 2022 15:37:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1644596138127/yccF2Cgv8.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>This is one post in a <a target="_blank" href="https://timschindler.blog/series/cyberark-haproxy">series focusing on load balancing various CyberArk components using HAProxy with a focus on application/service-based health checking.</a></em></p>
<p>Load balancing <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/Latest/en/Content/PASIMP/Introduction-to-PSMP.htm">CyberArk Privileged Session Manager for SSH (often referred to as PSMP)</a> with application-based health checking using HAProxy is a bit more complicated than doing the same with <a target="_blank" href="https://timschindler.blog/application-health-checking-and-load-balancing-cyberark-privileged-vault-web-access-with-haproxy">CyberArk Privileged Web Access Vault</a> or the <a target="_blank" href="https://timschindler.blog/load-balancing-cyberark-privileged-session-manager-html5-gateway-with-haproxy">PSM HTML5 Gateway</a> but still accomplishable with the advanced features HAProxy provides.</p>
<p>Using Expect we can build a simple script that tests the functionality of the PSMP by proxying a connection to a target server and ensuring we get the expected password prompt, providing a known, good credential to authenticate with, and ultimately seeing the expected bash prompt of the target server. We configure can HAProxy to execute it as part of it's health check in order to achieve application-based health checking.</p>
<p>As we are already familiar with HAProxy from <a target="_blank" href="https://timschindler.blog/series/cyberark-haproxy">load balancing other CyberArk components</a> we can focus our energy on building the script used as part of the health check.</p>
<h1 id="heading-what-is-expect">What is Expect?</h1>
<p><a target="_blank" href="https://core.tcl-lang.org/expect/index">Expect</a> is '... a tool for automating interactive applications such as telnet, ftp, passwd, fsck, rlogin, tip, etc.' It '... "talks" to other interactive programs according to a script. Following the script, Expect knows what can be expected from a program and what the correct response should be. An interpreted language provides branching and high-level control structures to direct the dialogue. In addition, the user can take control and interact directly when desired, afterward returning control to the script.' [0] In fact, Expect is used by CyberArk Central Password Manager's <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/Latest/en/Content/PASIMP/Plug-in-Terminal-Plugin-Controller.htm">Terminal Plugin Controller</a> as part of some of it's plugins.</p>
<p>Put it more straightforwardly: we can use Expect and it's commands to <code>spawn</code> a ssh connection to a target server using a PSMP, <code>expect</code> the login prompt as a response, and <code>send</code> keystrokes to complete the authentication. If we see the bash prompt of the target server then we can reasonably assume the PSMP service is running and healthy</p>
<p>[0] https://www.tcl.tk/man/expect5.31/expect.1.html</p>
<h1 id="heading-formulating-our-health-check">Formulating our health check</h1>
<p>Before introducing Expect lets first lets login manually to see what prompts we get. This will help us outline the steps we need to take in a script that will operate as our health check.</p>
<pre><code class="lang-bash">tim@docker01:~$ ssh tim@admin01@linux01.iosharp.lab@192.168.121
The authenticity of host <span class="hljs-string">'192.168.0.121 (192.168.0.121)'</span> can<span class="hljs-string">'t be established.
ECDSA key fingerprint is SHA256:FuAf0xfImgbvwwn3l4wTvwNUc6K7ZpdQsZmJrjUb2jc.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '</span>192.168.0.121<span class="hljs-string">' (ECDSA) to the list of known hosts.
Vault Password:

This session is being recorded
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-97-generic x86_64)

Last login: Tue Feb  8 14:25:40 2022 from 192.168.0.122
admin01@linux01:~$ exit
logout
Connection to linux01.iosharp.lab closed.
Connection to 192.168.0.121 closed.
tim@docker01:~$</span>
</code></pre>
<p>What is important to take note is what we are prompted for so that we can include them as <code>expect</code> commands in a script and where we need to provide input to move the process along so we can provide input in <code>send</code> commands.</p>
<p>If we run through the login process again, keeping in mind how it would look with Expect commands, we:</p>
<ol>
<li><code>spawn</code> a SSH connection to the target server proxying through a PSMP</li>
<li><code>expect</code> to be prompted when connecting to a machine for the first time</li>
<li><code>send</code> the word 'yes' to continue the process where we</li>
<li><code>expect</code> to be prompted for our credentials in order to authenticate to the Vault</li>
<li><code>send</code> the credential for the Vault user we are authenticating</li>
<li><code>expect</code> the prompt of the target system we are connecting to</li>
<li><code>send</code> exit in order to close the connection</li>
</ol>
<p>We have our steps, now we just need to put it together in a script.</p>
<h1 id="heading-creating-our-expect-script">Creating our Expect script</h1>
<p>After installing Expect using <code>apt</code> (my Docker host is running Ubuntu - depending on what your OS is your package manager may differ) we create <code>login.check</code> and store it in our home directory.</p>
<pre><code class="lang-bash">tim@docker01:~$ touch login.expect
tim@docker01:~$ vi login.expect
</code></pre>
<p>With the steps above as a basis our <code>login.check</code> should look like</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/usr/bin/expect</span>

spawn ssh tim@admin01@linux01.iosharp.lab@192.168.0.121
expect <span class="hljs-string">"yes/no"</span>
send <span class="hljs-string">"yes\r"</span>
expect <span class="hljs-string">"Vault Password:"</span>
send <span class="hljs-string">"Password1234!\r"</span>
expect <span class="hljs-string">"admin01@linux01:~$ "</span>
send <span class="hljs-string">"exit\r"</span>
</code></pre>
<p>At the top, similar to a Bash script, we define a <a target="_blank" href="https://linuxhandbook.com/shebang/">shebang</a> so that when we run the script the OS knows to invoke it with Expect.  </p>
<p>Afterwards, the first thing we do is spawn a connection to a target server using the PSMP whose health we want to validate. In anticipation of an unknown SSH host key, we <code>expect "yes\no"</code> and then <code>send "yes\r"</code>. We do not have to define the exact string we expect to receive as Expect allows us to use <a target="_blank" href="https://en.wikipedia.org/wiki/Regular_expression">regular expressions</a> in our <code>expect</code> commands. </p>
<p>When using <code>send</code> we need to remember to provide a <a target="_blank" href="https://en.wikipedia.org/wiki/Carriage_return">carriage return</a> using <code>\r</code> when we 'emulating' pressing Enter. We do the same thing with the prompt <code>Vault Password:</code> where we provide our password. </p>
<p>Finally we expect to see the bash prompt <strong>of the target server we are connecting to</strong> and then end the script by ending the SSH connection.</p>
<p>Executing our script we can see the results:</p>
<pre><code class="lang-bash">tim@docker01:~$ ./login.expect
spawn ssh tim@admin01@linux01.iosharp.lab@192.168.0.121
Vault Password:
Vault Password:tim@docker01:~$
</code></pre>
<p>It doesn't work. The SSH connection to the PSMP was successful as we received the <code>Vault Password:</code> prompt however we were not able to authenticate and eventually the timeout was reached and the script quit.</p>
<p>Because we were never prompted to accept the unknown SSH host key our script got hung up at that step. We can ensure we are never prompted regarding an unknown SSH host key by passing <code>-o StrictHostKeyChecking=no</code> as an argument to our SSH command and we can remove the <code>expect</code>/<code>send</code> from our script:</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/usr/bin/expect</span>

spawn ssh -o StrictHostKeyChecking=no tim@admin01@linux01.iosharp.lab@192.168.0.121
expect <span class="hljs-string">"Vault Password:"</span>
send <span class="hljs-string">"Password1234!\r"</span>
expect <span class="hljs-string">"admin01@linux01:~$ "</span>
send <span class="hljs-string">"exit\r"</span>
</code></pre>
<p>We execute our script once more:</p>
<pre><code class="lang-bash">tim@docker01:~$ ./login.expect
spawn ssh -o StrictHostKeyChecking=no tim@admin01@linux01.iosharp.lab@192.168.0.121
Vault Password:

This session is being recorded
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-97-generic x86_64)

Last login: Tue Feb  8 15:47:06 2022 from 192.168.0.122
admin01@linux01:~$ tim@docker01:~$
</code></pre>
<p>It works! We have a functioning basis for our health check but we should extend our script a bit more to handle some known behavior when the PSMP is <em>not</em> working.</p>
<h1 id="heading-handling-known-non-working-behavior-in-expect">Handling known, non-working behavior in Expect</h1>
<p>Expect allows us to throw exit codes. These exit codes can be used by other processes, like HAProxy, to know if the script completed fine or with an error.</p>
<p>Our script already handles the situation where the PSMP is working fine but we should attempt to capture the behavior of a non-functional PSMP and account for it in our script. We can then throw an exit code other than <code>0</code>, which signifies the login process and thus our script encountered a problem.</p>
<p>Like we did with crafting our Expect script with a working PSMP, lets see what the behavior of a PSMP is when the service is shut off.</p>
<p>With the PSMP service disabled, lets try to connect to a target server:</p>
<pre><code class="lang-bash">tim@docker01:~$ ssh tim@admin01@linux01.iosharp.lab@192.168.0.121
Vault Password:
Vault Password:
Vault Password:
tim@admin01@linux01.iosharp.lab@192.168.0.121: Permission denied (publickey,keyboard-interactive).
tim@docker01:~$
</code></pre>
<p>Despite putting in the correct password, we were prompted two more times and finally the connection closed.</p>
<p>Lets update our script to reflect this behavior when the PSMP service is not running:</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/usr/bin/expect</span>

spawn ssh -o StrictHostKeyChecking=no tim@admin01@linux01.iosharp.lab@192.168.0.121
expect {
    <span class="hljs-string">"Vault Password:"</span> { send <span class="hljs-string">"Password1234!\r"</span> }
}
expect {
    <span class="hljs-string">"admin01@linux01:~$ "</span> { <span class="hljs-built_in">exit</span> 0 }
    <span class="hljs-string">"Vault Password:"</span> { <span class="hljs-built_in">exit</span> 1 }
}
</code></pre>
<p>We refactor our script a bit when extending it to handle the case where the PSMP service is not running by organizing the statements into <code>expect</code> blocks.</p>
<p>Within our first <code>expect</code> block we handle the first prompt -- <code>Vault Password:</code> and send our password when prompted. </p>
<p>In the second <code>expect</code> block we handle what comes after the first prompt. If we receive the target server's expected bash prompt -- our success case -- we close with <code>exit 0</code>. If instead we receive <code>Vault Password:</code> once more, indicating that the login process with our known, good password failed, then we throw <code>exit 1</code>. We need these error codes as HAProxy will use them to determine the health of the server.</p>
<p>With the PSMP service still stopped, lets invoke our script and check the error code:</p>
<pre><code class="lang-bash">tim@docker01:~$ ./login.expect
spawn ssh -o StrictHostKeyChecking=no tim@admin01@linux01.iosharp.lab@192.168.0.121
Vault Password:
Vault Password:tim@docker01:~$ <span class="hljs-built_in">echo</span> $?
1
tim@docker01:~$
</code></pre>
<p>We use <code>$?</code> to get the exit code of <strong>the last command executed.</strong> As any exit code other than <code>0</code> indicates an error, this is exactly what we want. For good measure we should re-enable the PSMP service, run our script, and check the exit code:</p>
<pre><code class="lang-bash">tim@docker01:~$ ./login.expect
spawn ssh -o StrictHostKeyChecking=no tim@admin01@linux01.iosharp.lab@192.168.0.121
Vault Password:
Last failed login: Tue Feb  8 20:57:39 CET 2022 from 192.168.0.115 on ssh:notty
There were 3 failed login attempts since the last successful login.

This session is being recorded
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-97-generic x86_64)

Last login: Tue Feb  8 20:00:27 2022 from 192.168.0.122
admin01@linux01:~$ tim@docker01:~$ <span class="hljs-built_in">echo</span> $?
0
tim@docker01:~$
</code></pre>
<p>Both success and failure behavior is covered and return the appropriate exit codes. With our script in a suitable place lets introduce HAProxy into the mix.</p>
<h1 id="heading-haproxy-and-haproxycfg">HAProxy and <code>haproxy.cfg</code></h1>
<p>What HAProxy is, it's capabilities, and what a basic and functioning <code>haproxy.cfg</code> looks like is well covered in the posts where we load balance the <a target="_blank" href="https://timschindler.blog/application-health-checking-and-load-balancing-cyberark-privileged-vault-web-access-with-haproxy#heading-what-is-haproxy">PVWA</a> and <a target="_blank" href="https://timschindler.blog/load-balancing-cyberark-privileged-session-manager-html5-gateway-with-haproxy#heading-creating-our-haproxycfg">HTML5 Gateway</a>.</p>
<p>Once again we use Docker to run HAProxy however unlike done previously we cannot simply use the an existing image and mount our <code>haproxy.cfg</code> as we intend to use a script that leverages Expect and the OpenSSH client -- two programs not found in the <a target="_blank" href="https://hub.docker.com/_/haproxy/">HAProxy Docker images provided by the HAProxy team</a>. We need to built our own Docker image that includes our needed dependencies.</p>
<p>This this done by creating our own <a target="_blank" href="https://docs.docker.com/engine/reference/builder/">Dockerfile</a> which is nowhere as daunting as sounds as we can use an existing HAProxy Docker image as a base.</p>
<p>In a new working directory, move our script and create our <code>Dockerfile</code>:</p>
<pre><code class="lang-bash">tim@docker01:~$ mkdir haproxy-psmp
tim@docker01:~$ mv login.expect haproxy-psmp/
tim@docker01:~$ vi haproxy-psmp/Dockerfile
</code></pre>
<p>with the content</p>
<pre><code>FROM haproxy:latest

USER root
RUN apt<span class="hljs-operator">-</span>get update <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> apt<span class="hljs-operator">-</span>get install <span class="hljs-operator">-</span>y expect openssh<span class="hljs-operator">-</span>client <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> apt<span class="hljs-operator">-</span>get clean

USER haproxy
COPY haproxy.cfg <span class="hljs-operator">/</span>usr<span class="hljs-operator">/</span>local<span class="hljs-operator">/</span>etc<span class="hljs-operator">/</span>haproxy<span class="hljs-operator">/</span>haproxy.cfg
COPY login.expect <span class="hljs-operator">/</span><span class="hljs-keyword">var</span><span class="hljs-operator">/</span>lib<span class="hljs-operator">/</span>haproxy<span class="hljs-operator">/</span>login.expect
</code></pre><p>Before we build an image based on our <code>Dockerfile</code> we need to create the <code>haproxy.cfg</code> in our working directory with frontends and backends:</p>
<pre><code>defaults
  timeout client <span class="hljs-number">10</span>s
  timeout <span class="hljs-keyword">connect</span> <span class="hljs-number">10</span>s
  timeout <span class="hljs-keyword">server</span> <span class="hljs-number">10</span>s

<span class="hljs-keyword">global</span>
  insecure-fork-wanted
  <span class="hljs-keyword">external</span>-<span class="hljs-keyword">check</span>

frontend psmp_loadbalancer
  bind *:<span class="hljs-number">22</span>
  default_backend psmp_servers

backend psmp_servers
  <span class="hljs-keyword">option</span> tcp-<span class="hljs-keyword">check</span>
  tcp-<span class="hljs-keyword">check</span> expect string SSH<span class="hljs-number">-2.0</span>-
  <span class="hljs-keyword">option</span> <span class="hljs-keyword">external</span>-<span class="hljs-keyword">check</span>
  <span class="hljs-keyword">external</span>-<span class="hljs-keyword">check</span> command /var/lib/haproxy/<span class="hljs-keyword">login</span>.expect
  <span class="hljs-keyword">server</span> psmp01 <span class="hljs-number">192.168</span><span class="hljs-number">.0</span><span class="hljs-number">.122</span>:<span class="hljs-number">22</span> <span class="hljs-keyword">check</span> inter <span class="hljs-number">31</span>s
  <span class="hljs-keyword">server</span> psmp02 <span class="hljs-number">192.168</span><span class="hljs-number">.0</span><span class="hljs-number">.121</span>:<span class="hljs-number">22</span> <span class="hljs-keyword">check</span> inter <span class="hljs-number">31</span>s

frontend stats
  mode http
  bind *:<span class="hljs-number">8404</span>
  stats <span class="hljs-keyword">enable</span>
  stats uri /stats
  stats <span class="hljs-keyword">refresh</span> <span class="hljs-number">10</span>s
  stats <span class="hljs-keyword">admin</span> <span class="hljs-keyword">if</span> <span class="hljs-keyword">TRUE</span>
</code></pre><p>Focusing on what is in our <code>backend</code>, we have two health checks: a <a target="_blank" href="https://cbonte.github.io/haproxy-dconv/2.5/configuration.html#tcp-check%20expect"><code>tcp-check expect</code></a> that works similar to our Expect script in that it expects to receive a certain string -- the SSH banner -- when first connecting to a SSH server and an <a target="_blank" href="https://cbonte.github.io/haproxy-dconv/2.5/configuration.html#option%20external-check"><code>option external-check</code></a> that invokes the script we created. If the first check fails, most likely to the OpenSSH service not running, the HAProxy marks the server as unhealthy and doesn't bother with subsequent ones.</p>
<p>As <code>optional external-check</code> invokes processes and thus poses a security risk, we also need to explicitly add <code>insecure-fork-wanted</code> and <code>external-check</code> under the <code>global</code> section to tell HAProxy we really want to use it.</p>
<p>We still are not ready to build our image as our script has the target PSMP hard coded. <a target="_blank" href="https://cbonte.github.io/haproxy-dconv/2.5/configuration.html#external-check%20command"><code>external-check command</code></a> passes arguments four arguments to the command, the third being the address of the server the health check is being ran against. We need to update our script to take this argument.</p>
<p>As we already updating our script, we also take the opportunity to disable logging to <a target="_blank" href="https://www.computerhope.com/jargon/s/stdout.htm">Stdout</a> by setting <code>user_log 0</code>, create a CyberArk user <code>healthcheck</code> to explicitly authenticate to the Vault with for the purpose of our health check, and define the full path to the ssh binary. Our script now looks like:</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/usr/bin/expect</span>
user_log 0
<span class="hljs-built_in">set</span> psmp [lindex <span class="hljs-variable">$argv</span> 2]

spawn /usr/bin/ssh -o StrictHostKeyChecking=no healthcheck@admin01@linux01.iosharp.lab@<span class="hljs-variable">$psmp</span>
expect {
    <span class="hljs-string">"Vault Password:"</span> { send <span class="hljs-string">"Password1234!\r"</span> }
}
expect {
    <span class="hljs-string">"admin01@linux01:~$ "</span> { <span class="hljs-built_in">exit</span> 0 }
    <span class="hljs-string">"Vault Password:"</span> { <span class="hljs-built_in">exit</span> 1 }
}
</code></pre>
<h1 id="heading-building-our-docker-image-and-running-our-container">Building our Docker image and running our container</h1>
<p>Building the image is simple. We just need to run <code>docker build</code> with a tag we will remember:</p>
<pre><code class="lang-bash">tim@docker01:~/haproxy-psmp$ sudo docker build -t haproxy-psmp .
Sending build context to Docker daemon  4.608kB
Step 1/6 : FROM haproxy:latest
 ---&gt; 4313a6c47541
Step 2/6 : USER root
 ---&gt; Using cache
 ---&gt; 974bba5aa714
Step 3/6 : RUN apt-get update &amp;&amp; apt-get install -y expect openssh-client &amp;&amp; apt-get clean
 ---&gt; Using cache
 ---&gt; 625afeb7fa22
Step 4/6 : USER haproxy
 ---&gt; Using cache
 ---&gt; 2bba1cf696a7
Step 5/6 : COPY haproxy.cfg /usr/<span class="hljs-built_in">local</span>/etc/haproxy/haproxy.cfg
 ---&gt; 8ee4cfac7f22
Step 6/6 : COPY login.expect /var/lib/haproxy/login.expect
 ---&gt; 4672cc1f4c34
Successfully built 4672cc1f4c34
Successfully tagged haproxy-psmp:latest
tim@docker01:~/haproxy-psmp$
</code></pre>
<p>Now we just start the container, publishing the ports for our backend and stats. As we include our <code>haproxy.cfg</code> and <code>login.expect</code> as part of the image we do not need to mount them.</p>
<pre><code class="lang-bash">tim@docker01:~/haproxy-psmp$ sudo docker run \
    -p 8404:8404 \
    -p 23:22 \ 
    haproxy-psmp:latest
</code></pre>
<p><em>As we already have our Docker host's SSH daemon running on port 22, we need to have Docker publish HAProxy on 23.</em></p>
<p>Our container starts successfully and in the HAProxy logs we see processes exiting with code <code>0</code>, indicating the health check using our script is firing and ending successfully.</p>
<pre><code class="lang-bash">tim@docker01:~/haproxy-psmp$ sudo docker run -p 8404:8404 -p 23:22 haproxy-psmp:latest
[NOTICE]   (1) : New worker (8) forked
[NOTICE]   (1) : Loading success.
[NOTICE]   (1) : haproxy version is 2.5.1-86b093a
[NOTICE]   (1) : path to executable is /usr/<span class="hljs-built_in">local</span>/sbin/haproxy
[WARNING]  (1) : Process 12 exited with code 0 (Exit)
[WARNING]  (1) : Process 17 exited with code 0 (Exit)
[WARNING]  (1) : Process 22 exited with code 0 (Exit)
[WARNING]  (1) : Process 27 exited with code 0 (Exit)
[WARNING]  (1) : Process 32 exited with code 0 (Exit)
</code></pre>
<p>Checking our stats page, we see both PSMPs as green.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1644399952658/eTq69wnsv.png" alt="Screenshot 2022-02-09 at 10.45.22.png" /></p>
<p>Shutting off one of the PSMP services it goes red.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1644400464862/WzBObCNI2.png" alt="Screenshot 2022-02-09 at 10.53.45.png" /></p>
<p>We are still able to login through our HAProxy:</p>
<pre><code class="lang-bash">tim@docker01:~/haproxy-psmp$ ssh tim@admin01@linux01.iosharp.lab@192.168.0.115 -p 23
Vault Password:

This session is being recorded
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-97-generic x86_64)

Last login: Wed Feb  9 09:55:44 2022 from 192.168.0.122
admin01@linux01:~$ whoami
admin01
admin01@linux01:~$ hostname
linux01
admin01@linux01:~$ <span class="hljs-built_in">exit</span>
<span class="hljs-built_in">logout</span>
Connection to linux01.iosharp.lab closed.
Connection to 192.168.0.115 closed.
tim@docker01:~/haproxy-psmp$
</code></pre>
<h1 id="heading-going-forward">Going forward</h1>
<p>Though the health checking is more complicated than with the PVWA and HTML5 Gateway due to requiring the usage of an external script leveraging Except but being able to run HAProxy and the script in it's own Docker container makes the entire setup more manageable.</p>
<p>We can also add additional, non-working behavior in our script, a good candidate being when we receive <code>Password:</code> instead of <code>Vault Password:</code>, indicating there maybe something wrong with the <a target="_blank" href="https://www.redhat.com/sysadmin/pluggable-authentication-modules-pam">pluggable authentication module</a> stack. </p>
<p>How are you checking the health of your PSMPs? Provide some feedback in the comments!</p>
]]></content:encoded></item><item><title><![CDATA[Load balancing CyberArk Privileged Session Manager HTML5 Gateway with HAProxy]]></title><description><![CDATA[This is one post in a series focusing on load balancing various CyberArk components using HAProxy with a focus on application/service-based health checking.
With the experience we gained from load balancing CyberArk Privileged Vault Web Access with H...]]></description><link>https://timschindler.blog/load-balancing-cyberark-privileged-session-manager-html5-gateway-with-haproxy</link><guid isPermaLink="true">https://timschindler.blog/load-balancing-cyberark-privileged-session-manager-html5-gateway-with-haproxy</guid><category><![CDATA[Docker]]></category><category><![CDATA[proxy]]></category><category><![CDATA[Security]]></category><category><![CDATA[CyberSec]]></category><category><![CDATA[cyberark]]></category><dc:creator><![CDATA[Tim Schindler]]></dc:creator><pubDate>Tue, 08 Feb 2022 12:11:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1643833106852/P6b0xJ3h5.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>This is one post in a <a target="_blank" href="https://timschindler.blog/series/cyberark-haproxy">series focusing on load balancing various CyberArk components using HAProxy with a focus on application/service-based health checking.</a></em></p>
<p>With the experience we gained from <a target="_blank" href="https://timschindler.blog/application-health-checking-and-load-balancing-cyberark-privileged-vault-web-access-with-haproxy">load balancing CyberArk Privileged Vault Web Access with HAProxy</a>, load balancing the Privileged Session Manager HTML5 GW (PSM HTML5 GW) while incorporating application-based health checking with HAProxy is simple. CyberArk already provides an endpoint we can use for the check so it is all about crafting our HAProxy configuration file.</p>
<h1 id="heading-setting-up-haproxy">Setting up HAProxy</h1>
<p>Similar to how we did it with <a target="_blank" href="https://timschindler.blog/application-health-checking-and-load-balancing-cyberark-privileged-vault-web-access-with-haproxy#heading-setting-up-haproxy">load balancing the PVWA</a> we will use Docker and a HAProxy Docker image, passing our <code>haproxy.cfg</code> and TLS certificates in mounted volumes.</p>
<p>Lets create a directories to store our <code>haproxy.cfg</code> and certificate with it's private key.</p>
<pre><code class="lang-bash">tim@docker01:~$ mkdir -p haproxy-html5gw/haproxy
tim@docker01:~$ mkdir -p haproxy-html5gw/certificates
</code></pre>
<p>And generate a self-signed certificate. For any production environment we would make sure to use an issued certificate from a trusted certificate authority.</p>
<pre><code class="lang-bash">tim@docker01:~$ openssl req -x509 -newkey rsa:4096 -keyout haproxy-html5gw/certificates/tls.pem.key -out haproxy-html5gw/certificates/tls.pem -sha256 -days 365 -nodes
Generating a RSA private key
.....................................++++
.....................................................................................................................................++++
writing new private key to <span class="hljs-string">'haproxy-html5gw/certificates/tls.pem.key'</span>
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter <span class="hljs-string">'.'</span>, the field will be left blank.
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:192.168.0.115
Email Address []:
tim@docker01:~$
</code></pre>
<p>We created the certificate and it's private key under our personal user and with the file permissions the private key is created with the HAProxy process the user runs as cannot read the private key. Already knowing that the HAProxy process runs under the user ID 99 we go ahead and change ownership of the private key.</p>
<pre><code class="lang-bash">tim@docker01:~$ ls -la haproxy-html5gw/certificates/tls.pem.key
-rw------- 1 tim tim 3272 Feb  1 15:36 haproxy-html5gw/certificates/tls.pem.key
tim@docker01:~$ chown 99 haproxy-html5gw/certificates/tls.pem.key
tim@docker01:~$ ls -la haproxy-html5gw/certificates/tls.pem.key
-rw------- 1 99 tim 3272 Feb  1 15:36 haproxy-html5gw/certificates/tls.pem.key
tim@docker01:~$
</code></pre>
<h1 id="heading-creating-our-haproxycfg">Creating our <code>haproxy.cfg</code></h1>
<p>In <code>haproxy.cfg</code> we define our frontend and backend sections.</p>
<p>In the frontend section we define on which address and port HAProxy accepts traffic on and the TLS certificate used to encrypt traffic between HAProxy and the client. In the backend section we define the the individual servers HAProxy will relay traffic to. We can also define a load balancing strategy, health checking, and persistent sessions.</p>
<p>Lets start with a basic <code>haproxy.cfg</code>:</p>
<pre><code class="lang-bash">tim@docker01:~$ vi haproxy-html5gw/haproxy/haproxy.cfg
</code></pre>
<p>with the content</p>
<pre><code>frontend html5gw_loadbalancer
  bind <span class="hljs-operator">*</span>:<span class="hljs-number">443</span> ssl crt <span class="hljs-operator">/</span>usr<span class="hljs-operator">/</span>local<span class="hljs-operator">/</span>etc<span class="hljs-operator">/</span>certs<span class="hljs-operator">/</span>tls.pem
  default_backend html5gws

backend html5gws
  server html5gw1 <span class="hljs-number">192.168</span><span class="hljs-number">.0</span><span class="hljs-number">.113</span>:<span class="hljs-number">443</span> ssl verify none
</code></pre><p>We define a frontend to listen on port 443 and tell it to use the certificate we generated earlier (<em>HAProxy will look for a private key with the name <code>&lt;certificate filename&gt;.key</code> in the same directory.</em>) With <code>default_backend</code> we define the name of the backend that traffic coming from the frontend should be relayed to.</p>
<p>In the backend section we define a single HTML5GW to relay the traffic to. In reality we will have multiple but we can define just one and still be able to implement our concepts. The server name is arbitrary and we can call it whatever we want but the backend must be named <code>html5gws</code> as we used it as the value for <code>default_backend</code> in the frontend. </p>
<p>After specifying the address and IP of the server we state to connect with ssl and again because this is a lab environment we allow for invalid TLS certificates on the backend HTML5 servers with <code>verify none</code>.</p>
<p>We are ready to start our container with the certificates folder and the folder containing our <code>haproxy.cfg</code> as mounted volumes.</p>
<pre><code class="lang-bash">tim@docker01:~$ sudo docker run -p 443:443 -v /home/tim/haproxy-html5gw/haproxy:/usr/<span class="hljs-built_in">local</span>/etc/haproxy:ro -v /home/tim/haproxy-html5gw/certificates:/usr/<span class="hljs-built_in">local</span>/etc/certs:ro haproxy:latest
[NOTICE]   (1) : haproxy version is 2.5.1-86b093a
[NOTICE]   (1) : path to executable is /usr/<span class="hljs-built_in">local</span>/sbin/haproxy
[WARNING]  (1) : config : missing timeouts <span class="hljs-keyword">for</span> frontend <span class="hljs-string">'html5gw_loadbalancer'</span>.
   | While not properly invalid, you will certainly encounter various problems
   | with such a configuration. To fix this, please ensure that all following
   | timeouts are <span class="hljs-built_in">set</span> to a non-zero value: <span class="hljs-string">'client'</span>, <span class="hljs-string">'connect'</span>, <span class="hljs-string">'server'</span>.
[WARNING]  (1) : config : missing timeouts <span class="hljs-keyword">for</span> backend <span class="hljs-string">'html5gws'</span>.
   | While not properly invalid, you will certainly encounter various problems
   | with such a configuration. To fix this, please ensure that all following
   | timeouts are <span class="hljs-built_in">set</span> to a non-zero value: <span class="hljs-string">'client'</span>, <span class="hljs-string">'connect'</span>, <span class="hljs-string">'server'</span>.
[NOTICE]   (1) : New worker (8) forked
[NOTICE]   (1) : Loading success.
</code></pre>
<p>Ignoring the warnings about the timeouts that we will fix later, we navigate to <code>https://192.168.0.115/guac/direct</code> and are shown an error:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643732064831/qX22TZJwH.png" alt="Screenshot 2022-02-01 at 17.14.05.png" /></p>
<p>This error is fine and is not indicative of anything in our <code>haproxy.cfg</code> being wrong. When browsing directly to <code>https://192.168.0.115/guac/direct</code> our browser is making a <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET">GET request</a>, which is not supported by the HTML5 GW. Looking at the HTML5GW logs we can see the GET request being made:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643732279878/WPvNQoWHC.png" alt="Screenshot 2022-02-01 at 17.17.46.png" /></p>
<p>Now that we verified that HAProxy is relaying traffic received from the frontend to the backend we can start implementing our application-based health check and sticky sessions.</p>
<h1 id="heading-adding-application-health-checking">Adding application health checking</h1>
<p>Like we could have with the PVWA, we can enable basic health checking by adding the <code>check</code> keyword to our server defined in the backend section but we still run the risk of HAProxy relaying traffic to a HTML5 GW in a scenario where the Tomcat (or whatever web server you are using to host the HTML 5 GW Web App) server is running but the Apache Guacamole service that the HTML5 GW is based off of is not functional.</p>
<p><code>option httpchk</code> enables us to have more complex health checking. It makes a request to the server and if the HTTP response code is either 2xx or 3xx then HAProxy considers the server healthy and keeps relaying traffic to it. Without defining a URI as part of <code>option httpchk</code> the health check is done against <code>/</code>. As the HTML5 GW Web App lives under the <code>/gauc</code> path, not defining another URI may not give us true application-based health checking.</p>
<p>The <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/Latest/en/Content/PAS%20INST/Install_PSM_HTML5.htm?tocpath=Installation%7CInstall%20PAM%7CInstall%20PSM%7CAdvanced%20PSM%20Implementations%7CInstall%20PSM%20HTML5%20Gateway%7C_____0#HealthCheck">documentation for the PSM HTML5 GW</a> specifies a URI we should use when making our health check. </p>
<p>We need to make sure we specify <code>/guac/rest/healthcheck</code> with our <code>option httpchk</code> resulting in our <code>haproxy.cfg</code> looking like:</p>
<pre><code>frontend html5gw_loadbalancer
  bind <span class="hljs-operator">*</span>:<span class="hljs-number">443</span> ssl crt <span class="hljs-operator">/</span>usr<span class="hljs-operator">/</span>local<span class="hljs-operator">/</span>etc<span class="hljs-operator">/</span>certs<span class="hljs-operator">/</span>tls.pem
  default_backend html5gws

backend html5gws
  option httpchk GET <span class="hljs-operator">/</span>guac<span class="hljs-operator">/</span>rest<span class="hljs-operator">/</span>healthcheck
  server html5gw1 <span class="hljs-number">192.168</span><span class="hljs-number">.0</span><span class="hljs-number">.113</span>:<span class="hljs-number">443</span> check ssl verify none
</code></pre><p>We also must add the <code>check</code> keyword to our server otherwise the parameters of the health check are defined via <code>option httpchk</code> but the health check will never happen and the server will always be considered healthy. </p>
<h1 id="heading-sticky-sessions">Sticky sessions</h1>
<p>In the same <a target="_blank" href="https://timschindler.blog/application-health-checking-and-load-balancing-cyberark-privileged-vault-web-access-with-haproxy#heading-sticky-sessions">way we did it with the PVWA</a> we will use <a target="_blank" href="https://www.haproxy.com/blog/introduction-to-haproxy-stick-tables/">stick tables</a> again to implement sticky sessions although HAProxy offers more than one way to accomplish sticky sessions.</p>
<p>Our stick table captures client IP addresses and associates them to a backend server. The first time a client connects, HAProxy relays the traffic to a server and associates the client IP address to the server in the stick table. The next time the client IP address connects, HAProxy looks up the associated server in the stick table and relays the traffic to that server. We set a long expiration date of 36 hours for this association as our PSMs are configured to close idle PSM sessions at this point.</p>
<pre><code class="lang-bash">frontend html5gw_loadbalancer
  <span class="hljs-built_in">bind</span> *:443 ssl crt /usr/<span class="hljs-built_in">local</span>/etc/certs/tls.pem
  default_backend html5gws

backend html5gws
  stick-table <span class="hljs-built_in">type</span> ip size 1m expire 36h
  stick on src
  option httpchk GET /guac/rest/healthcheck
  server html5gw1 192.168.0.113:443 check ssl verify none
</code></pre>
<h1 id="heading-adding-stats">Adding <code>stats</code></h1>
<p>We <a target="_blank" href="https://timschindler.blog/application-health-checking-and-load-balancing-cyberark-privileged-vault-web-access-with-haproxy#heading-finishing-it-off-with-stats">already know how useful it is having a stats page</a> that we can use to see numerous information for our frontends and backends, among that the health of the servers in the backend. We use it to see various statistics regarding our frontends and backends and as we specify <code>stats admin if TRUE</code> we can perform administrative tasks like disabling health checks on servers or marking individual servers as healthy or unhealthy.</p>
<pre><code class="lang-bash">defaults
  timeout client 10s
  timeout connect 10s
  timeout server 10s

frontend html5gw_loadbalancer
  <span class="hljs-built_in">bind</span> *:443 ssl crt /usr/<span class="hljs-built_in">local</span>/etc/certs/tls.pem
  default_backend html5gws

backend html5gws
  stick-table <span class="hljs-built_in">type</span> ip size 1m expire 36h
  stick on src
  option httpchk GET /guac/rest/healthcheck
  server html5gw1 192.168.0.113:443 check ssl verify none

frontend stats
  mode http
  <span class="hljs-built_in">bind</span> *:8404
  stats <span class="hljs-built_in">enable</span>
  stats uri /stats
  stats refresh 10s
  stats admin <span class="hljs-keyword">if</span> TRUE
</code></pre>
<p>We also define values under <code>defaults</code> for the timeouts that HAProxy was complaining about missing when we first created our <code>haproxy.cfg</code>.</p>
<h1 id="heading-adding-a-second-html5-gw">Adding a second HTML5 GW</h1>
<p>So far we have been working with a single HTML5 GW but for sake of better being able to test our new <code>haproxy.cfg</code> lets add a second HTML5 GW into the mix. Because I am <a target="_blank" href="https://timschindler.blog/cyberark-html5-gateway-and-docker-compose">managing my HTML5 GW containers with Docker Compose</a> it is trivial to spin up a second HTML5 GW running on the same Docker host but with a different published port. </p>
<pre><code class="lang-bash">defaults
  timeout client 10s
  timeout connect 10s
  timeout server 10s

frontend html5gw_loadbalancer
  <span class="hljs-built_in">bind</span> *:443 ssl crt /usr/<span class="hljs-built_in">local</span>/etc/certs/tls.pem
  default_backend html5gws

backend html5gws
  stick-table <span class="hljs-built_in">type</span> ip size 1m expire 36h
  stick on src
  option httpchk GET /guac/rest/healthcheck
  server html5gw1 192.168.0.113:443 check ssl verify none
  server html5gw2 192.168.0.113:8443 check ssl verify none

frontend stats
  mode http
  <span class="hljs-built_in">bind</span> *:8404
  stats <span class="hljs-built_in">enable</span>
  stats uri /stats
  stats refresh 10s
  stats admin <span class="hljs-keyword">if</span> TRUE
</code></pre>
<h1 id="heading-testing-our-haproxycfg-with-the-help-of-stats">Testing our <code>haproxy.cfg</code> with the help of <code>stats</code></h1>
<p>We start our container once more:</p>
<pre><code class="lang-bash">tim@docker01:~$ sudo docker run \
    -p 8404:8404 \
    -p 443:443 \ 
    -v /home/tim/haproxy-html5gw/haproxy:/usr/<span class="hljs-built_in">local</span>/etc/haproxy:ro \
    -v /home/tim/haproxy-html5gw/certificates:/usr/<span class="hljs-built_in">local</span>/etc/certs:ro \
    haproxy:latest
</code></pre>
<p>Browsing to the stats we see frontend and backend -- and a bunch of green, which is a good sign!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643830775273/YXOfdFurXh.png" alt="Screenshot 2022-02-02 at 20.39.23.png" /></p>
<p>We navigate to <code>https://192.168.0.115/gauc/direct</code> and though we get an error as expected, refreshing the stats page shows all our connections being relayed to a single server.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643834071411/uZA0o8BO4.png" alt="Screenshot 2022-02-02 at 21.33.45.png" /></p>
<h1 id="heading-doing-more-with-haproxy">Doing more with HAProxy</h1>
<p>Our <code>haproxy.cfg</code> only scratches the surface in terms of the functionality HAProxy offers. We can adjust how often the health check happens by specifying the <a target="_blank" href="https://cbonte.github.io/haproxy-dconv/2.5/configuration.html#5.2-inter"><code>inter</code> parameter</a> for our servers if we find the default not aggressive enough and explicitly pick a load balancing method with the <a target="_blank" href="https://cbonte.github.io/haproxy-dconv/2.5/configuration.html#4-balance"><code>balance</code> keyword</a>.</p>
<p>As HAProxy is a Layer 4 proxy we can even load balance the <a target="_blank" href="https://cbonte.github.io/haproxy-dconv/2.5/configuration.html#option%20forwardfor">Privileged Session Manager</a> and the <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/Latest/en/Content/PAS%20INST/Installing-the-Privileged-Session-Manager-SSH-Proxy.htm?tocpath=Installation%7CInstall%20PAM%7CInstall%20PSM%20for%20SSH%7C_____0">Privileged Session Manager for SSH</a>!</p>
]]></content:encoded></item><item><title><![CDATA[Application health checking and load balancing CyberArk Privileged Vault Web Access with HAProxy]]></title><description><![CDATA[The Privileged Vault Web Access is the most straightforward component to load balance in CyberArk's Privileged Access Security solution but despite this it is still easy to misconfigure one of the most important aspects: the health check.
It is possi...]]></description><link>https://timschindler.blog/application-health-checking-and-load-balancing-cyberark-privileged-vault-web-access-with-haproxy</link><guid isPermaLink="true">https://timschindler.blog/application-health-checking-and-load-balancing-cyberark-privileged-vault-web-access-with-haproxy</guid><category><![CDATA[Security]]></category><category><![CDATA[proxy]]></category><category><![CDATA[Docker]]></category><category><![CDATA[CyberSec]]></category><category><![CDATA[cyberark]]></category><dc:creator><![CDATA[Tim Schindler]]></dc:creator><pubDate>Sat, 29 Jan 2022 17:36:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1643477694710/NCzEBBtr2.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The Privileged Vault Web Access is the most straightforward component to load balance in CyberArk's Privileged Access Security solution but despite this it is still easy to misconfigure one of the most important aspects: the health check.</p>
<p>It is possible to use whatever your load balancing solution's 'default' health check is but this typically leaves you with a health check that is server-based health check versus application-based. This could result in your load balancer still relaying traffic to a server where IIS is running but the PVWA service is not functional.</p>
<p>Whereas CyberArk provides documentation for other components on leveraging various endpoints to accomplish application-based health checks (<a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/Latest/en/Content/PSM/psm_healthcheck.htm">Privileged Session Manager</a>, <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/Latest/en/Content/PAS%20INST/Install_PSM_HTML5.htm?tocpath=Installation%7CInstall%20PAS%7CInstall%20PSM%7CAdvanced%20PSM%20Implementations%7CInstall%20PSM%20HTML5%20Gateway%7C_____0#HealthCheck">Privileged Session Manager HTML5 Gateway</a>), for the PVWA it offers only <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/Latest/en/Content/PAS%20INST/PVWA-install-multiple-PVWA-env.htm?tocpath=Installation%7CInstall%20PAS%7CInstall%20PVWA%7C_____8#Loadbalancerrequirements">the most basic load balancing requirements</a>. </p>
<p>Using HAProxy we will set up load balancing with a health check that evaluates the health of the PVWA service (our 'application') and not just IIS.</p>
<h1 id="heading-what-is-haproxy">What is HAProxy?</h1>
<p>From the official <a target="_blank" href="https://www.haproxy.org/">HAProxy</a> website it is '... a free, very fast and reliable reverse-proxy offering high availability, load balancing, and proxying for TCP and HTTP-based applications.' It can meet the basic load balancing requirements for the PVWA and also the 'notable' load balancer settings found in <a target="_blank" href="https://cyberark-customers.force.com/s/article/Load-Balancer-Implementation-with-PVWA-General-Guidelines">this knowledge article</a>. And just like the description implies we can use it to load balance in more complex scenarios like the load balancing PSMs.</p>
<h1 id="heading-setting-up-haproxy">Setting up HAProxy</h1>
<p>Like most software HAProxy is provided as a Docker image. With Docker we can quickly get HAProxy up and running, passing our <code>haproxy.cfg</code> and TLS certificates in mounted volumes.</p>
<p>We will need a place to store our <code>haproxy.cfg</code> which configures HAProxy and a place to store our certificate and it's private key so lets first create those directories.</p>
<pre><code class="lang-bash">tim@docker01:~$ mkdir haproxy
tim@docker01:~$ mkdir haproxy/haproxy
tim@docker01:~$ mkdir haproxy/certificates
</code></pre>
<p>We will generate a self-signed certificate as this is a lab environment but in production we would want a certificate issued from a trusted certificate authority.</p>
<pre><code class="lang-bash">tim@docker01:~$ <span class="hljs-built_in">cd</span> haproxy/certificates/
tim@docker01:~/haproxy/certificates$ openssl req -x509 -newkey rsa:4096 -keyout tls.pem.key -out tls.pem -sha256 -days 365 -nodes
Generating a RSA private key
..........++++
.......................++++
writing new private key to <span class="hljs-string">'tls.pem.key'</span>
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter <span class="hljs-string">'.'</span>, the field will be left blank.
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:192.168.0.115
Email Address []:
tim@docker01:~/haproxy/certificates$
</code></pre>
<p>With the file permissions and ownership the private key is created with the HAProxy process inside the container will not be able to read it. We will change the file to be owned by the user ID the HAProxy runs as inside the container.</p>
<pre><code class="lang-bash">tim@docker01:~/haproxy/certificates$ chown 99 tls.pem.key
</code></pre>
<h1 id="heading-creating-our-haproxycfg">Creating our <code>haproxy.cfg</code></h1>
<p>In a <code>haproxy.cfg</code> you will see two primary sections: one to represent the frontend and one to represent the backend.</p>
<p>In the frontend section, we specify settings around HAProxy receiving traffic. This includes configurations like the addresses and ports HAProxy is listening for traffic on, the TLS certificate used to secure communication between HAProxy and the client, as well as options to redirect HTTP traffic to HTTPS.</p>
<p>In the backend section, we specify the servers HAProxy will relay traffic to as well as options like load balancing strategy, more sophisticated health checking, and persistent sessions.</p>
<p>We will start with something simple just to ensure it works:</p>
<pre><code class="lang-bash">tim@docker01:~/haproxy/haproxy$
tim@docker01:~/haproxy/haproxy$ vi haproxy.cfg
</code></pre>
<p>with the content</p>
<pre><code>frontend pvwa_loadbalancer
  bind <span class="hljs-operator">*</span>:<span class="hljs-number">443</span> ssl crt <span class="hljs-operator">/</span>usr<span class="hljs-operator">/</span>local<span class="hljs-operator">/</span>etc<span class="hljs-operator">/</span>certs<span class="hljs-operator">/</span>tls.pem
  default_backend pvwas

backend pvwas
  server pvwa1 <span class="hljs-number">192.168</span><span class="hljs-number">.0</span><span class="hljs-number">.102</span>:<span class="hljs-number">443</span> ssl verify none
  server pvwa2 <span class="hljs-number">192.168</span><span class="hljs-number">.0</span><span class="hljs-number">.116</span>:<span class="hljs-number">443</span> ssl verify none
</code></pre><p>Our <code>haproxy.cfg</code> is extremely basic. We define a frontend to listen on port 443 and tell it to use the certificate we generated earlier (<em>we can just define the certificate and HAProxy will look for a private key with the name <code>&lt;certificate filename&gt;.key</code> in the same directory, which in our case is named <code>tls.pem.key</code>.</em>) With <code>default_backend</code> we define the name of the backend that traffic coming from the frontend should be relayed to.</p>
<p>In the backend section we define the individual servers traffic will be relayed to. The names <code>pvwa1</code> and <code>pvwa2</code> for the servers are arbitrary and we can call them whatever we want but the backend must be named <code>pvwas</code> as we used it as the value for the <code>default_backend</code> in the frontend. After specifying the address and IP of the server we state to connect with ssl and again because this is a lab environment we allow for invalid TLS certificates on the backend PVWA servers with <code>verify none</code>.</p>
<p>With our certificates generated and our initial <code>haproxy.cfg</code> we can start our container, making sure to mount the local volumes.</p>
<pre><code class="lang-bash">tim@docker01:~/haproxy$ sudo docker run -p 443:443 -v /home/tim/haproxy/haproxy:/usr/<span class="hljs-built_in">local</span>/etc/haproxy:ro -v /home/tim/haproxy/certificates:/usr/<span class="hljs-built_in">local</span>/etc/certs:ro haproxy:latest
[NOTICE]   (1) : haproxy version is 2.5.1-86b093a
[NOTICE]   (1) : path to executable is /usr/<span class="hljs-built_in">local</span>/sbin/haproxy
[WARNING]  (1) : config : missing timeouts <span class="hljs-keyword">for</span> frontend <span class="hljs-string">'pvwa_loadbalancer'</span>.
   | While not properly invalid, you will certainly encounter various problems
   | with such a configuration. To fix this, please ensure that all following
   | timeouts are <span class="hljs-built_in">set</span> to a non-zero value: <span class="hljs-string">'client'</span>, <span class="hljs-string">'connect'</span>, <span class="hljs-string">'server'</span>.
[WARNING]  (1) : config : missing timeouts <span class="hljs-keyword">for</span> backend <span class="hljs-string">'pvwas'</span>.
   | While not properly invalid, you will certainly encounter various problems
   | with such a configuration. To fix this, please ensure that all following
   | timeouts are <span class="hljs-built_in">set</span> to a non-zero value: <span class="hljs-string">'client'</span>, <span class="hljs-string">'connect'</span>, <span class="hljs-string">'server'</span>.
[NOTICE]   (1) : New worker (8) forked
[NOTICE]   (1) : Loading success.
</code></pre>
<p>Starting the container we see a few warnings about the lack of timeouts but navigating to the PVWA through our load balancer it loads!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643143481262/VWwoEsvzM.png" alt="Screenshot 2022-01-25 at 21.43.27.png" /></p>
<p>But after logging in we immediately get an error about our session expiring.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643143561475/U2DsXmJInj.png" alt="Screenshot 2022-01-25 at 21.44.04.png" /></p>
<p>Our basic <code>haproxy.cfg</code> is a bit too basic. There is no sticky sessions and we do not have a load balancing method configured so HAProxy is rotating through the PVWAs, sending the next request to the next server. Plus: we do not have ANY health checking, let alone something that could be considered an application-based health check.</p>
<h1 id="heading-adding-sticky-sessions-and-application-health-checking">Adding sticky sessions and application health checking</h1>
<p>We can add basic health checks by simply adding the <code>check</code> keyword to each of servers defined in the backend section but this alone will not check the health of the PVWA rather just of IIS. We need to also add the <a target="_blank" href="http://cbonte.github.io/haproxy-dconv/2.5/configuration.html#4-option%20httpchk">option httpchk</a> keyword to our backend section.</p>
<p><code>option httpchk</code> allows us to define a URI. It will make a request to that URI and if the response returns a HTTP code of 2xx or 3xx then HAProxy considers the server healthy.</p>
<p>As we want HAProxy to not relay traffic to a PVWA where the PVWA service is not working or cannot connect to the Vault even though IIS is running, we need to find the right URI that will return something that is NOT 2xx or 3xx under those conditions. To find this, lets start with looking at requests made in Firefox when we browse to a functional PVWA with the idea that one or more return should something other than 2xx or 3xx when something is wrong with the PVWA.</p>
<h2 id="heading-finding-a-uri-for-our-health-check">Finding a URI for our health check</h2>
<p>Lets approach it how a customer would by browsing directly to one of the PVWAs in Firefox. With the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Tools/Network_Monitor">Network Monitor tab in focus of Web Developer tools</a> open and filtering on just HTTP and XHR type of requests we navigate to one of the PVWAs:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643204660257/tgzZIPNh2.png" alt="Screenshot 2022-01-26 at 14.42.50.png" /></p>
<p>Looking at all the requests we want to focus on the ones that return 200. In theory all of these would be suitable for the health check but it could be that 200 is still returned even though the PVWA is not working. We should purposely break the PVWA and see what is returned then.  </p>
<p>Lets use the Windows Firewall and <a target="_blank" href="https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-firewall/create-an-outbound-port-rule">create a rule on the PVWA blocking outbound traffic on port 1858</a>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643465552009/CRkI2XEhT.png" alt="image.png" /></p>
<p>Ensuring that our rule is enabled we refresh the PVWA:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643466672766/wLF4mrv2Y.png" alt="Screenshot 2022-01-29 at 15.30.29.png" /></p>
<p>Even without a connection to the Vault the PVWA still loads -- sort of. In a non-functioning state IIS still returned a 200 for the first few requests which loaded the logon screen minus the fields to put in credentials. The request to <code>https://192.168.0.102/PasswordVault/api/settings/authentication</code> failed, however, so it seems like a perfect candidate to use for our health check.</p>
<p>Lets add it as part of a <code>option httpchk</code> in our backend in <code>haproxy.cfg</code> while at the same time adding values for the timeout configurations we saw warnings for when starting HAProxy the first time:</p>
<pre><code>defaults
  timeout client 10s
  timeout connect 10s
  timeout server 10s

frontend pvwa_loadbalancer
  bind <span class="hljs-operator">*</span>:<span class="hljs-number">443</span> ssl crt <span class="hljs-operator">/</span>usr<span class="hljs-operator">/</span>local<span class="hljs-operator">/</span>etc<span class="hljs-operator">/</span>certs<span class="hljs-operator">/</span>tls.pem
  default_backend pvwas

backend pvwas
  option httpchk GET <span class="hljs-operator">/</span>PasswordVault<span class="hljs-operator">/</span>api<span class="hljs-operator">/</span>settings<span class="hljs-operator">/</span>authentication
  server pvwa1 <span class="hljs-number">192.168</span><span class="hljs-number">.0</span><span class="hljs-number">.102</span>:<span class="hljs-number">443</span> check ssl verify none
  server pvwa2 <span class="hljs-number">192.168</span><span class="hljs-number">.0</span><span class="hljs-number">.116</span>:<span class="hljs-number">443</span> check ssl verify none
</code></pre><p>In addition we had to add the <code>check</code> keyword to each of the servers in our backend that we want it's health checked. We need this otherwise our <code>option httpchk</code> is effectively worthless.</p>
<h2 id="heading-sticky-sessions">Sticky sessions</h2>
<p>Sticky sessions can be accomplished a multitude of ways including via cookies or with hashing-based methods. We will use <a target="_blank" href="https://www.haproxy.com/blog/introduction-to-haproxy-stick-tables/">stick tables</a>.</p>
<p>We will use a stick table to capture the information of client IP addresses and associate it to a backend server. Every time a client IP address connects HAProxy will look in the stick table to see the previously-associated server and relay the traffic to it. We also set a time for this association to expire so a client is not eternally 'stuck' with a particular server.</p>
<pre><code>defaults
  timeout client <span class="hljs-number">10</span>s
  timeout <span class="hljs-keyword">connect</span> <span class="hljs-number">10</span>s
  timeout <span class="hljs-keyword">server</span> <span class="hljs-number">10</span>s

frontend pvwa_loadbalancer
  bind *:<span class="hljs-number">443</span> ssl crt /usr/<span class="hljs-keyword">local</span>/etc/certs/tls.pem
  default_backend pvwas

backend pvwas
  stick-<span class="hljs-keyword">table</span> <span class="hljs-keyword">type</span> ip size <span class="hljs-number">1</span>m expire <span class="hljs-number">15</span>m
  stick <span class="hljs-keyword">on</span> src
  <span class="hljs-keyword">option</span> httpchk <span class="hljs-keyword">GET</span> /PasswordVault/settings/authentication
  <span class="hljs-keyword">server</span> pvwa1 <span class="hljs-number">192.168</span><span class="hljs-number">.0</span><span class="hljs-number">.102</span>:<span class="hljs-number">443</span> <span class="hljs-keyword">check</span> ssl verify <span class="hljs-keyword">none</span>
  <span class="hljs-keyword">server</span> pvwa2 <span class="hljs-number">192.168</span><span class="hljs-number">.0</span><span class="hljs-number">.116</span>:<span class="hljs-number">443</span> <span class="hljs-keyword">check</span> ssl verify <span class="hljs-keyword">none</span>
</code></pre><h1 id="heading-finishing-it-off-with-stats">Finishing it off with <code>stats</code></h1>
<p>HAProxy provides a convenient stats page that we can use to see numerous information for our frontends and backends, among that the health of the servers in the backend. As a last act before we start our container with our new <code>haproxy.cfg</code> lets add a frontend for the stats page.</p>
<pre><code>defaults
  timeout client <span class="hljs-number">10</span>s
  timeout <span class="hljs-keyword">connect</span> <span class="hljs-number">10</span>s
  timeout <span class="hljs-keyword">server</span> <span class="hljs-number">10</span>s

frontend pvwa_loadbalancer
  bind *:<span class="hljs-number">443</span> ssl crt /usr/<span class="hljs-keyword">local</span>/etc/certs/tls.pem
  default_backend pvwas

backend pvwas
  <span class="hljs-keyword">option</span> httpchk <span class="hljs-keyword">GET</span> /PasswordVault/api/settings/authentication
  <span class="hljs-keyword">server</span> pvwa1 <span class="hljs-number">192.168</span><span class="hljs-number">.0</span><span class="hljs-number">.102</span>:<span class="hljs-number">443</span> <span class="hljs-keyword">check</span> ssl verify <span class="hljs-keyword">none</span>
  <span class="hljs-keyword">server</span> pvwa2 <span class="hljs-number">192.168</span><span class="hljs-number">.0</span><span class="hljs-number">.116</span>:<span class="hljs-number">443</span> <span class="hljs-keyword">check</span> ssl verify <span class="hljs-keyword">none</span>

frontend stats
  mode http
  bind *:<span class="hljs-number">8404</span>
  stats <span class="hljs-keyword">enable</span>
  stats uri /stats
  stats <span class="hljs-keyword">refresh</span> <span class="hljs-number">10</span>s
  stats <span class="hljs-keyword">admin</span> <span class="hljs-keyword">if</span> <span class="hljs-keyword">TRUE</span>
</code></pre><p>We run the <code>stats</code> page on it's own frontend. It will auto refresh every 10 seconds and because we are still in a lab environment we use the <code>stats admin</code> to always return <code>TRUE</code> (<a target="_blank" href="https://cbonte.github.io/haproxy-dconv/2.5/configuration.html#4.2-stats%20admin">if we want this production we should protect it with at least basic authentication like shown in the HAProxy examples</a>) so we can perform administrative tasks like disabling health checks on servers or the entire backend, marking individual servers as healthy or unhealthy.</p>
<p>We also need to remember to publish the port for our stats page when we start our container.</p>
<h1 id="heading-running-and-testing-our-new-haproxycfg">Running and testing our new <code>haproxy.cfg</code></h1>
<p>First we start our container like in the beginning but with the port 8404 published for our stats page:</p>
<pre><code>docker run <span class="hljs-operator">-</span>p <span class="hljs-number">443</span>:<span class="hljs-number">443</span> <span class="hljs-operator">-</span>p <span class="hljs-number">8404</span>:<span class="hljs-number">8404</span> <span class="hljs-operator">-</span>v <span class="hljs-operator">/</span>home<span class="hljs-operator">/</span>tim<span class="hljs-operator">/</span>haproxy<span class="hljs-operator">/</span>haproxy:<span class="hljs-operator">/</span>usr<span class="hljs-operator">/</span>local<span class="hljs-operator">/</span>etc<span class="hljs-operator">/</span>haproxy:ro <span class="hljs-operator">-</span>v <span class="hljs-operator">/</span>home<span class="hljs-operator">/</span>tim<span class="hljs-operator">/</span>haproxy<span class="hljs-operator">/</span>certificates:<span class="hljs-operator">/</span>usr<span class="hljs-operator">/</span>local<span class="hljs-operator">/</span>etc<span class="hljs-operator">/</span>certs:ro haproxy:latest
</code></pre><p>When we load the stats page we can see both the frontends and the backend we defined along with various statistics about them. On our backend we are able to administrator the individual servers or the backend as a whole:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643228411341/y9kfVQ-EK.png" alt="Screenshot 2022-01-26 at 21.17.46.png" /></p>
<p>Looking at one of the PVWAs we can observe the IIS logs to see the health check:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643475261851/yKZopiNb2.png" alt="Screenshot 2022-01-29 at 17.53.58.png" /></p>
<p>Stopping the IIS service or re-enabling the Windows Firewall rule we create to simulate the inability for the PVWA to connect to the Vault, the server in the backend goes red in our stats page and traffic is no longer relayed to it:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643228710471/a9ljWOd_I.png" alt="Screenshot 2022-01-26 at 21.24.54.png" /></p>
<h1 id="heading-going-forward">Going forward</h1>
<p>With a simple configuration we have a powerful load balancer with an application-based health check. We can effortlessly add more functionality like the <code>X-Forwarded-For</code> header using the <a target="_blank" href="https://cbonte.github.io/haproxy-dconv/2.5/configuration.html#option%20forwardfor"><code>option forwardfor</code></a> keyword to <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/Latest/en/Content/PAS%20INST/PVWA-install-multiple-PVWA-env.htm#Loadbalancerrequirements">capture the real client IP address when accessing the PVWA behind our load balancer</a>, HTTP to HTTPS auto redirection with <a target="_blank" href="https://cbonte.github.io/haproxy-dconv/2.5/configuration.html#4.2-redirect%20scheme"><code>redirect scheme</code></a>, and defining a load balance method with the <a target="_blank" href="https://cbonte.github.io/haproxy-dconv/2.5/configuration.html#balance"><code>balance</code></a> method.</p>
<p>As HAProxy is a Layer 4 load balancer it is not limited to just HTTP use cases. It can be used to load balance <a target="_blank" href="https://cbonte.github.io/haproxy-dconv/2.5/configuration.html#option%20forwardfor">Privileged Session Manager</a> and <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/Latest/en/Content/PAS%20INST/Installing-the-Privileged-Session-Manager-SSH-Proxy.htm?tocpath=Installation%7CInstall%20PAM%7CInstall%20PSM%20for%20SSH%7C_____0">Privileged Session Manager for SSH</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Creating CyberArk Connection Component / Connector packages]]></title><description><![CDATA[Unlike their platform package counterparts, packages for connection components (also referred to as Universal Connectors or just connectors in the CyberArk documentation) are not well documented. But with what is provided in the documentation, as wel...]]></description><link>https://timschindler.blog/creating-cyberark-connection-component-connector-packages</link><guid isPermaLink="true">https://timschindler.blog/creating-cyberark-connection-component-connector-packages</guid><category><![CDATA[Powershell]]></category><category><![CDATA[#cybersecurity]]></category><category><![CDATA[cyberark]]></category><dc:creator><![CDATA[Tim Schindler]]></dc:creator><pubDate>Mon, 10 Jan 2022 16:13:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1641831177644/co1NFefxx.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Unlike their <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/Latest/en/Content/Platforms/Platform-Packages-Import-Introduction.htm">platform package</a> counterparts, packages for connection components (also referred to as Universal Connectors or just connectors in the CyberArk documentation) are not well <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/Latest/en/Content/PASIMP/psm_Develop_universal_connector.htm">documented</a>. But with what is provided in the documentation, as well as stitching together the sample connection component package and the packages from the CyberArk Marketplace, we can get a decent idea of what the features and requirements are.</p>
<p>P.S.: I developed a PowerShell function that creates a package ready for import. It creates the optional <code>package.json</code> and connection component settings as well! Find it on GitHub:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/aaearon/New-PASExtensions">https://github.com/aaearon/New-PASExtensions</a></div>
<h1 id="heading-what-is-a-universal-connector-package">What is a Universal Connector package?</h1>
<p>It is simply a Zip archive containing the connection component files and optionally two more files: a <code>package.json</code> file that contains client application file paths to have AppLocker rules created based off of and a <code>.xml</code> file with the settings of the connection component. Based on what is included in the package, the connection component is deployed to all the PSMs in the environment, AppLocker rules are updated on each PSM, and the connection component settings are added to CyberArk.</p>
<p>Packages are imported one of three ways: either through the <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/Latest/en/Content/WebServices/ImportConnComponent.htm?tocpath=Developer%7CREST%20APIs%7CSession%20Management%7C_____1">Import connection component REST API endpoint</a>, through the <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/Latest/en/Content/PSM/PSM_universalConnectors.htm#ImportPSMconnectors">PVWA</a>, or by <a target="_blank" href="https://docs.cyberark.com/Product-Doc/OnlineHelp/PAS/Latest/en/Content/PASIMP/ConfigurePSMUniversalConnector.htm">directly uploading to the <code>PSMUniversalConnectors</code> safe through the PrivateArk client</a>.</p>
<h2 id="heading-connection-component-files">Connection component files</h2>
<p>This consists of the connection component itself (typically, a compiled AutoIT executable) and any supporting files (libraries as a <code>.dll</code>, for example.)</p>
<h2 id="heading-packagejson"><code>package.json</code></h2>
<p>The optional <code>package.json</code> file consists of two things: the name of the package it is included with (seems to be the ID of the connection component) and the <code>ClientAppPaths</code> property. It must be in the root of the package archive.</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"PackageName"</span>: <span class="hljs-string">"PSM-RealVNC"</span>,
    <span class="hljs-attr">"ClientAppPaths"</span>:[
        {
            <span class="hljs-attr">"Path"</span>:<span class="hljs-string">"C:\Program Files\RealVNC\VNC Viewer\vncviewer.exe"</span>
        }
    ]
}
</code></pre>
<p><em>An example of a <code>package.json</code> for the PSM-RealVNC connection component.</em></p>
<p>The <code>ClientAppPaths</code> array contains one or more <code>Path</code> properties with the value being a path belonging to the application for the connection component. <strong>Along with every connection component file that is an executable (ending with <code>.exe</code>), these files are added to the existing AppLocker rules on each PSM.</strong> </p>
<p>Notice that the value for the Path property is not escaped. Strangely, the deployment process that reads the <code>package.json</code> assumes the paths are unescaped and will escape them as part of the process.  <strong>If you escape the paths, the process will fail!</strong></p>
<p>If no <code>package.json</code> is included in the package archive, all connection component executables will still be added to the existing AppLocker rules.</p>
<h2 id="heading-connection-component-settings">Connection component settings</h2>
<p>Another optional file is the connection component settings represented in an XML file. The file needs to be named <code>CC-&lt;connection component ID&gt;.xml</code> and sit in the root of the package archive.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">ConnectionComponent</span> <span class="hljs-attr">Id</span>=<span class="hljs-string">"PSM-RealVNC"</span> <span class="hljs-attr">FullScreen</span>=<span class="hljs-string">"No"</span> <span class="hljs-attr">Height</span>=<span class="hljs-string">"768"</span> <span class="hljs-attr">Width</span>=<span class="hljs-string">"1024"</span> <span class="hljs-attr">EnableWindowScrollbar</span>=<span class="hljs-string">"No"</span> <span class="hljs-attr">EnableToolbars</span>=<span class="hljs-string">"No"</span> <span class="hljs-attr">DisplayName</span>=<span class="hljs-string">"RealVNC"</span> <span class="hljs-attr">Type</span>=<span class="hljs-string">"CyberArk.PasswordVault.TransparentConnection.PSM.PSMConnectionComponent, CyberArk.PasswordVault.TransparentConnection.PSM"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ComponentParameters</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">UserParameters</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">Name</span>=<span class="hljs-string">"AllowMappingLocalDrives"</span> <span class="hljs-attr">Type</span>=<span class="hljs-string">"CyberArk.TransparentConnection.BooleanUserParameter, CyberArk.PasswordVault.TransparentConnection"</span> <span class="hljs-attr">Value</span>=<span class="hljs-string">"Yes"</span> <span class="hljs-attr">Visible</span>=<span class="hljs-string">"Yes"</span> <span class="hljs-attr">Required</span>=<span class="hljs-string">"Yes"</span> <span class="hljs-attr">EnforceInDualControlRequest</span>=<span class="hljs-string">"No"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">Name</span>=<span class="hljs-string">"FullScreen"</span> <span class="hljs-attr">DisplayName</span>=<span class="hljs-string">"Real VNC Full Screen"</span> <span class="hljs-attr">Type</span>=<span class="hljs-string">"CyberArk.TransparentConnection.BooleanUserParameter, CyberArk.PasswordVault.TransparentConnection"</span> <span class="hljs-attr">Value</span>=<span class="hljs-string">"Yes"</span> <span class="hljs-attr">Visible</span>=<span class="hljs-string">"Yes"</span> <span class="hljs-attr">Required</span>=<span class="hljs-string">"Yes"</span> <span class="hljs-attr">EnforceInDualControlRequest</span>=<span class="hljs-string">"No"</span>/&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">UserParameters</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">TargetSettings</span> <span class="hljs-attr">Protocol</span>=<span class="hljs-string">"VNC"</span> <span class="hljs-attr">ClientApp</span>=<span class="hljs-string">"NA"</span> <span class="hljs-attr">ClientDispatcher</span>=<span class="hljs-string">"<span class="hljs-symbol">&amp;quot;</span>{PSMComponentsFolder}\PSMRealVNCDispatcher.exe<span class="hljs-symbol">&amp;quot;</span> <span class="hljs-symbol">&amp;quot;</span>{PSMComponentsFolder}<span class="hljs-symbol">&amp;quot;</span>"</span> <span class="hljs-attr">ClientInvokeType</span>=<span class="hljs-string">"Dispatcher"</span> <span class="hljs-attr">ConnectionComponentInitTimeout</span>=<span class="hljs-string">"20000"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ClientSpecific</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">Name</span>=<span class="hljs-string">"WaitBeforeCmdlineParmsHide"</span> <span class="hljs-attr">Value</span>=<span class="hljs-string">"2000"</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">Name</span>=<span class="hljs-string">"CmdLineParmsHideTimeout"</span> <span class="hljs-attr">Value</span>=<span class="hljs-string">"2000"</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">Name</span>=<span class="hljs-string">"WinWaitTimeout"</span> <span class="hljs-attr">Value</span>=<span class="hljs-string">"30"</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">Name</span>=<span class="hljs-string">"AuthenticationType"</span> <span class="hljs-attr">Value</span>=<span class="hljs-string">"VNC"</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">Name</span>=<span class="hljs-string">"ClientExecutable"</span> <span class="hljs-attr">Value</span>=<span class="hljs-string">"C:\Program Files\RealVNC\VNC Viewer\vncviewer.exe"</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">Name</span>=<span class="hljs-string">"IdentityCheck"</span> <span class="hljs-attr">Value</span>=<span class="hljs-string">"Enforce"</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">Name</span>=<span class="hljs-string">"IdentityCheckWinWaitTimeout"</span> <span class="hljs-attr">Value</span>=<span class="hljs-string">"3"</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Parameter</span> <span class="hljs-attr">Name</span>=<span class="hljs-string">"ContinueOnWarning"</span> <span class="hljs-attr">Value</span>=<span class="hljs-string">"Yes"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">ClientSpecific</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">LockAppWindow</span> <span class="hljs-attr">Enable</span>=<span class="hljs-string">"No"</span> <span class="hljs-attr">MainWindowClass</span>=<span class="hljs-string">"Authentication"</span> <span class="hljs-attr">Timeout</span>=<span class="hljs-string">"20000"</span> <span class="hljs-attr">SearchWindowWaitTimeout</span>=<span class="hljs-string">"30"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Capabilities</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Capability</span> <span class="hljs-attr">Id</span>=<span class="hljs-string">"KeystrokesAudit"</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Capability</span> <span class="hljs-attr">Id</span>=<span class="hljs-string">"KeystrokesTextRecorder"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Capabilities</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">TargetSettings</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ConnectionComponent</span>&gt;</span>
</code></pre>
<p><em>The connection component settings XML file taken from the PSM-RealVNC connection component package, available in the CyberArk Marketplace.</em></p>
<p>The format is exactly the same you would find in the <code>PVConfiguration.xml</code>, which holds a majority of the overall CyberArk settings you see in the PVWA and all of the platform-independent connection component settings. You can simply create and configure the connection component settings in your pre-production environment and copy and paste them into it's own file to be included in the package archive. <strong>This file only seems to be recognized when importing the package via the API endpoint or the PVWA. The settings in this file do not seem to be merged into the overall configuration when the package is uploaded directly to the <code>PSMUniversalConnectors</code> safe.</strong></p>
<p><em>In addition to the <code>PVWAConfig</code> safe, <code>PVConfiguration.xml</code> can be found on many of the components themselves -- including the PSM under <code>C:\Program Files (x86)\CyberArk\PSM\Temp</code>.</em></p>
<h1 id="heading-putting-it-all-together">Putting it all together</h1>
<p>Once you have the files you are going to include, you simply zip them together. Whereas the connection component files do not need to sit in the root of the package, the optional <code>package.json</code> and the XML for the connection component settings do.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1641828250956/NB7xUSX-0.png" alt="image.png" /></p>
<h1 id="heading-scripting-it-with-powershell">Scripting it with PowerShell!</h1>
<p>Creating a package is pretty simple once we know what needs to be done and how. Together with knowing some of the weird exceptions (for example, requiring unescaped Paths in <code>package.json</code> which result in malformed JSON object literals that do not play nice with serializers and de-serializers) we can script this all out.</p>
<p>I developed a PowerShell function <code>New-PASConnectionComponentPackage</code> that creates a package ready for import through the CyberArk REST API / PVWA or via direct upload into the Vault. It will create the optional <code>package.json</code> which helps generate the AppLocker rules plus extracts a connection component's settings out of an existing <code>PVConfiguration.xml</code> and adds it to the archive. </p>
<p>Here is an example usage of it:</p>
<pre><code class="lang-powershell">New-PASConnectionComponentPackage `
    -ConnectionComponentId PSM-SampleApp `
    -Path C:\SampleAppDispatcherFiles `
    -ConnectionComponentApplicationPaths @('C:\SampleApp\SampleApp.exe') `
    -CreateConnectionComponentXmlFile $true `
    -PVConfigurationPath 'C:\Program Files (x86)\CyberArk\PSM\Temp\PVConfiguration.xml' `
    -DestinationPath C:\ConnectionComponentPackages
</code></pre>
<p>Find it on GitHub:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/aaearon/New-PASExtensions">https://github.com/aaearon/New-PASExtensions</a></div>
]]></content:encoded></item></channel></rss>