有効なSSL/TLS証明書は、最新のアプリケーション環境の核となる要件です。しかし、残念ながら、アプリケーションの導入において、証明書の更新の管理が後回しにされることがよくあります。証明書には有効期限があり、DigiCertの証明書では約13か月、Let’s Encryptの証明書では90日とさまざまです。安全なアクセスを維持するためには、これらの証明書は失効前に更新/再発行する必要があります。運用チームは膨大な作業に追われていることが多いので、証明書の更新が見過ごされ、有効期限の間際で慌てて対応したり、最悪の場合は期限が過ぎたりすることもあります。
しかしもうこのような心配はしないで済みます。ある程度の計画と準備があれば、証明書の管理を自動化し、合理化できます。ここでは、以下の3つの技術を使ったKubernetes向けのソリューションについて説明します。
- Jetstack’s cert-manager
- Let’s Encrypt
- NGINX Ingress Controller
このブログでは、自動的に更新およびアップデートされる一意な証明書をエンドポイントに提供することで、証明書管理を簡素化する方法について学習できます。
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について
cert-managerプロジェクトは、KubernetesとOpenShiftで動作する証明書コントローラです。Kubernetesに導入されたcert-managerは、Ingress Controllerが必要とする証明書を自動的に発行し、それらが有効かつ最新であることを確認します。さらに、証明書の有効期限を追跡し、設定された時間間隔で更新を試みます。cert-managerは、数多くのパブリックおよびプライベートの発行者に対応していますが、ここではLet’s Encryptとの統合について説明します。
2種類のチャレンジ
Let’s Encryptを使用する場合、すべての証明書管理は自動的に処理されます。これは非常に便利ではありますが、問題となっている完全修飾ドメイン名(FQDN)を所有していることをサービスがどのように確認するかという問題も生じます。
この問題の解決にはチャレンジが使用され、これにより、特定のドメインのDNSレコードにアクセスできる人だけが提供できる検証リクエストに答える必要があります。チャレンジには以下の2つの形式があります。
- 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コントローラを使用してチャレンジトークンを提供することに注意してください。
- 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コントローラについて詳しくは、以下のブログをご覧ください。
- Kubernetes Networking 101 (Kubernetes Networkingの基本)
- Ingress Controllerの選択ガイド, Part 4 : NGINX Ingress Controllerのオプション
以下の例では、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の両方を使用できます。
- NGINXリポジトリを追加します。
- リポジトリを更新します。
- Ingressコントローラを導入します。
- 導入を確認し、IngressコントローラのEgressのIPアドレスを取得します。有効なIPアドレスがないと続行できないので注意してください。
$ 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の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を使用します。
- Helmリポジトリを追加します。
- リポジトリを更新します。
- cert-managerを導入します。
- 導入を検証します。
$ 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 Cafeのサンプルを使用します。これは、NGINXが提供するドキュメントでよく使用される例です。この例では、Ingressは導入しません。
- NGINX Ingress GitHubプロジェクトのクローンを作成します。
- examplesディレクトリに移動します。このディレクトリには、Ingressコントローラのさまざまな構成を示すいくつかの例が含まれています。ここでは、complete-exampleディレクトリ内に提供されている例を使用します。
- NGINX Cafeのサンプルを導入します。
kubectl get
コマンドを使用して、導入とサービスを検証します。PodにREADY
と表示され、サービスにrunning
と表示されていることを確認します。以下の例は、必要な代表的なサンプルを示しています。kubernetes
サービスは、NGINX Cafeのサンプルと同じネームスペース(デフォルト)で稼働しているシステムサービスであることに注意してください。
$ 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 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を設定する方法を示します。
- チャレンジにHTTP-01を使用してClusterIssuerを作成します。
- ClusterIssuerを検証します(readyと表示されているはずです)。
$ 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を使用する場合
この例では、DNS-01チャレンジを使用してドメインの所有権を認証するようにClusterIssuerを設定する方法を示しています。DNSプロバイダによっては、トークンを保存するためにKubernetes Secretの使用が必要になる場合があります。この例では、Cloudflareを使用します。ネームスペースの使用に注意してください。cert-managerネームスペースに導入されるcert-managerアプリケーションは、Secretにアクセスできる必要があります。
この例では、使用するアカウントから作成できる、CloudflareのAPIトークンが必要です。これは、以下の<API Token>行に記述する必要があります。Cloudflareを使用していない場合は、プロバイダのドキュメントに従う必要があります。
- APIトークンのSecretを作成します。
- チャレンジにDNS-01を使用して発行者を作成します。
- 発行者を検証します(readyと表示されているはずです★)。
$ 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の導入
これは、私たちが目指してきたことです。つまり、アプリケーションのための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.hosts
とspec.rules.host
には、cert.example.com
というホスト名が指定されています。これは、私たちのClusterIssuerが証明書を発行したホスト名です。spec.rules.http
セクションは、パスとそれらのパス上のリクエストに対応するバックエンドServicesを定義します。たとえば、/tea
へのトラフィックは、tea-svcのポート80に送られます。
- 上記のマニフェストをインストールに合わせて変更します。少なくとも
spec.rules.host
とspec.tls.hosts
の値を変更する必要がありますが、設定内のすべてのパラメータを確認する必要があります。 - マニフェストを適用します。
- 証明書が発行されるまで待ちます。READYフィールドに「True」という値が表示されます。
$ 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仮想サーバー/仮想ルートの使用
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
は、ルートと、それらのルートにヒットしたときに実行されるアクションの両方が定義されています。
- 上記のマニフェストをインストールに合わせて変更します。少なくとも
spec.host
の値を変更する必要がありますが、設定内のすべてのパラメータを確認する必要があります。 - マニフェストを適用します。
- 証明書が発行されるまで待ちます。Validのステータスが表示されます。
$ 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
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もダウンロードしてご利用できます。