yeg
2026-09-29 3b9c05861a8732def69cffd7359ddecacb542e74
docs(server): 补 HTTPS 上线记录与 nginx TLS 配置

nginx 站点新增 443(华为云 DV 证书,用含中间链的 bundle)+ 80 改 301 跳转 + HSTS max-age=86400。HSTS 刻意分两步:这张证书是人工续期的,一次给一年又忘续会把访问者锁在硬错误页上,等第一次续期成功后再提到 1 年并考虑 includeSubDomains。

台账记下:证书路径与到期日(2027-02-11)、人工续期提醒、HSTS 取舍理由,以及顺带解锁的两件事 —— 汇付五个回调地址可以用真实公网 HTTPS 了(F1 长期项)、小程序服务器域名可以填 https://www.dqrcfwpt.com。待办:退域名 dqrcfwpt.com 缺 A 记录;建议请云管理方关掉 8081。
2个文件已修改
145 ■■■■■ 已修改文件
.scratch/server-testenv/README.md 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.scratch/server-testenv/nginx-talent-card.conf 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.scratch/server-testenv/README.md
@@ -55,6 +55,35 @@
> 命令通道单次只能带约 4KB,所以 dist(4.4MB)走不了命令通道——这是当初需要人工 SFTP 传包的原因,
> 现在用「服务器自建」绕过去了。
## HTTPS(2026-09-29 已上线)
| 项 | 值 |
|---|---|
| 证书 | 华为云签发的 DV(TrustAsia 签发)。`/etc/nginx/ssl/talent-card/server.crt` 是**含中间链的 bundle**(4451 字节),`server.key` 权限 `600` |
| 覆盖域名(SAN) | `dqrcfwpt.com` + `www.dqrcfwpt.com` |
| **到期** | **2027-02-11** ⚠️ **不会自动续到本机** —— 到期前在华为云重新下载再传一次(或那时改用 Let's Encrypt:80 已通,certbot 一条命令且自带自动续期) |
| 端口 | 443 是正式入口;**80 只做 301 跳转**;8081 保留(见下) |
| HSTS | `max-age=86400`(1 天),**没有** `includeSubDomains` / `preload` |
| 公网实测 | 链 2 张、`Verify return code: 0 (ok)`、`curl` 不带 `-k` 返回 200、`http://` → 301、HSTS 头存在、`https://` Origin 的登录请求不再 403 |
**HSTS 为什么先只给 1 天**:这张证书是**人工续期**的。若一次给到一年又忘了续,浏览器会把访问者锁在硬错误页上、
连"点继续"的逃生口都没有。**等第一次续期成功之后**再把 `max-age` 提到 `31536000`,并考虑 `includeSubDomains`。
不加 `includeSubDomains` 的另一个原因:将来若新增子域(例如 OSS 的 `img.`)而它还没配好 HTTPS,
HSTS 会把那个子域一并锁死;`preload` 更是单向门,提交后很难退出。
**顺带解锁的两件事**(都在对方侧操作):
- **汇付的五个回调地址**现在可以是真实公网 HTTPS 了,形如
  `https://www.dqrcfwpt.com/api/payments/huifu/notify`——这解掉了 `.scratch/pending-order-payment-flow/README.md`
  里 F1 那个长期项。**要在汇付控台逐条注册**,且与本地 yml 的 `HUIFU_*_NOTIFY_URL` 保持一致。
- **微信小程序的服务器域名**可以填 `https://www.dqrcfwpt.com`(request / uploadFile / downloadFile 都要)。
**还没做的:**
- 退域名 `dqrcfwpt.com` **没有 A 记录**(公网解析实测 `ENODATA`)→ `https://dqrcfwpt.com` 用不了。
  在腾讯云补一条 `@` 的 A 记录即可(**证书已覆盖它,不用重签**)。
- **8081 仍是全公网可达的明文端口**,上面挂着后台 API。HTTPS 已稳定,建议请云管理方关掉它;
  在那之前它仍是唯一的明文入口。
- `CORS_ALLOWED_ORIGINS` 里没有 `http://www.dqrcfwpt.com`(80 那条)——**已无关紧要**,因为 80 现在 301 到 https。
## 已知边界
- **收不到汇付回调**:非公网环境的既定事实与既定设计(`docs/adr/0004-active-query-is-authoritative.md`、
@@ -88,6 +117,46 @@
python3 /root/check-weak-passwords.py     # 拿 69 个常见弱口令撞库里所有 password_hash,只打印命中项
```
## ⚠️ docker 的防火墙残留还在内存里 —— **不要去清它**
`docker` 2026-09-21 已彻底卸载(包 0 条、`/etc/docker` 与 `/data/docker` 已删、网桥已删、路由干净),
但 **iptables/nftables 里 docker 留下的链与规则仍在运行内存中**:
```
链:DOCKER / DOCKER-USER / DOCKER-FORWARD / DOCKER-CT / DOCKER-INTERNAL / DOCKER-BRIDGE
-A POSTROUTING -s 172.18.0.0/16 ! -o br-bced14232775 -j MASQUERADE
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A DOCKER ! -i br-bced14232775 -o br-bced14232775 -j DROP     (docker0 同款)
```
**已逐条核实为「当前无害」**:网卡只剩 `ens5` / `lo`,规则引用的 `docker0`、`br-bced14232775` 都不存在,
所以按出接口匹配的那些 DROP/ACCEPT **永不命中**;两条 MASQUERADE 只匹配源 `172.17`/`172.18.x`,而本机是
`172.192.19.1`,**不命中**(对堡垒机那条路也无影响:入向包不走 POSTROUTING,我们的回包源也不是 172.18.x)。
另外 `iptables -S INPUT` 只有 `-P INPUT ACCEPT`、没有任何规则,`ufw` 是 `inactive` ——
**本机对入向 80/443 不设限,那是云安全组挡的,与这些残留无关。**
**不要为了「清理干净」去动防火墙。** 收益是零(现在无害),风险是切断唯一入口:这台机器**没有 console/VNC**,
唯一管理通道是堡垒机。`AGENTS.md` 第一条纪律划的就是这条边界。
**而且根本不需要清 —— 它不持久:**
| 检查 | 结果 |
|---|---|
| `iptables-persistent` / `netfilter-persistent` | **没装** |
| `nftables.service` | 存在但 **disabled** → 开机不加载 nft 规则 |
| `/etc/nftables.conf` | 镜像自带的存根;服务 disabled,开机不会读它 |
| 开机时间 | 2026-09-20 17:59(截至 09-22 已连续运行 1 周 21 小时)|
**下一次重启时内核从空规则表起来,这些就自然消失了。不必为它专门安排重启。**
**这条也是 `AGENTS.md`「别再装 docker」的又一实证**:包卸了、目录删了、网桥没了,防火墙规则却还在内存里 ——
如果哪天有人重建 `docker0` 或把 `172.17`/`172.18` 网段带回来,这些规则会**立刻开始生效**,
而 `172.18.0.0/16` **正是堡垒机所在网段**(2026-09-21 失联事故的成因)。
> 如果将来**确实**要清(例如审计明确要求),前提是**先具备带外通道**(请服务商提供 console/VNC 或临时开通),
> 并挂 `systemd-run --on-active=5min /bin/bash -c '<恢复命令>'` 自动回滚,清完立刻复检堡垒机通道。
> **在没有带外通道之前,一律不做。**
## 凭据现状(2026-09-22 自查,因服务商「Web弱口令」告警)
告警内容:扫描器抓到一次成功登录 `{"account":"admin","password":"123456"}`。根因与仓库侧的修法见
.scratch/server-testenv/nginx-talent-card.conf
@@ -1,44 +1,60 @@
# 东区人才卡 —— 测试环境 nginx 站点(2026-09-22)
# 东区人才卡 —— 站点(2026-09-22)
#
# 部署位置:/etc/nginx/sites-available/talent-card  (再软链到 sites-enabled/)
#
# 为什么监听 8081:这台机器公网入向只开了 8081/8082,80/443/8080 都要备案号才能开。
# 三个 server 块共用 snippets/talent-card-locations.conf,
# 所以「前端托管 + /api 反代 + /uploads 反代」只有一份定义,加端口不会漂移。
#
# 为什么前端和 API 走同一个端口:admin 的 .env.example 里 VITE_API_BASE_URL 默认留空
# (同源,注释写明「留空即可避免浏览器跨域」)。同源之后 CORS 那一堆白名单就不必配了。
# 8081:备案前的唯一入口,现在保留(堡垒机/内网调试也走它)。
#       ⚠️ 它仍然是**全公网可达的明文端口**,上面挂着后台 API;HTTPS 稳定后建议请云管理方关掉。
# 80  :只做 301 跳转到 https。
# 443 :正式入口。证书是华为云签发的 DV 证书(TrustAsia 签发,链:叶子 → TrustAsia DV TLS RSA CA 2025
#       → DigiCert Global Root G2),SAN 覆盖 `dqrcfwpt.com` 与 `www.dqrcfwpt.com`。
#       ssl_certificate 必须指向**含中间链的 bundle**,不能只给叶子证书。
#
# 证书到期:2027-02-11。**华为云的证书不会自动续到本机**,到期前要在华为云重新下载再传一次
# (或那时改用 Let's Encrypt —— 80 已通,certbot 一条命令且自带自动续期)。
#
# HSTS 目前是**第一步**:max-age=86400(1 天),且**没有** includeSubDomains / preload。
# 观察一段时间没问题后,再把 max-age 提到 31536000(1 年)。不加 includeSubDomains 的原因:
# 一旦哪天新增子域(例如 OSS 的 img.)还没配好 HTTPS,HSTS 会把那个子域也锁死;而 preload 是单向门,更难退出。
# 第一次成功续期之后再考虑这两项。
server {
    listen 8081;
    server_name _;
    include snippets/talent-card-locations.conf;
}
    # 后端 spring.servlet.multipart.max-file-size 是 10MB,而 nginx 默认只放 1MB,
    # 不抬高的话图片上传会在 nginx 这层就 413,看后端日志什么都没有。
    client_max_body_size 20m;
# 80:只做跳转,不再直接提供内容
server {
    listen 80;
    server_name www.dqrcfwpt.com dqrcfwpt.com;
    return 301 https://$host$request_uri;
}
    # 进件/开通这类要打通道的接口慢,留出余量
    proxy_read_timeout 120s;
    proxy_send_timeout 120s;
server {
    listen 443 ssl;
    http2 on;
    server_name www.dqrcfwpt.com dqrcfwpt.com;
    location /api/ {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
    ssl_certificate         /etc/nginx/ssl/talent-card/server.crt;
    ssl_certificate_key     /etc/nginx/ssl/talent-card/server.key;
    # 同一份 bundle:叶子 + 中间链,供 OCSP stapling 校验用
    ssl_trusted_certificate /etc/nginx/ssl/talent-card/server.crt;
    # 上传的图片由后端自己托管(UploadResourceConfig 把 /uploads/** 映射到 app.upload.directory),
    # 所以这里只转发,不要用 alias 指向磁盘 —— 两处指向同一个目录早晚会不一致。
    location /uploads/ {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    ssl_protocols             TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;
    ssl_session_cache         shared:SSL:10m;
    ssl_session_timeout       1d;
    # 后台管理端 SPA(dist 尚未部署,目录里先放占位页)
    location / {
        root /data/talent-card/admin;
        try_files $uri $uri/ /index.html;
    }
    # stapling 需要能解析 OCSP 响应者的域名,没有 resolver 时它会静默不生效
    resolver 223.5.5.5 119.29.29.29 valid=300s;
    ssl_stapling on;
    ssl_stapling_verify on;
    # HSTS 第一步:1 天,先观察
    add_header Strict-Transport-Security "max-age=86400" always;
    include snippets/talent-card-locations.conf;
}