TT Lab
开始
学习 学习路径 课程

证书已经续期,浏览器却还显示旧证书

一行重定向挡住了续期

在 TT Lab 中继续学习

目标

在 Pod 内让 HTTP-01 挑战通过,重现 HTTP→HTTPS 重定向吞掉挑战而阻塞续期的事故,然后只把挑战路径设为例外来修复。编写用于常态检查的 probe.sh 和 cert-manager 的 Certificate 清单。

为什么重要

ACME 自动续期在首次签发成功之后,两个月内不会出现任何症状。其间加入的一行重定向,会在续期时阻塞挑战,而那时已经没有人会想起那次变更。这是我在家庭实验室中实际遇到的事故,一重现就一目了然。素材有两个——/opt/app/edge.py 是每次请求都会重新读取配置文件(/root/acme/edge.json)的边缘服务(HTTP 8080、HTTPS 8443),/opt/app/acme_va.py 是会跟随重定向并把正文与密钥授权进行比较的验证服务器。

步骤

  1. 令牌是 zk3r9QmZ7Vf1Yb2Ld8Ns4Hj6Xc5Pa0Wt,账户密钥指纹是 Q7pVw2xK9mN4rT1sB6yL0cH3fJ8dG5eA。请在 /root/acme/challenges/zk3r9QmZ7Vf1Yb2Ld8Ns4Hj6Xc5Pa0Wt 文件中写入密钥授权字符串。
  2. 创建 /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。
  3. 把 python3 /opt/app/acme_va.py --url http://127.0.0.1:8080/.well-known/acme-challenge/<토큰> --expect <키인가>(占位符依次为令牌、密钥授权字符串)的输出保存到 /root/acme/03-valid.txt。第一行必须是 VALID。
  4. 重现事故:把 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)。
  5. 修复:在保持重定向开启的情况下,把 /.well-known/acme-challenge/ 重新放回 exempt_paths。把验证服务器的输出保存到 /root/acme/05-fixed.txt(VALID),把 curl -sI http://127.0.0.1:8080/ 保存到 /root/acme/05-app.txt(仍然是 301)。
  6. 创建 /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。
  7. 在 /root/acme/certificate.yaml 中编写 cert-manager 的 Certificate——apiVersion: cert-manager.io/v1、spec.secretName、spec.dnsNames 中放入 www.lab.internal、spec.duration 和 spec.renewBefore(时间单位用 h)、spec.issuerRef.kind: ClusterIssuer。
  8. 在 /root/acme/08-report.md 中整理这次事故。必须包含 HTTP-01、作为证据看到的 301、设为例外的路径 .well-known/acme-challenge,以及 renewBefore 为什么会使事故延迟暴露。

参考

放置密钥授权文件

在 /root/acme/challenges/zk3r9QmZ7Vf1Yb2Ld8Ns4Hj6Xc5Pa0Wt 中写入密钥授权字符串(令牌.指纹)。

RFC 8555 的密钥授权,是把 token、句点和账户密钥指纹连接起来的字符串。文件名就是令牌本身,内容就是这一行。指纹已在说明中给出。

启动边缘服务并获取挑战

创建 /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。第一行必须是 VALID。

--url 是挑战 URL,--expect 是密钥授权字符串。输出的第一行是判定结果,其下逐行列出所跟随的路径。

重定向吞掉了挑战

把 edge.json 改为重定向 on、无例外,把 curl -sI 的输出保存到 /root/acme/04-curl.txt,把验证服务器的输出保存到 /root/acme/04-broken.txt。

用一行 python3 修改 JSON,出错会比较少。curl -sI 的第一行会显示 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。

要改的只是例外列表中的一行。应用路径(/)仍然要以 301 转到 HTTPS,只有挑战路径要在 HTTP 上直接返回 200。

查看续期是否被阻塞的一行

创建 /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 服务器来运行。

用 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 的时候挑战才会再次运行,这就是“延迟暴露”的原因。