"证书到底配在哪一层"这个问题,通常是在排查"证书不属于这个域名""应用拿到的协议是 http""跳转后掉回明文"时才被提出来的。三个症状同一个根源:TLS 在某处被解开,却没人记录过它在哪被解开。终止位置一定,后面每一跳的信道性质、可观测信息、证书续期责任就都跟着固定。这篇只谈位置的判据。
1. 先把"证书配在哪"拆成三个问题
| 问题 | 答案决定什么 | 常见错答 |
|---|---|---|
| 客户端看到的证书由谁签发 | 信任链、客户端兼容性 | "反代有证书就够了",忽略内层也在发证 |
| 走到应用时是明文还是 TLS | 内网是否需要加密、能否被旁路嗅探 | "内网安全",于是全链路明文 |
| 应用看到的协议与客户端 IP 从哪来 | 生成链接、限流、审计的准确性 | 直接信任 Host 与 X-Forwarded-* |
三个问题必须同时回答。只答第一个,就得到"外部 HTTPS、内部全明文、应用以为自己在 HTTP 上跑"——最常见也最难查。
2. 四个终止位置与各自的信任边界
| 位置 | 明文暴露段 | 私钥放在哪 | 运维代价 | 适合 |
|---|---|---|---|---|
| 边缘 CDN | 源站回源段(可配 TLS) | CDN 托管或源站持有 | 最低,证书与续期被托管 | 公网站点、静态为主 |
| 入口网关/反代 | 网关到应用 | 网关节点 | 中等,网关升级即全局风险 | 单体或少量服务 |
| 应用进程内 | 无(进程内解密) | 每个应用实例 | 高,证书分发到所有副本 | 服务少、要求端到端 |
| 全程透传(L4) | 无 | 后端 | 中,L4 不解密则功能受限 | 需要端到端、或不可解密 |
四种常被混用:CDN 终止 + 回源再建 TLS + 网关终止 + 内网明文。审计要的是这份逐段清单,不是"我们上了 HTTPS"。
3. 终止越靠前,后面是否明文——安全与运维的同一个决策
这不是两个议题。把终止点前移,同时得到三件东西和失去三件东西:
| 前移得到 | 前移失去 |
|---|---|
| 证书集中管理,续期只做一次 | 内网段明文,横向移动可直接嗅探 |
| 后端不需要处理握手,CPU 压力集中 | 应用无法直接看到真实客户端证书 |
| 统一 WAF、限流、日志入口 | 单一节点成为全局单点与性能瓶颈 |
反向移动则相反:内网每段都要有自己的证书与轮换,换来"任何一跳被拿下都不等于全链路明文"。
判据是内网段的信任等级是否一致。同机、同 VPC、无第三方运维,明文可接受;跨机房、跨云、跨承接方,明文段就是没有名字的信任漏洞。
4. mTLS:适用面窄,运维成本高
| 场景 | mTLS 是否合适 | 原因 |
|---|---|---|
| 服务网格内部身份 | 合适 | 有统一 CA 与自动轮换,证书生命周期被平台接管 |
| 第三方 B2B 对接 | 合适 | 双方可预先交换 CA,连接数有限 |
| 移动端/浏览器直连 | 不合适 | 客户端证书分发与吊销成本极高 |
| 临时脚本调用内部 API | 不合适 | 证书分发比 token 更难回收 |
mTLS 的成本不在握手,在证书生命周期:签发、轮换、吊销、过期告警都要有归属人。没有自动轮换的 mTLS,半年后必有一次"凌晨全部连接失败"。
# 网格侧的典型配置形态(字段名按所用实现核对)
mtls:
mode: STRICT # PERMISSIVE 只应用于迁移窗口
ca:
rotateBefore: 720h # 早于过期前轮换,留出重试窗口
workloadSelector:
app: payments
5. HTTP/2 与 HTTP/3 对拓扑的约束
| 层 | 能否协商 h2 | 关键约束 |
|---|---|---|
| 客户端到 CDN | 通常可以 | CDN 支持 ALPN 即可 |
| CDN 到源站 | 常被降级为 h1.1 | 需显式开启回源 HTTP/2,且源站要支持 |
| 网关到应用 | 取决于网关与应用的协议 | 明文 h2c 支持不一致,多数走 h1.1 |
| 端到端 h3 | 基本不成立 | h3 依赖 QUIC,通常只在最后一跳到边缘之间 |
结论:边缘支持不等于全链支持。一次请求可能第一段 h3、第二段 h1.1、第三段 h2c,多路复用收益只在前两段成立。
curl -sI --http2 https://TARGET/ | head -3
# 逐层验证:对 CDN、源站、内网入口各查一次 ALPN 与协议版本
openssl s_client -connect TARGET:443 -alpn h2 </dev/null 2>/dev/null | grep ALPN
6. X-Forwarded-* 的信任问题
| 头 | 谁应该写 | 应用必须校验什么 | 不校验的后果 |
|---|---|---|---|
X-Forwarded-For |
最外层可信代理追加 | 只取最右侧可信跳之后的第一个 | 客户端自带该头即可伪造来源 IP |
X-Forwarded-Proto |
终止 TLS 的那一层 | 只在该层覆盖后信任 | 生成 http:// 链接、Cookie 缺 Secure |
X-Forwarded-Host |
同上 | 与 Host 白名单比对 |
密码重置链接指向攻击者域名 |
Forwarded(RFC 7239) |
同上 | 同上,注意多值解析 | 同上 |
伪造的根源永远是入口没有剥离客户端自带的同名头。正确顺序是:入口代理先清空再写入,应用只信任来自已知代理网段的值。
# 入口层:先清空,再按实际连接写入
proxy_set_header X-Forwarded-For "";
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
跨两层时 $remote_addr 已是上一层代理地址,需 real_ip 配合可信网段还原;漏了这一步,日志全是代理 IP,风控与限流一起失效。
7. 重定向与 HSTS 在多层代理下的坑
| 现象 | 成因 | 处理 |
|---|---|---|
| 无限重定向 | 应用看到 http,跳 https;边缘看到 https 又跳回 |
应用层信任 X-Forwarded-Proto,或只在一层做跳转 |
| 跳转后端口/协议丢失 | 应用拼接绝对 URL 时用了内部地址 | 用相对跳转,或配置外部基址 |
| HSTS 首次不生效 | 首个响应是明文跳转 | 跳转响应也要带 Strict-Transport-Security |
| 内网 IP 被 HSTS 锁死 | 浏览器对 IP 也记录 | 只对确定域名下发,max-age 先小后大 |
includeSubDomains 打挂旧子域 |
旧子域只有 HTTP | 先盘点全部子域再开 |
add_header Strict-Transport-Security "max-age=300" always; # 灰度期用短值
# 稳定后再升到 31536000,并在确认全部子域支持 HTTPS 后才加 includeSubDomains
HSTS 单向:下发长 max-age 后,客户端在到期前不会再用 HTTP。所以"先短后长"是唯一稳妥路径。
8. 证书管理:三种形态的取舍
| 形态 | 覆盖范围 | 风险 | 适用 |
|---|---|---|---|
| 单域名证书 | 一个 FQDN | 数量爆炸,续期漏一个就断 | 域名极少 |
| 多域名(SAN) | 一组 FQDN | 任一新域名要重签所有节点 | 域名固定且集中 |
| 通配符 | 一级子域全部 | 一处泄露即全部可仿冒 | 子域动态、无法枚举 |
通配符不覆盖 a.b.example.com(多级子域),也不覆盖裸域,这两点是最常见的证书不匹配来源。
# 自动续期后的验收:不只看退出码,要看实际生效的证书与剩余天数
openssl s_client -connect TARGET:443 -servername TARGET </dev/null 2>/dev/null \
| openssl x509 -noout -subject -enddate
私钥位置比签发方式更重要:能读到私钥就能冒充该域名。判据是"私钥主机的管理员数量",不是文件权限 600。
9. 会话复用与 TLS 1.3 的 0-RTT
| 机制 | 省下的往返 | 风险 |
|---|---|---|
| Session ID / Session Ticket | 完整握手降为 1-RTT 或 0-RTT | Ticket 密钥轮换不当会削弱前向保密 |
| TLS 1.3 0-RTT | 首个请求即可带数据 | 可重放:早期数据可被攻击者截获重发 |
| 连接复用(keep-alive) | 完全省掉握手 | 长连接把证书轮换的生效时间推迟到下次建连 |
0-RTT 可重放,意味着非幂等请求都不该走 0-RTT——在终止层限制其范围,而不是在应用层做去重兜底。
10. 加解密对延迟的影响量级
| 项 | 典型量级 | 说明 |
|---|---|---|
| TLS 1.2 完整握手 | 约 2 RTT | 含证书链传输与验证 |
| TLS 1.3 完整握手 | 约 1 RTT | 证书链仍要传 |
| 会话恢复 | 0–1 RTT | 取决于票据命中 |
| 单请求加解密开销 | 亚毫秒到毫秒级 | 与报文大小、密码套件相关 |
决定体验的不是单次加解密,是握手次数:终止越靠前,后端越容易复用长连接,握手总数更少;后移到每个服务,握手数随服务数增长。
11. 与 SSO、可观测、合规的叠加
| 关注点 | 终止在前(CDN/网关) | 终止在后(应用/透传) |
|---|---|---|
| 单点登录 | 可在网关做统一重定向与 Cookie 域 | 每个应用各做一套 |
| 客户端证书认证 | 网关可见证书主体,可映射为身份 | 应用自行解析,标准不统一 |
| 可观测 | 能看到完整 URL、头、状态码 | 只能看到 SNI、IP、流量特征 |
| 合规(数据不出域) | 解密点集中在少数节点,好审计 | 解密分散,审计面变大 |
合规把拓扑推向两个相反方向:"中间节点不得解密"与"必须能审计内容"无法同时满足,只能分区——敏感流量透传,普通流量在网关终止。
12. 排错
| 现象 | 原因 | 处理 |
|---|---|---|
| 浏览器报证书不属于该域名 | 某一层仍在用旧证书或默认证书 | 逐层用 openssl s_client -servername 查实际证书 |
| 应用生成的所有链接都是 http | 未信任 X-Forwarded-Proto |
在终止层覆盖该头,应用侧收敛信任来源 |
| 客户端 IP 全是同一个地址 | 未配置可信网段还原 | 配 real_ip 或让入口写 X-Forwarded-For |
| 部分客户端握手失败 | 协议版本/套件过窄,或证书链缺中间证书 | 补齐 fullchain,保留 TLS 1.2 兼容 |
| 轮换证书后未生效 | 连接复用让旧会话继续 | 主动断开长连接或缩短会话票据有效期 |
13. 边界
| 场景 | 更合适的做法 | 理由 |
|---|---|---|
| 纯内网、单机部署 | 直接用自签证书或明文 | 没有中间人面,拓扑复杂度换不到收益 |
| 已有服务网格统一接管 | 交给网格的 mTLS 策略 | 自建设计会与平台策略冲突 |
| 强合规不可解密 | 全程 L4 透传,牺牲可观测 | 用流量特征与端侧日志补足 |
14. 自测
- "证书配在哪一层"背后其实要同时回答哪三个问题?
- 把终止点从应用前移到 CDN,内网段的性质发生了什么变化?
- 边缘支持 HTTP/3,是否意味着端到端都是 h3?为什么?
- 客户端自带的
X-Forwarded-For为什么能生效,入口该怎么处理? - HSTS 为什么必须"先短
max-age后长"? - 什么情况下透传(不解密)反而是更正确的选择?