Fredrik Erlandsson

Logo

PhD in Computer science, data scientist at Consid AB.

Accessing an Azure Keyvault from an Azure Function

2021-09-16

The following describes how an Azure Function written in Python3 can access secrets stored in an Azure Keyvault using ManagedIdentityCredential.

Create an Azure Function

Microsofts documentation on Azure Functions describes the most important steps.

Login and create app (assumes that storage account already exitst).

az login --use-device-code
az functionapp list \
  --resource-group ${RESOURCE_GROUP} \
  --subscription ${SUBSCRIPTION}
az config param-persist on

az functionapp create \
  -n ${FUNCTION_APP} \
  --storage-account ${STORAGE_ACCOUNT_NAME} \
  --consumption-plan-location ${LOCATION} \
  --runtime python \
  --resource-group ${RESOURCE_GROUP}

Create Key Vault and assign accessrights

The most of the following descriptions are adopted from Mark Heath’s blog.

Create a Key Vault and Store a Secret

az keyvault create -n ${KEY_VAULT_NAME} --resource-group ${RESOURCE_GROUP}

# Save a secret in the key vault
SECRET_NAME="MySecret"
az keyvault secret set -n ${SECRET_NAME} --vault-name ${KEY_VAULT_NAME} \
    --value "Super secret value!"

# View the secret
az keyvault secret show -n ${SECRET_NAME} --vault-name ${KEY_VAULT_NAME} 

Assign access rights for the function to the keyvault

# Get the principalId from the functionapp
PRINCIPAL_ID=$(az functionapp identity show -n ${FUNCTION_APP} --query principalId)

# Assign a managed identity
az functionapp identity assign --name ${FUNCTION_APP} --resource-group ${RESOURCE_GROUP}

# Grant the Managed Identity Read Access to Key Vault
az keyvault set-policy -n ${KEY_VAULT_NAME} \
  --object-id ${PRINCIPAL_ID} \
  --resource-group ${RESOURCE_GROUP} \
  --secret-permissions get list 

Create the code for the function app

The following is an example Python code that can be used to access secrets stored in the Key Vault.

File ${FUNCTION_APP}/requrements.txt

azure-functions==1.6.0
azure-identity==1.5.0
azure-keyvault-secrets==4.2.0

File ${FUNCTION_APP}/read_secrets/function.json

{
  "scriptFile": "__init__.py",
  "bindings": [
    {
      "authLevel": "function",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": ["get"]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "$return"
    }
  ]
}

File ${FUNCTION_APP}/read_secrets/__init__.py

import logging

import azure.functions as func
from azure.identity.aio import ManagedIdentityCredential
from azure.keyvault.secrets.aio import SecretClient

KEY_VAULT = "change me to the right key vault"
KEY_VAULT_URL = f"https://{KEY_VAULT}.vault.azure.net/"

KV_ONE = "name of first secret"
KV_TWO = "name of second secret"

async def read_keys(secrets):
    ret = {}
    try:
        async with SecretClient(
            vault_url=KEY_VAULT_URL,
            credential=ManagedIdentityCredential(),
            logging_enable=True,
        ) as secret_client:
            for secret in secrets:
                ret[secret] = (await secret_client.get_secret(secret)).value
    except Exception as excep:
        logging.error("%s", excep)
    finally:
        await secret_client.close()
    return ret


async def main(req: func.HttpRequest) -> func.HttpResponse:
    """Main function."""
    logging.info("Python HTTP trigger function processed a request.")

    secrets = await read_keys(
        (KV_ONE, KV_TWO)
    )
    logging.debug("%s", secrets.keys())

    return func.HttpResponse(
        f'{{"status": "ok", "read secrets": "{secrets.keys()}"}}',
        mimetype="application/json",
        status_code=200,
    )

Don’t forget to publish the updated function.

func azure functionapp publish ${FUNCTION_APP}