リダイレクト一行が更新を止めた
目標
HTTP-01チャレンジをPodの中で通し、HTTPからHTTPSへのリダイレクトがチャレンジを飲み込んで更新が塞がれる事故を再現したあと、チャレンジのパスだけを例外にして直します。常時確認用のprobe.shと、cert-manager Certificateマニフェストを作成します。
なぜ重要なのか
ACMEの自動更新は、最初の発行が成功すると、その後の2か月間、何の症状もありません。その間に入れたリダイレクトの1行が、更新の時点でチャレンジを塞ぎ、そのときには誰もその変更を思い出せません。ホームラボで実際に経験した事故で、再現すれば一目でわかります。用意されているものは2つです。/opt/app/edge.pyは、設定ファイル(/root/acme/edge.json)をリクエストのたびに読み直すエッジ(HTTP 8080・HTTPS 8443)で、/opt/app/acme_va.pyは、リダイレクトを追いかけながら、本文をキー認可と比べる検証サーバーです。
ステップ
- トークンは
zk3r9QmZ7Vf1Yb2Ld8Ns4Hj6Xc5Pa0Wt、アカウント鍵のフィンガープリントはQ7pVw2xK9mN4rT1sB6yL0cH3fJ8dG5eAです。キー認可の文字列を、次のファイルに入れてください:/root/acme/challenges/zk3r9QmZ7Vf1Yb2Ld8Ns4Hj6Xc5Pa0Wt - 設定ファイルを作成してください(保存先:
/root/acme/edge.json)。http_portは8080、https_portは8443、redirect_to_httpsはfalse、exempt_pathsには/.well-known/acme-challenge/、challenge_dirは/root/acme/challengesとします。python3 /opt/app/edge.py --config /root/acme/edge.jsonを起動し、curl -s http://127.0.0.1:8080/.well-known/acme-challenge/<토큰>の本文を保存してください(保存先:/root/acme/02-fetch.txt。プレースホルダーはトークンです)。 python3 /opt/app/acme_va.py --url http://127.0.0.1:8080/.well-known/acme-challenge/<토큰> --expect <키인가>の出力を保存してください(保存先:/root/acme/03-valid.txt。プレースホルダーはトークンとキー認可です)。1行目がVALIDである必要があります。- 事故の再現:
edge.jsonのredirect_to_httpsをtrueに、exempt_pathsを空のリストに変えてください。curl -sI http://127.0.0.1:8080/.well-known/acme-challenge/xの出力を保存し(保存先:/root/acme/04-curl.txt)、検証サーバーの出力も保存してください(保存先:/root/acme/04-broken.txt。INVALIDである必要があります)。 - 修正: リダイレクトはオンのまま、
exempt_pathsに/.well-known/acme-challenge/を再び入れてください。検証サーバーの出力を保存し(保存先:/root/acme/05-fixed.txt、VALIDになります)、curl -sI http://127.0.0.1:8080/も保存してください(保存先:/root/acme/05-app.txt、依然として301です)。 - 次のスクリプトを作成してください:
/root/acme/probe.sh <http://host:port>。<base>/.well-known/acme-challenge/probe-checkのステータスコードが3xxならREDIRECTを出力して1、それ以外(200・404など)ならOKを出力して0で終了する必要があります。http://127.0.0.1:8080に対する出力を保存してください(保存先:/root/acme/06-probe.txt)。 - cert-managerの
Certificateを書いてください(保存先:/root/acme/certificate.yaml)。apiVersion: cert-manager.io/v1、spec.secretName、spec.dnsNamesにwww.lab.internal、spec.durationとspec.renewBefore(時間の単位はh)、spec.issuerRef.kind: ClusterIssuerを含めてください。 - 事故をまとめてください(保存先:
/root/acme/08-report.md)。HTTP-01、証拠として見た301、例外にしたパス.well-known/acme-challenge、そしてrenewBeforeがなぜ事故の発覚を遅らせるのかを含める必要があります。
参考
- edge.jsonはリクエストのたびに読み直されるので、直したあとにエッジを再起動する必要はありません。
- 検証サーバーは、失敗しても出力を出します。
> 파일で保存すれば、終了コードに関係なく残ります(プレースホルダーはファイル名です)。 curl -sIはHEADリクエストで、ステータス行とヘッダーだけを表示します。Location:ヘッダーがどこに向かっているかを見てください。- よくある間違い: ステップ4で
exempt_pathsを空にしないと、事故が再現されません。ステップ7でrenewBefore: 15dのようにdを使うと、cert-managerは受け付けません。
キー認可ファイルを置く
/root/acme/challenges/zk3r9QmZ7Vf1Yb2Ld8Ns4Hj6Xc5Pa0Wtにキー認可の文字列(トークン.フィンガープリント)を入れてください。
RFC 8555のキー認可は、token、ピリオド、アカウント鍵のフィンガープリントをつなげた文字列です。ファイル名はトークンそのまま、内容はその1行です。フィンガープリントは、指示文に与えられています。
エッジを起動してチャレンジを受け取る
/root/acme/edge.json(リダイレクトoff、チャレンジのパスは例外)を作成してエッジを起動し、チャレンジURLの本文を/root/acme/02-fetch.txtに保存してください。
JSONのキー名は、指示文のとおりです。エッジはnohupでバックグラウンドに置き、ログは/root/acme/edge.logに出力してください。curl -sで受け取った本文が、ステップ1のキー認可と同じである必要があります。
検証サーバーに確認してもらう
acme_va.pyを実行して、出力を/root/acme/03-valid.txtに保存してください。1行目がVALIDである必要があります。
--urlはチャレンジURL、--expectはキー認可の文字列です。出力の1行目が判定で、その下にたどったパスが1行ずつ出ます。
リダイレクトがチャレンジを飲み込む
edge.jsonを、リダイレクトon・例外なしに変更し、curl -sIの出力を/root/acme/04-curl.txtに、検証サーバーの出力を/root/acme/04-broken.txtに保存してください。
python3の1行でJSONを直すと、ミスが少なくなります。curl -sIの1行目に301、Locationヘッダーにhttpsが見え、検証サーバーは301 → 404をたどった末にINVALIDを出します。
チャレンジのパスだけを例外にする
リダイレクトはオンのまま、exempt_pathsにチャレンジのパスを入れ、検証の出力を/root/acme/05-fixed.txtに、curl -sI http://127.0.0.1:8080/を/root/acme/05-app.txtに保存してください。
直すのは、例外リストの1行です。アプリのパス(/)は、依然として301でHTTPSに行く必要があり、チャレンジのパスだけが、HTTPですぐに200を返す必要があります。
更新が塞がれているかを見る1行
/root/acme/probe.sh <http://host:port>を作成し(3xxならREDIRECT・1、そうでなければOK・0)、http://127.0.0.1:8080の出力を/root/acme/06-probe.txtに保存してください。
curl -s -o /dev/null -w '%{http_code}'でステータスコードだけを受け取り、caseで振り分ければよいです。404はOKです。ファイルがないことは更新を塞がず、塞ぐのはリダイレクトだけです。採点ツールは、301のサーバーと404のサーバーを1つずつ立ち上げて実行してみます。
cert-managerで宣言する
/root/acme/certificate.yamlに、Certificate(cert-manager.io/v1、secretName、dnsNamesにwww.lab.internal、duration・renewBeforeはh単位、issuerRef.kindはClusterIssuer)を書いてください。
ドキュメントの例の構造に従い、値はこのラボのものにしてください。durationはrenewBeforeより大きい必要があり、どちらもGoの時間文字列(h・m・s)です。ほかのネームスペースからも使う発行者は、ClusterIssuerです。
事故レポート
/root/acme/08-report.mdに、HTTP-01が塞がれた経緯、証拠(301)、例外にしたパス、renewBeforeが事故の発覚を遅らせる理由を書いてください。
ステップ4のcurlの結果が証拠で、ステップ5の例外リストが修正です。発行直後には症状がなく、renewBeforeの時点になって初めてチャレンジが再び動くのが、「発覚が遅れる」理由です。