ACME challenges [HTTP01]
概念が掴みにくい用語
- チャレンジ (challenges)
ACME クライアント(cert-manager)がドメインを所有しているのを確認すること - Issuer (発行者)
証明書を発行(issue, issuance)する認証局 (CA; Certificate Authority) のこと
→ 種類は SelfSigned, CA, Vault, Venafi(ベナファイ), External, ACME(HTTP01, DNS01) - SAN (Subject Alternative Name; サン)
1つの証明書に2つ以上のホスト (CN; Common Name)
[e.g. www.example.com, example.com] を含めることができる追加名のこと
cert-manager
特徴
- 証明書の管理更新を自動化できるクラウドネイティブな OSS
- APIを利用する場合は
acme.cert-manager.io.io/v1, cert-manger.io/v1
等がある - ユースケースとしては自動的に Let’s Encrypt を発行して、ローテションしてくれる Issuer である ACME(HTTP01, DNS01) が使用されることが多いように思う
参照: https://cert-manager.io/docs/configuration/acme/
- Helm を利用すると Webhook と CA InjecterはValidatingAdmissionWebhook , MutatingAdmissionWebhook , CustomResourceWebhookConversion の CRD を使うが、Webhook のためだから使ってないよう
- 一度に同時処理できる Challenge は 60
https://github.com/jetstack/cert-manager/blob/ddff78f011558e64186d61f7c693edced1496afa/pkg/controller/acmechallenges/scheduler/scheduler.go#L31-L33
簡単な証明書作成の流れ
概念図
- cert-manager の CRD 一式をデプロイ
- Issuer を発行
- 認証したいドメイン用の Certificate を作成
- 発行された Secret を Ingress(SSL終端) で利用
HTTP01
- HTTP01 チャレンジでは、ACME サーバのリクエストに対応するチャレンジソルバー Pod(Pod), Service, Ingress が一時的に作成される
→ pods 内の/.well-known/acme-challenge/XYZ
を使用してドメインの所在確認(チャレンジ)を行う - 上記 Ingress は、https://cert-manager.io/docs/configuration/acme/http01/#options Issuer の設定を変更することで既存の Ingress がチャレンジリゾルバー Pod との通信を担うことも可能
- 一時的 Ingress には
kubernetes.io/ingress.class: [ingressClass field]
のアノテーションがつく
【NOTE】HTTP01 チャレンジは https 通信ではなく http 通信であるため、http 等を拒否している場合は、チャレンジの通信を別途パススルーする必要がある。下記に参考までに VCL (Varnish Custom Controller の設定を載せておく
sub vcl_recv { #リクエストパスが、./well-known/acme-challenge/XXXだった場合、HTTPリクエストをパススルーするる if (req.url.path ~ "^/\.well-known/acme-challenge/.+") { if (table.lookup(custom_domains, req.http.host) == "dev") { set req.backend = F_dev_http; } elseif (table.lookup(custom_domains, req.http.host) == "stg") { set req.backend = F_stg_http; ... } }
証明書の期限に関して
デフォルトの期限 (duration) は90日 (15日前に更新)、 renewBefore
(defalut: durationの2/3) か duration の2/3のいずれか遅い方で更新される
ハマりどころ
証明書更新の検証を行いたい場合、Certificate リソースの spec.renewBefore
を記述する必要があるが、作成時間から起算して、spec.renewBefore
を 10m
と記述すると作成されない。
なぜなら、証明書の期限から逆算して計算されるため、10分後に更新したい場合は、 60(分) × 24(時間) × 90(日) = 129600 – 10 = 129590 で 129590m
と記述する必要がある
ただし、あまりに短くしすぎてしまうと HTTP01 チャレンジのレート制限がかかってしまうため、こちら利用したい場合は予め HTTP01 の レート制限 を確認する必要がある。
【NOTE】ACME サーバはステージング用 (https://acme-staging-v02.api.letsencrypt.org/directory) と本番用(https://acme-v02.api.letsencrypt.org/directory) があるが、レート制限があるためステージングはステージング用で利用することを強く勧める
Ingress SSL終端とCertificateの自動作成
Ingress の annotation に cert-manager.io/issuer: [Issuer名] を付与することで Issuer から Ingress での SSL 終端用の証明書 (Certificate; 厳密には Secret に証明書が内包されている) を自動的に発行することができる
→ 自動的に作成されるのは便利だが、Self で Certificate を用意する際に利用できる spec.secretTemplate
のように一部 Ingress 経由作成の Certificate では利用できないパラメータが存在するので、利用用途として注意する必要がある
もし、一部の Certificate の設定をIngressで利用したい場合は Securing Ingress Resources
証明書の運用に関して
Certificates
を発行したい namespace にそれぞれIssuer
リソースを作る必要がある
→ もし、namespace に依存しない Issuer を作成したい場合はClusterIssuer
リソースを作成することで問題を回避することができる- Certificates リソースが
sepc.rotationPolicy: Always
ならば証明書が更新されるたびにtls.key
も更新される
→ 言い換えると、更新されるのは証明書だけで、秘密鍵は更新されないようになっている - 上記に併せて Certificates リソースを削除しても、対応する Secret は削除されない
→ Certificate を削除すると Secret が自動的に削除するにはコントローラーに--enable-certificate-owner-ref
フラグを追加する必要がある - ACME チャレンジで発行される証明書 (Secretリソース)には
tls.crt
(leaf と intermediate 証明書がある) とtls.key
(チャレンジ時に発行される一時的 Secret 由来) が含まれている - CertificateRequest は Certificate が作られるか、仕様が変わるか、更新が必要になると自動的に作成される
証明書の設定項目(抜粋)
spec.dnsNames
DNSNames is a list of DNS subjectAltNames to be set on the Certificate.spec.secretName
Certificats リソースによって作成されるtls.key, tls.crt
が含まれた Secret リソース名
その他細かい仕様
- HTTP01 (ingress, contour) と +α
dnsZones
セレクタの cloudDNS がある - DNS01 のセレクタ(満たすべき要件)は
matchLabel
>dnsNames
(ワイルドカード利用不可) >dnsZones
(ワイルドカード)の順で優先される
e.g. www.sys.example.com のために example.com ではなくて sys.example.com が指定される。また、example.com を設定するとサブドメイン *.example.com もリゾルブしてくれる
間違いポイント
Issuer が発行する秘密鍵 (privateKeySecretRef
) と、Certificate が作成する証明書 (secretName
) は、前者は ACME/Let’s Encrypt のアカウント秘密鍵で、secretName は作成されるドメインの証明書と秘密鍵が含まれている
ACME チャレンジ (protocolの仕組み)
特徴
- 正式名称: ACME (Automated Certificate Management Environment) protocol
- 人的介入なしに CA から証明証を得れる, Let’s Encrypt 使用, 80 port のみ許可, 2019 IETF 標準
- ACME client – Web server, JSON over HTTPS ※ cert-manager では Pod が作られる
- ACME server – チャレンジ(ドメインが本人のものであるか検証)を発行(issue)する
※ cert-manager の HTTP01 チャレンジでは Let’s Encrypt のサーバを指す
証明書作成プロセス
- [Web Server: クライアント] 特定のドメインの証明書を求める
- [ACME Server: サーバ] チャレンジ (HTTP-01: http://example.com/.well-known/acme-challenge/XXXToken, DNS-01; DNS record) の開始
- [Web Server: クライアント] ファイルを作成, HTTP resource を返して、validation する
- [ACME Server: サーバ] GET http://example.com/.well-known/acme-challenge/XXXToken リクエスト
- [Web Server: クライアント] キーペア (
privateKeySecretRef
) とそれを利用した CSR を作成、CSR を ACME Server に送る (全ての CSR はアカウントの秘密鍵でサインされている) - [ACME Server: サーバ] 署名を確認し、 署名入りの証明書を発行 (issue) する
備考
HTTP01 チャレンジ
- 途中で自己署名証明書や有効期限切れの証明書が存在する可能性があるため
HTTP01 チャレンジでは http(80) → https(443) のリダイレクトのみ許可 IPアドレス✗ - HTTP01 チャレンジではワイルドカード証明書が発行できない (DNS-01は可能)
全てのウェブサーバで同じファイルを設定する必要がある
DNS01 チャレンジ
- DNS01 チャレンジでは TXT レコード(トークンとアカウント鍵から)を使用し(クライアント)、
_acme-challenge.<YOUR_DOMAIN>
の値として設定(DNS server)する必要がある - DNS01 チャレンジでは Web サーバ上に API のクレデンシャルを置くリスクがある
TLS-ALPN01 チャレンジ
- Caddy では実装されているが、nginx, Apache でサポートされていない, 443 利用上記のやりとりを cert-manager の CertificateRequest (Order, Challenge) で実施しているよう
デバック
デバックするときは大体以下のコマンドから始めている
$ kubectl get cert,cr,order,challenge
- 大体、Certificate か Challenge リソースを見て原因調査を始めることが多い
- 失敗しているときは残っている一時的な Ingress や Service や Pod も
- 加えて、challengeのリクエストが帰ってくるか curl で確認したりもよい
- トークンは Challenge の
spec.token
を確認すればよい
$ curl -sv --resolve challenge-test.example.com:80:XX.XX.XX.XX(IngressのグローバルIP) \ http://challenge-test.example.com/.well-known/acme-challenge/hogehogehogehoeghoehogehoge