Secure Credentials with Self-Signed Certificates for PowerShell Scripts
Published May 15 2019 12:46 PM 7,170 Views
Microsoft

First published on TECHNET on Jan 14, 2019


Hello everyone, I'm Preston K. Parsard, specializing in Platforms, Azure Infrastructure and Automation topics, and I'd like to share some insights for securing PowerShell credentials using certificates. This post is based on a recent customer project, but we'll also wrap a story around it on behalf of our made-up friends at our fictitous company Adatum.com.

 

Adatum.com is a new IT consulting firm, and the DevSecOps team is evaluating approaches to securing PowerShell credentials so they can recommend the most suitable method for their customers considering credential security for scripting. At a minimum, the team also wants to adhere to the ISO/IEC 2700 family of standards to help their customers keep information assets secure when implementing any recommended solution. The sprint cycle for this team is an aggressive one week, which is how much time they schedule with each customer to provide a solution, so let's begin our initial lightning sprint together!

 

Meet the Team

 

 

Figure 1: Adatum DevSecOps Team

 

Dev: Dave Delta

 

Dave is the developer and a recent computer science college graduate focusing on .NET, Python     and PowerShell for infrastructure development projects.

 

Sec: Samantha Sierra

 

Samantha is the team lead and an expeirenced engineer with a 10 year background in IT     security, and have also held development, operations and management roles throughout her     career.

 

Ops: Oliver Oscar

 

Oliver is fairly new to IT and has 3 years experience with Windows administration and Active     Directory. (Yes, I realize that Dave and Oliver have a striking resemblance, but they're actually not twins. Trust me. ;) )

 

This post discusses:

 

•    The reasons for making secure credentials available for interactive or scheduled scripts

 

•    Certificate requirements

 

•    Infrastructure requirements

 

•    Scenarios for accounts to host combinations

 

•    Creating and using certificates for credential encryption and decryption in scripts

 

Technologies discussed:

 

PowerShell, PKI

 

Project Site: http://bit.ly/2Q8KY9m



Why Should we Make Secure Credentials avaialble for scripts?

 

Samantha covers the rationale provided by Fabrikam, the teams customer, for secure retrieval and use of credentials in their PowerShell scripts. It's Monday , the first day of the weekly sprint cycle at the dialy scrum standup. The customer is Fabrikam, which is a small regional petro-chemical refining company with operations in the south central US. They want to be able to launch a script either interactivlely or by means of a scheduled task or job, and after the initial setup, not be prompted for any required credential sets associated with service accounts during script execution.

 

One particular use case cited by the customer is to interactively and remotely test DNS name resolution for their 700+ member servers in their primary data center. This is because they're planning a major DNS migration project and need to test and report the results for a pilot subset of servers for name resolution, before expanding the scope to the entire set of systems. They want to be able to do this on demand to verify name resolution, troubleshoot or spot check any inconsistencies for multiple servers at a time, but not have to worry about always being prompted for and supplying credentials to reach these remote machines.

 

Since Fabrikam has several administrators with Active Directory priviledged accounts that will be running commands or scripts, they also need to support scenarios where multiple administrators can log onto multiple machines, either on a set of designated jump/development servers or each individuals own workstation to launch interactive code or setup scheduled scripts. The solution must only allow administrators to import or use the encryption/decryption certificate if they know the private key password. This is like using a shared secret that will allow the flexibility of the multiple accounts, multiple hosts scenario. When the combined certificate creation/password retrieval script runs, it should also implement transcript logging so that an audit trail is maintained. Furthermore, transcript logging will aid diagnostic efforts and debugging when required.

 

While there are other alternatives for achieving password encryption, such as issuing certificates from their PKI servers or using the ConvertFrom-SecureString cmdlet method, Samantha proposed starting the first sprint using self-signed certificates. There are also other methods of cloning certificates, such as using keytool . This utility however, is a command line tool that must be downloaded and would not integrate natively into a PowerShell solution.

 

A decision must ultimately be made by the customers security governance team whether using self-signed certificates is an acceptable risk. A PKI enterprise certificate server solution would normally be warranted if Fabrikam needed a wide scale distribution of cient certificates for general user or client authentication, but in this case, there will be no more than 3 administrators on the Fabrikam team using the script, so Fabrikam has decided that self-signed certificates are sufficient for this scope after-all. Samantha would still like her DevSecOps team to explore a solution (later) which integrates Active Directory Certificate Services as a Certificate Authority for a PKI based implementation of this project, so that the team can provide this as an option to other clients in the future. For now, they will proceed as planned with self-signed certificates.

 

She also noted that if a ConvertFrom-SecureString implementation was employed, this would restrict Fabrikam to using only a single priviledged account on a single host per protected service account. With the proposed solution using certificates, Adatum can allow their customers to achieve greater flexibility for more account-host combination scenarios. We'll will explore the details of the 1) Single Account, Single Host (SASH) combination later, but for now, here are some basic images that represent all scenarios at a glance.

 

 

Figure 1 Single Account, Single Host (SASH)

 

 

Figure 2 Single Account, Multiple Hosts (SAMH)

 

 

Figure 3 Multiple Accounts, Single Host (MASH)

 

 

Figure 4 Multiple Accounts, Multiple Host (MAMH)

 

Finally, this implmentation should also allow administrators to create multiple credential sets for multiple service accounts, where each service account will have it's own folder of artifacts. These artifacts will include a username, password and certificate files, stored and secured using access control entries on a central file server.

 

On Tuesday , Samantha suggests that in order to develop a general outline of the script process for everyone on the team, they should first document that process. David, having some recent exposure to UML modeling from college, volunteers to create an activity diagram that will serve both as their guide to develop the solution and will also be included in the customer documentation for this project when completed.

 

 

Figure 5 Set-SelfSignedCertCreds.ps1 Script Sequence of Activities



DETAILS

 

David finished the diagram on Tuesdsay, and on Wednesday morning, he decides that to better explain the process, he will also need to lists the details of the step.

 

 

IMPORTANT: The following steps are associated with running this script initially to create the certificate and encrypted account credentials with the -ExportCert parameter.

 

Set-SelfSignedCertCreds.ps1 Script Sequence of Activities Details: -ExportCert:$true

 

Step 1: When you run this script initially, it must be executed using the -ExportCert parameter. This is because a self-signed certificate is required to encrypt a specified service account password. The service account name value used with the -svcAccountName parameter and the encrypted password, along with the certificate, are all exported to the directory path entered for the -netDirectory parameter. The -logDirectory parameter is used for indicating the path that will be used for the transcript file. To execute the Set-SelfSignedCertCreds.ps1 script for the first time, use the following parameter set as shown in Example 1 of the Get-Help -Name .\Set-SelfSignedCertCreds.ps1 -ShowWindow results which is also shown here for convenience:

 

EXAMPLE 1

 

[WITH the -ExportCert switch parameter]

 

.\Set-SelfSignedCertCreds.ps1 -netDirectory "\\<server>\<share>\<directory>" -logDirectory "\\<server>\<share>\logs" -svcAccountName <svcAccountName> -ExportCert -Verbose

 

In this example, a new self-signed certificate will be created and installed. The service account password for the service account name specified will be encrypted and exported to a file share, along with the username.

 

The certificate will also be exported from the current machine. The verbose switch is added to show details of certain operations.

 

NOTE: If you are using VSCode to run this script, use this expression to dot source the script so that the variables will be available in your session after the script executes.

 

. .\Set-SelfSignedCertCreds.ps1 -netDirectory "\\<server>\<share>\<directory>" -logDirectory "\\<server>\<share>\logs" -svcAccountName <svcAccountName> -Verbose

 

IMPORTANT: At this point, service credentials can't be requested yet for decryption unless the script is run again without the -ExportCert parameter. The following steps assumes that the -ExportCert parameter switch was specified with the intent to create the certificate to encrypt and export the service account password, username and the encryption/decryption certificate.

 

Step 2b: Since we are assuming that the -ExportCert switch was specified, the self-signed certificate will now be created and installed into the Cert:\CurrentUser\My certificate store of the currently logged on user, which is Dave Delta using the alias: usr.g1.s1@dev.adatum.com. When the certificate is created, a password for the private key must also be specified. The certificate will have the following properties to support secure encryption and decryption of the service account password:

 

# Create parameters for document encryption certificate

 

$SelfSignedCertParams =

 

@{

 

KeyDescription = "PowerShell Script Encryption-Decryption Key"

 

Provider = "Microsoft Enhanced RSA and AES Cryptographic Provider"

 

KeyFriendlyName = "PSScriptEncryptDecryptKey"

 

FriendlyName = "$svcAccountName-PSScriptCipherCert"

 

Subject = "$svcAccountName-PSScriptCipherCert"

 

KeyUsage = "DataEncipherment"

 

Type = "DocumentEncryptionCert"

 

HashAlgorithm = "sha256"

 

CertStoreLocation = "Cert:\CurrentUser\My"

 

} # end params

 

Step 4: A prompt appears for the service account password. As you enter this password, the character values will be obscured from view.

 

Step 5: The service account password is encrypted using the public key of the certificate.

 

Step 6: To ensure that the encrypted password can be centrally accessible for other administrators in the domain and protected with file system and share permissions, as well as backed up, it is then written to a file and exported to a file server share path that was previously specified by the -netDirectory parameter as the artifacts location.

 

NOTE: A sub directory will be created with the common name of the service account so that if multiple service account credentials are encrypted and managed, each service account will have its own unique subfolder. This subfolder will contain the service account password, username and certificate file.

 

Step 7: In this step, the script will also write the service account user name to a file and export it to the -netDirectory artifacts path.

 

Step 8: Finally, the previously installed Self-Signed certificate will be exported to the -netDirectory artifacts path so that it can be subsequently retrieved from a central source by other administrators. These administrators can re-run the script without the -ExportCert parameter to retrieve and decrypt the service account credentials from the same or any other machines that supports the PowerShell PKI module.

 

 

IMPORTANT: The following steps are associated with running this script with the intent to retreive and decrypt an existing encrypted service account credential set by NOT using the

 

-ExportCert parameter.

 

Set-SelfSignedCertCreds.ps1 Script Sequence of Activities Details: -ExportCert:$false

 

Step 1: When you run this script without the -ExportCert parameter, the credentials and certificates must already exist on the file server from a previous execution of the script with the -ExportCert parameter. The -logDirectory parameter is used for specifying the path for the transcript and log files. To execute the Set-SelfSignedCertCreds.ps1 script for using an existing certificate, use the following parameter set as Shown in Examples 2 and 3 of the Get-Help -Name .\Set-SelfSignedCertCreds.ps1 -ShowWindow results which is also shown here:

 

EXAMPLE 2

 

[WITHOUT the -ExportCert switch parameter]

 

.\Set-SelfSignedCertCreds.ps1 -netDirectory "\\<server>\<share>\<directory>" -logDirectory "\\<server>\<share>\logs" -svcAccountName <svcAccountName> -Verbose

 

This command will import the self-signed certificate associated with the service account name if required on a machine, retrieve the previously exported credentials, then use the certificate to decrypt the password component of the credential.

 

NOTE: If you are using VSCode to run this script, use this expression to dot source the script so that the variables will be available in your session after the script executes.

 

. .\Set-SelfSignedCertCreds.ps1 -netDirectory "\\<server>\<share>\<directory>" -logDirectory "\\<server>\<share>\logs" -svcAccountName <svcAccountName> -Verbose.

 

EXAMPLE 3

 

[WITHOUT THE -ExportCert AND WITH the -SuppressPrompts switch parameter]

 

.\Set-SelfSignedCertCreds.ps1 -netDirectory "\\<server>\<share>\<directory>" -logDirectory "\\<server>\<share>\logs" -svcAccountName <svcAccountName> -SuppressPrompts -Verbose

 

This command will import the self-signed certificate if required on a machine, retrieve the previously exported credentials associated with the service account name specified, then use the certificate to decrypt the password component of the credential. In this case, all interactive prompts will be suppressed, but transcript logging will continue.

 

This switch is intended for non-interactive scenarios such as dot sourcing this script from another in order to retrieve the service account credential set for use in the main script.

 

NOTE: If you are using VSCode to run this script, use this expression to dot source the script so that the variables will be available in your session after the script executes.

 

. .\Set-SelfSignedCertCreds.ps1 -netDirectory "\\<server>\<share>\<directory>" -logDirectory "\\<server>\<share>\logs" -svcAccountName <svcAccountName> -Verbose

 

Step 2a1: If example 2 in Step 1 was used where the -SuppressPrompt parameter was NOT used, the following list of tasks will be presented to the administrator, along with a prompt to continue or terminate the script.

 

The following actions will be performed:

 

1. Import the certificate $($SelfSignedCertParams.Subject) if not already imported and installed to $($SelfSignedCertParams.CertStoreLocation ) for the current user account."

 

2. Retrieve the username and password credential set for the service account that will be used to execute scripts.

 

3. Decrypt the password for the service account using the imported certificate.

 

4. Construct a credential set based on the retrieved service account username and the decrypted service account password.

 

PLEASE PRESS [ENTER] TO CONTINUE OR [CTRL-C] TO QUIT.

 

Step 2a2: The *.pfx certificate will be imported from the file server into the current user certificate store at Cert:/CurrentUser/My

 

Step 9: The service account username will be retrieved from the file server.

 

Step 10: The service account password is retrieved from the file server and decrypted.

 

Step 11: Next, a credential object will be constructed from both the retrieved service account username and the decrypted password.

 

Step 12: Finally, the credential set will be displayed to show that its components were successfully retrieved from the file server and constructed properly. This credential object can now be used for executing commands or scripts which require this service identity for authentication. This is what the credential set will look like when displayed in the console:

 

UserName : svc.ps.script@dev.adatum.com

 

Password : System.Security.SecureString

 

Now that we have a basic idea of the process flow, let's look at the topology for this implmentation.

 

On Wednesday afternoon, Samantha reviews the activity diagram and the detailed list, but would also like to have a physical view of the process, both to reinforce the teams own understanding and also to include in the project Wiki for the customer. Oliver, having the most operations experience, will create an infrastructure diagram on Thursday that will coincide with the steps already outlined from David's list above. It's late morning now on Thursday and as Oliver is completing this task, he decides to embelish the diagram with a few logical elements for the -ExportCert and -SuppressPrompts switched parameters.

 

Figure 6 Set-SelfSignedCertCreds.ps1 Infrastructure View

 

By Thursday evening, Samantha is delighted at the combined efforts of the team so far to produce all this cool documentation, and during the daily scrum meeting the team reviews the results and of a test plan that David developed and executed earlier that day. Here is the use case image for the test plan.

 

 

Figure 7 Integration Test Plan

 

Testing Interactive Authentication

 

# Test parameters

 

# TASK-ITEM: Update these parameters with your own custom values for your environment.

 

$remoteTestMachine = "<remoteTestMachine>"

 

$scriptPath = "<scriptPath>"

 

$scriptContent = "Get-ChildItem -Path 'c:\'"

 

# Test case 1.0: To test a command interactively, use the following expression:

 

# tc1.1 Interactive command test

 

Invoke-Command -Computername $remoteTestMachine `

 

-ScriptBlock { Get-Childitem -Path "c:\" } -Credential $svcAccountCred

 

A successful result should look similar to the following output:

 

 

Figure 8 Interactive Directory Listing Test

 

To test scheduling a scheduled job, use the following code snippet.

 

# Test case 2.0: Register scheduled job using a script file, which contains the code: Get-ChildItem -Path "c:\"

 

# tc2.1 Register the job using the script file

 

Register-ScheduledJob -Name psjob1 -FilePath $scriptPath -Credential $svcAccountCred

 

# tc2.2 Create a trigger for 10 seconds from now

 

$trigger1 = New-JobTrigger -At (Get-Date).AddSeconds(10) -Once -Verbose

 

# t2.3 Add the trigger to the job

 

Add-JobTrigger -Name psjob1 -Trigger $trigger1 -Verbose

 

# t2.4 After 20 seconds, get the job information.

 

Start-Sleep -seconds 20 -Verbose

 

Get-ScheduledJob -Name psjob1 -Verbose

 

# t2.5 Retrieve the results

 

Receive-Job -Name psjob1 -Keep -Verbose

 

# t2.6 The scheduled jobs will appear at in the Task Scheduler at the path: Microsoft\Windows\PowerShell\ScheduledJobs

 

# t2.7 Remove the job

 

Get-ScheduledJob -Name psjob1 | Unregister-ScheduledJob -Verbose

 

The console view of the commands and results should resemble this:

 

 

Figure 9 Scheduled Directory Listing from Script File Test

 

For the final test case, we'll scheduled another job but use a script block instead of a script this time.

 

# Test case 3.0: Register scheduled job using a script block

 

# t3.1 Register scheduled job

 

Register-ScheduledJob -Name psjob2 `

 

-ScriptBlock { Get-ChildItem -Path "\\azrads1003.dev.adatum.com\c$" } `

 

-Credential $svcAccountCred -Verbose

 

# t3.2 Create a trigger for 10 seconds from now

 

$trigger = New-JobTrigger -At (Get-Date).AddSeconds(10) -Once -Verbose

 

# t3.3 Add the trigger to the job

 

Add-JobTrigger -Name psjob2 -Trigger $trigger -Verbose

 

# t3.4 After 20 seconds, get the job information.

 

Start-Sleep -seconds 20 -Verbose

 

Get-ScheduledJob -Name psjob2 -Verbose

 

# t3.5 Retrieve the results

 

Receive-Job -Name psjob2 -Keep -Verbose

 

# t3.6 The scheduled jobs will appear at in the Task Scheduler at the

 

# path: Microsoft\Windows\PowerShell\ScheduledJobs

 

# t3.6 Remove the job

 

Get-ScheduledJob -Name psjob2 | Unregister-ScheduledJob -Verbose

 

Executing this snippet produces the result shown here:

 

 

Figure 10 Scheduled Directory Listing from Script Block Test

 

On Friday morning, the team meets with the Fabrikam customers for a sprint review to demonstrate the solution. Although David knows the most intricate details of the project, he can't present it because he's fictitious, remember? So I'll just have to show the demo on his behalf: (This is the best part if you've made it this far and would just prefer to watch a video ;))

 

Demo: See the video of the demo here .

 

Wrapping Up

 

With this solution, you get the convenience of avoiding manual password entries, the security of encrypted credentials and the capability of expanding this to multiple accounts across multiple systems.

 

Although you can use the ConvertFrom-SecureString cmdlet, PKI based certificates, external command line utilities or even certain cloud based services, these approaches may not always suit your specific needs. Using document encryption self-signed certificates may be more appropriate to reduce or eliminate dependencies on the PKI team if it's separate from Dev, Sec, and or Ops, or to quickly prototype solutions in Dev/Test environments as proofs of concepts before expanding the scale to a production based PKI implementation. You can even use document encryption certificates for Desired State Configuration deployments.

 

If your security requirements include reducing administrative effort while satisfying compliance and maintaining an audit trail, then the method described in this post may be just what you were looking for. Besides, having this knowledge may help train new techies when they onboard, or even just the intrinsic satisfaction that comes from being able to fully report or explain it to management :smiling_face_with_smiling_eyes:

 

Where's the Source Code?


The source code can be viewed at: http://bit.ly/2QagVOq and downloaded from: http://bit.ly/2QX75Vt


ACKNOWLEDGEMENTS


I'd like to extend a personal thanks and appreciation to the contributors of all the resources listed below, which helped me to sufficiently research the topic for this post. I'd like to also give a quick shout-out to one of my colleagues Bill Grauer, who gave me the idea and an initial script to implement a similar requirement for one of our customers. Thanks also to Dale Vincent for his technical review and Brandon Wilson for formatting suggestions.


REFERENCES

    1. Using Self-Signed Certificates to Encrypt Text

 

    1. Using a Certificate to Encrypt Credentials…

 

    1. Using a Certificate to Encrypt Credentials… (Update)

 

    1. ISO Standard 27005, Edition 3:v1:en

 

    1. ISO-IEC 27001 Information Security

 

    1. PowerShell Code to Store User Credentials Encrypted for Re-use

 

    1. Encrypt and Store your Passwords and use them for remote…

 

    1. ConvertFrom-SecureString

 

    1. Data Protection API

 

    1. Searching for File attributes

 

    1. Function to Create Certificate Template in ADCS…

 

    1. Clone Certificate with New Identity (keytool)

 

    1. Keytool, Oracle Java SE Documentation

 

1 Comment
Version history
Last update:
‎Aug 28 2019 02:42 PM
Updated by: