• xiye's avatar
    fix(uas): JWT 私钥不再随 fat jar 发布 + 测试服改用外置密钥(第二轮独立复核 B-1/B-2/M-1) · c591893a
    xiye authored
    发现(2026-09-27 第二轮独立复核,逐条可复现):
    - 测试服 /oauth2/jwks 公布的公钥就是仓库里那把历史上已随版本库公开的开发私钥
      (实测:jar 内嵌公钥 modulus == 运行时 JWKS 公钥;外置挂载的那把根本没被使用);
    - 原因是容器跑 test profile,而 application-test.yml 只覆盖 issuer,基础配置仍是
      classpath:keys/jwt/*.pem;compose 注入的 UAS_JWT_* 当时没有任何配置项消费;
    - 且 HEAD 打包仍把 private-key.pem 打进 BOOT-INF/classes/** —— 拿到 jar 或仓库历史
      的人可以伪造任意用户令牌(UAS 签发、CMS/devprogress/push 都信任同一把公钥)。
    
    修复:
    - 删除 src/main/resources/keys/jwt/*.pem;evelan-auth-boot/pom.xml 显式排除
      keys/** 与 **/*.pem,fat jar 内不再有任何私钥(构建后实测 pem 条目 = 0);
    - 密钥改为三档外置注入:dev 用 $HOME/.evelan/keys/jwt(新增 scripts/gen-jwt-keys.ps1
      生成,start-uas-dev.ps1 在缺密钥时自动生成);test/prod profile 用无默认值的
      UAS_JWT_*(缺配启动失败,fail-fast);集成测试改用 src/test/resources/keys/test-jwt
      下的新密钥对(与历史泄露那把无关);
    - JwtProperties 增加启动自检:key-id / 公钥位置 / 私钥位置若仍是未解析的占位符,
      直接启动失败。实测坑:Spring 对无法解析的占位符是"原样保留",compose 漏传
      UAS_JWT_KEY_ID 时不报错,而是把字面量当 kid 发到 /oauth2/jwks;
    - 回归守卫:JwtKeyMaterialPackagingTest(类路径或 main 资源里再出现 pem 即变红)+
      JwtPropertiesValidationTest(回退成静默兜底即变红)。
    
    验证:
    - 单测:evelan-auth-web 9/9(JwtConfigTest 5 + JwtPropertiesValidationTest 4)、
      evelan-auth-boot 35/35(含 oauth2it 集成测试 14 例,已换用测试专用密钥);
      evelan-auth-web 全量 436 例里 3 例 Testcontainers 用例因本机无 Docker 报环境错,
      与本改动无关(同机上一轮日志中该 3 例为通过);
    - 测试服部署(docker compose -p evelan-infra -f docker-compose.infra.yml
      -f docker-compose.agent.yml --env-file .env.test up -d --build --no-deps evelan-uas)后:
      容器内 jar sha 与本地一致(c58b3fbb3f502d89)、jar 内 pem 条目 0、
      JWKS kid=evelan-auth-rsa-key-20260912-030509 且 modulus == 外置挂载公钥、
      四容器 healthy、启动 ERROR 0;
    - 重启三宿主刷新 JWKS 缓存(12h)后回归:UAS UI 冒烟 6/6、push WS 7/7、
      push 管理端 6/6、越权探针 10/10、LinkDev 远端 E2E 59 通过/0 失败/11 跳过、
      CMS 远端 E2E 11 通过/0 失败/3 跳过。
    c591893a