Skip to content

Azure Key Vault

aws sm

Azure Key Vault

External Secrets Operator integrates with Azure Key Vault via SecretStore and ClusterSecretStore resources. The operator can fetch secrets, keys and certificates stored in Azure Key Vault and synchronize them as Kubernetes secrets using an ExternalSecret. Vice versa, it can also push secrets from Kubernetes into Azure Key Vault as secrets, keys and certificates by using a PushSecret.

ESO supports connecting to different cloud flavours azure supports: PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud and AzureStackCloud (for Azure Stack Hub/Edge). You have to specify the environmentType and point to the correct cloud flavour. This defaults to PublicCloud.

For environments with non-standard endpoints (Azure Stack, Azure China with AKS Workload Identity, etc.), you can provide custom cloud configuration to override the default endpoints. See the Custom Cloud Configuration section below.

Configure SecretStore

The Azure Key Vault provider is fully compatible with both namespaced SecretStore and cluster-wide ClusterSecretStore resources. For simplicity, this article refers only to SecretStore, but the same is applicable also for ClusterSecretStore. To establish the integration with Azure Key Vault, specify azurekv as the provider and provide the vault URL and authentication details. The vault URL is the URL of your Key Vault instance, which typically looks like https://<your-keyvault-name>.vault.azure.net. For Azure Stack, it may have a different format, so make sure to use the correct URL for your environment. See below for different authentication methods.

apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
  name: azure-store
spec:
  provider:
    azurekv:
      # URL of your Key Vault instance, see: https://docs.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates
      vaultUrl: "https://xx-xxxx-xx.vault.azure.net"
apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
  name: azure-store
spec:
  provider:
    azurekv:
      # URL of your Key Vault instance, see: https://docs.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates
      vaultUrl: "https://xx-xxxx-xx.vault.azure.net"

Authentication

ESO supports multiple authentication methods to connect to Azure Key Vault:

  • Service Principal
  • Managed Identity (AAD Pod Identity)
  • Workload Identity

Regardless of which authentication method is used to authenticate to Azure Key Vault, the identity which is assigned to External Secrets Operator needs to have proper permissions to access the Key Vault. Both RBAC and legacy Access Policy based Key Vaults are supported.

The required permissions depend on the type of objects you want to manage (secrets, keys, certificates) and the operations you want to perform (read, write, delete, etc.). For example, to grant External Secrets Operator permissions to synchronize secrets and certificates using an ExternalSecret, the minimum required permissions are either the Key Vault Secrets User and Key Vault Certificates User RBAC roles, alternatively for Access Policy based Key Vaults, the Get permission over secrets and certificates.

Service Principal

To use a Service Principal's credentials for authentication, you need to create a Service Principal in Entra ID and grant it the necessary permissions to access the Key Vault. Then, you can create a Kubernetes Secret containing the Service Principal's credentials:

  • Client ID
  • Either Client Secret or Client Certificate (in PEM format)

Reference this secret in your SecretStore configuration.

apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
  name: azure-store-spn-secret
spec:
  provider:
    azurekv:
      # Azure tenant ID, see: https://docs.microsoft.com/en-us/azure/active-directory/fundamentals/active-directory-how-to-find-tenant
      tenantId: "2ed1d494-6c5a-4c5d-aa24-479446fb844d"
      # URL of your Key Vault instance, see: https://docs.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates
      vaultUrl: "https://kvtestpushsecret.vault.azure.net"
      authSecretRef:
        # Reference to Kubernetes Secret name containing the Service Principal client ID under the key `ClientID`
        clientId:
          name: azure-secret-sp
          key: ClientID
        # Reference to Kubernetes Secret name containing the Service Principal client secret under the key `ClientSecret`
        clientSecret:
          name: azure-secret-sp
          key: ClientSecret
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
  name: azure-store-spn-certificate
spec:
  provider:
    azurekv:
      # Azure tenant ID, see: https://docs.microsoft.com/en-us/azure/active-directory/fundamentals/active-directory-how-to-find-tenant
      tenantId: "2ed1d494-6c5a-4c5d-aa24-479446fb844d"
      # URL of your Key Vault instance, see: https://docs.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates
      vaultUrl: "https://kvtestpushsecret.vault.azure.net"
      authSecretRef:
        # Reference to Kubernetes Secret name containing the Service Principal client ID under the key `ClientID`
        clientId:
          name: azure-secret-sp
          key: ClientID
        # Reference to Kubernetes Secret name containing the Service Principal client certificate in PEM format under the key `ClientCertificate`
        clientCertificate:
          name: azure-secret-sp
          key: ClientCertificate

Managed Identity (AAD Pod Identity)

Warning

This authentication option uses AAD Pod Identity which is deprecated, it is recommended to use the Workload Identity authentication instead.

To use a Managed Identity with AAD Pod Identity for authentication, you need to create a Managed Identity in Azure and grant it the necessary permissions to access the Key Vault. Then, assign that Managed Identity to the External Secrets Operator using AAD Pod Identity. Finally, you can reference that identity in your SecretStore configuration.

If you have multiple identities assigned to External Secrets Operator, you can specify which of them to use by providing its client ID in the field spec.provider.azurekv.identityId on the SecretStore. If only one identity is assigned, you can omit the identityId field.

apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
  name: azure-store
spec:
  provider:
    azurekv:
      authType: ManagedIdentity
      # Optionally set the ID of the Managed Identity, if multiple identities are assigned to External Secrets Operator.
      identityId: "00000000-0000-0000-0000-000000000000"
      # URL of your Key Vault instance, see: https://docs.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates
      vaultUrl: "https://my-keyvault-name.vault.azure.net"

Workload Identity

Workload Identity is the recommended authentication method for Azure Key Vault. It allows the External Secrets Operator to authenticate to Azure Key Vault using the identity of a Kubernetes Service Account, without needing to manage secrets or credentials. This is achieved by creating a trust relationship between your Kubernetes cluster and the Entra ID identity you want to use. This process is known as Workload Identity Federation. The identity in Entra ID can be either an Application, a user-assigned Managed Identity or a Service Principal.

The workload identity federation can be done in various ways, for instance using Terraform, the Azure Portal or Azure CLI. We found the azwi CLI very helpful. The Azure Workload Identity Quick Start Guide is a good place to get started.

This is basically a two step process:

  1. Create a Kubernetes Service Account (guide)

azwi serviceaccount create phase sa \
  --aad-application-name "${APPLICATION_NAME}" \
  --service-account-namespace "${SERVICE_ACCOUNT_NAMESPACE}" \
  --service-account-name "${SERVICE_ACCOUNT_NAME}"
2. Configure the trust relationship between Azure AD and Kubernetes (guide)

azwi serviceaccount create phase federated-identity \
  --aad-application-name "${APPLICATION_NAME}" \
  --service-account-namespace "${SERVICE_ACCOUNT_NAMESPACE}" \
  --service-account-name "${SERVICE_ACCOUNT_NAME}" \
  --service-account-issuer-url "${SERVICE_ACCOUNT_ISSUER}"

With these prerequisites met you can configure External Secrets Operator to use that Service Account. You have two options:

Mounted Service Account

You run the controller and mount that particular service account into the pod by adding the label azure.workload.identity/use: "true"to the pod. That grants everyone who is able to create a secret store or reference a correctly configured one the ability to read secrets. This approach is usually not recommended. But may make sense when you want to share an identity with multiple namespaces. Also see our Multi-Tenancy Guide for design considerations.

apiVersion: v1
kind: ServiceAccount
metadata:
  # This service account was created by azwi
  name: workload-identity-sa
  annotations:
    azure.workload.identity/client-id: 7d8cdf74-xxxx-xxxx-xxxx-274d963d358b
    azure.workload.identity/tenant-id: 5a02a20e-xxxx-xxxx-xxxx-0ad5b634c5d8
---
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
  name: azure-store
spec:
  provider:
    azurekv:
      authType: WorkloadIdentity
      vaultUrl: "https://xx-xxxx-xx.vault.azure.net"
      # Note: no serviceAccountRef was provided
Referenced Service Account

You run the controller without service account (effectively without azure permissions). Now you have to configure the SecretStore and set the serviceAccountRef and point to the service account you have just created. This is usually the recommended approach. It makes sense for everyone who wants to run the controller without Azure permissions and delegate authentication via service accounts in particular namespaces. Also see our Multi-Tenancy Guide for design considerations.

apiVersion: v1
kind: ServiceAccount
metadata:
  # this service account was created by azwi
  name: workload-identity-sa
  annotations:
    azure.workload.identity/client-id: 7d8cdf74-xxxx-xxxx-xxxx-274d963d358b
    azure.workload.identity/tenant-id: 5a02a20e-xxxx-xxxx-xxxx-0ad5b634c5d8
---
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
  name: azure-store
spec:
  provider:
    azurekv:
      authType: WorkloadIdentity
      vaultUrl: "https://xx-xxxx-xx.vault.azure.net"
      serviceAccountRef:
        name: workload-identity-sa

In case you don't have the clientId when deploying the SecretStore, such as when deploying a Helm chart that includes instructions for creating a Managed Identity using Azure Service Operator next to the SecretStore definition, you may encounter an interpolation problem. Helm lacks dependency management, which means it can create an issue when the clientId is only known after everything is deployed. Although the Service Account can inject clientId and tenantId into a pod, it doesn't support secretKeyRef/configMapKeyRef. Therefore, you can deliver the clientId and tenantId directly, bypassing the Service Account.

The following example demonstrates using the secretRef field to directly deliver the clientId and tenantId to the SecretStore while utilizing Workload Identity authentication.

apiVersion: v1
kind: ServiceAccount
metadata:
  # This service account was created by azwi
  name: workload-identity-sa
  annotations: {}
---
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
  name: azure-store
spec:
  provider:
    azurekv:
      # tenantId spec option #1
      tenantId: "5a02a20e-xxxx-xxxx-xxxx-0ad5b634c5d8"
      authType: WorkloadIdentity
      vaultUrl: "https://xx-xxxx-xx.vault.azure.net"
      serviceAccountRef:
        name: workload-identity-sa
      authSecretRef:
        clientId:
          name: umi-secret
          key: clientId
        # tenantId spec option #2
        tenantId:
          name: umi-secret
          key: tenantId

Custom Cloud Configuration

External Secrets Operator supports custom cloud endpoints for Azure Stack Hub, Azure Stack Edge, and other scenarios where the default cloud endpoints don't match your environment. This feature requires using the new Azure SDK.

Azure China Workload Identity

Azure China's AKS uses a different OIDC issuer (login.partner.microsoftonline.cn) than the standard China Cloud endpoint (login.chinacloudapi.cn). When using Workload Identity with AKS in Azure China, you need to override the Active Directory endpoint:

apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
  name: azure-china-workload-identity
spec:
  provider:
    azurekv:
      vaultUrl: "https://my-vault.vault.azure.cn"
      environmentType: ChinaCloud
      authType: WorkloadIdentity
      # REQUIRED: Must be true to use custom cloud configuration
      useAzureSDK: true
      # Override the Active Directory endpoint to match AKS OIDC issuer
      customCloudConfig:
        activeDirectoryEndpoint: "https://login.partner.microsoftonline.cn/"
        keyVaultEndpoint: "https://vault.azure.cn/"
        resourceManagerEndpoint: "https://management.chinacloudapi.cn/"
      serviceAccountRef:
        name: my-service-account
        namespace: default

Azure Stack Configuration

For Azure Stack Hub or Azure Stack Edge environments:

apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: azure-stack-backend
spec:
  provider:
    azurekv:
      vaultUrl: "https://my-vault.vault.local.azurestack.external/"
      # REQUIRED: Must be set to AzureStackCloud for custom environments
      environmentType: AzureStackCloud
      # REQUIRED: Must be true for Azure Stack (legacy SDK doesn't support custom clouds)
      useAzureSDK: true
      # REQUIRED: Custom cloud endpoints for your Azure Stack deployment
      customCloudConfig:
        # Azure Active Directory endpoint for authentication
        activeDirectoryEndpoint: "https://login.microsoftonline.com/"
        # Optional: Key Vault endpoint if different from vaultUrl domain
        keyVaultEndpoint: "https://vault.local.azurestack.external/"
        # Optional: Resource Manager endpoint for resource operations
        resourceManagerEndpoint: "https://management.local.azurestack.external/"
      # ... rest of authentication configuration (Service Principal example)
      authType: ServicePrincipal
      tenantId: "your-tenant-id"
      authSecretRef:
        clientId:
          name: azure-secret
          key: client-id
        clientSecret:
          name: azure-secret
          key: client-secret

Note

  • useAzureSDK: true is mandatory when using customCloudConfig
  • customCloudConfig can be used with any environmentType (PublicCloud, ChinaCloud, etc.)
  • For AzureStackCloud, customCloudConfig is required
  • Contact your Azure Stack administrator for the correct endpoint URLs

Object Types

Azure Key Vault has different object types; Secrets, Keys and Certificates, all of which are supported. To explicitly select which object type to fetch via an ExternalSecret or push via a PushSecret, prefix the spec.data[].remoteRef.key field with either key, secret or cert. If no prefix is provided, the operator defaults to secret.

Object Type Prefix Description
Secret secret (default) Generic secrets, such as passwords and database connection strings.
Key key Cryptographic keys represented as JSON Web Key [JWK] objects. Azure Key Vault does not export the private key. You may want to use template functions to transform this JWK into PEM encoded PKIX ASN.1 DER format.
Certificate cert X509 certificates, for example TLS certificates. You may want to use template functions to transform this into your desired encoding.

Creating an ExternalSecret

To synchronize secrets from Azure Key Vault into Kubernetes, you need to create an ExternalSecret which references the SecretStore or ClusterSecretStore you configured. You specify the name of the secret in Azure Key Vault that you want to synchronize and the name of the Kubernetes secret that should be created.

apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: database-credentials
spec:
  refreshInterval: 1h0m0s
  secretStoreRef:
    kind: SecretStore
    name: azure-store

  target:
    name: database-credentials
    creationPolicy: Owner

  data:
  # name of the SECRET in the Azure KV (no prefix is by default a SECRET)
  - secretKey: database-username
    remoteRef:
      key: database-username

  # explicit type and name of secret in the Azure KV
  - secretKey: database-password
    remoteRef:
      key: secret/database-password

  # metadataPolicy to fetch all the tags in JSON format
  - secretKey: database-credentials-metadata
    remoteRef:
      key: database-credentials
      metadataPolicy: Fetch

  # metadataPolicy to fetch a specific tag which name must be in property
  - secretKey: database-credentials
    remoteRef:
      key: database-credentials
      metadataPolicy: Fetch
      property: environment

  # type/name of certificate in the Azure KV
  # raw value will be returned, use templating features for data processing
  - secretKey: db-client-cert
    remoteRef:
      key: cert/db-client-cert

  # type/name of the public key in the Azure KV
  # the key is returned PEM encoded
  - secretKey: encryption-pubkey
    remoteRef:
      key: key/encryption-pubkey

The operator will fetch the Azure Key Vault secret and inject it as a Secret. View the created secret by running the following command:

kubectl get secret secret-to-be-created -n <namespace> -o jsonpath='{.data.dev-secret-test}' | base64 -d

To select all secrets inside the key vault or all tags inside a secret, you can use the dataFrom directive:

apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: all-secrets
spec:
  refreshInterval: 1h0m0s           # rate ESO pulls Azure Key Vault
  secretStoreRef:
    kind: SecretStore
    name: azure-store           # name of the SecretStore (or kind specified)
  target:
    name: all-secrets           # name of the k8s Secret to be created
    creationPolicy: Owner
  dataFrom:
  # find all secrets starting with dev-
  - find:
      name:
        regexp: "^dev"
  # find all secrets with tags
  - find:
      tags:
        environment: dev

  # extract data from a json value
  - extract:
      key: database-credentials

  # fetch tags from `database-credentials`
  # and store them as individual keys in a secret
  - extract:
      key: database-credentials
      metadataPolicy: Fetch

To fetch a P12 certificate (also known as PKCS12 or PFX) from Azure Key Vault and inject it as a Secret of type kubernetes.io/tls, you can use the following configuration. Note that template functions are used to transform the certificate from its original format into a PEM encoded format that Kubernetes expects for TLS secrets.

apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: tls-client-credentials
spec:
  refreshInterval: 1h0m0s
  secretStoreRef:
    kind: SecretStore
    name: azure-store
  target:
    template:
      type: kubernetes.io/tls
      engineVersion: v2
      data:
        tls.crt: '{{ .tls | b64dec | pkcs12cert }}'
        tls.key: '{{ .tls | b64dec | pkcs12key }}'
  data:
  - secretKey: tls
    remoteRef:
      # Azure Key Vault certificates must be fetched as secret/cert-name
      key: secret/tls-client-credentials

Creating a PushSecret

You can push secrets from Kubernetes into Azure Key Vault as secrets, keys or certificates by using a PushSecret. A PushSecret references a Kubernetes Secret as the source of the data. The operator can create, update or delete the corresponding secret in Azure Key Vault to match the desired state defined in the PushSecret.

Pushing to a Secret

Pushing to a Secret requires no previous setup. Provided you have a Kubernetes Secret available, you can create a PushSecret which references it to have it created on Azure Key Vault. You can optionally set metadata such as content type or tags. The operator will read the data from the Kubernetes Secret and push it to Azure Key Vault as a secret.

apiVersion: v1
kind: Secret
metadata:
  name: source-secret
stringData:
  source-key: "my-secret"
---
apiVersion: external-secrets.io/v1alpha1
kind: PushSecret
metadata:
  name: pushsecret-example
  namespace: default
spec:
  refreshInterval: 1h0m0s # Refresh interval for which push secret will reconcile
  deletionPolicy: Delete
  secretStoreRefs: # A list of secret stores to push secrets to
    - name: azure-store
      kind: SecretStore
  selector:
    secret:
      name: source-secret # Source Kubernetes secret to be pushed
  data:
    - match:
        secretKey: source-key # Source Kubernetes secret key containing the secret
        remoteRef:
          remoteKey: my-azkv-secret-name
      metadata:
        apiVersion: kubernetes.external-secrets.io/v1alpha1
        kind: PushSecretMetadata
        spec:
          expirationDate: "2024-12-31T23:59:59Z" # Expiration date for the secret in Azure Key Vault
          tags: # Tags to be added to the secret in Azure Key Vault
            Content-Type: application/json

Note

In order to create a PushSecret targeting Secrets, the Key Vault Secrets Officer role, alternatively Access Policy permissions Set and Delete for Secrets must be granted to the identity configured on the SecretStore.

Pushing to a Key

The first step is to generate a valid private key. Supported formats include PRIVATE KEY, RSA PRIVATE KEY AND EC PRIVATE KEY (EC/PKCS1/PKCS8 types). After uploading your key to a Kubernetes Secret, the next step is to create a PushSecret manifest with the following configuration:

apiVersion: v1
kind: Secret
metadata:
  name: source-key
data:
  tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlKSndJQkFBS0NBZ0VBcTRJTEFXRkZRdXNCMTFtYk1FQ2ZSRjh2WUJWeVhqYmFBczN5SE5RWXBNbUNWNkt0CmxKOVcrMlRMRUc3WnlhN1hwTGNuTlc1QWtOM3FrYW1zWGNiV0dLMUZIK3BKcXlKK2RkaktrMjlXa1RHYnV2THgKdkNWSGp6cndPN0NFdTVGWmIvV2NxcjMzb2l4YWdwNlBFYVZKR0t6U3hJaFMvZDlXR1JuN0MySnhKRnlaWlBLWgpYY01KOEg0TmZ5UDcrWjVZTjJMaWQ4eWdWUlpDWXgzQktadXdsQmdwMkpjMmpkN0x4WmQrYmx3REdGUEw4VHpPCk5LZjFRT2JndmdWY2E4M0NWVTJLZ3p0R0M0YVhkVDR3TkYrV25Hdmgza1JrVlBqMDRsbms4Z001M0ZJZ0dJV2oKalphRVd2b2RBMFJSWEtwL3IyMFI4aFRRNXlZMGJ4ejVSQVJyTEdkY1BlejRJY3FSZG5nUm10VldkQ29ZMzZNMgphdE9HRnNPd0ZCbEpuRVhUY0dxWXlnbnZYQlVzVXRNSmdYY3pDWlB1czlTODZtTGxjajlZK3BNb3FSY0NpMCtOCjBtd3VvNUt3dG1tSjBURnBXR3RLS0VSVUpOZkduNWIxekZrWGxsZSt5ODMzMkN2Nk9yWXNCemNya0pFOGJHRmUKTytZY1lqYytGblJpN3JhWXpwTzRReDJ5dzUxSGRScis3aldxNnFrejEzUlZya3hSQ1NFVW5wbVE1M0RvSTZjWgpDakN0UjZWZ3VVY0xnYndFM2w0dlluV0VXMHVNNmZjenlQem1LVis5M0RFd1U4Q3pXR09neTJrNjBYcmx2YkJwCjYrbFdlZVZnNzJJZjZEb0oyVFZZQStFZUpOWDBpaHVUTmpDNmk3VVZYdm5KRWNhSnFTRXE1QTY4OUs4Q0F3RUEKQVFLQ0FnQWN3S2x0cXN2OHd2OUZCaDJ4UWpReE55L3ZFTWxpcUJsMmZPWkpGUG1vcnF1dVczUjBSUjVFK1FuZQpFR2RzbTJaRmsvcjd4eWNGNGw1UDJ6MHRYNGRIRGMxWDQyUkVUMzBaN3FWUGdFdm4vWVFaSEYrUVprT1A3SmFYCnV5a1ZkUEdraG0ya1prS2Nxb2psK3dVTE5VV0M0SDVaT20ySGFDaTcvcElLdjQ4dVJHUG0rNURnbWpFUlkyQ0oKM3hPQUxwNmxjbXQ3SUJBRkU3MC9kcDZLaGpKZE1ZdmFac2RiazIxZ0M5ekRUYU9yTVdrd1lUeEVzWis1S0x1bQp2NmxWM1dIbUFTRG1qVXBaNWs5LzlWUUpnN2p4TWxqa2RWeklyaEFIM29BMlhub1Z5S0xlMlpDb3pRSVZhbmJ3CnRFUmJuNjNXVUJmQkdPSkl6aXZlTU9KTkY5eUxoOTBrWmszaDR0N3dqWFNodUR2SGp2ZmVaVzhjOStTVTh3VlUKTlRZQzUxaHFKYXNDdWdHa0NVZGp3V0pucXc4QU1VNGZFQkM0V1JBRWpKMTdYMDVJNmt3c2V2ZVRrNjlmOTNWSgoxS3ZVcmpKTkNpeVVXVWVzc0lrWllacGxJZnYrbExJUWNrTmVpOFdRRjV0RTh0Z09heHJLZXBWMW81NlkvT2tUCmFyYjg1Z2VYb0Z0Tm9NMUd6TkxqQnQrZ2pIY2owcExZakZ5L2Jsa2ZrZnRNMW1hN0U3L3ROK0d2bHBhQXE3RUcKNTc3a2xoNXJGWGQza09meVY1U3E3cmFQWDRZOWlPSU5EaXBVblpXcENHYjRHQS8wSFozdWpacTB6SlQ4Z0NyaQpSQndBRFBVY0J0UzYxUzE0WjJhU0Q1R1NKUmFHbFNGdVRoY1lxR0MrK08xcTllbkRpUUtDQVFFQTJjN21EN2FvClRlcExYRklWMzU0Zk5QTFhGYm9JSXZPZHhLVnYvR25NajZhMVhCd3RPdWhlQTlmNlhacUY2ZXViVmtLK1ZobWgKR1k5dm5nWHd2ZHBiZTIzdmN5d2duYWxTSDVYRGZnaE9LZU5ZMDJSYnhtWlVTMEtvWGRhSStHVDA3QWN6ZFFkaQpMRnBYTWtybmQwZC9taHZGNkVxN0t0Mkw2YkVoSXptQU5sTWMyY1lBeEIwY0UzRjJLQTNqV3dyQjRuMUZrRTlQCnMzby9tbmVXNEswMVlMVEsyMGhPOTlNeG5oNzNTV1h1SWFXdlI3T1pRcHFEMWFtYXBGUGlqY0RlRVpQczVUMFIKNEk3aVczNWF4YTl5WncxSkYrMTV5aEhkTTNHZllGaXJudy8zRlpQL282RzBJeW50YUZLNkFGU0dIaHpMcEs1awphSWRMYWVBbWlMYnpmUUtDQVFFQXlaVExET1h5V2VTZjUwV0oxUzVtTHJDMVJUNUI4K1dvZzhiVSsvSWxZTjF6Cm82eTM2QkVJcTlMWGhUUTl6cGp0MlcwNTRHa3FjU1hKcTJtajFHSEE5Q3FCTGNTaldyNHR6ZHFXTzREcnoxN0gKVCtpaEZqZ016R2praXhNSlpkZ2JoWGprQ3RkVEMzdGJhaFBiNjN4TjJGM0d6aE8xRmRUVWZ6bXM3WkVzWmRhYgpTaFZaaUFBOU40dnpmYWYyZ3I2SlB2azZwbEdpV2hvT2xkclRvVG4zSFRPNGJNYWJxc20vSFNTU2FyNUtXTUlXCnZlSVN4YjFoQTFIL1dTMXcvN1dqVHJ1UUZqWDRtU1BkT2lqL3hZblVWWjcrTjhLN3VKREJZNjcrWXVNM2RQNHgKdUJ2RjcvdDdwd3g0b002QnhBbGpQZExwZ2dxRFFLb3drVk9reFZ3b213S0NBUUJJS0pOdmdVUWhEQTRMZCtabgpQeXQzanp4U3BsOHJ0U24vakErZHdDOVZLQlhOZmtnOXk5M1p5Q1BaL3VkK3A5KytwRDRLcUZNRzlNNDF2Q0lWCnc5R3JBckRocHl6bkRzRjJWVmQrMmFHTG54WStjbkUxT1pHVG5YSEtKTmtiOGRaeW03QWdoV0d3Ny8wVFhGMXkKMXUwZlVUUXYwUkpSRVRUWkp5V2pWZGwwSmZUWThSQXY2TFQwZkJKNUVxRFArTEJqS0wxeklkTjEwbnBmNGw3Sgo4SmhPZ1piekx2RjZpUzFYQlV0SHRjMCt1SFZwZThhNm1oWXpJdzFvZzZINjlIcWR1RFF6ZmhmK0hWaEFsNHZiCkVsVUVieEpZS3dTK1BVemJUamxPNGhGNWtRQjYxWjFMeUxhMUw1N0hnU0MrRzBLVGwxYWdLR1o3ZXRjeExHR1gKeVlUQkFvSUJBRms2NWc3VmtzdTc2aFJqc2JtT0NtbE1pMUVWVi9od2hvR2VlQlQyZ1JrNXJjQ2I2ZVJ0OWRxcApRQUdVdUc5RlByUHFKNTV3cnZyYThVUlJSTlgwVjRjOWNXVWpEL1JSRHRGNm10bklIWm56cUdKMDVTbUNzaGVoCnJ0anBHbFhjcllJTm0xUTVNR2Q2dVdKaFhBNEhQaVl5akpnWUhTYUd5WEZ2eEY1OHpweGR2T3UwTzZkNkE1OGMKOGpHRE1obDU0aUxnQzlnbmRxaFB0SGtkSG1UVjFjODFYOE8ydnAyQkpIbndBR2dEeDhFMldQN0FuZkt0KzgyTwpkR3V6TTd2ZFdXYTJtL2RZK0t4Qk5lSlMxN1ZIWjVobkFyMElGRFNFenpZaTlqUXJ4QmFqbHJxYWdLblVOazRoCnRSdnBqWU9MYkVTbm9mbVFVYjFFR0srYnlPb2IrMVVDZ2dFQUJJTFZ0eVV6cFNobW1FN0crZ3I0aGpBb0UxQlgKTDd2SHVIbGdrMStNbFR6RlNLZXpZUDY4RnFsRjRocEFRT2kxTnN3Sy9zaXppdGEycUdUUDJyd1d3NkJUUW9wawplbkdDaEtWNUp0SHRBeDZ2bEZ0aUxUVzE5QTlvUXZEbEllYmNsaFRob2ZWeHV0NFI4RC8vSXZRQXRxbm5jUFFuCnZ5RzRUakl4VCtsWDZqcXdHbUlwRVI4TlpLdjVXU2EzOVVNdlF0ZUJ3Q1hUZEF6Wnlqc0RjRENodlJVRno3S2YKNVlMZ1pVdEt2cEZnbVNYNGF0b2t1TCt5Nm9LYm93Tld6bVdhNzhHbzRLUlhGK2xxUk5OL0dTM0lkM01MdDNmKwovLzRvcWNZa1lyU0dEbjJPenRabGpFcjFrK0NCQ0Rvc3pFMms2b2ZmN2pBck1YUG5McUVXQXkrWDdBPT0KLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0K
---
apiVersion: external-secrets.io/v1alpha1
kind: PushSecret
metadata:
  name: pushsecret-example
  namespace: default
spec:
  refreshInterval: 1h0m0s # Refresh interval for which push secret will reconcile
  deletionPolicy: Delete
  secretStoreRefs: # A list of secret stores to push secrets to
    - name: azure-store
      kind: SecretStore
  selector:
    secret:
      name: source-key # Source Kubernetes secret to be pushed
  data:
    - match:
        secretKey: tls.key # Source Kubernetes secret key containing the JWK
        remoteRef:
          remoteKey: key/my-azkv-key-name

Note

In order to create a PushSecret targeting keys, the Key Vault Crypto Officer role, alternatively Access Policy permissions Import and Delete for Keys must be granted to the identity configured on the SecretStore.

Pushing to a Certificate

The P12 format (also known as PKCS12 or PFX) is recommended format for importing certificates to Azure Key Vault. The P12 file must contain both the certificate and the private key. Make sure to use PKCS8 format for the private key, as Azure Key Vault does not support PKCS1 format. Additionally, only password-less P12 files are supported.

Provided you have a Kubernetes Secret with a P12 certificate, you can push it to Azure Key Vault by creating a PushSecret with the following configuration. The operator will read the P12 file from the Kubernetes Secret and import it into Azure Key Vault as a certificate.

apiVersion: v1
kind: Secret
metadata:
  name: source-certificate
data:
  cert.p12: <BASE64_ENCODED_P12_CERTIFICATE>
---
apiVersion: external-secrets.io/v1alpha1
kind: PushSecret
metadata:
  name: pushsecret-example
  namespace: default
spec:
  refreshInterval: 1h0m0s # Refresh interval for which push secret will reconcile
  deletionPolicy: Delete
  secretStoreRefs: # A list of secret stores to push secrets to
    - name: azure-store
      kind: SecretStore
  selector:
    secret:
      name: source-certificate # Source Kubernetes secret to be pushed
  data:
    - match:
        secretKey: cert.p12 # Source Kubernetes secret key containing the P12 certificate
        remoteRef:
          remoteKey: cert/my-azkv-cert-name

Provided you have a Kubernetes Secret with the certificate and private key in PEM format, you can use the following configuration to transform it into a P12 file and push it to Azure Key Vault. The operator will read the certificate and private key from the Kubernetes Secret, convert them into a P12 file using template functions, and import it into Azure Key Vault as a certificate.

apiVersion: v1
kind: Secret
metadata:
  name: source-certificate
data:
  tls.crt: <BASE64_ENCODED_PEM_CERTIFICATE>
  tls.key: <BASE64_ENCODED_PEM_KEY>
---
apiVersion: external-secrets.io/v1alpha1
kind: PushSecret
metadata:
  name: pushsecret-example
  namespace: default
spec:
  refreshInterval: 1h0m0s # Refresh interval for which push secret will reconcile
  deletionPolicy: Delete
  secretStoreRefs: # A list of secret stores to push secrets to
    - name: azure-store
      kind: SecretStore
  selector:
    secret:
      name: source-certificate # Source Kubernetes secret to be pushed
  template:
    engineVersion: v2
    data:
      # Use the `fullPemToPkcs12` function to convert the PEM-encoded certificate chain (certificate + intermediate certificates) + private key into a P12 file.
      # You can also use the `pemToPkcs12` function if you only want to include the certificate + private key without the intermediate certificates.
      cert.p12: '{{ fullPemToPkcs12 (index . "tls.crt" | toString) (index . "tls.key" | toString) | b64dec }}'
  data:
    - match:
        secretKey: cert.p12 # Reference to the generated P12 file in the template data
        remoteRef:
          remoteKey: cert/my-azkv-cert-name

If you are using cert-manager and its Certificate resource to generate the Kubernetes Secret in a PEM format as shown above, make sure to set spec.privateKey.encoding to PKCS8. By default, cert-manager generates private keys with PKCS1 encoding, which is not supported by Azure Key Vault.

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: dummy-certificate
spec:
  dnsNames:
    - dummy.local
  issuerRef:
    group: cert-manager.io
    kind: ClusterIssuer
    name: letsencrypt-prod
  secretName: source-certificate
  privateKey:
    encoding: PKCS8 # Must be PKCS8 for Azure Key Vault
    algorithm: RSA
    size: 2048

Note

In order to create a PushSecret targeting certificates, the Key Vault Certificates Officer role, alternatively Access Policy permissions Import and Delete for Certificates must be granted to the identity configured on the SecretStore.