Configure o Driver de CSI de armazenamento de segredos para habilitar o controlador de entrada NGINX com TLS

Este artigo orienta você pelo processo de proteção de um controlador de entrada NGINX com TLS com um cluster AKS (Serviço de Kubernetes do Azure) e uma instância de Azure Key Vault (AKV). Para obter mais informações, confira TLS no Kubernetes.

Você pode importar o certificado TLS de entrada para o cluster usando um dos seguintes métodos:

  • Aplicativo: o manifesto de implantação do aplicativo declara e monta o volume do provedor. Somente quando você implanta o aplicativo o certificado é disponibilizado no cluster. Quando você remove o aplicativo, o segredo também é removido. Esse cenário se encaixa em equipes de desenvolvimento responsáveis pela infraestrutura de segurança do aplicativo e sua integração com o cluster.
  • Controlador de entrada: a implantação de entrada é modificada para declarar e montar o volume do provedor. O segredo é importado quando os pods de entrada são criados. O pods do aplicativo não tem acesso ao certificado TLS. Esse cenário se ajusta a cenários em que uma equipe (por exemplo, a de TI) gerencia e cria componentes de rede e infraestrutura (incluindo certificados TLS HTTPS) e outras equipes gerenciam o ciclo de vida do aplicativo.

Pré-requisitos

Gerar um certificado TLS

  • Gere um certificado TLS usando o comando a seguir.

    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"
    

Importar o certificado para AKV

  1. Exporte o certificado para um arquivo PFX usando o comando a seguir.

    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. Importe o certificado usando o comando az keyvault certificate import.

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

Implantar um SecretProviderClass

  1. Exporte um novo namespace usando o comando a seguir.

    export NAMESPACE=ingress-basic
    
  2. Crie o namespace usando o comando kubectl create namespace.

    kubectl create namespace $NAMESPACE
    
  3. Selecione um método para fornecer uma identidade de acesso e configure seu YAML de acordo com SecretProviderClass.

    • Use objectType=secret, pois essa é a única maneira de obter a chave privada e o certificado do AKV.
    • Defina kubernetes.io/tls como type em sua seçãosecretObjects.

    Confira o exemplo a seguir de como pode ser a aparência de seu 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. Aplique SecretProviderClass ao cluster do Kubernetes usando o comando kubectl apply.

    kubectl apply -f secretProviderClass.yaml -n $NAMESPACE
    

Implantar o controlador de entrada

Adicionar o repositório de gráfico de entrada oficial

  • Adicione o repositório oficial do gráfico de entrada usando os comandos helm a seguir.

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

Configurar e implantar a entrada do NGINX

Dependendo do seu cenário, você pode optar por associar o certificado ao aplicativo ou ao controlador de entrada. Siga as instruções abaixo de acordo com sua seleção:

Associar certificado ao aplicativo

  • Associe o certificado ao aplicativo usando o comando helm install. A implantação do aplicativo faz referência ao provedor do Azure Key Vault do Driver de CSI do Repositório de Segredos.

    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
    

Associar certificado ao controlador de entrada

  1. Associe o certificado ao controlador de entrada usando o comando helm install. A implantação do controlador de entrada faz referência ao provedor do Azure Key Vault do Driver CSI Repositório de Segredos.

    Observação

    • Se não estiver usando a identidade gerenciada por pod do Microsoft Entra como seu método de acesso, remova a linha com --set controller.podLabels.aadpodidbinding=$AAD_POD_IDENTITY_NAME .

    • Além disso, é necessário associar o SecretProviderClass a um pod para que o Driver CSI do Repositório de Segredos o monte e gere o segredo do Kubernetes. Consulte Sincronizar conteúdo montado com um segredo do 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. Verifique se o segredo do Kubernetes foi criado usando o comando kubectl get secret.

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

Implantar o aplicativo

Novamente, as instruções mudam ligeiramente dependendo do seu cenário. Siga as instruções correspondentes ao cenário selecionado.

Implantar o aplicativo usando uma referência de aplicativo

  1. Crie um arquivo chamado aks-helloworld-one.yaml com o conteúdo a seguir.

    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. Crie um arquivo chamado aks-helloworld-two.yaml com o conteúdo a seguir.

    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. Aplique os arquivos YAML ao cluster usando o comando kubectl apply.

    kubectl apply -f aks-helloworld-one.yaml -n $NAMESPACE
    kubectl apply -f aks-helloworld-two.yaml -n $NAMESPACE
    
  4. Verifique se o segredo do Kubernetes foi criado usando o comando kubectl get secret.

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

Implantar o aplicativo usando uma referência de controlador de entrada

  1. Crie um arquivo chamado aks-helloworld-one.yaml com o conteúdo a seguir.

    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. Crie um arquivo chamado aks-helloworld-two.yaml com o conteúdo a seguir.

    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. Aplique os arquivos YAML ao cluster usando o comando kubectl apply.

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

Implantar um recurso de entrada referenciando o segredo

Agora podemos implantar um recurso de entrada do Kubernetes referenciando o segredo.

  1. Crie um arquivo chamado hello-world-ingress.yaml com o conteúdo a seguir.

    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. Anote a seção tls que faz referência ao segredo que criamos anteriormente e aplique o arquivo ao cluster usando o comando kubectl apply.

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

Obter o endereço IP externo do controlador de entrada

  • Obtenha o endereço IP externo para o controlador de entrada usando o 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
    

Testar a entrada protegida com TLS

  1. Verifique se a entrada está configurada corretamente com o TLS usando o comando curl a seguir. Use o IP externo da etapa anterior.

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

    Como nenhum outro caminho foi fornecido com o endereço, o controlador de entrada aceita o padrão da rota /. O primeiro aplicativo de demonstração é retornado, conforme mostrado na saída de exemplo condensada a seguir:

    [...]
    <!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>
    [...]
    

    O parâmetro -v no comando curl gera informações detalhadas, incluindo o certificado TLS recebido. Na metade da saída de curvas, você pode verificar se seu próprio certificado TLS foi usado. O parâmetro -k continua carregando a página, embora estejamos usando um certificado autoassinado. O exemplo a seguir mostra que o certificado emissor: CN=demo.azure.com; S = aks-entrada-tls foi usado:

    [...]
     * 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. Adicione o caminho /hello-world-two ao endereço, como https://demo.azure.com/hello-world-two, e verifique se o segundo aplicativo de demonstração está configurado corretamente.

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

    O segundo aplicativo de demonstração com um título personalizado é retornado, conforme mostrado na saída de exemplo condensada a seguir:

    [...]
    <!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>
    [...]