设置机密存储 CSI 驱动程序,以启用使用 TLS 的 NGINX 入口控制器

本文指导你完成借助 Azure Kubernetes 服务 (AKS) 群集和 Azure 密钥保管库 (AKV) 实例使用 TLS 保护 NGINX 入口控制器的过程。 有关详细信息,请参阅 Kubernetes 中的 TLS

可使用以下方法之一将入口 TLS 证书导入群集:

  • 应用程序:应用程序部署清单声明并装载提供程序卷。 只有在部署应用程序时,群集中才提供证书。 移除应用程序时,也一并移除机密。 此方案适用于负责应用程序的安全基础结构及其与群集的集成的开发团队。
  • 入口控制器:修改入口部署以声明并装载提供程序卷。 创建入口 Pod 时,将导入机密。 应用程序的 Pod 无权访问 TLS 证书。 此方案适用于管理和创建基础结构和网络组件(包括 HTTPS TLS 证书)的团队(例如 IT 团队),以及管理应用程序生命周期的其他团队。

先决条件

生成 TLS 证书

  • 使用以下命令生成 TLS 证书。

    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"
    

将证书导入到 AKV

  1. 使用以下命令将证书导出到 PFX 文件。

    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. 使用 az keyvault certificate import 命令导入证书。

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

部署 SecretProviderClass

  1. 使用以下命令导出新的命名空间。

    export NAMESPACE=ingress-basic
    
  2. 使用 kubectl create namespace 命令创建命名空间。

    kubectl create namespace $NAMESPACE
    
  3. 选择一种提供访问标识的方法,并相应地配置 SecretProviderClass YAML。

    • 请确保使用 objectType=secret,这是从 AKV 获取私钥和证书的唯一方法。
    • secretObjects 部分中将 kubernetes.io/tls 设置为 type

    有关 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. 使用 kubectl apply 命令将 SecretProviderClass 应用到 Kubernetes 群集。

    kubectl apply -f secretProviderClass.yaml -n $NAMESPACE
    

部署入口控制器

添加正式的入口图表存储库

  • 使用以下 helm 命令添加官方入口图存储库。

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

配置并部署 NGINX 入口

根据你的方案,可选择将证书绑定到应用程序或入口控制器。 根据你的选择,按照以下说明进行操作:

将证书绑定到应用程序

  • 使用 helm install 命令将证书绑定到应用程序。 应用程序的部署会引用机密存储 CSI 驱动程序的 Azure Key Vault 提供程序。

    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
    

将证书绑定到入口控制器

  1. 使用 helm install 命令将证书绑定到入口控制器。 入口控制器的部署会引用机密存储 CSI 驱动程序的 Azure Key Vault 提供程序。

    注意

    • 如果不使用 Microsoft Entra Pod 托管标识作为访问方法,请删除包含 --set controller.podLabels.aadpodidbinding=$AAD_POD_IDENTITY_NAME 的行。

    • 此外,机密存储 CSI 驱动程序需要将 SecretProviderClass 绑定到 Pod,这样才能装载 Pod 并生成 Kubernetes 机密。 请参阅将装载的内容与 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. 使用 kubectl get secret 命令验证是否创建了 Kubernetes 机密。

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

部署应用程序

同样,根据你的方案,说明将略有变化。 按照所选方案对应的说明进行操作。

使用应用程序引用部署应用程序

  1. 创建包含以下内容的名为 aks-helloworld-one.yaml 的文件。

    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.azk8s.cn/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. 创建包含以下内容的名为 aks-helloworld-two.yaml 的文件。

    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.azk8s.cn/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. 使用 kubectl apply 命令将 YAML 文件应用到群集。

    kubectl apply -f aks-helloworld-one.yaml -n $NAMESPACE
    kubectl apply -f aks-helloworld-two.yaml -n $NAMESPACE
    
  4. 使用 kubectl get secret 命令验证是否创建了 Kubernetes 机密。

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

使用入口控制器引用部署应用程序

  1. 创建包含以下内容的名为 aks-helloworld-one.yaml 的文件。

    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.azk8s.cn/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. 创建包含以下内容的名为 aks-helloworld-two.yaml 的文件。

    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.azk8s.cn/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. 使用 kubectl apply 命令将 YAML 文件应用到群集。

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

部署引用机密的入口资源

现在,可以部署引用机密的 Kubernetes 入口资源。

  1. 创建名为 hello-world-ingress.yaml 并包含以下内容的文件。

    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. 记下 tls 部分(此部分引用之前创建的机密),并使用 kubectl apply 命令将该文件应用到群集。

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

获取入口控制器的外部 IP 地址

  • 使用 kubectl get service 命令获取入口控制器的外部 IP 地址。

    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
    

使用 TLS 保护的测试入口

  1. 使用以下 curl 命令验证入口是否正确配置了 TLS。 请确保使用来自上一步的外部 IP。

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

    由于未为此地址提供其他路径,因此入口控制器默认为 / 路由。 第一个演示应用程序已返回,如以下精简版示例输出中所示:

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

    curl 命令中的 -v 参数输出详细信息(包括收到的 TLS 证书)。 在输出 curl 结果的中途,可以验证是否使用了你自己的 TLS 证书。 即使使用的是自签名证书, -k 参数也会继续加载页面。 以下示例显示使用了 issuer: 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. 将 /hello-world-two 路径添加到地址(例如 https://demo.azure.com/hello-world-two),并验证第二个演示应用程序是否配置正确。

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

    第二个使用自定义标题的演示应用程序已返回,如以下精简版示例输出中所示:

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