loading...

Using Event Grid, Azure Keyvault and Azure Functions

omiossec profile image Olivier Miossec ・5 min read

Azure KeyVault is an essential tool. It stores password and key and can be using during ARM Template provisioning, so you don’t have to leave any password in a script. It’s a best practice and another good practice is to change the password used for VM provisioning often.

Azure KeyVault is a service to store passwords, encryption keys and certificates. Access to a secret in Azure KeyVault requires authentication and authorization.
To use Azure KeyVault for VM provisioning, the user (or the tools) need to be authenticated in the Azure Subscription where the key vault is hosted and to be authorized to use the secret. For each type of secret, there is a set of authorization to list, read or set the secret. And about ARM template provisioning you also need to set the option “Azure Resource Manager for template deployment”

If passwords are stored in Azure Keyvault, how can you automate password update? How can you be sure to update the password without having to connect to the Azure Portal?

There are several options, a PowerShell script, Azure Automation, …. And also the support of Azure Event Grid for Azure Key Vault (in preview).

Event Grid is an event service to manage event between Azure services and event consumers. Event Grid manages event routing, high availability and scaling.

In this case, the service is Azure KeyVault and for the event consumer, I choose Azure Functions.
To build this system two things are needed.

  • At least on Azure keyVault with multiple secrets (and enabled for deployment if they are using with ARM templates)
  • An Azure function with a managed identity to handle password change when it expires

The first step is to deploy the Azure Functions App, the KeyVault and the Managed Identity. To build it, I choose to use ARM Template.
This template will deploy the function app plan, the storage account and the function App. But as we need to interact with Azure Keyvault, we need an identity for the function App. Managed Identity is like a service account, it allows to run the service with a special kind of Azure Ad account that can be used in Role-Based Access Control.

There are two types a managed identity for Azure Functions, System Assigned and User Assigned. System Managed Identity tied the identity to the function while the user assigned is a separate object that can be shared across several objects.

I choose System Managed Identity because it’s the most robust option. But you can try User Managed Identity. This option allows you to use the object in multiple services, Azure Functions and Logic App for example.
In arm Template creating a System Managed Identity is done with few lines of code in the website resource.

            "identity": {
                "type": "SystemAssigned"
            },

Next, The Keyvault. The service must be deployed with two options, it must allow the managed identity to perform secret management and the Azure Resource Manager for template deployment must be enabled.
To add the managed identity object to the Keyvault definition is a little more complex. First, you need to retrieve the current Tenant ID.

"TenantID": "[subscription().tenantId]"

Then you need to retrieve the ObjectId of the System Managed Identity.

                        "tenantId": "[variables('TenantID')]",
                        "objectId": "[reference(concat('Microsoft.Web/sites/',  parameters('functionAppName'), '/providers/Microsoft.ManagedIdentity/Identities/default'), '2015-08-31-PREVIEW').principalId]",

You can find the complete template here

Now that we must create the script to update secrets and configure the event to send notifications to the function.
We need to create a function with an Event Grid trigger

 {
      "type": "eventGridTrigger",
      "name": "eventGridEvent",
      "direction": "in"
    }

The function app needs one extension Microsoft.Azure.WebJobs.Extensions.EventGrid. The extension is part of the Azure Functions Extension Bundle. It can be installed via the host.json file.

"extensionBundle" = @{
                            "id" = "Microsoft.Azure.Functions.ExtensionBundle"
                            "version"= "[1.*, 2.0.0)"
                        }

I noticed that you may need to wait a few minutes before using the function.

The binding object, eventGridEventObject will contain the JSON object send by Event Grid. This object contains the event data generated by Event Grid when something happens on Key vault.
The data are always the same regardless of the event, password expiration, key expired, …
The schema looks like

    { 
        "id":"00eccf70-95a7-4e7c-8299-2eb17ee9ad64", 
        "topic":"/subscriptions/<SubscriptionId>/resourceGroups/<ResourceGroupName>/providers/Microsoft.KeyVault/vaults/<VaultName>", 
        "subject":"my", 
        "eventType":"Microsoft.KeyVault.SecretExpired", 
        "eventTime":"2019-07-25T01:08:33.1036736Z", 
        "data":{ 
            "Id":"https://<VaultName>.vault.azure.net/secrets/my/1bd018e9ff404bab8a63667861cbb34f", 
            "vaultName":"<VaultName>", 
            "objectType":"Secret", 
            "objectName":"my", 
            "version":" ee059b2bb5bc48398a53b168c6cdcb10", 
            "nbf":"1559081980", 
            "exp":"1559082102" 
            }, 
        "dataVersion":"1", 
        "metadataVersion":"1"     
    } 

Only password expiration needs to be managed, we only need to take care to the Microsoft.KeyVault.SecretExpired. To see which secret has expired, the data is in data.ObjectName.

We need the vault name to make the function work with any Keyvault in the subscription, to get the keyvaultname data.vaultName.

Now that we know which secret to change how to update the password?

Azure function imports automatically the Azure PowerShell module. This enables the use of Set-AzKeyVaultSecret to set a password and the expiration date.

To generate a new password we can not use the System.Web.Security.MemberShip class. This class is part of the .Net Framework and not .Net Core Framework. It will not work.

$InputArray= ([char[]]([char]33..[char]95) + [char[]]([char]97..[char]126))
$GeneratedPassword = (Get-Random -Count 26 -InputObject ([char[]]$InputArray)) -join ''

The GeneratedPassord variable type is System.String but the Set-AzKeyVaultSecret only accept SecureString data

Last thing, the function needs to trace what happened. We need to log the action, the KeyVault Name, the date of the change and the secret name, but not the password.

To do this, I use a simple Azure Storage Table in an output binding.

        $logData = @{ 
            "VaultName" = $eventGridEventObject.data.vaultName
            "partitionKey" = "vaultlog"
            "SecretName" = $eventGridEventObject.data.objectName
            "SecretChangeDate"= Get-Date
            "rowKey" = (new-guid).guid 
        }

        Push-OutputBinding -Name keyvaultlogtable -Value $logData

Finally we need to connect the keyvault to the function to capture the password expiration event and get the data.
As Event Grid in Azure Keyvault is still in preview there is no direct link. If you look in the portal the Events section is absent.
You will need to use this URL

Go to Events and click + Event Subscription
Give a name for the event and in Event Types select only Secret Expired.
In the Endpoint, type select Azure Function and choose the functions App created before and the function.
In ARM it should look like something like this

{
    "name": "test",
    "properties": {
        "topic": "/subscriptions/<SubscriptionId>/resourceGroups/<KeyvaultResourceGroupName>/providers/Microsoft.KeyVault/vaults/<VaultName>",
        "destination": {
            "endpointType": "AzureFunction",
            "properties": {
                "resourceId": "/subscriptions/<SubscriptionId>/resourceGroups/<FunctionAppResourceGroupName>/providers/Microsoft.Web/sites/<FunctionAppName>/functions/<FunctionName>",
                "maxEventsPerBatch": 1,
                "preferredBatchSizeInKilobytes": 64
            }
        },
        "filter": {
            "includedEventTypes": [
                "Microsoft.KeyVault.SecretExpired"
            ],
            "advancedFilters": []
        },
        "labels": [],
        "eventDeliverySchema": "EventGridSchema"
    }
}

Now that the event grid is in place, you can test by creating a secret with an expiration date in a few minutes and by checking the table in the Function App storage account.
Event Grid in Azure KeyVault is in preview, but you can start to build solutions.

The complete solution is visible here

Discussion

pic
Editor guide