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

CertificateRequestCertificate を作ると自動的に作られるカスタムリソースです。名前から想像できるとおり 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

OrderCertificateRequest から自動的に作られるカスタムリソースです。ここで初めて Let's Encrypt のサーバと通信が行われます。 Order には証明書を発行するために必要な諸々の情報が含まれており、Let's Encrypt に ACME プロトコルで証明書の発行を要求します。

$ kubectl get order
NAME                                   STATE   AGE
wildcard-example-com-wzqk5-762877340   valid   5d18h

ChallengeOrder から自動的に作られるカスタムリソースです。 Issuer / ClusterIssuer で指定したドメインの検証方法をもとに、HTTP01 か DNS01 に関する検証リソースを生成します。

最初に挙げた ClusterIssuer の例だと DNS01 を指定しているので、Route 53 の example.com の Hosted zone に対して _acme-challenge.example.com という TXT レコードを追加して検証が行われます。 Challenge によって追加された TXT レコードと Challenge のカスタムリソースそのものは、ドメインの検証が完了すると削除されます。なので、普段は目にすることがありません。

まとめ

ここまでの説明をフローチャートにまとめると次のような関連性になります。数字はリソースが作られる順番です。

cert-manager のフローチャート

Troubleshooting のページにも書いてありますが、上手くいかない場合は親のカスタムリソースから kubectl getkubectl describe すると、どこで問題が起きているか突き止めることができます。