/ code snippets

Powershell: Cached Credentials in Scheduled Tasks

I recently came across a situation in which I needed to schedule a task that would run a powershell script that accesses systems using non-windows accounts. This turned out to have a fascinating solution once I combined the storage capabilities of the local credential manager and a Secret Server service. As a result of my investigation, I have two powershell functions that return credential objects from the two sources. Using one or both in a script run by a scheduled task results in a more secure method of handing credentials within an autonomous context.

Credential Manager

For the sake of the explanation, let's construct an example situation. I need to generate a report based on some simple database queries. This report needs to happen at a very specific time of day, but running the script manually is extremely inconvenient, and therefore out of the question. This script needs a set of credentials for a local account on the database.

We can't leave the details in the script (that's how bad things happen), so we need to find some way to secure the password. Fortunately, we can utilize the windows Credential Manager and store the necessary password there. Note the following code:

Function Get-CredentialManagerCred($target) {
    # Get the credential object
    $credential_entry = Read-Creds -target $target
    If ($credential_entry -eq $Null) {
        Write-Warning "Failed to find credential with target: '$target'"
        Return
    }
    $secure_string = ConvertTo-SecureString -String $credential_entry.CredentialBlob -AsPlainText -Force
    Return New-Object System.Management.Automation.PSCredential -ArgumentList $credential_entry.UserName, $secure_string
}

Note: This relies on the Powershell Credentials Manager commandlets being available. Specifically, only the Read-Creds commandlet is used, so you could copy the source from CretMan.ps1 into your own script if that simplifies your situation.

This function simply takes the target name for a credential within the credential manager of the current users context, and return a Powershell credential the username and password for that credential.

Secret Server

The credentials for the local account on the database are stored within Secret Server, which helps to provide a centralized location for password management. When the password for the database account is changed, it gets updated within Secret Server. We need to update the script to get the database credentials from Secret Server rather than the credential manager, so that we're always using the current credentials, even when they are changed.

Thycotic has provided an example powershell snippet that retrieves a credential via the secret server API. While writing this, I found another example using Windows Authentication while accessing the API, which may be an improvement.

Here I've modified the example to create and return a windows credential object, just as I did with the Credential Manager example.

Function Get-SecretServerCred ($ss_credential, $url, $secret_id) {
    # This gets the specified secret server credentials
    # The url might look something like:
    # https://localhost/SecretServer/webservices/SSWebservice.asmx?wsdl
    Try {
        $proxy = New-WebServiceProxy -uri $url -UseDefaultCredential -ErrorAction Stop
    }
    Catch { Return }

    $username = $ss_credential.GetNetworkCredential().username
    $password = $ss_credential.GetNetworkCredential().password 
    $auth_result = $proxy.Authenticate($username, $password, '', 'mydomain.local')
    If ($auth_result.Errors.length -gt 0){
        Write-Warning $auth_result.Errors[0]
        Return
    }
    $query_result = $proxy.GetSecret($auth_result.Token, $secret_id, $False, $Null)
    If ($query_result.Errors.length -gt 0) {
        Write-Warning $query_result.Errors[0]
        Return
    }

    # start building the credentials object
    $secret_username = $query_result.Secret.Items[1].Value
    $secret_password = ConvertTo-SecureString -String $query_result.Secret.Items[2].Value -AsPlainText -Force
    Return New-Object -typename System.Management.Automation.PSCredential -argumentlist $secret_username, $secret_password
}

Scheduled Task

We can now use these two functions to make our scheduled tasks provide a cached credential, while also making it resilient to password changes in that the database account can be frequently changed without impacting the script (changes to the account used to access secret server would require updates to the cached credential).

Here I am showing a simple example of the two previous functions in use. Retrieve the credential from credential manager, and use that to authenticate to the secret server. Take the credential from secret server and use that to connect to the database, where Get-DatabaseInformation is some function you've created to handle all database interaction.

$ss_credential = Get-CredentialManagerCred "target:mydomain.local\myaccount";
$ss_url = "https://ss.mydomain.local/SecretServer/webservices/SSWebservice.asmx?wsdl";
$ss_secret_id = 1234;
$db_credential = Get-SecretServerCred $ss_credential $ss_url $ss_secret_id;
Get-DatabaseInformation $db_credential

Conclusion

These two functions can be tied together to quickly make a script usable from the context of a scheduled task, where the task may need to run as a separate user, and may be accessing resources using non windows credentials (a local database account in this case).

Possible improvements can be made here in the following areas:

  1. The secret server provides a method of using Windows Authentication, which may cut out the need to reference the credential stored within the credential manager. This assumes the account that the scheduled task is running as has access to the secret server.

  2. There might be an easier way to reference credentials within the Credential Manager. I found it to be a slight annoyance to have to dot-source the CredMan.ps1 script (though given a proper powershell setup, this might not be a problem), so finding an easier method of reading from the Credential Manager could reduce complexity.