Edit

Share via


Use the Secret Store extension to fetch secrets for offline access in Azure Arc-enabled Kubernetes clusters

The Azure Key Vault Secret Store extension for Kubernetes (SSE) automatically synchronizes secrets from an Azure Key Vault to an Azure Arc-enabled Kubernetes cluster for offline access. This means you can use Azure Key Vault to store, maintain, and rotate your secrets, even when running your Kubernetes cluster in a semi-disconnected state. Synchronized secrets are stored in the cluster secret store, making them available as Kubernetes secrets to be used in all the usual ways: mounted as data volumes, or exposed as environment variables to a container in a pod.

Synchronized secrets are critical business assets, so the SSE secures them through isolated namespaces, role-based access control (RBAC) policies, and limited permissions for the synchronization controller. For extra protection, encrypt the Kubernetes secret store on your cluster.

This article shows you how to install and configure the SSE as an Azure Arc-enabled Kubernetes extension.

Tip

The SSE is recommended for clusters outside of Azure cloud where connectivity to Azure Key Vault may not be perfect. SSE by its nature creates copies of your secrets in the Kubernetes secrets store. If you prefer to avoid creating local copies of secrets and your cluster has perfect connectivity to Azure Key Vault, then you can use the online-only Azure Key Vault Secrets Provider extension for secret access in your Arc-enabled Kubernetes clusters. It is not recommended to run both the online Azure Key Vault Secrets Provider extension and the offline SSE side-by-side in the same cluster.

Prerequisites

  • An Arc-enabled cluster. This can be one that you connected to yourself (this guide assumes a K3s cluster, and provides guidance on how to Arc-enable it.) or a Microsoft-managed AKS enabled by Azure Arc cluster. The cluster must be running Kubernetes version 1.27 or higher.
  • Ensure you meet the general prerequisites for cluster extensions, including the latest version of the k8s-extension Azure CLI extension.
  • cert-manager is required to support TLS for intracluster log communication. The examples later in this guide direct you though installation. For more information about cert-manager, see cert-manager.io

Install the Azure CLI and sign in, if you haven't already:

az login

Before you begin, set environment variables to be used for configuring Azure and cluster resources. If you already have a managed identity, Azure Key Vault, or other resource listed here, update the names in the environment variables to reflect those resources. Note that the KEYVAULT_NAME must be globally unique; keyvault creation will fail later if this name is already in use within Azure.

export RESOURCE_GROUP="AzureArcTest"
export CLUSTER_NAME="AzureArcTest1"
export LOCATION="EastUS"
export SUBSCRIPTION="$(az account show --query id --output tsv)"
export AZURE_TENANT_ID="$(az account show -s $SUBSCRIPTION --query tenantId --output tsv)"
export CURRENT_USER="$(az ad signed-in-user show --query userPrincipalName --output tsv)"
export KEYVAULT_NAME="my-UNIQUE-kv-name"
export KEYVAULT_SECRET_NAME="my-secret"
export USER_ASSIGNED_IDENTITY_NAME="my-identity"
export FEDERATED_IDENTITY_CREDENTIAL_NAME="my-credential"
export KUBERNETES_NAMESPACE="my-namespace"
export SERVICE_ACCOUNT_NAME="my-service-account"

Create the resource group if necessary:

az group create --name ${RESOURCE_GROUP}  --___location ${LOCATION}

Activate workload identity federation in your cluster

The SSE uses a feature called workload identity federation to access and synchronize Azure Key Vault secrets. This section describes how to set the feature up. The following sections will explain how it's used in detail.

Tip

The following steps are based on the How-to guide for configuring Arc-enabled Kubernetes with workload identity federation. Refer to that documentation for any additional assistance.

If your cluster isn't yet connected to Azure Arc, follow these steps. During these steps, enable workload identity federation as part of the connect command:

az connectedk8s connect --name ${CLUSTER_NAME} --resource-group ${RESOURCE_GROUP} --enable-oidc-issuer

If your cluster is already connected to Azure Arc, enable workload identity using the update command:

az connectedk8s update --name ${CLUSTER_NAME} --resource-group ${RESOURCE_GROUP} --enable-oidc-issuer

Now configure your cluster to issue service account tokens with a new issuer URL (service-account-issuer) that enables Microsoft Entra ID to find the public keys necessary for it to validate these tokens. These public keys are for the cluster's own service account token issuer, and they were obtained and cloud-hosted at this URL as a result of the --enable-oidc-issuer option that you set earlier.

Optionally, you can also configure limits on the SSE's own permissions as a privileged resource running in the control plane by configuring OwnerReferencesPermissionEnforcement admission controller. This admission controller constrains how much the SSE can change other objects in the cluster.

  1. Configure your kube-apiserver with the issuer URL field and permissions enforcement. The following example is for a k3s cluster. Your cluster may have different means for changing API server arguments: --kube-apiserver-arg="--service-account-issuer=${SERVICE_ACCOUNT_ISSUER}", --kube-apiserver-arg=service-account-max-token-expiration=24h and --kube-apiserver-arg="--enable-admission-plugins=OwnerReferencesPermissionEnforcement".

    • Get the service account issuer URL.

      export SERVICE_ACCOUNT_ISSUER="$(az connectedk8s show --name ${CLUSTER_NAME} --resource-group ${RESOURCE_GROUP} --query "oidcIssuerProfile.issuerUrl" --output tsv)"
      echo $SERVICE_ACCOUNT_ISSUER
      
    • Update the API server arguments in the K3s cluster's config file.

      cat <<EOF > /tmp/k3s-config.yaml
      kube-apiserver-arg:
          - 'service-account-issuer=${SERVICE_ACCOUNT_ISSUER}'
          - 'service-account-max-token-expiration=24h'
          - 'enable-admission-plugins=OwnerReferencesPermissionEnforcement'
      EOF
      sudo mv /tmp/k3s-config.yaml /etc/rancher/k3s/config.yaml
      
  2. Restart your kube-apiserver.

    sudo systemctl restart k3s
    
  3. (optional) Verify the service account issuer has been configured correctly:

    kubectl cluster-info dump | grep service-account-issuer
    

Create a secret and configure an identity to access it

To access and synchronize a given Azure Key Vault secret, the SSE requires access to an Azure managed identity with appropriate Azure permissions to access that secret. The managed identity must be linked to a Kubernetes service account using the workload identity feature that you activated earlier. The SSE uses the associated federated Azure managed identity to pull secrets from Azure Key Vault to your Kubernetes secret store. The following sections describe how to set this up.

Create an Azure Key Vault

Create an Azure Key Vault and add a secret. If you already have an Azure Key Vault and secret, you can skip this section.

  1. Create an Azure Key Vault:

    az keyvault create --resource-group "${RESOURCE_GROUP}" --___location "${LOCATION}" --name "${KEYVAULT_NAME}" --enable-rbac-authorization
    
  2. Give yourself 'Secrets Officer' permissions on the vault, so you can create a secret:

    az role assignment create --role "Key Vault Secrets Officer" --assignee ${CURRENT_USER} --scope /subscriptions/${SUBSCRIPTION}/resourcegroups/${RESOURCE_GROUP}/providers/Microsoft.KeyVault/vaults/${KEYVAULT_NAME}
    
  3. Create a secret and update it so you have two versions:

    az keyvault secret set --vault-name "${KEYVAULT_NAME}" --name "${KEYVAULT_SECRET_NAME}" --value 'Hello!'
    az keyvault secret set --vault-name "${KEYVAULT_NAME}" --name "${KEYVAULT_SECRET_NAME}" --value 'Hello2'
    

Create a user-assigned managed identity

Next, create a user-assigned managed identity and give it permissions to access the Azure Key Vault. If you already have a managed identity with Key Vault Reader and Key Vault Secrets User permissions to the Azure Key Vault, you can skip this section. For more information, see Create a user-assigned managed identity and Using Azure RBAC secret, key, and certificate permissions with Key Vault.

  1. Create the user-assigned managed identity:

    az identity create --name "${USER_ASSIGNED_IDENTITY_NAME}" --resource-group "${RESOURCE_GROUP}" --___location "${LOCATION}" --subscription "${SUBSCRIPTION}"
    
  2. Give the identity Key Vault Reader and Key Vault Secrets User permissions. You may need to wait a moment for replication of the identity creation before these commands succeed:

    export USER_ASSIGNED_CLIENT_ID="$(az identity show --resource-group "${RESOURCE_GROUP}" --name "${USER_ASSIGNED_IDENTITY_NAME}" --query 'clientId' -otsv)"
    az role assignment create --role "Key Vault Reader" --assignee "${USER_ASSIGNED_CLIENT_ID}" --scope /subscriptions/${SUBSCRIPTION}/resourcegroups/${RESOURCE_GROUP}/providers/Microsoft.KeyVault/vaults/${KEYVAULT_NAME}
    az role assignment create --role "Key Vault Secrets User" --assignee "${USER_ASSIGNED_CLIENT_ID}" --scope /subscriptions/${SUBSCRIPTION}/resourcegroups/${RESOURCE_GROUP}/providers/Microsoft.KeyVault/vaults/${KEYVAULT_NAME}
    

Create a federated identity credential

Create a Kubernetes service account for the workload that needs access to secrets. Then, create a federated identity credential to link between the managed identity, the OIDC service account issuer, and the Kubernetes service account.

  1. Create a Kubernetes service account that will be federated to the managed identity. Annotate it with details of the associated user-assigned managed identity.

    kubectl create ns ${KUBERNETES_NAMESPACE}
    
    cat <<EOF | kubectl apply -f -
      apiVersion: v1
      kind: ServiceAccount
      metadata:
        name: ${SERVICE_ACCOUNT_NAME}
        namespace: ${KUBERNETES_NAMESPACE}
    EOF
    
  2. Create a federated identity credential:

    az identity federated-credential create --name ${FEDERATED_IDENTITY_CREDENTIAL_NAME} --identity-name ${USER_ASSIGNED_IDENTITY_NAME} --resource-group ${RESOURCE_GROUP} --issuer ${SERVICE_ACCOUNT_ISSUER} --subject system:serviceaccount:${KUBERNETES_NAMESPACE}:${SERVICE_ACCOUNT_NAME} --audience api://AzureADTokenExchange
    

Install the SSE

The SSE is available as an Azure Arc extension. An Azure Arc-enabled Kubernetes cluster can be extended with Azure Arc-enabled Kubernetes extensions. Extensions enable Azure capabilities on your connected cluster and provide an Azure Resource Manager-driven experience for the extension installation and lifecycle management.

cert-manager and trust-manager are also required for secure communication of logs between cluster services and must be installed before the Arc extension.

  1. Install cert-manager.

    helm repo add jetstack https://charts.jetstack.io/ --force-update
    helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --set crds.enabled=true 
    
  2. Install trust-manager.

    helm upgrade trust-manager jetstack/trust-manager --install --namespace cert-manager --wait
    
  3. Install the SSE to your Arc-enabled cluster using the following command:

    az k8s-extension create \
      --cluster-name ${CLUSTER_NAME} \
      --cluster-type connectedClusters \
      --extension-type microsoft.azure.secretstore \
      --resource-group ${RESOURCE_GROUP} \
      --name ssarcextension \
      --scope cluster
    

    If desired, you can optionally modify the default rotation poll interval by adding --configuration-settings rotationPollIntervalInSeconds=<time_in_seconds> (see configuration reference).

Configure the SSE

Configure the installed extension with information about your Azure Key Vault and which secrets to synchronize to your cluster by defining instances of Kubernetes custom resources.

SSE can be configured with a single simplified resource designed to suit most use cases, or the SSE internal components can be configured directly via two resources. The simplified configuration is a preview feature and may benefit from minor changes in upcoming versions. The direct configuration style will remain available for all deployments.

The easiest way to configure SSE is to create an AKVSync custom resource. This resource captures details about your AKV instance, which secrets to fetch, and where to put them in Kubernetes' secret store.

cat <<EOF > akvsync.yaml
kind: AKVSync
apiVersion: secret-sync.x-k8s.io/v1alpha1
metadata:
  name: my-akv-secrets
  namespace: ${KUBERNETES_NAMESPACE}
spec:
  keyvaultName: ${KEYVAULT_NAME}
  clientID: "${USER_ASSIGNED_CLIENT_ID}"
  tenantID: "${AZURE_TENANT_ID}"
  serviceAccountName: ${SERVICE_ACCOUNT_NAME}
  objects:
    - secretInAKV: ${KEYVAULT_SECRET_NAME}
EOF

See AKVSync reference for additional configuration guidance.

Apply the configuration

Apply the configuration custom resource (CR) using the kubectl apply command:

kubectl apply -f ./akvsync.yaml

When an AKVSync configuration is applied SSE automatically generates equivalent direct configuration resources. Do not modify the autogenerated SecretSync and SecretProviderClass resources, they will be updated as needed automatically.

The SSE automatically looks for the secrets and begins syncing them to the cluster.

Observe secrets synchronizing to the cluster

Once the configuration is applied, secrets begin syncing to the cluster automatically at the cadence specified when installing the SSE.

View synchronized secrets

View the secrets synchronized to the cluster by running the following command:

# View a list of all secrets in the namespace
kubectl get secrets -n ${KUBERNETES_NAMESPACE}

Tip

Add -o yaml or -o json to change the output format of kubectl get and kubectl describe commands.

View secret value

To view the synchronized secret value, now stored in the Kubernetes secret store, use the following command:

kubectl get secret <NAME> -n ${KUBERNETES_NAMESPACE} -o jsonpath="{.data.v0}" | base64 -d && echo

<NAME> is ${KEYVAULT_SECRET_NAME} if using the simplified configuration example, or secret-sync-name if using the direct configuration example.

Troubleshooting

See the troubleshooting guide for assistance diagnosing and resolving issues.

Remove the SSE

To remove the SSE and stop synchronizing secrets, uninstall it with the az k8s-extension delete command:

az k8s-extension delete --name ssarcextension --cluster-name $CLUSTER_NAME  --resource-group $RESOURCE_GROUP  --cluster-type connectedClusters    

Uninstalling the extension doesn't remove secrets or CRDs (AKVSync, SecretSync, or SecretProviderClass) from the cluster. These objects must be removed directly with kubectl.

By default, deleting SecretSync or AKVSync resource removes all secrets defined within them, but secrets may persist if:

  • You modified ownership of any of the secrets.
  • You changed the garbage collection settings in your cluster, including setting different finalizers.

In these cases, secrets must be deleted directly using kubectl.

Next steps