Managing objects in CyberArk with PowerShell Desired State Configuration

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, files, Active Directory users, and application-specific settings are either present or absent through the use of configurations.

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 Universal Connector package or after deploying a CPM plugin as part of a continuous deployment pipeline. It can be used to manage the rules within an PSMConfigureAppLocker.xml file or invoke the automated installation or upgrade processes for components.

Though with the CyberArkDsc module, PowerShell DSC can also be used to manage CyberArk Vault objects such as safes, safe memberships, and accounts.

Why use PowerShell DSC to manage Vault objects?

Using PowerShell DSC to manage Vault objects makes sense for the same reasons configuration management makes sense for infrastructure.

  • Combat configuration drift - 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.

  • Idempotency - 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.

  • Treat Vault objects as code - 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.

  • Included in PowerShell 5.1 and above - 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.

Using PowerShell DSC and CyberArkDsc to populate an empty Vault

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.

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.

Note: For the sake of brevity, knowledge of PowerShell DSC concepts and terminology such as resources and configurations is assumed.

Installing the CyberArkDsc and ActiveDirectoryDsc modules

Installing the ActiveDirectoryDsc module is straightforward as it can be installed from the PowerShell Gallery like any other PowerShell module. The ActiveDirectoryDsc wiki has information on getting started.

CyberArkDsc is not available in the PowerShell Gallery and needs to be manually installed. As by default configurations are executed under the SYSTEM account, CyberArkDsc needs to, along with it's dependency psPAS, exist in a non-user-specific folder location defined in $env:PSModulePath -- for example: $env:ProgramFiles\WindowsPowerShell\Modules.

We can use Get-DscResource to ensure that both DSC modules are installed correctly.

PS > Get-DscResource | Where-Object {$_.Name -eq 'CYA_Account' -or $_.Name -eq 'ADUser'}

ImplementedAs   Name                      ModuleName                     Version    Properties
-------------   ----                      ----------                     -------    ----------
PowerShell      ADUser                    ActiveDirectoryDsc             6.2.0      {DomainName, UserName, AccountNotDelegated, All...
PowerShell      CYA_Account               CyberArkDsc                    0.0.2      {Address, AuthenticationType, Credential, Ensur...

PS >

Writing our configuration

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:

  1. Creating two users in Active Directory, one to be used as a reconcile account.

  2. Adding the reconcile account as a member to the Domain Admins group in Active Directory so it can reconcile the other.

  3. Creating two safes to store the accounts in: one for the reconcile account, the other.

  4. Assigning a membership to each safe enabling an existing Active Directory group named CyberArk Administrators full permissions to the reconcile account safe and another, existing Active Directory group named Windows Administrators with list, retrieve, and use accounts to the other.

  5. 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.

Defining the base of our configuration

Each DSC resource in CyberArkDsc takes at least four parameters: PvwaUrl, AuthenticationType, Credential, and SkipCertificateCheck. We define these as parameters our configuration accepts. In addition, we define an InitialPassword parameter that will be used when creating the reconcile account's Active Directory user and when onboarding into CyberArk.

We will have a single node -- localhost -- as we will run the DSC configuration on the same machine we compile it on. Our resources will go inside it.

Both Credential and InitialPassword will be passed PSCredential objects. As this is a lab environment I will not go through the effort to have the credentials be properly secured in the resulting, compiled configuration so I set a switch to allow the passwords to be stored plaintext.

Configuration CreateLab_Configuration {
    param(
        $PvwaUrl,
        $AuthenticationType,
        $Credential,
        $SkipCertificateCheck,

        $InitialPassword
    )

    Node localhost {

    }

}

$ConfigData = @{
    AllNodes = @(
        @{
            NodeName                    = 'localhost'
            PSDscAllowDomainUser        = $true
            PSDscAllowPlainTextPassword = $true
        }
    )
}

$VaultCredential = New-Object System.Management.Automation.PSCredential('allison', (ConvertTo-SecureString 'Password!' -AsPlainText -Force))
$Password = New-Object System.Management.Automation.PSCredential('banana', (ConvertTo-SecureString 'P@$$W0RD12345' -AsPlainText -Force))

$ConfigurationParameters = @{
    ConfigurationData    = $ConfigData

    PvwaUrl              = 'https://192.168.137.101'
    AuthenticationType   = 'LDAP'
    SkipCertificateCheck = $true
    Credential           = $VaultCredential
    InitialPassword      = $Password
}

CreateLab_Configuration @ConfigurationParameters

Defining resources

Our configuration will have nine resources. Using documentation, our familiarity with managing Vault objects via psPAS and Get-DscResource -Name ADUser,ADGroup,CYA_Account,CYA_Safe,CYA_SafeMember -Syntax to know how to define each resource, our complete configuration looks like:

Configuration CreateLab_Configuration {
    param(
        $PvwaUrl,
        $AuthenticationType,
        $Credential,
        $SkipCertificateCheck,

        $InitialPassword
    )

    Import-DscResource -ModuleName 'ActiveDirectoryDsc' -Name 'ADUser', 'ADGroup'
    Import-DscResource -ModuleName 'CyberArkDsc'

    Node 'localhost' {

        ADUser 'iosharp\windowsReconcile50' {
            Ensure     = 'Present'
            UserName   = 'windowsReconcile50'
            DomainName = 'iosharp.dev'
            Password   = $InitialPassword
            PasswordNeverResets =  $true

            Credential = $Credential
        }

        ADUser 'iosharp\windowsAdmin50' {
            Ensure     = 'Present'
            UserName   = 'windowsAdmin50'
            DomainName = 'iosharp.dev'
            Password   = $InitialPassword
            PasswordNeverResets =  $true

            Credential = $Credential
        }

        ADGroup 'Domain Admins' {
            Ensure           = 'Present'
            GroupName        = 'Domain Admins'
            MembersToInclude = 'windowsReconcile50'
            Credential       = $Credential

            DependsOn        = '[ADUser]iosharp\windowsReconcile50'
        }

        CYA_Safe 'WinRec50' {
            Ensure                = 'Present'

            SafeName              = 'WinRec50'
            Description           = 'Windows Reconcile Safe'
            ManagingCPM           = 'PasswordManager'
            NumberOfDaysRetention = '1'

            PvwaUrl               = $PvwaUrl
            AuthenticationType    = $AuthenticationType
            SkipCertificateCheck  = $SkipCertificateCheck
            Credential            = $Credential
        }

        CYA_Safe 'WinAdmins50' {
            Ensure                = 'Present'

            SafeName              = 'WinAdmins50'
            Description           = 'Windows Admins Safe'
            ManagingCPM           = 'PasswordManager'
            NumberOfDaysRetention = '1'

            PvwaUrl               = $PvwaUrl
            AuthenticationType    = $AuthenticationType
            SkipCertificateCheck  = $SkipCertificateCheck
            Credential            = $Credential
        }

        CYA_SafeMember 'WinRec\CyberArk Administrators' {
            Ensure                                 = 'Present'

            SafeName                               = 'WinRec50'
            MemberName                             = 'CyberArk Administrators'
            SearchIn                               = 'iosharp.dev'
            UseAccounts                            = $true
            RetrieveAccounts                       = $true
            ListAccounts                           = $true
            AddAccounts                            = $true
            UpdateAccountContent                   = $true
            UpdateAccountProperties                = $true
            InitiateCPMAccountManagementOperations = $true
            SpecifyNextAccountContent              = $true
            RenameAccounts                         = $true
            DeleteAccounts                         = $true
            UnlockAccounts                         = $true
            ManageSafe                             = $true
            ManageSafeMembers                      = $true
            BackupSafe                             = $true
            ViewAuditLog                           = $true
            ViewSafeMembers                        = $true
            AccessWithoutConfirmation              = $true
            CreateFolders                          = $true
            DeleteFolders                          = $true
            MoveAccountsAndFolders                 = $true
            RequestsAuthorizationLevel1            = $true
            RequestsAuthorizationLevel2            = $false

            PvwaUrl                                = $PvwaUrl
            AuthenticationType                     = $AuthenticationType
            SkipCertificateCheck                   = $SkipCertificateCheck
            Credential                             = $Credential

            DependsOn                              = '[CYA_Safe]WinRec50'
        }

        CYA_SafeMember 'WinAdmins\Windows Administrators' {
            Ensure               = 'Present'

            SafeName             = 'WinAdmins50'
            MemberName           = 'Windows Administrators'
            SearchIn             = 'iosharp.dev'
            UseAccounts          = $true
            RetrieveAccounts     = $true
            ListAccounts         = $true
            ViewSafeMembers      = $true

            PvwaUrl              = $PvwaUrl
            AuthenticationType   = $AuthenticationType
            SkipCertificateCheck = $SkipCertificateCheck
            Credential           = $Credential

            DependsOn            = '[CYA_Safe]WinAdmins50'
        }

        CYA_Account 'windowsReconcile50' {
            Ensure               = 'Present'
            UserName             = 'windowsReconcile50'
            Address              = 'iosharp.dev'
            PlatformId           = 'ioSHARPWindowsDomainReconcileAccount'
            SafeName             = 'WinRec50'
            Name                 = 'windowsReconcile50@iosharp.dev'
            Password             = $InitialPassword

            PvwaUrl              = 'https://192.168.137.101'
            AuthenticationType   = 'LDAP'
            SkipCertificateCheck = $true
            Credential           = $VaultCredential

            DependsOn            = '[CYA_Safe]WinRec50'
        }

        CYA_Account 'windowsAdmin50' {
            Ensure               = 'Present'
            UserName             = 'windowsAdmin50'
            Address              = 'iosharp.dev'
            PlatformId           = 'ioSHARPWindowsDomainAccount'
            SafeName             = 'WinAdmins50'
            Name                 = 'windowsAdmin50@iosharp.dev'
            ReconcileAccount     = @{
                Name   = 'windowsReconcile50@iosharp.dev'
                Safe   = 'WinRec50'
                Folder = 'root'
            }

            PvwaUrl              = 'https://192.168.137.101'
            AuthenticationType   = 'LDAP'
            SkipCertificateCheck = $true
            Credential           = $VaultCredential

            DependsOn            = '[CYA_Account]windowsReconcile50', '[CYA_Safe]WinAdmins50'

        }

    }
}

$ConfigData = @{
    AllNodes = @(
        @{
            NodeName                    = 'localhost'
            PSDscAllowDomainUser        = $true
            PSDscAllowPlainTextPassword = $true
        }
    )
}

$VaultCredential = New-Object System.Management.Automation.PSCredential('allison', (ConvertTo-SecureString 'Password!' -AsPlainText -Force))
$Password = New-Object System.Management.Automation.PSCredential('banana', (ConvertTo-SecureString 'P@$$W0RD12345' -AsPlainText -Force))

$ConfigurationParameters = @{
    ConfigurationData    = $ConfigData

    PvwaUrl              = 'https://192.168.137.101'
    AuthenticationType   = 'LDAP'
    SkipCertificateCheck = $true
    Credential           = $VaultCredential
    InitialPassword      = $Password
}

CreateLab_Configuration @ConfigurationParameters

Compiling and running our configuration

We dot source our saved configuration file to compile it:

PS C:\Users\Tim.IOSHARP\Desktop\DSC> . .\CreateLab.ps1


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


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        9/16/2022   7:43 PM          15494 localhost.mof


PS C:\Users\Tim.IOSHARP\Desktop\DSC>

We can use Test-DscConfiguration to see if our resources in their desired state.

PS C:\Users\Tim.IOSHARP\Desktop\DSC> Test-DscConfiguration .\CreateLab_Configuration\ | Select-Object -ExpandProperty ResourcesNotInDesiredState | Select-Object ResourceId

ResourceId
----------
[ADUser]iosharp\windowsReconcile50
[ADUser]iosharp\windowsAdmin50
[ADGroup]Domain Admins
[CYA_Safe]WinRec50
[CYA_Safe]WinAdmins50
[CYA_SafeMember]WinRec\CyberArk Administrators
[CYA_SafeMember]WinAdmins\Windows Administrators
[CYA_Account]windowsReconcile50
[CYA_Account]windowsAdmin50


PS C:\Users\Tim.IOSHARP\Desktop\DSC>

No surprise, none of our resources are as they do not even exist.

From there, we can start the configuration using Start-DscConfiguration and passing the Wait parameter.

PS C:\Users\Tim.IOSHARP\Desktop\DSC> Start-DscConfiguration .\CreateLab_Configuration\ -Wait
PS C:\Users\Tim.IOSHARP\Desktop\DSC>

We do not get any output from Start-DscConfiguration which is exactly what we want. Running Test-DscConfiguration but this time looking at resources in the desired state shows us all nine of them.

PS C:\Users\Tim.IOSHARP\Desktop\DSC> Test-DscConfiguration .\CreateLab_Configuration\ | Select-Object -ExpandProperty ResourcesInDesiredState | Select-Object ResourceId

ResourceId
----------
[ADUser]iosharp\windowsReconcile50
[ADUser]iosharp\windowsAdmin50
[ADGroup]Domain Admins
[CYA_Safe]WinRec50
[CYA_Safe]WinAdmins50
[CYA_SafeMember]WinRec\CyberArk Administrators
[CYA_SafeMember]WinAdmins\Windows Administrators
[CYA_Account]windowsReconcile50
[CYA_Account]windowsAdmin50


PS C:\Users\Tim.IOSHARP\Desktop\DSC>

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. windowsAdmin50 has windowsReconcile50 defined as it's reconcile just as we defined in our DSC configuration.

image.png

Changing existing Vault objects with PowerShell DSC

Our configuration creates the objects when they are missing but what happens if they already exist in some form?

We make small changes to our accounts -- change their names, unlink the reconcile account, different platform -- with the goal that running Start-DscConfiguration will adjust them back to what is defined in our configuration.

image.png

A quick Test-DscConfiguration shows that DSC knows the accounts in the Vault are incorrect based on the configuration:

PS C:\Users\Tim.IOSHARP\Desktop\DSC> Test-DscConfiguration .\CreateLab_Configuration\ | Select-Object -ExpandProperty ResourcesNotInDesiredState | Select-Object ResourceId

ResourceId
----------
[CYA_Account]windowsReconcile50
[CYA_Account]windowsAdmin50

PS C:\Users\Tim.IOSHARP\Desktop\DSC>

Simply running Start-DscConfiguration 'silently' adjusts the accounts in CyberArk to their desired configuration.

image.png

image.png

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.

The CyberArkDsc module can be found on GitHub. Currently it only has resources for safes, safe memberships, and accounts. Pull requests are welcome!