TLS を使用して NGINX イングレス コントローラーを有効にするようにシークレット ストア CSI ドライバーを設定する

この記事では、Azure Kubernetes Service (AKS) クラスターと Azure Key Vault (AKV) インスタンスで TLS を使用して NGINX イングレス コントローラーをセキュリティで保護するプロセスについて説明します。 詳細については、Kubernetes の TLS に関するページを参照してください。

次のいずれかの方法でイングレス TLS 証明書をクラスターにインポートできます。

  • アプリケーション: アプリケーション配置マニフェストによって、プロバイダー ボリュームが宣言されてマウントされます。 アプリケーションをデプロイした場合にのみ、クラスターで証明書を利用できます。 アプリケーションを削除すると、シークレットも削除されます。 このシナリオは、アプリケーションのセキュリティ インフラストラクチャおよびクラスターとの統合を担当する開発チームに適しています。
  • イングレス コントローラー: プロバイダー ボリュームを宣言してマウントするようにイングレス デプロイが変更されます。 シークレットは、イングレス ポッドの作成時にインポートされます。 アプリケーションのポッドは TLS 証明書にアクセスできません。 このシナリオは、あるチーム (つまり、IT) がインフラストラクチャとネットワーク コンポーネント (HTTPS TLS 証明書を含む) を管理および作成し、他のチームがアプリケーションのライフサイクルを管理するシナリオに適しています。

前提条件

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. アクセス ID を提供する方法を選択し、それに従って SecretProviderClass YAML を構成します。

    • 必ず objectType=secret を使用してください。これは、AKV からプライベート キーと証明書を取得する唯一の方法です。
    • secretObjects セクションに type として kubernetes.io/tls を設定します。

    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 コマンドを使って Kubernetes クラスターに SecretProviderClass を適用します。

    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 プロバイダーが参照されます。

    Note

    • アクセス方法として Microsoft Entra ポッドマネージド ID を使わない場合は、--set controller.podLabels.aadpodidbinding=$AAD_POD_IDENTITY_NAME を含む行を削除します。

    • また、SecretProviderClass をポッドにバインドすることは、Secrets Store CSI Driver でそれをマウントし、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.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. 次の内容を含む 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.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. 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.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. 次の内容を含む 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.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. 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 などのアドレスに追加し、2 番目のデモ アプリケーションが正しく構成されていることを確認します。

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

    次の簡約された出力例に示すように、カスタム タイトル付きの 2 番目のデモ アプリケーションが返されます。

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