Continuous Deployment of CyberArk Platforms using GitHub Actions
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 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.
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.
Being that platforms are represented by both an XML and INI file 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 New-PASExtensions 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?
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 continuous deployment workflow.
Platforms and platform packages
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.
According to the documentation a platform package is straightforward: it is just a Zip file that contains a CPM policy file (an INI file with the name Policy-$PolicyId.ini
), a PVWA settings file (a XML file with the name Policy-$PolicyId.xml
), and (optionally) the CPM plugin files (executables, PowerShell scripts, TPC prompts and process files, or a combination.)
Once created, the Zip file is imported through the PVWA and the CPM plugin files are copied automatically to the CPM.
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.
Understanding the import process of a platform's package
The best place to start is downloading a plugin from the CyberArk Marketplace, importing it, and observing the behavior.
I chose the RealVNC Service Mode 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.
Upon importing the platform package for the first time we see that it is done successfully and is added under Imported Platforms:
With the knowledge we already have from programmatically changing CyberArk platform properties using PowerShell we know that the PVWA settings file Policy-RealVNC.xml
was likely merged into Policies.xml
in the PVWAConfig
safe and the CPM policy file Policy-RealVNC.ini
was dropped into the Policies
folder of the PasswordManagerShared
safe but what happened to the CPM plugin files made up of the PowerShell script and the prompts and process files?
As with anything to do with platforms and their files, the first place to look should be the PasswordManagerShared
safe.
First lets take a quick look to make sure the CPM policy file Policy-RealVNC.ini
landed in the Policies
folder like we expected:
What is interesting to note is that as part of the import process the CPM policy file was renamed from Policy-RealVNC.ini
to Policy-RealVNCServiceMode.ini
, which is the PolicyID
of the plugin.
Moving on to finding the CPM plugins files, inside the same safe we see the ImportedPlatforms
folder and a sub-folder named Policy-RealVNCServiceMode
that contains our CPM plugin files. The sub-folder seems to be named based off the convention Policy-$PolicyId
, which is something we should keep in mind for our CD workflow:
Step 3 of creating a platform package explains that the CPM plugin files will be dumped into the bin
folder of the CPM. Checking the folder and pm.log
we see that is the case:
The distribution of CPM plugin files is similar to that of what we see with the deployment of Universal Connectors on multiple PSMs. This works for the first deployment of a new platform but what about consecutive deployments for updated platforms or CPM plugin files?
Updating platforms and CPM plugin files
Although we manually imported the platform through the PVWA we know that an API endpoint to do the same exists. 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.
Unfortunately trying to import a package for a platform that shares the PolicyID
or PolicyName
of an existing platform is not possible. As the platform of an account is defined through an account property you could first delete the platform and then import it but at the very least any Master Policy exceptions are also deleted and must be manually re-created.
We saw that as part of the import process our platform's CPM plugin files were stashed in their own sub-folder under ImportedPlatforms
in the PasswordManagerShared
safe where they then ended up in the bin
folder of the CPM. What happens when we upload a new version of an existing file directly into the Vault?
Uploading new CPM plugin files into the Vault
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.
Inside of RealVNCProcess.ini
we add a new comment to the file:
and then upload it back into the safe:
After waiting some minutes, searching the pm.log
for any log entry comes up with nothing:
and inside the bin
folder RealVNCProcess.ini
is still the same:
Out of curiosity, lets see what happens when we restart the CPM service:
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.
Looking again in pm.log
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 Policy-RealVNCServiceMode.ini
and uploading that?
Manually triggering the download of new CPM plugin files by the CPM
We make another change to RealVNCProcess.ini
:
and upload it to the Vault. We then head to the Policies
folder in the PasswordManagerShared
safe and download a copy of Policy-RealVNCServiceMode.ini
.
But before we make any change to Policy-RealVNCServiceMode.ini
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 Last Modified
date of the file -- which changes when the file is uploaded again.
Simply re-uploading Policy-RealVNCServiceMode.ini
and thus it having a new Last Modified
date was enough to trigger CyberArk to distribute our updated CPM plugin file.
Putting it all together in a continuous deployment workflow
I am using GitHub Actions with a self-hosted Windows runner as PACLI/PoShPACLI needs to communicate with the Vault.
FYI: All the code can be found in the below repos.
Setting up the GitHub repository
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 -- RealVNCServiceMode
in our case -- in the platforms
folder.
PS C:\Users\Tim\Projects\cyberark-extensions-continuous-delivery> Get-ChildItem
Directory: C:\Users\Tim\Projects\cyberark-extensions-continuous-delivery
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 7/13/2022 4:27 PM .git
d---- 6/19/2022 1:40 PM .github
d---- 7/10/2022 11:19 AM platforms
-a--- 7/13/2022 4:27 PM 621 deploy.ps1
-a--- 6/19/2022 12:16 PM 1070 LICENSE
-a--- 7/13/2022 4:27 PM 1950 README.md
PS C:\Users\Tim\Projects\cyberark-extensions-continuous-delivery> Get-ChildItem .\platforms\RealVNCServiceMode\
Directory: C:\Users\Tim\Projects\cyberark-extensions-continuous-delivery\platforms\RealVNCServiceMode
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 7/10/2022 11:19 AM 235 LICENSE
-a--- 7/13/2022 4:27 PM 6642 Policy-RealVNCServiceMode.ini
-a--- 7/11/2022 2:40 PM 673 Policy-RealVNCServiceMode.xml
-a--- 7/10/2022 11:19 AM 2566 realvnc.ps1
-a--- 7/10/2022 11:19 AM 4177 RealVNCProcess.ini
-a--- 7/10/2022 11:19 AM 1432 RealVNCPrompts.ini
Note that Policy-RealVNC.ini
was renamed to Policy-RealVNCServiceMode.ini
!
Inside the root of the repository, I have created a small PowerShell script deploy.ps1
to be invoked by our GitHub Actions workflow that calls a custom function I wrote -- Update-PASPlatformFiles.ps1
, part of the Deploy-PASExtensions 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:
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
The script is pretty simple. It imports the needed modules, builds up the PACLI session, and for each platform in $BasePlatformFolder
updates it's files and then tears down the PACLI session.
The last step is adding the password of the user you will connect to the Vault via PACLI with as a secret to the GitHub repository. Use VAULT_PASSWORD
as the secret name otherwise you will need to adapt the workflow below with the correct secret name.
Installing the GitHub Actions self-hosted runner
Using the GitHub Actions documentation we install a self-hosted runner and add it to the repository we created above.
It also makes sense to make sure PACLI and PoShPACLI are installed and working on the same machine the self-hosted runner is on.
Creating the GitHub Actions workflow
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.
name: Deploy platform files to CyberArk
on:
push:
workflow_dispatch:
jobs:
deploy:
name: Deploy platform files to the Vault
runs-on: self-hosted
steps:
- uses: actions/checkout@v3
- name: Checkout Deploy-PASExtensions repo
uses: actions/checkout@v3
with:
repository: aaearon/deploy-pasextensions
path: deploy-pasextensions
- name: Uploads platform files to the Vault
shell: powershell
run: |
$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\'
We need to overwrite $env:HOMEDRIVE
and $env:HomePath
as these do not seem to exist inside the runner and thus thrown an error. A PSCredential
object is created with the VAULT_PASSWORD
secret stored in the GitHub repository and is passed as an argument to deploy.ps1
along with other arguments that match my target CyberArk environment.
At this point we can push everything to GitHub.
Making a change that will kick off the continuous deployment workflow
With the GitHub repository set up, self-hosted runner installed, and GitHub Actions workflow defined we are ready to see if it works.
We make two small changes to the RealVNCServiceMode
platform, commit them, and then push: Change ImmediateInterval
to 6 and add the Banana
property as optional.
PS C:\Users\Tim\Projects\cyberark-extensions-continuous-delivery> git show 36aa8ad2ec2fb1afe12c6f34bcbb794551ab0ae4
commit 36aa8ad2ec2fb1afe12c6f34bcbb794551ab0ae4 (HEAD -> main, origin/main, origin/HEAD)
Author: Tim Schindler <tim@iosharp.com>
Date: Wed Jul 13 17:01:15 2022 +0200
add banana property and change immediateinterval
diff --git a/platforms/RealVNCServiceMode/Policy-RealVNCServiceMode.ini b/platforms/RealVNCServiceMode/Policy-RealVNCServiceMode.ini
index 93fbcbf..b78d453 100644
--- a/platforms/RealVNCServiceMode/Policy-RealVNCServiceMode.ini
+++ b/platforms/RealVNCServiceMode/Policy-RealVNCServiceMode.ini
@@ -2,7 +2,7 @@ PolicyID=RealVNCServiceMode ;Mandatory, unique identifier for
PolicyName=Real VNC Service Mode via PowerShell ;Short description
SearchForUsages=No ;Expected values: yes/no
PolicyType=Regular ;Expected values: regular, usage, group
-ImmediateInterval=5 ;In minutes
+ImmediateInterval=6 ;In minutes
Interval=1440 ;In minutes
MaxConcurrentConnections=3 ;Expected values: integer
AllowedSafes=.* ;Regular expression of Safes pattern. Must be set if Reconciliation is enabled.
diff --git a/platforms/RealVNCServiceMode/Policy-RealVNCServiceMode.xml b/platforms/RealVNCServiceMode/Policy-RealVNCServiceMode.xml
index 549e6ba..0097911 100644
--- a/platforms/RealVNCServiceMode/Policy-RealVNCServiceMode.xml
+++ b/platforms/RealVNCServiceMode/Policy-RealVNCServiceMode.xml
@@ -7,7 +7,7 @@
<Property Name="Address" />
</Required>
<Optional>
-
+ <Property Name="Banana" />
<Property Name="Description" />
</Optional>
</Properties>
PS C:\Users\Tim\Projects\cyberark-extensions-continuous-delivery>
Upon seeing the successful execution of the GitHub Actions workflow lets look in the PVWA to see if our changes are reflected:
Without having to do anything -- no restart of a CPM, no iisreset
on the PVWAs, no changes to any platform in an attempt to force a 'refresh' of Policies.xml
-- the changes in our commit take effect.
Going forward
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.
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."