cert-manager のカスタムリソースの関連性について調べてみた
Posted on
Kubernetes 上で Let's Encrypt の証明書を取得するために cert-manager を使う機会がありました。証明書を取得するのは簡単でしたが、cert-manager が自動的に作るカスタムリソースの関連性がよくわからなかったので調べてみました。
Let's Encrypt は ACME (Automated Certificate Management Environment) プロトコルによって証明書の発行が自動化されています。 cert-manager のドキュメントと合わせて RFC 8555 を読んでおくと、このあと出てくるカスタムリソースの理解が深まります。
なお、以下ではドメイン名を example.com
に置き換えています。
Issuer / ClusterIssuer
署名付き証明書を発行できる認証局 (CA) を表すカスタムリソースです。今回は Let's Encrypt で証明書を取得したいので spec.acme.server
で Let's Encrypt の API サーバを指定しています。
Issuer
は namespace に閉じたリソースで ClusterIssuer
は namespace を跨いだリソースです。
$ kubectl get clusterissuer
NAME READY AGE
letsencrypt True 5d12h
$ kubectl get clusterissuer letsencrypt -o yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
# (snip)
spec:
acme:
email: admin@example.com
preferredChain: ""
privateKeySecretRef:
name: letsencrypt
server: https://acme-v02.api.letsencrypt.org/directory
solvers:
- dns01:
route53:
hostedZoneID: xxxxxxxxxxxxxx
region: ap-northeast-1
secretAccessKeySecretRef:
name: ""
selector:
dnsZones:
- example.com
Certificate
X.509 証明書を定義するカスタムリソースです。 Certificate
が作られると後述する CertificateRequest
などが作られ、証明書の発行が要求されます。
$ kubectl get certificate
NAME READY SECRET AGE
wildcard-example-com True wildcard-example-com 5d17h
$ kubectl get certificate wildcard-example-com -o yaml
apiVersion: cert-manager.io/v1
kind: Certificate
# (snip)
spec:
dnsNames:
- '*.example.com'
duration: 2160h0m0s
issuerRef:
kind: ClusterIssuer
name: letsencrypt
renewBefore: 360h0m0s
secretName: wildcard-example-com
Certificate
は証明書の概念で、秘密鍵の実体は Secret
に保存されています。
$ kubectl get secret wildcard-example-com
NAME TYPE DATA AGE
wildcard-example-com kubernetes.io/tls 2 5d17h
CertificateRequest
CertificateRequest
は Certificate
を作ると自動的に作られるカスタムリソースです。名前から想像できるとおり CertificateRequest
には CSR が含まれています。
$ kubectl get certificaterequest
NAME APPROVED DENIED READY ISSUER REQUESTOR AGE
wildcard-example-com-wzqk5 True True letsencrypt system:serviceaccount:cert-manager:cert-manager 5d17h
$ kubectl get certificaterequest wildcard-example-com-wzqk5 -o yaml
apiVersion: cert-manager.io/v1
kind: CertificateRequest
# (snip)
spec:
duration: 2160h0m0s
groups:
- system:serviceaccounts
- system:serviceaccounts:cert-manager
- system:authenticated
issuerRef:
kind: ClusterIssuer
name: letsencrypt
request: LS0tLS1CRUdJTiBDRVJ...
spec.request
の値を Base64 デコードして openssl コマンドに渡せば CSR の内容を確認できます。
$ kubectl get certificaterequest wildcard-example-com-wzqk5 -o json | jq -r ".spec.request" | base64 -d | openssl req -text -noout
Certificate Request:
Data:
Version: 0 (0x0)
Subject:
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
(snip)
Order / Challenge
Order
は CertificateRequest
から自動的に作られるカスタムリソースです。ここで初めて Let's Encrypt のサーバと通信が行われます。 Order
には証明書を発行するために必要な諸々の情報が含まれており、Let's Encrypt に ACME プロトコルで証明書の発行を要求します。
$ kubectl get order
NAME STATE AGE
wildcard-example-com-wzqk5-762877340 valid 5d18h
Challenge
は Order
から自動的に作られるカスタムリソースです。 Issuer
/ ClusterIssuer
で指定したドメインの検証方法をもとに、HTTP01 か DNS01 に関する検証リソースを生成します。
最初に挙げた ClusterIssuer
の例だと DNS01 を指定しているので、Route 53 の example.com
の Hosted zone に対して _acme-challenge.example.com
という TXT レコードを追加して検証が行われます。 Challenge
によって追加された TXT レコードと Challenge
のカスタムリソースそのものは、ドメインの検証が完了すると削除されます。なので、普段は目にすることがありません。
まとめ
ここまでの説明をフローチャートにまとめると次のような関連性になります。数字はリソースが作られる順番です。
Troubleshooting のページにも書いてありますが、上手くいかない場合は親のカスタムリソースから kubectl get
や kubectl describe
すると、どこで問題が起きているか突き止めることができます。