目标机:攀枝花政务云(Ubuntu 26.04.1),公网 117.172.73.106,入向只开 8081/8082。
2026-09-22 建成。**这是开发测试环境,不是生产**(无域名/备案/TLS;生产验收清单见docs/huifu/08-上线与环境.md §4)。
| 路径 | 内容 |
|---|---|
/data/talent-card/app.jar |
后端 fat jar(103 MiB)。**2026-09-29 更新到提交 61ba794**:V155 已应用(迁移 149 条、库版本 155、merchant_onboarding_application.business_address 列已建)。上一版留在 app.jar.bak-20260929-1326,回滚直接换回即可(V155 只加一列可空列,旧代码忽略它,安全) |
/data/talent-card/admin/ |
后台管理端 dist(nginx 托管) |
/data/talent-card/uploads/ |
上传落盘目录(对应 UPLOAD_DIR) |
/data/talent-card/src/ |
从 Gitblit 克隆的源码,服务器上构建 admin 用 |
/data/talent-card/redeploy-admin.sh |
admin 重部署脚本(见下) |
/data/talent-card/app-flyway-verify.log |
2026-09-22 空库跑 148 个迁移的原始证据 |
/etc/talent-card/env |
唯一的部署变量来源(600),systemd 的 EnvironmentFile |
/etc/systemd/system/talent-card.service |
后端单元(active + enabled) |
/etc/nginx/sites-available/talent-card |
8081 站点:/api/ /uploads/ 反代,/ 托管 dist |
/data/mysql |
MySQL datadir(从 /var/lib/mysql 迁入,AppArmor 走 local/usr.sbin.mysqld 覆盖) |
/var/lib/mysql |
迁移前的原始副本,**回滚用**,确认稳定后可删 |
/etc/talent-card/env,10 个键)改配置只改这个文件,不要在单元里写 Environment= —— systemd 里 EnvironmentFile 会覆盖同名Environment=,变量散两处迟早踩坑。改完 systemctl restart talent-card。
| 键 | 为什么必须有 |
|---|---|
MYSQL_URL |
默认值指向办公室开发库(192.168.110.13)。这里指向 127.0.0.1。**必须带 rewriteBatchedStatements=true**:不带时 batchUpdate 在驱动层退化成逐条发送,实测同一批 15.4 万行从 28.5 秒变成 986 秒(约 100 倍)。 |
MYSQL_USERNAME / MYSQL_PASSWORD |
业务账号 talent_card@localhost(caching_sha2_password)。密码只在这个文件里。 |
REDIS_PORT / REDIS_PASSWORD |
默认值是 6368 + 一个弱口令,与实际装的 Redis 不符 → Actuator 的 Redis 健康指示器会让 /actuator/health 永远 503。业务代码零调用 Redis,**但健康探针会用到**。 |
SPRING_FLYWAY_ENABLED |
application.yml 里 flyway.enabled: false 是硬编码的,测试环境要跟着迁移走,所以常开。 |
SERVER_ADDRESS |
后端只绑 127.0.0.1,对外一律经 nginx。 |
UPLOAD_DIR |
上传目录指到 /data(系统盘只有 40G)。 |
APP_PRIVACY_ENCRYPTION_KEY |
默认值是**仓库里公开的开发 key**。SensitiveDataCrypto 是 SHA-256(该字符串) 派生 AES key,所以任意字符串合法。**必须在写入任何隐私数据之前设**——事后换会让旧数据解不开。 |
CORS_ALLOWED_ORIGINS |
必须包含浏览器实际访问的 origin,哪怕是同源。 浏览器对同源的非简单请求(POST + JSON)也会带 Origin 头,而 ApiCorsConfig 只要看到 Origin 就按白名单校验,不在名单里直接 403 Invalid CORS request。漏了它的症状就是「登录 403」。当前值:http://117.172.73.106:8081、http://117.172.73.106:8082、http://www.dqrcfwpt.com:8081、https://www.dqrcfwpt.com(后者为将来上 TLS 预留)。**换域名 / 换端口 / 换协议都会换 origin,都要补** —— 2026-09-22 因为换域名又踩了一次。 |
allowed-origin-patterns(http://localhost:[*])没被覆盖,所以本地 vite / HBuilderX 调试来源照旧放行。
bash /data/talent-card/redeploy-admin.sh # 默认拉 main,可传分支
它做:git fetch → npm ci → npm run build → 替换 /data/talent-card/admin/。
高德 key 等构建期变量放 /etc/talent-card/admin-build.env(600),脚本会自动拷成构建用的 .env。
不要把 key 放进仓库:talent-card-admin/.gitignore 里只忽略了 *.local,**没有忽略 .env**。
之所以能在服务器上构建:Gitblit git://120.76.84.145/java/talent-card.git 匿名可读(已实测ls-remote / clone 都不需要凭据),package-lock.json 的 resolved 指向 registry.npmmirror.com
(可达)。服务器上装了 git 2.53.0 / nodejs 22.22.1 / npm 9.2.0。
命令通道单次只能带约 4KB,所以 dist(4.4MB)走不了命令通道——这是当初需要人工 SFTP 传包的原因,
现在用「服务器自建」绕过去了。
| 项 | 值 |
|---|---|
| 证书 | 华为云签发的 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 一条命令且自带自动续期) |
| 端口 | 8081 = 测试环境(完整功能);**443 = 生产预留,现在是占位页**(/data/talent-card/placeholder/index.html,正文「本域名对应的生产环境尚未部署」);80 只做 301 跳转到 https |
| 生产上线时 | 把 443 块里的 root 与 location 换成 include snippets/talent-card-locations.conf;,80 的 301 不用动。该域名上的 /api 目前返回 503 JSON,避免请求落到静态页被误解为可用 |
| 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、.scratch/pending-order-payment-flow/README.md 的 F1)。收敛靠读接口惰性查询 + 60 秒一轮的对账快车道,www.dqrcfwpt.com → 117.172.73.106(2026-09-22 解析)。**访问必须带端口**:http://www.dqrcfwpt.com:8081 —— 80/443 未备案开不出来,实测不带端口是超时。退域名 dqrcfwpt.comENODATA),要用不带 www 的形式得先补记录。server_name _ 是通配,所以域名访问无需改站点配置(实测 http://www.dqrcfwpt.com:8081/ 返回 200)。talent_card,字符集 utf8mb4 / utf8mb4_0900_ai_ci(与测试库同口径,ResetTestDatabase.java 只写 charset 不写 collate;**不要**照抄 init-local-mysql.sql 的 utf8mb4_unicode_ci,那是 5.7 开发库用的)。VALUES() is deprecated(1287)仍可运行,但该语法未来版本会被移除。adminV76__admin_operator_authentication.sql:44-47 会种一个账号,其口令哈希**经实测就是 123456**
(用 node 按 V76 的参数算 PBKDF2-HMAC-SHA256 / 210000 轮 / 盐 AQIDBAUG…,结果与迁移里写死的值逐字符相同)。where not exists 的写法意味着**每一个新库都会得到这个已知口令**。
所以每次用空库部署(包括将来的生产)之后,必须立刻把 admin 的口令换掉,否则服务商的安全扫描会
再次报「Web弱口令」,而且这次是真的能被登进去。
根治要改代码/加迁移(引导口令从 ADMIN_BOOTSTRAP_PASSWORD 注入 + 首登强制改密),
属后台账号体系、不属支付链路 —— 已按 AGENTS.md 写成.scratch/weak-credentials-hardening/issues/01-bootstrap-admin-and-password-policy.md。
在这台机器上,快速自查有没有遗留的弱口令账号:
python3 /root/check-weak-passwords.py # 拿 69 个常见弱口令撞库里所有 password_hash,只打印命中项
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 '<恢复命令>'自动回滚,清完立刻复检堡垒机通道。
在没有带外通道之前,一律不做。
告警内容:扫描器抓到一次成功登录 {"account":"admin","password":"123456"}。根因与仓库侧的修法见.scratch/weak-credentials-hardening/issues/01-bootstrap-admin-and-password-policy.md(引导账号 + 无强度策略)。
这台机器上的现状:
| 项 | 现状 | 结论 |
|---|---|---|
后台 admin 账号 |
已重置;用 /root/check-weak-passwords.py 拿 69 个常见弱口令撞 PBKDF2 哈希,**0 命中** |
✓ |
MySQL root / debian-sys-maint |
auth_socket,**不启用密码认证**(只能以本机 root 身份连) |
✓ |
MySQL talent_card |
32 位随机、四类字符齐全,仅 localhost,caching_sha2_password |
✓ |
| Redis | 32 位随机、四类字符齐全,仅监听 127.0.0.1 |
✓ |
/etc/mysql/** 里存的明文口令 |
无 | ✓ |
/etc/talent-card/env |
600 root |
✓ |
OS 账号 root / ubuntu |
两个都设了口令,且 ubuntu 在 sudo 组 |
⚠️ 见下 |
| sshd | PermitRootLogin yes + PasswordAuthentication yes |
⚠️ 见下 |
⚠️ 两条必须由人自己处理的:
credentials.txt 里(按约定不读进上下文),所以 root / ubuntu 的口令强度ubuntu 在 sudo 组,等于第二个 root)。PermitRootLogin —— 堡垒机就是用 root + 口令登录的,AGENTS.md 记的那次失联事故同类)。正确做法是把口令换强,而不是关入口。口令已按四类字符口径轮换(2026-09-22):MySQL 与 Redis 的口令都重生成过,现为 32 位、含大小写/数字/
特殊符号(符号只用 @%^*+_.-,避开会破坏 systemd EnvironmentFile 解析与 shell 展开的 $ " ' \ ! 和空格)。
验证方式是 /actuator/health 为 UP —— 这一条同时证明新 MySQL 口令(Hikari 建连 + Flyway 校验)
与新 Redis 口令都对,另外用新口令走 TCP 登录 MySQL、redis-cli -a 取到 PONG。
轮换流程(已实跑):
mysql -e "alter user \"talent_card\"@\"localhost\" identified by \"<新>\""/etc/talent-card/env 的 MYSQL_PASSWORD / REDIS_PASSWORD/etc/redis/redis.conf 的 requirepass → systemctl restart redis-serversystemctl restart talent-card → 验 health三个坑(都踩过):
redis-cli config set requirepass 会报 NOAUTH。 库上已经设过口令时,未认证不许改。redis-cli -a <旧> config set ...,要么**直接改配置文件再重启**(更省事,本次就是靠这一步生效的)。/root/talent-card-env.bak,旧的 .bak.2/.bak.3/.bak.4 与 env-before-rotate.bak 都删了。root@localhost 走 auth_socket,口令改坏了也能用 root 身份重设。另一个角度:告警详情页能看到完整请求体(含口令),是因为 8081 是纯 HTTP。口令换强了,
但**传输仍是明文**(浏览器→服务器、以及服务商的安全设备都能看到)。根治要 HTTPS(域名+备案)。
在此之前可选:后台管理端不放在公网端口上暴露。这条只是提醒,已决定接受公网暴露。