有効なSSL/TLS証明書は、最新のアプリケーション環境の核となる要件です。しかし、残念ながら、アプリケーションの導入において、証明書の更新の管理が後回しにされることがよくあります。証明書には有効期限があり、DigiCertの証明書では約13か月、Let’s Encryptの証明書では90日とさまざまです。安全なアクセスを維持するためには、これらの証明書は失効前に更新/再発行する必要があります。運用チームは膨大な作業に追われていることが多いので、証明書の更新が見過ごされ、有効期限の間際で慌てて対応したり、最悪の場合は期限が過ぎたりすることもあります。
しかしもうこのような心配はしないで済みます。ある程度の計画と準備があれば、証明書の管理を自動化し、合理化できます。ここでは、以下の3つの技術を使ったKubernetes向けのソリューションについて説明します。
このブログでは、自動的に更新およびアップデートされる一意な証明書をエンドポイントに提供することで、証明書管理を簡素化する方法について学習できます。
技術的な詳細に入る前に、いくつかの用語の定義について確認しておきます。「TLS証明書」という用語は、私たちのIngressコントローラでHTTPS接続を有効にするために必要な以下の2つのコンポーネントを指します。
証明書と秘密鍵はどちらもLet’s Encryptによって発行されます。TLS証明書の仕組みについて詳しくは、DigiCertの記事「How TLS/SSL Certificates Work」をご覧ください。
Kubernetesでは、これら2つのコンポーネントはSecretsとして保存されます。NGINX Ingress Controllerやcert-managerなどのKubernetesワークロードは、これらのSecretを書き込み、読み取ることができます。Secretは、Kubernetesインストールにアクセスできるユーザーによって管理することもできます。
cert-managerプロジェクトは、KubernetesとOpenShiftで動作する証明書コントローラです。Kubernetesに導入されたcert-managerは、Ingress Controllerが必要とする証明書を自動的に発行し、それらが有効かつ最新であることを確認します。さらに、証明書の有効期限を追跡し、設定された時間間隔で更新を試みます。cert-managerは、数多くのパブリックおよびプライベートの発行者に対応していますが、ここではLet’s Encryptとの統合について説明します。
Let’s Encryptを使用する場合、すべての証明書管理は自動的に処理されます。これは非常に便利ではありますが、問題となっている完全修飾ドメイン名(FQDN)を所有していることをサービスがどのように確認するかという問題も生じます。
この問題の解決にはチャレンジが使用され、これにより、特定のドメインのDNSレコードにアクセスできる人だけが提供できる検証リクエストに答える必要があります。チャレンジには以下の2つの形式があります。
HTTP-01は、DNSプロバイダへの直接アクセスが必要ないので、証明書を生成する最も簡単な方法です。このタイプのチャレンジは、常にポート80(HTTP)を介して行われます。HTTP-01チャレンジを使用する場合、cert-managerは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アクセスが必要です。
最も簡単な導入方法は、Helmを使用することです。この導入では、Kubernetes IngressとNGINX Virtual Server CRDの両方を使用できます。
$ helm repo add nginx-stable https://helm.nginx.com/stable
"nginx-stable" has been added to your repositories
$ 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!⎈
$ 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.
$ 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プロバイダにより異なります。この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を導入します。ここでも、導入にはHelmを使用します。
$ helm repo add jetstack https://charts.jetstack.io
"jetstack" has been added to your repositories
$ 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!⎈
$ 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/
$ 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が提供するドキュメントでよく使用される例です。この例では、Ingressは導入しません。
$ 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.
$ cd ./kubernetes-ingress/examples/ingress-resources/complete-example
$ kubectl apply -f ./cafe.yaml
deployment.apps/coffee created
service/coffee-svc created
deployment.apps/tea created
service/tea-svc created
kubectl get
コマンドを使用して、導入とサービスを検証します。PodにREADY
と表示され、サービスにrunning
と表示されていることを確認します。以下の例は、必要な代表的なサンプルを示しています。kubernetes
サービスは、NGINX Cafeのサンプルと同じネームスペース(デフォルト)で稼働しているシステムサービスであることに注意してください。
$ 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
cert-manager内では、ClusterIssuerを使用して証明書を発行できます。これは、クラスタスコープのオブジェクトであり、任意のネームスペースから参照され、定義された証明書発行機関へのあらゆる証明書要求で使用できます。この例では、Let’s Encrypt証明書に対するすべての証明書要求をこのClusterIssuerで処理できます。
選択したチャレンジタイプのClusterIssuerを導入します。この記事の対象範囲外ですが、ClusterIssuerに複数のリゾルバを指定(セレクタフィールドに基づいて選択)できる高度な構成オプションがあります。
Automated Certificate Management Environment(ACME)プロトコルは、ドメイン名を所有しているかどうか、さらにLet’s Encrypt証明書を発行できるかどうかを判断するために使用されます。このチャレンジのために、渡す必要のあるパラメータは以下のとおりです。
この例では、HTTP-01チャレンジを使用してドメインの所有権を証明し、証明書を受け取るようにClusterIssuerを設定する方法を示します。
$ 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
$ kubectl get clusterissuer NAME READY AGE prod-issuer True 34s
この例では、DNS-01チャレンジを使用してドメインの所有権を認証するようにClusterIssuerを設定する方法を示しています。DNSプロバイダによっては、トークンを保存するためにKubernetes Secretの使用が必要になる場合があります。この例では、Cloudflareを使用します。ネームスペースの使用に注意してください。cert-managerネームスペースに導入されるcert-managerアプリケーションは、Secretにアクセスできる必要があります。
この例では、使用するアカウントから作成できる、CloudflareのAPIトークンが必要です。これは、以下の<API Token>行に記述する必要があります。Cloudflareを使用していない場合は、プロバイダのドキュメントに従う必要があります。
$ 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
$ 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
$ kubectl get clusterissuer NAME READY AGE prod-issuer True 31m
これは、私たちが目指してきたことです。つまり、アプリケーションのためのIngressリソースを導入します。これにより、先ほど導入したNGINX Cafeアプリケーションにトラフィックがルーティングされます。
標準的な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
ここで、マニフェストのいくつかの重要な部分について確認します。
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.hosts
とspec.rules.host
には、cert.example.com
というホスト名が指定されています。これは、私たちのClusterIssuerが証明書を発行したホスト名です。spec.rules.http
セクションは、パスとそれらのパス上のリクエストに対応するバックエンドServicesを定義します。たとえば、/tea
へのトラフィックは、tea-svcのポート80に送られます。spec.rules.host
とspec.tls.hosts
の値を変更する必要がありますが、設定内のすべてのパラメータを確認する必要があります。
$ kubectl apply -f ./cafe-virtual-server.yaml
virtualserver.k8s.nginx.org/cafe created
$ kubectl get certificates NAME READY SECRET AGE certificate.cert-manager.io/cafe-secret True cafe-secret 37m
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
ここでも、マニフェストのいくつかの重要な部分について確認します。
spec.tls.secret
Kubernetes Secretリソースには、Let’s Encryptで証明書を発行する際に返される証明書キーが格納されます。spec.host
には、cert.example.com
というホスト名が指定されています。これは、私たちのClusterIssuerが証明書を発行したホスト名です。spec.upstreams
の値は、ポートを含むバックエンドのServicesを指しています。spec.routes
は、ルートと、それらのルートにヒットしたときに実行されるアクションの両方が定義されています。spec.host
の値を変更する必要がありますが、設定内のすべてのパラメータを確認する必要があります。
$ kubectl apply -f ./cafe-virtual-server.yaml
virtualserver.k8s.nginx.org/cafe created
$ 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
証明書をテストします。テストにはさまざまな方法を使用できます。以下の例では、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 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."