Azure Runbook – Secure Credential Management

Every automation script eventually needs to interact with something that requires authentication – a database, an API, a third-party service, or an on-premises system. When connecting back to on-premises resources such as Active Directory (AD DS), SMB file shares, or SQL databases, picking the right authentication method is critical for security and reliability. This post will explore what is method is available for automation accounts runbook.

Azure runbook hybrid group executes the PowerShell session as local SYSTEM account by default for windows environment. In order to run it as other user – such as account that requires access to network shares and on premise Active directory services, the username/password can be pulled from Automation account credential assets or from azure key vault.

If the runbook requires authenticating to Microsoft Graph, the runbook can automate the login via certificate based authentication (CBA) by pulling the cert from key vault.

This post will explore the option of credential handling by using the following Azure resources – Azure Automation Accounts and Azure Key vault.


Prerequesite:

Make sure the member has access to read the credentials set from either Automation accounts or key vault first.

Role assignment for Automation Accounts to access shared resources:

Role assignment for Azure Key Vault:


Azure Automation Accounts:

Automation Account Credentials (Credential Asset):

Credentials store a username and password pair encrypted at rest. Credential assets securely store sensitive authentication pairs—specifically a Username and Password—to prevent hardcoding credentials in scripts. It also store security details that are retrieved directly as standard PSCredential objects for PowerShell runbooks or DSC configurations.

Example of runbook script:

$Mycred = Get-AutomationPSCredential -Name 'SVC_AD'

Invoke-Command -computer "DC3" -Credential $mycred -ScriptBlock{
#Pass it to your command
Write-output "Current computer name is: $env:computername. The current user is: $env:USERNAME"}

Automation Account Certificates:

Azure Automation Certificate Assets securely store x509 public (.cer) or private (.pfx) certificates for non-interactive authentication, payload signing, or secure channel establishing within runbooks.

If your PFX file is not password protected (default policy doesn’t have password), you can’t use Azure portal to upload the certificate. The portal requires a password for the upload. To work around this, run the following PowerShell script, replacing the respective placeholders. Ensure you run this in PowerShell version 7 or later. 

** Download the certificate in .PFX format first and save it locally before executing command **

Use the commands below to import the cert if it cannot be done via GUI:

Install-Module -Name Az.Automation
$certificateName = “AzMsGraph929”
$PfxCertPath = “C:\Users\Sli\Downloads\az-keyvault-red929-Az-MsGraph-Cert-929.pfx”
$ResourceGroup = “newhire”
$AutomateAccountName = "AzAutomationRed929"

New-AzAutomationCertificate -AutomationAccountName $AutomateAccountName -Name $certificateName -Path $PfxCertPath -Exportable -ResourceGroupName $ResourceGroup

Example of runbook script:

# Fetch the certificate asset
$cert = Get-AutomationCertificate -Name "AzMsGraph929"

# Authenticate to Microsoft Graph or Azure using the object
Connect-MgGraph -ClientId "Client-ID" -TenantId "Tenant-ID" -Certificate $cert

Automation Account Variables:

Azure Automation uses Variables as shared resources to store and retrieve values used across multiple runbooks and DSC configurations. Variable types include String, Integer, Date, Time, Boolean, and Null. Variables can be encrypted for storing sensitive data, a setting that must be enabled at the time of creation.

Example of runbook script:

# 1. Retrieve the plain text username variable
$username = "SVC_AD_Provision_01@red929.com"

# 2. Retrieve the encrypted password variable (automatically decrypted at runtime). Call variable with Get-AutomationVariable

$passwordString = Get-AutomationVariable -Name "Automation_Pass"

# 3. Convert the plain text password string into a SecureString
$securePassword = ConvertTo-SecureString $passwordString -AsPlainText -Force

# 4. Construct the PSCredential object
$myCred = New-Object System.Management.Automation.PSCredential($username, $securePassword)

Invoke-Command -computer "DC3" -Credential $mycred -ScriptBlock{

# 5. Pass it to your command
Write-output "Current computer name is: $env:computername. The current user is: $env:USERNAME"}


Azure Key Vault (Recommended):

Azure Key Vault is a cloud-based service designed to securely store and control access to sensitive application data, eliminating the need to hardcode credentials or keys in application source code.

For maximum security and centralized secret management, integrate your runbooks with Azure Key Vault. Store all secrets/keys/cert all in a single vault for centralized management.

Azure Key Vault Keys:

Manages asymmetric cryptographic keys (software or Hardware Security Module [HSM]-backed) used for data encryption at rest (e.g., Azure Disk Encryption, SQL Transparent Data Encryption) and signature generation.

Example of runbook script: The following example retrieve keys with managed system identity (MSI). On premise azure arc device has the role “Key Vault Crypto User” assigned.

# Connect to Azure using Managed Identity (Execute from automation account runbook)
Connect-AzAccount -Identity

# Retrieve an asymmetric cryptographic key object from Azure Key Vault
$VaultName = "Az-KeyVault-Red929"
$KeyName = "KvKey929"

$Key = Get-AzKeyVaultKey -VaultName $VaultName -Name $KeyName

# Display key details (Thumbprint, Key ID, Type)
$Key

Azure Key Vault Secrets:

Securely stores strings up to 25 KB, such as database connection strings, passwords, and API tokens.

Example of runbook script: The following example retrieve key vault secret with managed system identity (MSI). The following role is assigned: “Key Vault Secrets User”


# Authenticate using the system-assigned managed identity
Connect-AzAccount -Identity

# Grab secret from key vault
Write-output "`nThe following secret pulled from azure key vault"
$secret = Get-AzKeyVaultSecret -VaultName "Az-KeyVault-Red929" -Name "Secret-Az-929" -AsPlainText

# output
$secret

# Retrieve secret from key vault in order to trigger invoke-command with credential parameter. The secrets are converted to PSCredential object.

# Connect to vault via managed identity. Ensure the managed identity for the device has access to key vault
Connect-AzAccount -identity

#Define variables
$vaultName = "Az-KeyVault-Red929"
$secretName = "SVC-AD-Provision-929"
$userName = "Red929\SVC_AD_Provision_01"

#Retrieve the secret value as a secure string
$secretValue = Get-AzKeyVaultSecret -VaultName $vaultName -Name $secretName -AsPlainText

#Convert the plain text secret into a secure string
$securePassword = ConvertTo-SecureString $secretValue -AsPlainText -Force

#Construct the PSCredential object
$credential = New-Object System.Management.Automation.PSCredential ($userName, $securePassword)

invoke-command -computername DC3 -scriptblock {

whoami

} -credential $credential

Azure Key Vault Certificates:

Automates the provisioning, lifecycle management, and renewal of public or private Transport Layer Security/Secure Sockets Layer (TLS/SSL) certificates.

Example of runbook script: For hybrid worker group device, use MSI (managed service identity) to connect to key vault to retrieve cert. Then connect to Microsoft graph using the certificate. The following role “Key vault reader” is assigned.

>> Script for Microsoft Graph CBA with Azure Key vault for on premise device << (Click to reveal script)
<#
.SYNOPSIS
Automated Certificate-Based Authentication (CBA) to Microsoft Graph for Hybrid Worker Groups.
.DESCRIPTION
Checks local machine cert store for required certificate. If missing, retrieves it from
Azure Key Vault directly in-memory, imports it to Cert:\LocalMachine\My, and authenticates via Connect-MgGraph. The script
assumes the private key does not have password set.
.NOTES
Goal: Authenticate with certificate against msgraph using powershell.
- Script is for hybrid worker groups that require certificate-based authentication (CBA) to Microsoft Graph.
***** Requires Interactive Login ONCE *****
# Requirements:
1. The app is already registered
2. API permissions configure
3. Key vault created with new certificate
4. Certificate uploaded to app (AzMsGraph) in azure portal
-----------------------------------
The following modules are required for certificate authentication:
Microsoft.Graph.Authentication
Az.Accounts
Az.KeyVault
#>
#########################################################
# Set Variables #
#########################################################
$TenantID = "TenantID"
$Subscription = "subscriptionID"
$AppID = "applicationID"
$vaultname = "keyvaultname"
$certname = "certificatename"
$Cert_Subject= "Cn=domain.com"
#####################################################
# Declare Functions #
#####################################################
#Install module if not available
function Module_Install {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]$ModuleName
)
# Check if module is loaded -
$Module = get-module -Name $ModuleName
# If module is not loaded - try import it
if(-not $Module){
Write-Warning "Attempting to import $ModuleName module.......`n"
import-module $ModuleName -Force
# Check module post import
$Module_Check = get-module -Name $ModuleName
# Module path
$Path = (Get-Module -ListAvailable $ModuleName).path
if(Test-Path -path $Path){
Write-Output "Imported $ModuleName from:`n$path.......`n"
}
# If importing module fails - install it then import it
if(-not $Module_Check){
# Install the module
Write-Output "`n####### Installing $ModuleName #######`n"
Install-Module -Name $ModuleName -Repository PSGallery -Force -AllowClobber -Scope AllUsers
# Import the module
Write-Output "`nImporting $ModuleName module.......`n"
import-module $ModuleName -Force
# Check for imported module
if(get-module -Name $ModuleName){
Write-Warning "`n##### $ModuleName module is installed and imported successfully #####`n"
# Module path
$Path = (Get-Module -ListAvailable $ModuleName).path
Write-Output "`nImported $ModuleName from:`n$path.......`n"
}else{
Write-Warning "`n##### $ModuleName module is NOT installed #####`n"
}
}
}
}# End function [Module_Install]
#########################################################
# Start Install modules #
#########################################################
# Get NuGet if not available
$Nuget = Get-PackageProvider NuGet
if (-not $Nuget) {
Write-Output "`n ####### Installing provider NuGet #######`n"
Install-PackageProvider -Name NuGet -Confirm:$false -Force -ErrorAction SilentlyContinue
}
# Install Required Modules
$RequiredModules = @('Microsoft.Graph.Authentication', 'Az.Accounts', 'Az.KeyVault')
foreach ($module in $RequiredModules) {
Module_Install -ModuleName $module
}
#########################################################
# certificate check #
#########################################################
# Check if cert if present and loaded on local machine personal store.
Write-Output "`nVerifying if certificate is present on Cert:\LocalMachine\My....`n"
$cert_check = Get-ChildItem Cert:\LocalMachine\My -Recurse | where {$_.Subject –like "$cert_subject"}
#########################################################
# Start - Connect to Azure vault to get certificate #
#########################################################
# If the cert does NOT exist - connect to azure to retrieve it from key vault
if(-not $cert_check){
# Connect to azure first. ##### Interactive login REQUIRED FOR FIRST TIME sign on #####
Connect-AzAccount -Tenant $TenantID -Subscription $Subscription
# Directly import into machine store from memory (without creating a temporary disk file)
$CertificateSecret = Get-AzKeyVaultSecret -VaultName $vaultname -Name $certname -AsPlainText
$CertificateBytes = [System.Convert]::FromBase64String($CertificateSecret)
$flags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeySet -bor `
[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($CertificateBytes, "", $flags)
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store("My", "LocalMachine")
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
$store.Add($cert)
$store.Close()
}
#########################################################
# End - Connect to Azure vault to get certificate #
#########################################################
#########################################################
# Start - Connect to Microsoft Graph (CBA) #
#########################################################
$Cert_Thumbprint = (Get-ChildItem Cert:\LocalMachine\My -Recurse | where {$_.Subject –like "$cert_subject"}).Thumbprint
# If Cert just got imported - proceed to connect with thumbprint
if($Cert_Thumbprint){
Write-Warning "`n##### Import of certificate from key vault successful #####`n"
Write-Output "`n##### Connecting to Microsoft Graph with the thumbprint #####`n"
Connect-MgGraph -TenantId $tenantid -ClientId $appid -CertificateThumbprint $Cert_Thumbprint
# If cert already exist in store - use it to authenticate
}elseif($cert_check){
$Cert_Thumbprint = (Get-ChildItem Cert:\LocalMachine\My -Recurse | where {$_.Subject –like "$cert_subject"}).Thumbprint
Write-Warning "`n##### Certificate is already imported into the store.... Connecting to Microsoft Graph with the thumbprint #####`n"
Connect-MgGraph -TenantId $tenantid -ClientId $appid -CertificateThumbprint $Cert_Thumbprint
}else{
Write-Error "#### Import of certificate from azure vault failed... please try again! #### "
}
#########################################################
# End - Connect to Microsoft Graph (CBA) #
#########################################################


Custom credentials for Hybrid workers:

Assign the Custom Credential to the Hybrid Group to execute the runbook session as that account instead of local SYSTEM. This is more for special use cases and not something I would set up for this project.

Once the asset is created, enforce this identity across the execution layer.

Inside your Automation Account, scroll down to Process Automation and select Hybrid Worker Groups.

1.Click on the specific Hybrid Worker Group where your runbook will run.
2.Select Settings from the worker group details menu.
3.Change the Hybrid Worker credentials toggle from Default to Custom.
4.Use the dropdown to select the credential asset you created
5.Click Save.


Notes:

1. If the device is on premise – it is recommended to call key vault in order to obtain secret instead of embedding the secret as a runbook variable. Use managed identity as it is secure and passwordless. Managed identity also works for hybrid worker groups if the on prem server is enrolled under Azure arc.

If on premise server is not enrolled in azure arc, it will output the following error:

2. For automating with managed identity authentication – Make sure the on premise azure arc machine has the correct role assigned in order to retrieve key vault data

3. Encrypted variables and credential passwords have important properties:

* They are encrypted at rest using a per-account encryption key
* They can only be decrypted inside a runbook running in the same Automation account
* They cannot be read through the Azure portal, REST API, or CLI after creation

However, anyone with Contributor access to the Automation account can create a runbook that reads and outputs the values. That last point is critical. Automation credentials and variables are only as secure as the access controls on the Automation account itself.

4. If using invoke-command from runbook to target device – make sure the account that is invoking that session has local admin rights. Possible double hop issue may occur if the credential parameter for invoke-command is not pscredential object. I suggest using Azure automation credential asset to pass on credentials when using invoke-command.

Security Best Practices Summary

1. Use managed identities wherever possible. They eliminate the need to manage credentials entirely for Azure resources.

2. Never use Write-Output with secret values. Runbook output is stored in job logs and visible to anyone with access to the Automation account.

3. Prefer Key Vault over Automation variables for sensitive data. Key Vault gives you audit logging and granular access control that Automation assets lack.

4. Lock down Automation account access. Use Azure RBAC to limit who can create and edit runbooks. A malicious runbook could exfiltrate any credential or variable stored in the account.

5. Enable diagnostic logging. Turn on diagnostic logs for both the Automation account and Key Vault so you have an audit trail of secret access.

6. Set secret expiration dates. Both Key Vault secrets and Automation credentials should have documented expiration and rotation schedules.

7. Use separate Key Vaults for different environments. Do not share a Key Vault between production and development.

8. Review access regularly. Audit who has access to your Automation accounts and Key Vaults quarterly.

Leave a Reply