Search code examples
azureazure-rm-template

Use an ARM template object as a parameter containing a key vault reference


I am attempting to make a generic ARM Template for deploying a VM, that will accept an object parameter that will be passed to a custom script extension and mapped into environment variables on the deployed machine. I want to avoid naming specific variables to maximise re-use. I would also like to be able to use key vault secrets in some cases.

The docs describe using the string function should encode the object parameter as a json string, but when I get the script to output the raw value it is not valid json, and hasn't expanded the key vault reference or even enumerated the value of the regular property. The regular key vault reference for adminPassword works exactly as expected.

My ARM template looks like this:

"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"languageVersion": "2.0",
"parameters": {
    "adminPassword": {
        "type": "secureString"
    },
    "envVars": {
        "type": "object",
        "defaultValue": null               
        }
    }

And I pass the value into the script using "commandToExecute": "[concat('powershell -ExecutionPolicy Unrestricted -File ', 'script-name.ps1', ' -envVarsJson ''', string(parameters('envVars')), '''')]"

And the parameters file values:

"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {        
    "adminPassword": {
        "reference": {
            "keyVault": {
                "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-rg/providers/Microsoft.KeyVault/vaults/my-vault"
            },
        "secretName": "admin-password"
        }
    },
    "envVars": {
        "value": {
            "apiKey": {
                "reference": {
                    "keyVault": {
                        "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-rg/providers/Microsoft.KeyVault/vaults/my-vault"
                    },
                "secretName": "api-key"
                }
            },
            "otherValue":{
                "value": "Hello World!"
            }
        }
    }
}

And the value the script receives looks like this: '{apiKey:{reference:{keyVault:{id:/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-rg/providers/Microsoft.KeyVault/vaults/my-vault},secretName:api-key}},otherValue:{value:Hello World!}}'


Solution

  • After a lot of faffing around I have come to the conclusion that trying to incorporate Key Vault secrets into other parameters might be possible via nested templates, but is probably a bad idea, as any parameters retrieved in this way will no longer be secure.

    For anybody who finds this question and is looking to pass arbitrary object parameters to a PowerShell script the method that worked for me is to base64 encode the jsonified object.

    "commandToExecute": "[concat('powershell -ExecutionPolicy Unrestricted -File ', 'script-name.ps1', ' -envVars ', base64(string(parameters('envVars'))))]"
    

    And then in the PowerShell script:

    param (
        [string]$envVars = ''
    )
    
    if ($envVars)
    {
        $envDecoded = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($envVars)) |
            ConvertFrom-Json
    
        $envDecoded | Get-Member -MemberType NoteProperty | ForEach-Object {
            [System.Environment]::SetEnvironmentVariable($_.Name, $envDecoded."$($_.Name)", 'Machine') 
        }
    }