powershellkubernetescryptographyazure-aksazure-keyvault

How do I get a certificate (public and private key) into a windows container in AKS?


Given a windows container running inside Azure Kubernetes Service (AKS). How do I get a certificate (PFX) that I've stored in Azure Key Vault (AKV) stored in the local certificate store of the container?


Solution

  • N.B. This assumes you've already successfully gotten AKS wired up and talking to AKV. Pause and start elsewhere if you've not successfully brought simple passwords across into the environment of your windows container yet.

    The trick is to recognise that when you install a certificate (PFX) into keyvault this is accessed as two separate objects and you can get these pulled into the environment as a combined PEM if you setup your k8s secret provider appropriately.

    First you must setup your k8s secrets to request it as an objecttype of 'secret' (not key or cert) e.g. :

    apiVersion: secrets-store.csi.x-k8s.io/v1
    kind: SecretProviderClass
    metadata:
      name: sc-demo-keyvault-csi
    spec:
      provider: azure
      parameters:
        usePodIdentity: "false"
        useVMManagedIdentity: "true"                                   # Set to true for using managed identity
        userAssignedIdentityID: <redacted>   # Set the clientID of the user-assigned managed identity to use
        keyvaultName: <redacted>                                     # Set to the name of your key vault
        objects:  |
          array:
            - |
              objectName: testcert           # keyvault secret name
              objectType: secret             # getting a cert as a secret returns the public & private key pair as a pem, a type of cert just returns the public key (https://azure.github.io/secrets-store-csi-driver-provider-azure/docs/configurations/getting-certs-and-keys/)
        tenantId: <REDACTED>                # The tenant ID of the key vault
      secretObjects:
      - data:
        - key: secretcert
          objectName: testcert
        secretName: foosecret
        type: Opaque
    

    Once this is done and you've mapped the secret through to your container as an environment variable in your deployment/pod description e.g.

    apiVersion: apps/v1
    kind: Deployment
    spec:
      template:
        spec:
          containers:
            - name: test
              env:
                - name: SIGNING_KEYPAIR
                  valueFrom:
                    secretKeyRef:
                      name: foosecret
                      key: secretcert
              volumeMounts:
                - name : secrets-store01-inline
                  mountPath: "/mnt/secrets-store"
                  readOnly: true
          volumes:
            - name: secrets-store01-inline
              csi:
                driver: secrets-store.csi.k8s.io
                readOnly: true
                volumeAttributes:
                  secretProviderClass: 'sc-demo-keyvault-csi'
    

    If you were to fire up your windows container at this point you'd find your environment contains a PEM file (I assume there's a potential issue here around the size of the certificates but not something I@ve run into.)

    So then we just need to take that PEM, reconstruct into into a PFX file, load it into the certificate store in the container and apply the appropriate permissions.

    Something like this works in powershell:

    # Extract the keys from the environment variable
    $matches = [regex]::match($Env:SIGNING_KEYPAIR,'(?smi)-----BEGIN PRIVATE KEY-----\s*(.+)-----END PRIVATE KEY-----\s*-----BEGIN CERTIFICATE-----\s*(.+)-----END CERTIFICATE-----')
    $PRIVATE_KEY= $matches.Groups[1].Value
    $PUBLIC_KEY= $matches.Groups[2].Value
    
    # Write them out to a random file pair
    $RANDOM_FILE= New-Guid
    Out-File -FilePath "$RANDOM_FILE.key" -InputObject $PRIVATE_KEY
    Out-File -FilePath "$RANDOM_FILE.cer" -InputObject $PUBLIC_KEY
    
    # Create the PFX (the .key file will be attached as it shares the same filename)
    & certutil -p "ignored,$RANDOM_FILE" -MergePFX "$RANDOM_FILE.cer" "$RANDOM_FILE.pfx"  | Out-Null
    $c= Import-PfxCertificate -Password (ConvertTo-SecureString -String "$RANDOM_FILE" -AsPlainText -Force) -FilePath "$RANDOM_FILE.pfx" -CertStoreLocation "Cert:\LocalMachine\My"
    
    # Cleanup the environment
    # (doesn't really improve the security position, but I'd rather not have secrets in two places)
    Remove-Item "$RANDOM_FILE.*"
    

    At this point you should have everything you need ($c.Thumbprint) to setup appropriate access to the private key as you would normally do.

    The approach described here definitely works on containers based on mcr.microsoft.com/dotnet/framework/aspnet:4.8-windowsservercore-ltsc2019 . YMMV for containers based on other base containers.