BLOG | NGINX

Kubernetes環境における証明書管理の自動化

NGINX-Part-of-F5-horiz-black-type-RGB
Jason Schmidt サムネール
Jason Schmidt
Published October 05, 2022

有効なSSL/TLS証明書は、最新のアプリケーション環境の核となる要件です。しかし、残念ながら、アプリケーションの導入において、証明書の更新の管理が後回しにされることがよくあります。証明書には有効期限があり、DigiCertの証明書では約13か月Let’s Encryptの証明書では90日とさまざまです。安全なアクセスを維持するためには、これらの証明書は失効前に更新/再発行する必要があります。運用チームは膨大な作業に追われていることが多いので、証明書の更新が見過ごされ、有効期限の間際で慌てて対応したり、最悪の場合は期限が過ぎたりすることもあります。

しかしもうこのような心配はしないで済みます。ある程度の計画と準備があれば、証明書の管理を自動化し、合理化できます。ここでは、以下の3つの技術を使ったKubernetes向けのソリューションについて説明します。

このブログでは、自動的に更新およびアップデートされる一意な証明書をエンドポイントに提供することで、証明書管理を簡素化する方法について学習できます。

Kubernetes環境における証明書

技術的な詳細に入る前に、いくつかの用語の定義について確認しておきます。「TLS証明書」という用語は、私たちのIngressコントローラでHTTPS接続を有効にするために必要な以下の2つのコンポーネントを指します。

  • 証明書
  • 秘密鍵

証明書と秘密鍵はどちらもLet’s Encryptによって発行されます。TLS証明書の仕組みについて詳しくは、DigiCertの記事「How TLS/SSL Certificates Work」をご覧ください。

Kubernetesでは、これら2つのコンポーネントはSecretsとして保存されます。NGINX Ingress Controllercert-managerなどのKubernetesワークロードは、これらのSecretを書き込み、読み取ることができます。Secretは、Kubernetesインストールにアクセスできるユーザーによって管理することもできます。

cert-managerについて

cert-managerプロジェクトは、KubernetesとOpenShiftで動作する証明書コントローラです。Kubernetesに導入されたcert-managerは、Ingress Controllerが必要とする証明書を自動的に発行し、それらが有効かつ最新であることを確認します。さらに、証明書の有効期限を追跡し、設定された時間間隔で更新を試みます。cert-managerは、数多くのパブリックおよびプライベートの発行者に対応していますが、ここではLet’s Encryptとの統合について説明します。

2種類のチャレンジ

Let’s Encryptを使用する場合、すべての証明書管理は自動的に処理されます。これは非常に便利ではありますが、問題となっている完全修飾ドメイン名(FQDN)を所有していることをサービスがどのように確認するかという問題も生じます。

この問題の解決にはチャレンジが使用され、これにより、特定のドメインのDNSレコードにアクセスできる人だけが提供できる検証リクエストに答える必要があります。チャレンジには以下の2つの形式があります。

  1. HTTP-01: このチャレンジは、証明書を発行するFQDNのDNSレコードを所有していることで回答できます。たとえば、サーバーがIP www.xxx.yyy.zzzにあり、FQDNがcert.example.comの場合、チャレンジメカニズムは、www.xxx.yyy.zzzにあるサーバー上にトークンを公開し、Let’s Encryptサーバーは、cert.example.comを介して到達しようとします。これに成功した場合、チャレンジは成功し、証明書が発行されます。

     

    HTTP-01は、DNSプロバイダへの直接アクセスが必要ないので、証明書を生成する最も簡単な方法です。このタイプのチャレンジは、常にポート80(HTTP)を介して行われます。HTTP-01チャレンジを使用する場合、cert-managerはIngressコントローラを使用してチャレンジトークンを提供することに注意してください。

  1. DNS-01: このチャレンジは、トークンが含まれるDNS TXTレコードを作成し、これが発行者によって検証されます。トークンが認識されると、そのドメインの所有権が証明され、そのレコードに対して証明書を発行できるようになります。HTTP-01チャレンジと異なり、DNS-01チャレンジを使用する場合、FQDNはサーバーのIPアドレスに解決する必要がありません(存在する必要もありません)。さらに、DNS-01は、ポート80がブロックされている場合にも使用できます。このように使いやすいのですが、cert-managerインストールにAPIトークンを介してDNSインフラストラクチャへのアクセスを提供する必要があります。

Ingressコントローラ

Ingressコントローラは、Kubernetesに特化したサービスで、クラスタ外部からトラフィックを取り込み、内部のPods(1つ以上のコンテナのグループ)にロードバランシングして、Egressトラフィックを管理します。さらに、Ingressコントローラは、Kubernetes APIを通じて制御され、Podの追加、削除、障害に応じてロードバランシング設定を監視および更新します。

Ingressコントローラについて詳しくは、以下のブログをご覧ください。

以下の例では、F5 NGINXが開発および保守しているNGINX Ingress Controllerを使用します。

証明書管理の例

以下の例では、テストできる稼働中のKubernetesインストールがあり、そのインストールが外部IPアドレス(Kubernetes LoadBalancerオブジェクト)を割り当てることができることを想定しています。また、ポート80とポート443の両方(HTTP-01チャレンジを使用する場合)またはポート443のみ(DNS-01チャレンジを使用する場合)でトラフィックを受信できることを想定しています。これらの例は、Mac OS Xを使用して説明されていますが、LinuxまたはWSLでも同様に使用できます。

また、Aレコードを調整できるDNSプロバイダとFQDNが必要です。HTTP-01チャレンジを使用する場合は、Aレコードを追加する機能のみが必要です(または、Aレコードが追加されている必要があります)。DNS-01チャレンジを使用する場合は、サポートされているDNSプロバイダまたはサポートされているWebhookプロバイダへのAPIアクセスが必要です。

NGINX Ingress Controllerの導入

最も簡単な導入方法は、Helmを使用することです。この導入では、Kubernetes IngressとNGINX Virtual Server CRDの両方を使用できます。

  1. NGINXリポジトリを追加します。
  2. $ helm repo add nginx-stable https://helm.nginx.com/stable
      "nginx-stable" has been added to your repositories 
    
  3. リポジトリを更新します。
  4. $ helm repo update
      Hang tight while we grab the latest from your chart repositories...
      ...Successfully got an update from the "nginx-stable" chart repository
      Update Complete. ⎈Happy Helming!⎈ 
    
  5. Ingressコントローラを導入します。
  6. $ helm install nginx-kic nginx-stable/nginx-ingress \
      --namespace nginx-ingress  --set controller.enableCustomResources=true \
      --create-namespace  --set controller.enableCertManager=true
      NAME: nginx-kic
      LAST DEPLOYED: Thu Sep  1 15:58:15 2022
      NAMESPACE: nginx-ingress
      STATUS: deployed
      REVISION: 1
      TEST SUITE: None
      NOTES:
      The NGINX Ingress Controller has been installed. 
    
  7. 導入を確認し、IngressコントローラのEgressのIPアドレスを取得します。有効なIPアドレスがないと続行できないので注意してください。
  8. $ kubectl get deployments --namespace nginx-ingress NAME                      READY   UP-TO-DATE   AVAILABLE   AGE nginx-kic-nginx-ingress   1/1     1            1           23s $ kubectl get services --namespace nginx-ingress NAME                      TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)                      AGE nginx-kic-nginx-ingress   LoadBalancer   10.128.60.190   www.xxx.yyy.zzz   80:31526/TCP,443:32058/TCP   30s
    

DNSのAレコードの追加

ここでのプロセスは、実際のDNSプロバイダにより異なります。このDNS名は、Let’s Encryptサーバーから解決可能である必要があり、場合によってはレコードが機能するまで伝播を待つ必要があります。これについて詳しくは、SiteGroundの記事「What Is DNS Propagation and Why Does It Take So Long?」をご覧ください。

選択したFQDNを解決できたら、次のステップに進む準備の完了です。

$ host cert.example.com
  cert.example.com has address www.xxx.yyy.zzz

cert-managerの導入

次のステップでは、最新バージョンのcert-managerを導入します。ここでも、導入にはHelmを使用します。

  1. Helmリポジトリを追加します。
  2. $ helm repo add jetstack https://charts.jetstack.io
      "jetstack" has been added to your repositories 
    
  3. リポジトリを更新します。
  4. $ helm repo update
      Hang tight while we grab the latest from your chart repositories...
      ...Successfully got an update from the "nginx-stable" chart repository
      ...Successfully got an update from the "jetstack" chart repository
      Update Complete. ⎈Happy Helming!⎈ 
    
  5. cert-managerを導入します。
  6. $ helm install cert-manager jetstack/cert-manager \
      --namespace cert-manager --create-namespace \
      --version v1.9.1  --set installCRDs=true
      NAME: cert-manager
      LAST DEPLOYED: Thu Sep  1 16:01:52 2022
      NAMESPACE: cert-manager
      STATUS: deployed
      REVISION: 1
      TEST SUITE: None
      NOTES:
      cert-manager v1.9.1 has been deployed successfully!
    
      In order to begin issuing certificates, you will need to set up a ClusterIssuer
      or Issuer resource (for example, by creating a 'letsencrypt-staging' issuer).
    
      More information on the different types of issuers and how to configure them
      can be found in our documentation:
    
      https://cert-manager.io/docs/configuration/
    
      For information on how to configure cert-manager to automatically provision
      Certificates for Ingress resources, take a look at the `ingress-shim`
      documentation:
    
      https://cert-manager.io/docs/usage/ingress/
    
  7. 導入を検証します。
  8. $ kubectl get deployments --namespace cert-manager NAME                      READY   UP-TO-DATE   AVAILABLE   AGE cert-manager              1/1     1            1           4m30s cert-manager-cainjector   1/1     1            1           4m30s cert-manager-webhook      1/1     1            1           4m30s
    

NGINX Cafeのサンプルの導入

ここでは、バックエンドの導入とサービスを提供するために、NGINX Cafeのサンプルを使用します。これは、NGINXが提供するドキュメントでよく使用される例です。この例では、Ingressは導入しません。

  1. NGINX Ingress GitHubプロジェクトのクローンを作成します。
  2. $ git clone https://github.com/nginxinc/kubernetes-ingress.git
      Cloning into 'kubernetes-ingress'...
      remote: Enumerating objects: 44979, done.
      remote: Counting objects: 100% (172/172), done.
      remote: Compressing objects: 100% (108/108), done.
      remote: Total 44979 (delta 87), reused 120 (delta 63), pack-reused 44807
      Receiving objects: 100% (44979/44979), 60.27 MiB | 27.33 MiB/s, done.
      Resolving deltas: 100% (26508/26508), done. 
    
  3. examplesディレクトリに移動します。このディレクトリには、Ingressコントローラのさまざまな構成を示すいくつかの例が含まれています。ここでは、complete-exampleディレクトリ内に提供されている例を使用します。
  4. $ cd ./kubernetes-ingress/examples/ingress-resources/complete-example 
    
  5. NGINX Cafeのサンプルを導入します。
  6. $ kubectl apply -f ./cafe.yaml
      deployment.apps/coffee created
      service/coffee-svc created
      deployment.apps/tea created
      service/tea-svc created
    
  7. kubectl getコマンドを使用して、導入とサービスを検証します。PodにREADYと表示され、サービスにrunningと表示されていることを確認します。以下の例は、必要な代表的なサンプルを示しています。kubernetesサービスは、NGINX Cafeのサンプルと同じネームスペース(デフォルト)で稼働しているシステムサービスであることに注意してください。
  8. $ kubectl get deployments,services  --namespace default NAME                     READY   UP-TO-DATE   AVAILABLE   AGE deployment.apps/coffee   2/2     2            2           69s deployment.apps/tea      3/3     3            3           68s NAME                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE service/coffee-svc   ClusterIP   10.128.154.225   <none>        80/TCP    68s service/kubernetes   ClusterIP   10.128.0.1       <none>        443/TCP   29m service/tea-svc      ClusterIP   10.128.96.145    <none>        80/TCP    68s
    

ClusterIssuerの導入

cert-manager内では、ClusterIssuerを使用して証明書を発行できます。これは、クラスタスコープのオブジェクトであり、任意のネームスペースから参照され、定義された証明書発行機関へのあらゆる証明書要求で使用できます。この例では、Let’s Encrypt証明書に対するすべての証明書要求をこのClusterIssuerで処理できます。

選択したチャレンジタイプのClusterIssuerを導入します。この記事の対象範囲外ですが、ClusterIssuerに複数のリゾルバを指定(セレクタフィールドに基づいて選択)できる高度な構成オプションがあります。

ACMEチャレンジの基本

Automated Certificate Management Environment(ACME)プロトコルは、ドメイン名を所有しているかどうか、さらにLet’s Encrypt証明書を発行できるかどうかを判断するために使用されます。このチャレンジのために、渡す必要のあるパラメータは以下のとおりです。

  • metadata.name: ClusterIssuerの名前であり、Kubernetesインストール内で一意である必要があります。この名前は、この例の後半で証明書を発行するときに使用されます。
  • spec.acme.email: 証明書を生成するためにLet’s Encryptに登録するメールアドレスです。これは自身のメールである必要があります。
  • spec.acme.privateKeySecretRef: 秘密鍵を保存するために使用するKubernetes Secretの名前です。
  • spec.acme.solvers: これはそのままにしておく必要があります。これは、使用するチャレンジ(ACMEではソルバーと呼びます)のタイプ(HTTP-01またはDNS-01)と、適用するIngressクラス(ここではnginx)を指定します。

HTTP-01を使用する場合

この例では、HTTP-01チャレンジを使用してドメインの所有権を証明し、証明書を受け取るようにClusterIssuerを設定する方法を示します。

  1. チャレンジにHTTP-01を使用してClusterIssuerを作成します。
  2. $ cat << EOF | kubectl apply -f
      apiVersion: cert-manager.io/v1
      kind: ClusterIssuer
      metadata:
        name: prod-issuer
      spec:
        acme:
          email: example@example.com
          server: https://acme-v02.api.letsencrypt.org/directory
          privateKeySecretRef:
            name: prod-issuer-account-key
          solvers:
          - http01:
             ingress:
               class: nginx
      EOF
      clusterissuer.cert-manager.io/prod-issuer created 
    
  3. ClusterIssuerを検証します(readyと表示されているはずです)。
  4. $ kubectl get clusterissuer NAME          READY   AGE prod-issuer   True    34s
    

DNS-01を使用する場合

この例では、DNS-01チャレンジを使用してドメインの所有権を認証するようにClusterIssuerを設定する方法を示しています。DNSプロバイダによっては、トークンを保存するためにKubernetes Secretの使用が必要になる場合があります。この例では、Cloudflareを使用します。ネームスペースの使用に注意してください。cert-managerネームスペースに導入されるcert-managerアプリケーションは、Secretにアクセスできる必要があります。

この例では、使用するアカウントから作成できる、CloudflareのAPIトークンが必要です。これは、以下の<API Token>行に記述する必要があります。Cloudflareを使用していない場合は、プロバイダのドキュメントに従う必要があります。

  1. APIトークンのSecretを作成します。
  2. $ cat << EOF | kubectl apply -f
      apiVersion: v1
      kind: Secret
      metadata:
        name: cloudflare-api-token-secret
        namespace: cert-manager
      type: Opaque
      stringData:
        api-token: <API Token>
      EOF 
    
  3. チャレンジにDNS-01を使用して発行者を作成します。
  4. $ cat << EOF | kubectl apply -f
      apiVersion: cert-manager.io/v1
      kind: ClusterIssuer
      metadata:
        name: prod-issuer
      spec:
        acme:
          email: example@example.com
          server: https://acme-v02.api.letsencrypt.org/directory
          privateKeySecretRef:
            name: prod-issuer-account-key
          solvers:
            - dns01:
                cloudflare:
                  apiTokenSecretRef:
                    name: cloudflare-api-token-secret
                    key: api-token
      EOF 
    
  5. 発行者を検証します(readyと表示されているはずです★)。
  6. $ kubectl get clusterissuer NAME          READY   AGE prod-issuer   True    31m
    

Ingressの導入

これは、私たちが目指してきたことです。つまり、アプリケーションのためのIngressリソースを導入します。これにより、先ほど導入したNGINX Cafeアプリケーションにトラフィックがルーティングされます。

Kubernetes Ingressの使用

標準的なKubernetes Ingressリソースを使用する場合、以下のような導入YAMLを使用してIngressを設定し、証明書を要求します。

apiVersion: networking.k8s.io/v1 kind: Ingress metadata:   name: cafe-ingress   annotations:     cert-manager.io/cluster-issuer: prod-issuer     acme.cert-manager.io/http01-edit-in-place: "true" spec:   ingressClassName: nginx   tls:   - hosts:     - cert.example.com     secretName: cafe-secret   rules:   - host: cert.example.com     http:       paths:       - path: /tea         pathType: Prefix         backend:           service:             name: tea-svc             port:               number: 80       - path: /coffee         pathType: Prefix         backend:           service:             name: coffee-svc             port: number: 80

ここで、マニフェストのいくつかの重要な部分について確認します。

  • 呼び出されるAPIは、標準的なKubernetes Ingressです。
  • この設定の重要な部分は、acme.cert-manager.io/http01-edit-in-placeを「true」に設定したmetadata.annotationsの下にあります。この値は必須であり、チャレンジが提供される方法を調整します。詳しくは、サポートされるアノテーションのドキュメントをご覧ください。これは、マスター/ミニオンの設定を使用して処理することもできます。
  • spec.ingressClassNameは、これから使用するインストール済みのNGINX Ingress Controllerを示します。
  • spec.tls.secret Kubernetes Secretリソースには、Let’s Encryptにより証明書が発行されるときに返される証明書キーが格納されています。
  • spec.tls.hostsspec.rules.hostには、cert.example.comというホスト名が指定されています。これは、私たちのClusterIssuerが証明書を発行したホスト名です。
  • spec.rules.httpセクションは、パスとそれらのパス上のリクエストに対応するバックエンドServicesを定義します。たとえば、/teaへのトラフィックは、tea-svcのポート80に送られます。
  1. 上記のマニフェストをインストールに合わせて変更します。少なくともspec.rules.hostspec.tls.hostsの値を変更する必要がありますが、設定内のすべてのパラメータを確認する必要があります。
  2. マニフェストを適用します。
  3. $  kubectl apply -f ./cafe-virtual-server.yaml
      virtualserver.k8s.nginx.org/cafe created 
    
  4. 証明書が発行されるまで待ちます。READYフィールドに「True」という値が表示されます。
  5. $ kubectl get certificates NAME                                      READY   SECRET        AGE certificate.cert-manager.io/cafe-secret   True    cafe-secret   37m
    

NGINX仮想サーバー/仮想ルートの使用

NGINX CRDを使用する場合、以下の導入YAMLを使用してIngressを設定する必要があります。


  apiVersion: k8s.nginx.org/v1
  kind: VirtualServer
  metadata:
    name: cafe
  spec:
    host: cert.example.com
    tls:
      secret: cafe-secret
      cert-manager:
        cluster-issuer: prod-issuer
    upstreams:
      - name: tea
        service: tea-svc
        port: 80
      - name: coffee
        service: coffee-svc
        port: 80
    routes:
      - path: /tea
        action:
          pass: tea
      - path: /coffee
        action:
          pass: coffee

ここでも、マニフェストのいくつかの重要な部分について確認します。

  • 呼び出されるAPIは、VirtualServerリソースのNGINX固有のk8s.nginx.org/v1です。
  • spec.tls.secret Kubernetes Secretリソースには、Let’s Encryptで証明書を発行する際に返される証明書キーが格納されます。
  • spec.hostには、cert.example.comというホスト名が指定されています。これは、私たちのClusterIssuerが証明書を発行したホスト名です。
  • spec.upstreamsの値は、ポートを含むバックエンドのServicesを指しています。
  • spec.routesは、ルートと、それらのルートにヒットしたときに実行されるアクションの両方が定義されています。
  1. 上記のマニフェストをインストールに合わせて変更します。少なくともspec.hostの値を変更する必要がありますが、設定内のすべてのパラメータを確認する必要があります。
  2. マニフェストを適用します。
  3. $  kubectl apply -f ./cafe-virtual-server.yaml
      virtualserver.k8s.nginx.org/cafe created
    
  4. 証明書が発行されるまで待ちます。Validのステータスが表示されます。
  5. $ kubectl get VirtualServers NAME   STATE   HOST                    IP             PORTS      AGE cafe   Valid   cert.example.com   www.xxx.yyy.zzz   [80,443]   51m
    

証明書の表示

証明書は、Kubernetes APIを介して表示できます。APIを使用して、証明書のサイズや関連する秘密鍵など、証明書に関する詳細を表示できます。

$ kubectl describe secret cafe-secret
  Name:         cafe-secret
  Namespace:    default
  Labels:       <none>
  Annotations:  cert-manager.io/alt-names: cert.example.com
                cert-manager.io/certificate-name: cafe-secret
                cert-manager.io/common-name: cert.example.com
                cert-manager.io/ip-sans:
                cert-manager.io/issuer-group:
                cert-manager.io/issuer-kind: ClusterIssuer
                cert-manager.io/issuer-name: prod-issuer
                cert-manager.io/uri-sans:Type:  kubernetes.io/tlsData ==== tls.crt:  5607 bytes tls.key:  1675 bytes

実際の証明書と鍵を確認したい場合は、以下のコマンドを実行して確認できます。(注:これは、Kubernetes Secretの弱点であり、必要なアクセス権限があれば誰でも証明書と鍵を確認できてしまいます。)

$ kubectl get secret cafe-secret -o yaml

Ingressのテスト

証明書をテストします。テストにはさまざまな方法を使用できます。以下の例では、cURLを使用します。成功した場合は、前述のようなブロックにより、サーバー名、サーバーの内部アドレス、日付、選択したURI(ルート)(coffeeまたはtea)、リクエストIDが表示されます。失敗した場合は、ほとんどの場合は400または301のHTTPエラーコードが表示されます。

$ curl https://cert.example.com/tea Server address: 10.2.0.6:8080 Server name: tea-5c457db9-l4pvq Date: 02/Sep/2022:15:21:06 +0000 URI: /tea Request ID: d736db9f696423c6212ffc70cd7ebecf $ curl https://cert.example.com/coffee Server address: 10.2.2.6:8080 Server name: coffee-7c86d7d67c-kjddk Date: 02/Sep/2022:15:21:10 +0000 URI: /coffee Request ID: 4ea3aa1c87d2f1d80a706dde91f31d54

証明書の更新

冒頭で、この方式では証明書の更新を管理する必要がなくなると約束しました。しかし、その方法についてはまだ説明していません。その理由は、cert-managerの核として組み込まれている部分だからです。この自動処理では、cert-managerが、証明書が存在しない、失効している、失効から15日以内である、またはユーザーがCLIを介して新しい証明書を要求していることを認識すると、新しい証明書が自動的に要求されます。非常に便利です。

よくある質問

NGINX Plusとは何ですか?

NGINX Plus契約者である場合、NGINX Ingress Controllerのインストールが必要になることが唯一の違いです。これに対応するために上記のHelmコマンドを変更する方法については、NGINX DocsのInstallation Helmセクションをご覧ください。

どのタイプのチャレンジを使用すべきですか?

これは、ユースケースに大きく依存します。

HTTP-01チャレンジ方式では、ポート80がインターネットに開かれていて、DNSのAレコードがIngressコントローラのIPアドレスに対して適切に設定されている必要があります。この方式では、Aレコードを作成する以外に、DNSプロバイダにアクセスする必要はありません。

DNS-01チャレンジ方式は、ポート80をインターネットに公開できない場合に使用できます。必要なことは、cert-managerがDNSプロバイダへのEgressアクセスを持っていることだけです。ただし、この方式では、DNSプロバイダのAPIにアクセスできる必要がありますが、必要なアクセスレベルはプロバイダによって異なります。

問題はどのようにトラブルシューティングできますか?

Kubernetesは非常に複雑であるため、的を絞ったトラブルシューティング情報を提供することは難しいことです。問題が発生した場合、NGINX CommunityのSlackからお問い合わせください(NGINX Plus契約者の方は通常のサポートオプションをご利用いただけます)。

すぐに利用開始

NGINX Ingress ControllerとNGINX App Protect WAFおよびDoSの30日間の無料トライアルをお試しください。また、常に無料のNGINX Service Meshもダウンロードしてご利用できます。


"This blog post may reference products that are no longer available and/or no longer supported. For the most current information about available F5 NGINX products and solutions, explore our NGINX product family. NGINX is now part of F5. All previous NGINX.com links will redirect to similar NGINX content on F5.com."