azurepowershellazure-active-directory

Issue with Azure Automation Account and Key Vault Certificate Retrieval


I'm facing an issue when attempting to retrieve a certificate from Azure Key Vault within an Azure Automation Runbook. Despite verifying that the certificate exists, I encounter errors when running the script. The automation account has been assigned the Key Vault Certificate User and Key Vault Secrets User roles, and I’ve ensured the API permissions are correct (Microsoft Graph Sites.FullControl.All, Microsoft Graph User.Read, SharePoint Sites.FullControl.All). I also checked in Entra ID, and confirmed that certificate-based authentication is enabled.

Error Output:

Completed


Environments                                                                                           Context
------------                                                                                           -------
{[AzureChinaCloud, AzureChinaCloud], [AzureCloud, AzureCloud], [AzureUSGovernment, AzureUSGovernment]} Microsoft.Azure.…
Exception calling ".ctor" with "3" argument(s): "Array may not be empty or null. (Parameter 'rawData')"
System.Management.Automation.ParameterBindingValidationException: Cannot bind argument to parameter 'ClientCertificate' because it is null.
   at System.Management.Automation.ParameterBinderBase.ValidateNullOrEmptyArgument(CommandParameterInternal parameter, CompiledCommandParameter parameterMetadata, Type argumentType, Object parameterValue, Boolean recurseIntoCollections)
   at System.Management.Automation.ParameterBinderBase.BindParameter(CommandParameterInternal parameter, CompiledCommandParameter parameterMetadata, ParameterBindingFlags flags)
   at System.Management.Automation.CmdletParameterBinderController.BindParameter(CommandParameterInternal argument, MergedCompiledCommandParameter parameter, ParameterBindingFlags flags)
   at System.Management.Automation.CmdletParameterBinderController.BindParameter(UInt32 parameterSets, CommandParameterInternal argument, MergedCompiledCommandParameter parameter, ParameterBindingFlags flags)
   at System.Management.Automation.CmdletParameterBinderController.BindNamedParameter(UInt32 parameterSets, CommandParameterInternal argument, MergedCompiledCommandParameter parameter)
   at System.Management.Automation.ParameterBinderController.BindNamedParameters(UInt32 parameterSets, Collection`1 arguments)
   at System.Management.Automation.CmdletParameterBinderController.BindCommandLineParametersNoValidation(Collection`1 arguments)
   at System.Management.Automation.CmdletParameterBinderController.BindCommandLineParameters(Collection`1 arguments)
   at System.Management.Automation.CommandProcessor.BindCommandLineParameters()
   at System.Management.Automation.CommandProcessor.Prepare(IDictionary psDefaultParameterValues)
   at System.Management.Automation.CommandProcessorBase.DoPrepare(IDictionary psDefaultParameterValues)
   at System.Management.Automation.Internal.PipelineProcessor.Start(Boolean incomingStream)
   at System.Management.Automation.Internal.PipelineProcessor.SynchronousExecuteEnumerate(Object input)
--- End of stack trace from previous location ---
   at System.Management.Automation.Internal.PipelineProcessor.SynchronousExecuteEnumerate(Object input)
   at System.Management.Automation.PipelineOps.InvokePipeline(Object input, Boolean ignoreInput, CommandParameterInternal[][] pipeElements, CommandBaseAst[] pipeElementAsts, CommandRedirection[][] commandRedirections, FunctionContext funcContext)
   at System.Management.Automation.Interpreter.ActionCallInstruction`6.Run(InterpretedFrame frame)
   at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
   at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)

I'm not entirely confident I am going about this all the best way as I've relied on ChatGPT for guidance.

PS Script:

# Login with Managed Identity (for Azure Automation Account)
Write-Host "Logging in with Automation Account Managed Identity..."
Connect-AzAccount -Identity
Write-Host "Logged in with Managed Identity."

# Set API Key
$APIKey = Get-AutomationVariable -Name '...'
Write-Host "API Key retrieved: $APIKey"

# Set SharePoint Site URL and Folder
$SharePointSiteUrl = "..."
$SharePointFolder = "..."

# Set Azure App Registration credentials
$tenantId = "..."
$clientId = "..."

# Retrieve the certificate from Azure Key Vault
Write-Host "Retrieving certificate from Azure Key Vault..."
$certificate = Get-AzKeyVaultCertificate -VaultName '...' -Name '...'

# Check if certificate retrieval was successful
if ($certificate -eq $null) {
    Write-Host "Error: Certificate not found in Key Vault!"
    exit
}

# Convert from Base64 to Byte Array and create the certificate object
$pfxBytes = [Convert]::FromBase64String($certificate.SecretValueText)
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($pfxBytes, $null, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)
Write-Host "Certificate retrieved successfully."

# Use the certificate to authenticate with Azure AD and get an access token
Write-Host "Authenticating with Azure AD using the certificate..."
$GraphConnection = Get-MsalToken -ClientCertificate $cert -ClientId $clientId -TenantId $tenantId

# Get the access token from the authentication response
$Token = $GraphConnection.AccessToken

# Check if the token was retrieved
if (-not $Token) {
    Write-Host "Error: Failed to retrieve access token!"
    exit
}

Write-Host "Access token retrieved successfully."

All placeholders have been replaced with the actual details in the script, rather than leaving them as '...'


Solution

  • To retrieve certificate from Azure Key Vault and generate the access token, make use of Get-AzKeyVaultSecret command instead of Get-AzKeyVaultCertificate command.

    # Parameters
    $tenantID = 'TenantID'  
    $clientID = 'ClientID'   
    $keyVaultName = 'autokvruk'  
    $certName = 'rukcerttdyypfx'  
    $scope = 'https://graph.microsoft.com/.default' 
    
    Connect-AzAccount -Identity
    
    # Retrieve the certificate from Azure Key Vault as a Base64-encoded string
    $pfxSecret = Get-AzKeyVaultSecret -VaultName $keyVaultName -Name $certName -AsPlainText
    
    # Convert the Base64-encoded string to bytes
    $secretByte = [Convert]::FromBase64String($pfxSecret)
    
    # Load the certificate using the X509Certificate2 class
    $x509Cert = New-Object Security.Cryptography.X509Certificates.X509Certificate2($secretByte, $null, [Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)
    
    # Get the MSAL token using the certificate
    try {
        $token = Get-MsalToken -ClientId $clientID -TenantId $tenantID -ClientCertificate $x509Cert -Scope $scope
        Write-Host "Successfully retrieved the token."
        Write-Host "Access Token: $($token.AccessToken)"
    } catch {
        Write-Host "Error getting the token: $_"
    }
    

    Access token generated successfully:

    enter image description here

    Uploaded the .pfx cert in the Azure Key Vault:

    enter image description here

    And uploaded the .cer certificate to the Microsoft Entra ID application:

    enter image description here

    Reference:

    powershell - Retrieving a PFX certificate from Azure Key Vault - Stack Overflow by me