项目配置、API 内容加密与敏感数据脱敏
# 项目配置、API 内容加密与敏感数据脱敏
本轮新增三项已实现能力:后端环境配置文件、可切换的请求/响应 JSON 内容加密,以及独立的 fago-security 脱敏模块。生产强制启用内容加密,开发和测试默认关闭;上线推荐可信 HTTPS 入口与真实部署私钥,HTTP 部署时 API 进程启动打 WARN、不拒绝启动。
# 项目配置
后端 fago/configs/ 提供 config.development.yaml、config.test.yaml、config.production.yaml。APP_ENV 选择环境,默认 development。优先级是 环境变量 > 所选环境文件 > 内置默认值;不把不同环境叠加,不支持热重载。启动生成只读快照,所有后端进程共用数据库、Redis 与关闭期限配置,API 再校验 Cookie、Origin、私钥。
开发脚本从当前目录向上自动寻找 configs/ 或 fago/configs/。部署建议显式配置:
export APP_ENV=production
export APP_CONFIG_FILE=/etc/fago/config.production.yaml
export APP_PUBLIC_ORIGIN=https://admin.example.com
export APP_API_ENCRYPTION_KEY_ID=release-20261022
export APP_API_ENCRYPTION_PRIVATE_KEY_FILE=/etc/fago/api-private.pem
# APP_DATABASE_DSN、APP_REDIS_ADDR、APP_REDIS_PASSWORD 通过部署凭证管理注入,不提交真实值。
显式指定的文件缺失、未知/重复字段、无效 YAML 或非法值都会拒绝启动,错误链不携带文件原文。先解析 YAML 再展开标量 ${变量名},密码中的冒号和换行不会改变 YAML 结构。私钥相对路径以配置文件目录为基准。完整字段表见后端 configs/README.md。
| 项 | 开发/测试默认 | 生产配置 |
|---|---|---|
api_encryption.enabled | false | true,拒绝通过环境变量关闭 |
auth.cookie_profile | development-http,仅 loopback | 推荐 https 与精确 Origin;HTTP 时启动 WARN,刷新 Cookie 按 Origin 选用 __Secure- 或 dev_ |
api_encryption.suite | rsa-aes | 同左。APP_API_ENCRYPTION_SUITE 覆盖 YAML |
api_encryption.private_key_file | 不使用;手动启用加密时按套件提供 PEM | 外部 PEM,API 启动必填 |
api_encryption.key_id | 手动启用时设置 | 随部署/轮换提供 |
api_encryption.window_seconds | 120,允许 30–300 秒 | 同左;Redis 去重 TTL 为两倍窗口加 1 秒 |
内容加密套件缺省是 rsa-aes。只接受 rsa-aes 与 sm2-sm4。环境变量 APP_API_ENCRYPTION_SUITE 覆盖 YAML 的 api_encryption.suite;变量存在但为空时仍按未配置处理,填成 rsa-aes。rsa-aes 使用 RSA PEM,PKCS#1 或 PKCS#8,2048–4096 位,建议 3072 位。sm2-sm4 使用 SM2 PKCS#8 PEM。切换套件必须更换 PEM,已有密钥文件不会被改写成另一套件;文件类型与套件不符时启动失败。SM4 内容密钥和国密响应密钥都是 16 字节。SM2 与 SM4 的安全强度按 128 位计,短于现有 AES-256。文件权限不宽于 0600。不把私钥写入 YAML、前端或仓库;不自动生成生产临时私钥。多个 API 实例共享部署私钥与 Redis。当前按新私钥及 key_id 协调切换,客户端刷新页面获取新公钥,写请求不自动重放;暂不支持多把旧私钥并存的无缝轮换。
传输层 TLCP、口令 SM3,以及商用密码产品认证都未实现。本节只说明应用内容加密,不表示二级等保或商密认证已通过。
# API 加密协议
采用标准 JWE Compact Serialization(RFC 7516) (opens new window)。缺省套件 rsa-aes 的请求保护头是 RSA-OAEP-256 / A256GCM,响应保护头是 dir / A256GCM;这条路径的 Go 实现使用 go-jose/v4 v4.1.6,前端使用 jose v6.2.12,响应密钥 32 字节。sm2-sm4 的请求保护头是 SM2 / SM4-GCM,响应保护头是 dir / SM4-GCM,国密响应密钥为 16 字节。同一环境只启用发现结果中的一套,不接受客户端协商。禁止压缩、外部密钥 URL 或静态前端共享密钥。
- 前端通过同源 GET
/api/security/config获取enabled、protocol=fago-jwe-v1和suite(推荐 HTTPS)。缺省suite为rsa-aes。开启时额外返回公钥和窗口秒数:RSA 公开字段为kty/n/e/kid/alg/use,SM2 公开字段为kty/crv/x/y/kid/alg/use。端点与健康探测保持普通 JSON,并设置 no-store。发现失败不降级,10 秒超时;策略只在当前页面内存保存。 - 每个请求随机生成 24 字节请求号。
rsa-aes再生成 32 字节响应 AES 密钥;sm2-sm4的内容密钥和响应密钥都是 16 字节。JWE 内部 JSON 包含id、毫秒timestamp、大写method、URL 编码的path、query、原业务body、base64urlresponseKey。JWE 保护头固定alg/enc/kid/typ=fago-request+jwe,算法取值由套件决定。 - GET/HEAD 将 JWE 放
X-Fago-Payload,其它方法以Content-Type: application/jose发送 JWE 正文。生产 URL 禁止明文查询参数;查询参数进入密文。JWE 上限 32 KiB,业务正文上限 16 KiB,query 上限 2 KiB;当前 M1 仅适配 JSON,文件/流式协议归后续实现。 - 服务端校验认证标签、key_id、算法、方法/路径绑定、时间窗口和响应密钥,再通过 Redis SET NX 原子认领请求号。Redis 故障返回 503,重复或过期请求拒绝;不会因为加密成功就跳过认证、Origin、租户、会话和权限检查。
- 成功解密后的请求,包括权限拒绝、会话错误、重放拒绝和 panic 的统一 envelope,都使用该请求独立响应密钥加密,
Content-Type: application/jose、X-Fago-Encryption: fago-jwe-v1,保护头typ=fago-response+jwe、rid=请求号。HTTP 状态、requestId/授权版本头与 Cookie 保持原合同。无法解密时只返回固定格式错误,不返回业务内容;响应加密失败不得回退原业务明文。 - 前端先验标签及请求号,再解包业务码,因此现有 401001 单次刷新语义不变;401002/403 不刷新。重试恢复原逻辑 JSON 后重新生成请求号和密钥,不复用旧 JWE。缺失/损坏密文或响应号不匹配一律失败,不降级或自动刷新。
内容加密保护 JSON 正文与查询值;HTTP 头、Cookie、状态码与路径仍推荐由 TLS 保护。公钥发现推荐同源 HTTPS;允许 HTTP 时 API 启动 WARN。OpenAPI 同时描述开发 JSON 和生产 JWE 媒体类型;JSON schema 是解密后的业务合同。
# 敏感数据脱敏模块
fago/modules/fago-security 是第 14 个独立 Go 模块。脱敏发生在 DTO/日志出口,原始业务和数据库值不变,不影响查询、比较与认证。
| 规则 | 输出规则 |
|---|---|
| 手机号 | 标准 11 位保留前 3、后 4;其它格式全遮盖 |
| 邮箱 | 保留本地部分首字及域名;短值/异常格式全遮盖 |
| 身份证 | 18 位格式保留前 3、后 4;异常格式全遮盖 |
| 银行卡 | 12–19 位数字只保留后 4 |
| 姓名 | 按 Unicode 字符保留首字,其余遮盖;单字全遮盖 |
| 密码、token、Cookie、DSN、其它凭证及未知规则 | 全遮盖 |
Mask选择明确规则;空值保持空,畸形值不回退原文。Sensitive显式标注 DTO 字段,JSON、String/调试格式、slog 均只输出遮盖值。真实本人姓名是否展示由业务合同决定,M1 本人资料保留已授权的本人姓名。MaskJSON递归处理嵌套对象/数组的指定字段,保留大整数;凭证键始终全遮盖,外部规则不能放宽;无效 JSON 返回 Oops,绝不回退原文。RedactingHandler已接入 API/worker/管理进程日志,覆盖预绑定、Group、常见凭证命名与敏感字段;未审查的任意对象拒绝展开。自由文本仍禁止拼接密码、凭证、请求体或底层错误链。
上述是脱敏能力,不是可逆数据存储加密,也不允许绕过业务权限读取原始值。未来需要“显示原文”的业务功能时须另行定义权限和审计。
# 已运行的验证
- 14 模块 workspace race/vet、
GOWORK=off独立测试、原有 MySQL/PostgreSQL 身份/并发/迁移回归。 - Go 真实 RSA/AES 测试覆盖算法、完整性、key_id、方法/路径、过期/未来时间、私钥权限;统一 HTTP 出口覆盖成功、403、panic、重放、Redis 不可用及明文拒绝。
- 前端 36 项测试通过:7 项基础 JWE 覆盖 GET 查询、POST 正文、独立随机密钥、401 解密、请求号/密文校验、明文开发和发现失败;10 项组合测试分别对 admin/platform 验证并发及迟到 401 单刷新、每次重试重新加密、403/401002/刷新失败;7 项 Vue 页面竞争测试证明旧撤销/返回登录/列表结果不清除后来身份,保留正常退出及未确认提示;其余 12 项为原有身份/导航回归。
- 两个真实 Go API 分别连接专用 MySQL、PostgreSQL,使用 Node JOSE 跑完整业务合同;每库租户 15、平台 14 项,共 58 项加密业务响应验证,另验证重复密文与明文请求拒绝。
- 本机浏览器临时显式开启开发环境加密,验证 Cookie 会话重载恢复、个人资料读取和退出。随后恢复开发默认明文,生产强制加密;M1 原可信 HTTPS 浏览器门槛已由用户在 2026-10-23 的整体验收确认结项;本段 HTTP 验证仍单独保留其证据范围。

复现实际加密合同检查时使用 scripts/smoke-m1-contract.mjs,明确设置 FAGO_SMOKE_DISPOSABLE=1、本机 FAGO_API_URL、FAGO_SMOKE_ORIGIN、FAGO_SMOKE_ENCRYPTION=1 和专用测试账号环境变量。脚本会执行首次改密/撤销,禁止使用真实用户账号;只输出断言统计,不输出密码、token、Cookie 或请求原文。