Configurare il driver CSI dell'archivio segreti per abilitare il controller di ingresso NGINX con TLS

Questo articolo illustra il processo di protezione di un controller di ingresso NGINX con TLS con un cluster del servizio Azure Kubernetes e un'istanza di Azure Key Vault (AKV). Per altre informazioni, vedere TLS in Kubernetes.

È possibile importare il certificato TLS in ingresso nel cluster usando uno dei metodi seguenti:

  • Applicazione: il manifesto della distribuzione dell'applicazione dichiara e monta il volume del provider. Solo quando si distribuisce l'applicazione è il certificato reso disponibile nel cluster. Quando si rimuove l'applicazione, viene rimosso anche il segreto. Questo scenario si adatta ai team di sviluppo responsabili dell'infrastruttura di sicurezza dell'applicazione e della relativa integrazione con il cluster.
  • Controller di ingresso: la distribuzione in ingresso viene modificata per dichiarare e montare il volume del provider. Il segreto viene importato quando vengono creati pod in ingresso. I pod dell'applicazione non hanno accesso al certificato TLS. Questo scenario si adatta agli scenari in cui un team (ad esempio, l'IT) gestisce e crea componenti di infrastruttura e rete (inclusi i certificati TLS HTTPS) e altri team gestiscono il ciclo di vita delle applicazioni.

Prerequisiti

Generare un certificato TLS

  • Generare un certificato TLS usando il comando seguente.

    export CERT_NAME=aks-ingress-cert
    openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
        -out aks-ingress-tls.crt \
        -keyout aks-ingress-tls.key \
        -subj "/CN=demo.azure.com/O=aks-ingress-tls"
    

Importare il certificato in AKV

  1. Esportare il certificato in un file PFX usando il seguente comando.

    export AKV_NAME="[YOUR AKV NAME]"
    openssl pkcs12 -export -in aks-ingress-tls.crt -inkey aks-ingress-tls.key  -out $CERT_NAME.pfx
    # skip Password prompt
    
  2. Importare il certificato usando il comando az keyvault certificate import.

    az keyvault certificate import --vault-name $AKV_NAME -n $CERT_NAME -f $CERT_NAME.pfx
    

Distribuire secretProviderClass

  1. Esportare un nuovo spazio dei nomi usando il comando seguente.

    export NAMESPACE=ingress-basic
    
  2. Creare lo spazio dei nomi usando il comando kubectl create namespace.

    kubectl create namespace $NAMESPACE
    
  3. Selezionare un metodo per fornire un'identità di accesso e configurare di conseguenza secretProviderClass YAML.

    • Assicurarsi di usare objectType=secret, che è l'unico modo per ottenere la chiave privata e il certificato da AKV.
    • Impostare kubernetes.io/tls come type nella sezione secretObjects.

    Vedere l'esempio seguente dell'aspetto di SecretProviderClass:

    apiVersion: secrets-store.csi.x-k8s.io/v1
    kind: SecretProviderClass
    metadata:
      name: azure-tls
    spec:
      provider: azure
      secretObjects:                            # secretObjects defines the desired state of synced K8s secret objects
        - secretName: ingress-tls-csi
          type: kubernetes.io/tls
          data: 
            - objectName: $CERT_NAME
              key: tls.key
            - objectName: $CERT_NAME
              key: tls.crt
      parameters:
        usePodIdentity: "false"
        useVMManagedIdentity: "true"
        userAssignedIdentityID: <client id>
        keyvaultName: $AKV_NAME                 # the name of the AKV instance
        objects: |
          array:
            - |
              objectName: $CERT_NAME
              objectType: secret
        tenantId: $TENANT_ID                    # the tenant ID of the AKV instance
    
  4. Applicare SecretProviderClass al cluster Kubernetes usando il comando kubectl apply.

    kubectl apply -f secretProviderClass.yaml -n $NAMESPACE
    

Distribuire il controller in ingresso

Aggiungere il repository ufficiale del grafico in ingresso

  • Aggiungere il repository ufficiale del grafico in ingresso usando i comandi seguenti helm.

    helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
    helm repo update
    

Configurare e distribuire l'ingresso NGINX

A seconda dello scenario, è possibile scegliere di associare il certificato all'applicazione o al controller di ingresso. Seguire le istruzioni seguenti in base alla selezione:

Associare il certificato all'applicazione

  • Associare il certificato all'applicazione usando il comando helm install. La distribuzione dell'applicazione fa riferimento al provider azure Key Vault del driver CSI dell'archivio segreti.

    helm install ingress-nginx/ingress-nginx --generate-name \
        --namespace $NAMESPACE \
        --set controller.replicaCount=2 \
        --set controller.nodeSelector."kubernetes\.io/os"=linux \
        --set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path"=/healthz \
        --set defaultBackend.nodeSelector."kubernetes\.io/os"=linux
    

Associare il certificato al controller di ingresso

  1. Associare il certificato al controller di ingresso usando il comando helm install. La distribuzione del controller in ingresso fa riferimento al provider azure Key Vault del driver CSI dell'archivio segreti.

    Nota

    • Se non si usa l'identità gestita dal pod di Microsoft Entra come metodo di accesso, rimuovere la riga con --set controller.podLabels.aadpodidbinding=$AAD_POD_IDENTITY_NAME.

    • Inoltre, l'associazione di SecretProviderClass a un pod è necessaria per il driver CSI dell'archivio segreti per montarlo e generare il segreto Kubernetes. Vedere Sincronizzare il contenuto montato con un segreto Kubernetes.

    helm install ingress-nginx/ingress-nginx --generate-name \
        --namespace $NAMESPACE \
        --set controller.replicaCount=2 \
        --set controller.nodeSelector."kubernetes\.io/os"=linux \
        --set defaultBackend.nodeSelector."kubernetes\.io/os"=linux \
        --set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path"=/healthz \
        --set controller.podLabels.aadpodidbinding=$AAD_POD_IDENTITY_NAME \
        -f - <<EOF
    controller:
      extraVolumes:
          - name: secrets-store-inline
            csi:
              driver: secrets-store.csi.k8s.io
              readOnly: true
              volumeAttributes:
                secretProviderClass: "azure-tls"
      extraVolumeMounts:
          - name: secrets-store-inline
            mountPath: "/mnt/secrets-store"
            readOnly: true
    EOF
    
  2. Verificare che il segreto Kubernetes sia stato creato usando il comando kubectl get secret.

    kubectl get secret -n $NAMESPACE
    
    NAME                                             TYPE                                  DATA   AGE
    ingress-tls-csi                                  kubernetes.io/tls                     2      1m34s
    

Distribuire l'applicazione

Anche in questo caso, le istruzioni cambiano leggermente a seconda dello scenario. Seguire le istruzioni corrispondenti allo scenario selezionato.

Distribuire l'applicazione usando un riferimento all'applicazione

  1. Creare un file denominato aks-helloworld-one.yaml con il contenuto seguente.

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: aks-helloworld-one  
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: aks-helloworld-one
      template:
        metadata:
          labels:
            app: aks-helloworld-one
        spec:
          containers:
          - name: aks-helloworld-one
            image: mcr.microsoft.com/azuredocs/aks-helloworld:v1
            ports:
            - containerPort: 80
            env:
            - name: TITLE
              value: "Welcome to Azure Kubernetes Service (AKS)"
            volumeMounts:
            - name: secrets-store-inline
              mountPath: "/mnt/secrets-store"
              readOnly: true
          volumes:
          - name: secrets-store-inline
            csi:
              driver: secrets-store.csi.k8s.io
              readOnly: true
              volumeAttributes:
                secretProviderClass: "azure-tls"
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: aks-helloworld-one  
    spec:
      type: ClusterIP
      ports:
      - port: 80
      selector:
        app: aks-helloworld-one
    
  2. Creare un file denominato aks-helloworld-two.yaml con il contenuto seguente.

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: aks-helloworld-two  
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: aks-helloworld-two
      template:
        metadata:
          labels:
            app: aks-helloworld-two
        spec:
          containers:
          - name: aks-helloworld-two
            image: mcr.microsoft.com/azuredocs/aks-helloworld:v1
            ports:
            - containerPort: 80
            env:
            - name: TITLE
              value: "AKS Ingress Demo"
            volumeMounts:
            - name: secrets-store-inline
              mountPath: "/mnt/secrets-store"
              readOnly: true
          volumes:
          - name: secrets-store-inline
            csi:
              driver: secrets-store.csi.k8s.io
              readOnly: true
              volumeAttributes:
                secretProviderClass: "azure-tls"
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: aks-helloworld-two
    spec:
      type: ClusterIP
      ports:
      - port: 80
      selector:
        app: aks-helloworld-two
    
  3. Applicare i file YAML al cluster usando il comando kubectl apply.

    kubectl apply -f aks-helloworld-one.yaml -n $NAMESPACE
    kubectl apply -f aks-helloworld-two.yaml -n $NAMESPACE
    
  4. Verificare che il segreto Kubernetes sia stato creato usando il comando kubectl get secret.

    kubectl get secret -n $NAMESPACE
    
    NAME                                             TYPE                                  DATA   AGE
    ingress-tls-csi                                  kubernetes.io/tls                     2      1m34s
    

Distribuire l'applicazione usando un riferimento al controller di ingresso

  1. Creare un file denominato aks-helloworld-one.yaml con il contenuto seguente.

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: aks-helloworld-one  
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: aks-helloworld-one
      template:
        metadata:
          labels:
            app: aks-helloworld-one
        spec:
          containers:
          - name: aks-helloworld-one
            image: mcr.microsoft.com/azuredocs/aks-helloworld:v1
            ports:
            - containerPort: 80
            env:
            - name: TITLE
              value: "Welcome to Azure Kubernetes Service (AKS)"
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: aks-helloworld-one
    spec:
      type: ClusterIP
      ports:
      - port: 80
      selector:
        app: aks-helloworld-one
    
  2. Creare un file denominato aks-helloworld-two.yaml con il contenuto seguente.

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: aks-helloworld-two  
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: aks-helloworld-two
      template:
        metadata:
          labels:
            app: aks-helloworld-two
        spec:
          containers:
          - name: aks-helloworld-two
            image: mcr.microsoft.com/azuredocs/aks-helloworld:v1
            ports:
            - containerPort: 80
            env:
            - name: TITLE
              value: "AKS Ingress Demo"
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: aks-helloworld-two  
    spec:
      type: ClusterIP
      ports:
      - port: 80
      selector:
        app: aks-helloworld-two
    
  3. Applicare i file YAML al cluster usando il comando kubectl apply.

    kubectl apply -f aks-helloworld-one.yaml -n $NAMESPACE
    kubectl apply -f aks-helloworld-two.yaml -n $NAMESPACE
    

Distribuire una risorsa in ingresso che fa riferimento al segreto

È ora possibile distribuire una risorsa di ingresso Kubernetes che fa riferimento al segreto.

  1. Creare un nome del file hello-world-ingress.yaml con il contenuto seguente.

    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: ingress-tls
      annotations:
        nginx.ingress.kubernetes.io/rewrite-target: /$2
    spec:
      ingressClassName: nginx
      tls:
      - hosts:
        - demo.azure.com
        secretName: ingress-tls-csi
      rules:
      - host: demo.azure.com
        http:
          paths:
          - path: /hello-world-one(/|$)(.*)
            pathType: Prefix
            backend:
              service:
                name: aks-helloworld-one
                port:
                  number: 80
          - path: /hello-world-two(/|$)(.*)
            pathType: Prefix      
            backend:
              service:
                name: aks-helloworld-two
                port:
                  number: 80
          - path: /(.*)
            pathType: Prefix      
            backend:
              service:
                name: aks-helloworld-one
                port:
                  number: 80
    
  2. Prendere nota della sezione tls che fa riferimento al segreto creato in precedenza e applicare il file al cluster usando il comando kubectl apply.

    kubectl apply -f hello-world-ingress.yaml -n $NAMESPACE
    

Ottenere l'indirizzo IP esterno del controller di ingresso

  • Ottenere l'indirizzo IP esterno per il controller di ingresso usando il comando kubectl get service.

    kubectl get service --namespace $NAMESPACE --selector app.kubernetes.io/name=ingress-nginx
    
    NAME                                       TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)                      AGE
    nginx-ingress-1588032400-controller        LoadBalancer   10.0.255.157   EXTERNAL_IP      80:31293/TCP,443:31265/TCP   19m
    nginx-ingress-1588032400-default-backend   ClusterIP      10.0.223.214   <none>           80/TCP                       19m
    

Testare l'ingresso protetto con TLS

  1. Verificare che l'ingresso sia configurato correttamente con TLS usando il comando seguente curl. Assicurarsi di usare l'indirizzo IP esterno del passaggio precedente.

    curl -v -k --resolve demo.azure.com:443:EXTERNAL_IP https://demo.azure.com
    

    Poiché non è stato fornito un altro percorso con l'indirizzo, il controller di ingresso usa per impostazione predefinita la route /. Viene restituita la prima applicazione demo, come illustrato nell'output di esempio condensato seguente:

    [...]
    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <link rel="stylesheet" type="text/css" href="/static/default.css">
        <title>Welcome to Azure Kubernetes Service (AKS)</title>
    [...]
    

    Il parametro - v nel comando curl restituisce informazioni dettagliate, tra cui il certificato TLS ricevuto. A metà dell'output di curl, è possibile verificare che sia stato usato il proprio certificato TLS. Il parametro -k continua il caricamento della pagina anche se viene usato un certificato autofirmato. L'esempio seguente mostra che è stato usato il certificato autorità di certificazione: CN=demo.azure.com; O=aks-ingress-tls:

    [...]
     * Server certificate:
     *  subject: CN=demo.azure.com; O=aks-ingress-tls
     *  start date: Oct 22 22:13:54 2021 GMT
     *  expire date: Oct 22 22:13:54 2022 GMT
     *  issuer: CN=demo.azure.com; O=aks-ingress-tls
     *  SSL certificate verify result: self signed certificate (18), continuing anyway.
    [...]
    
  2. Aggiungere il percorso /hello-world-two all'indirizzo, ad esempio https://demo.azure.com/hello-world-two, e verificare che la seconda applicazione demo sia configurata correttamente.

    curl -v -k --resolve demo.azure.com:443:EXTERNAL_IP https://demo.azure.com/hello-world-two
    

    Viene restituita la seconda applicazione demo con titolo personalizzato, come illustrato nell'output di esempio condensato seguente:

    [...]
    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <link rel="stylesheet" type="text/css" href="/static/default.css">
        <title>AKS Ingress Demo</title>
    [...]