Search code examples
azurekuberneteskubernetes-ingressazure-aks

Reference TLS certificate used by AKS's Ingress from Azure Key Vault


I'm setting up an ingress for the application resides in the AKS. But ran into a problem on binding the certificate to the ingress.

As you can see below, I am trying to reference ingress-cert from the KV and use it in the ingress through SecretProviderClass

apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: {{ include "secretProvider.name" . }}
spec:
  provider: azure
  secretObjects:
    - secretName: ingress-tls-csi
      type: kubernetes.io/tls
      data:
        - objectName: ingress-cert
          key: tls.key
        - objectName: ingress-cert
          key: tls.crt
  parameters:
    usePodIdentity: "false"
    useVMManagedIdentity: "true"
    userAssignedIdentityID: {{ .Values.keyVault.identity }}
    keyvaultName: {{ .Values.keyVault.name }}
    objects: |
      array:
        - |
          objectName: ingress-cert
          objectType: secret
    tenantId: {{ .Values.keyVault.tenant }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ include "ingress.name" . }}
  annotations:
    kubernetes.io/ingress.class: azure/application-gateway
    kubernetes.io/ingress.allow-http: "false"
    appgw.ingress.kubernetes.io/override-frontend-port: "443"
spec:
  tls:
    - hosts:
        - {{ .Values.ingress.host }}
      secretName: ingress-tls-csi
    # Property `rules` is omitted

It's working fine when accessing other secrets from pods through env but for the ingress this is the output on describing it:

Name:             my-ingress
Namespace:        my-namespace
Address:          x.x.x.x
Default backend:  default-http-backend:80
TLS:
  ingress-tls-csi terminates my.example.com
Rules:
  Host                   Path  Backends
  ----                   ----  --------
Annotations:             appgw.ingress.kubernetes.io/override-frontend-port: 443
                         kubernetes.io/ingress.allow-http: false
                         kubernetes.io/ingress.class: azure/application-gateway
Events:
  Type     Reason          Age                   From                       Message
  ----     ------          ----                  ----                       -------
  Warning  SecretNotFound  3m3s (x2 over 3m10s)  azure/application-gateway  Unable to find the secret associated to secretId: [my-namespace/ingress-tls-csi]

I've set up the KV integration by following the Use the Azure Key Vault Provider for Secrets Store CSI Driver in an AKS cluster documentation.

But upon following the Set up Secrets Store CSI Driver to enable NGINX Ingress Controller with TLS as a hint on how to implement the same with AGIC. I noticed that the certificate is added as a secret inside the AKS which is then referenced inside the ingress with secretName: ingress-tls-csi.

kubectl get secret -n $NAMESPACE

NAME                                             TYPE                                  DATA   AGE
ingress-tls-csi                                  kubernetes.io/tls                     2      1m34s

I assume that ingress can't reference the secret directly from SecretProviderClass as the example in the documentation need to use the ingress-tls-csi as a secret object which I assumed (again) created by ingress-nginx chart.

My question is how can I implement the same as the ingress-nginx example with AGIC?


Additional information:

  • I used AGIC with Azure CNI networking.
  • Ingress is currently working with manually added certificate with kubectl command. The reason I need to use the one from KV is the AKS will also be used by other people deploying under the same domain but different namespace and I think it's a bad idea to give direct access to certificate's private key.

Solution

  • As I couldn't find a way to integrate the Ingress with the Azure Key Vault, I've implemented a workaround with GitHub Actions to retrieve the certificate and add it to the AKS. Because most of them are bash commands, the workaround isn't exclusive to GitHub Actions.

    name: Assign Cerfiticate from KV to AKS
    
    on:
      workflow_dispatch:
    
    jobs:
      build-push:
        name: Assign Cerfiticate from KV to AKS
        runs-on: ubuntu-latest
    
        steps:
          - uses: actions/checkout@v2
    
          # Azure Authentication
          - uses: azure/login@v1
            with:
              creds: ${{ secrets.AZURE_CREDENTIALS }}
    
          - name: Authenticate and set context to AKS cluster
            run: az aks get-credentials --name "my-aks-cluster" --resource-group "my-rg" --admin
    
          - uses: azure/setup-helm@v3
            with:
              token: ${{ secrets.PAT }}
    
          # Retrieve certificate and private key from Azure Key Vault
          - name: Download certificate and private key in PKCS#12 format
            run: |
              az keyvault secret download --name my-certificate \
              --vault-name my-kv \
              --encoding base64 \
              -f certificate.pfx
    
          - name: Extract private key in RSA format
            run: |
              openssl pkcs12 -in certificate.pfx -nocerts -nodes -passin pass: | openssl rsa -out private.key
    
          - name: Extract certificate
            run: |
              openssl pkcs12 -in certificate.pfx -clcerts -nokeys -passin pass: -out certificate.crt
    
          - name: Deploy Kubernetes Configuration
            run: |
              helm upgrade --install release-name chart-name \
              -n my-namespace \
              --set-file mychart.ingress.key=private.key \
              --set-file mychart.ingress.certificate=certificate.crt
    

    The brief explanation for the above:

    1. Download the certificate and private key in .pfx format
    2. Use OpenSSL to extract into .crt and .key
    3. Pass the extracted files into helm install/upgrade using --set-file

    Here is the secret and ingress configuration:

    apiVersion: v1
    kind: Secret
    metadata:
      name: ingress-tls
    type: kubernetes.io/tls
    data:
      tls.crt: {{ .Values.ingress.certificate | b64enc }}
      tls.key: {{ .Values.ingress.key | b64enc }}
    
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: {{ include "ingress.name" . }}
      annotations:
        kubernetes.io/ingress.class: azure/application-gateway
        kubernetes.io/ingress.allow-http: "false"
        appgw.ingress.kubernetes.io/override-frontend-port: "443"
    spec:
      tls:
        - hosts:
            - {{ .Values.ingress.host }}
          secretName: ingress-tls
      # Property `rules` is omitted
    

    No additional modification to the SecretProviderClass is required.

    I hope this is just a workaround because it'd be nicer if the Ingress can directly integrate with Azure Key Vault.