SafeW如何为CI/CD流水线注入短期签名密钥?

2026年3月16日SafeW技术团队密钥管理
密钥注入CI/CD短期签名流水线配置安全
SafeW短期签名密钥注入方法, 如何为CI/CD流水线添加短期密钥, SafeW签名密钥配置步骤, CI/CD短期密钥与长期密钥区别, SafeW密钥注入失败排查, 短期签名密钥最佳注入时机, SafeW密钥生命周期管理, CI/CD安全签名方案

功能定位:为什么要在 CI/CD 里用“短命”密钥

传统做法把签名私钥长期放在仓库变量或 K8s Secret 里,一旦泄露就得全链路轮换。SafeW 的“短期签名密钥注入”把生命周期压缩到“Job 级”:流水线启动时向 SafeW 网关申请一张仅对该 Job 有效的签名证书,Job 结束立即吊销,私钥不落盘、不进缓存、也不写入环境变量文件。后文用“插件”简称这一机制。

插件与 SafeW 原有的“冷端 NFC 一碰签名”相互独立:前者服务自动化流水线,后者服务人工手机端。算法仅支持 ECDSA/secp256r1 与 Ed25519,RSA 不在范围内;有效期最短 5 min、最长 60 min,不可续期,到期后必须重新申请。

功能定位:为什么要在 CI/CD 里用“短命”密钥
功能定位:为什么要在 CI/CD 里用“短命”密钥

版本差异与迁移前提

截至最新版本(SafeW 控制台 6.4.2,网关 API 2026.02.28)才开放插件。若左侧菜单看不到“CI/CD 插件中心”,请让组织 Owner 在“设置-组织-实验功能”里手动开启“第三方流水线集成”开关;该开关默认关闭,且仅 Owner 角色可见。

老版本若使用“永久 API Key + 手动上传 keystore”,需先清理旧 Secret,再接入插件,否则会出现“双签名”导致链上验证失败。

控制台一次性配置:建立信任链

步骤 1:注册流水线身份

进入 SafeW 控制台 → 左上角切换至“组织视图” → CI/CD 插件中心 → 添加流水线。平台要求填写三项:

  • 流水线名称:仅做展示,建议与 GitLab/GitHub 仓库同名,方便溯源。
  • 可信域名:插件签发 JWT 时会校验 aud 字段,必须包含你的 CI 域名,例如 gitlab.example.com。
  • 公钥指纹:把流水线运行器预置的 SSH Ed25519 公钥粘贴进来,用于后续建立双向 TLS。

提交后,控制台返回“插件 ID + 插件 Secret”,Secret 仅展示一次,请立即写入 CI 的受保护变量(GitLab 叫 Protected Variable,GitHub 叫 Encrypted Secret),切勿写进 .yml。

步骤 2:配置吊销策略

在同一页面继续向下滚动,可设置“Job 成功/失败自动吊销”与“最大可签发数量”。经验性观察:若并发 Job 数超过 200,建议把“最大可签发”调到 500,否则网关会返回 429,导致排队时间拉长。

流水线接入:GitLab 示例

以下 .gitlab-ci.yml 片段演示如何为 Android APK 签名。核心思路:在 before_script 阶段调用 SafeW CLI 申请密钥,签名完成后在 after_script 阶段主动吊销。

variables:
  SAFEW_PLUGIN_ID: $SAFEW_PLUGIN_ID  // 受保护变量
  SAFEW_PLUGIN_SECRET: $SAFEW_PLUGIN_SECRET  // 受保护变量

.inject_key:
  image: safew/cli:6.4.2
  before_script:
    - safew ci inject --aud gitlab.example.com --validity 10m --out /tmp/sign.key
  after_script:
    - safew ci revoke --key-file /tmp/sign.key || true

build:
  extends: .inject_key
  stage: build
  script:
    - ./gradlew assembleRelease
    - apksigner sign --ks /tmp/sign.key --ks-pass pass:empty --out signed.apk app-release-unsigned.apk
  artifacts:
    paths: [signed.apk]

注意:--ks-pass 必须给空口令,插件返回的密钥已受会话密钥加密,CLI 在内存中解密后喂给 apksigner,全程不落盘。

流水线接入:GitHub Actions 差异点

GitHub 环境缺少原生“after_script”,需把吊销步骤写在独立 job,并用 always() 保证触发:

jobs:
  sign:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Inject key
        id: inject
        run: |
          safew ci inject --aud github.com --validity 10m --out $RUNNER_TEMP/sign.key
          echo "key=$RUNNER_TEMP/sign.key" >> $GITHUB_OUTPUT
      - name: Sign artifact
        run: |
          apksigner sign --ks ${{steps.inject.outputs.key}} --ks-pass pass:empty --out signed.apk unsigned.apk
      - name: Upload
        uses: actions/upload-artifact@v4
        with: {name: signed-apk, path: signed.apk}
  revoke:
    needs: sign
    if: always()
    runs-on: ubuntu-latest
    steps:
      - run: safew ci revoke --key-file ${{needs.sign.outputs.key}}

经验性观察:GitHub 并发上限 256,若组织仓库较多,建议把 --validity 调到 15 min,给排队留缓冲。

与自建 Jenkins 的协同

Jenkins 用户需先在“凭据存储”新增类型为“SafeW Plugin Secret”的条目(插件已上架 Jenkins Update Center,搜索“SafeW Short-Lived Signing”)。随后在 Pipeline 中:

stage('Inject Key') {
  steps {
    safewInject validity: 10, unit: 'MINUTES', audience: 'jenkins.example.com'
  }
}
stage('Sign') {
  steps {
    sh 'apksigner sign --ks $SAFEW_KEY_FILE --ks-pass pass:empty --out signed.apk unsigned.apk'
  }
}
stage('Revoke') {
  steps {
    safewRevoke() // 无论上游是否失败都执行
  }
}

注意:Jenkins“回放”功能会重复执行 Pipeline,若手动点击“Replay”,同一 Job ID 会多次申请密钥,触发 SafeW 侧“重放保护”返回 409。解决方法是临时调高“最大可签发”或改用不同 Job 名称前缀。

与自建 Jenkins 的协同
与自建 Jenkins 的协同

例外与取舍:什么时候不该用

1. 需要 RSA 2048 且链上合约只认 RSA,插件无法支持,只能回退永久密钥。
2. 流水线运行环境完全离线(冷端),插件需访问 SafeW 网关,显然不满足。
3. 签名过程必须人工二次确认(如金融 U 盾),插件的自动化特性与合规冲突。
4. 单次 Job 运行时间超过 60 min,插件最大有效期仍不够,需把大任务拆段,中间重新申请。

警告

若把插件返回的密钥文件误写入 artifacts 并公开发布,等于把私钥泄露到互联网。SafeW 虽会强制吊销,但已签名文件无法撤回,请务必在 artifacts 上传前把 /tmp/sign.key 加入 exclude 列表。

故障排查速查表

现象最可能原因验证方法处置
CLI 返回 403aud 字段与控制台可信域名不一致对比 --aud 参数与控制台修改 .yml 或控制台任一侧域名
429 Too Many并发超限观察控制台“实时签发数”临时调高上限或降低并发
apksigner 报“keystore 损坏”--ks-pass 给了非空口令重新运行并加 -v改成 pass:empty
吊销失败 404Job 被手动取消,密钥已自动吊销查看网关日志忽略即可,属预期行为

验证与观测方法

1. 在流水线末尾加打印:keytool -list -keystore /tmp/sign.key -storepass empty,可见证书有效期仅剩几分钟,证明“短命”生效。
2. 在 SafeW 控制台“审计日志”搜索流水线 ID,可拉出完整链路:签发时间、吊销时间、对应 Job URL,方便合规抽查。
3. 若想量化提速,可在旧流程与新流程各跑 30 次,记录“签名环节”耗时。经验性观察:网络稳定时,新流程平均缩短数十秒,主要省掉下载永久 keystore 的解密环节。

适用/不适用场景清单

  • ✅ 移动 App 日构建 200+ 次,需要快速签名并上传测试分发平台。
  • ✅ 微服务镜像内嵌 SBOM 签名,每次构建镜像即重新签名,无需长期保管私钥。
  • ❌ 嵌入式固件签名后需保存 10 年供监管审计,要求密钥归档,插件自动吊销不符合法规。
  • ❌ 链上 NFT 合约只认 RSA 4096,插件算法不支持。

最佳实践 6 条

  1. 把 --validity 设为 Job 历史最长耗时再留 30% 缓冲,既省配额又防超时。
  2. 一个仓库只注册一个插件 ID,多分支用 Job 名称区分,方便审计。
  3. 吊销步骤必须加 always() 或 post-always,防止失败分支把密钥遗留在内存。
  4. 不要把插件 Secret 用于本地调试,本地请用 SafeW 桌面端“临时签名”功能,避免审计日志混杂。
  5. 每月检查一次“最大可签发”利用率,高于 80% 就调大,低于 20% 就调小,节省组织级配额。
  6. 若同时使用 GitLab 与 GitHub,给不同平台创建不同插件 ID,万一某平台泄露可单独吊销,不影响另一方。

FAQ(结构化数据)

插件支持哪些算法?

目前仅支持 ECDSA/secp256r1 与 Ed25519,RSA 全系不在路线图内。

有效期最长能调多少?

控制台上限 60 min,不能再长;若 Job 耗时更长,需拆分阶段重新申请。

吊销失败会不会产生费用?

SafeW 对成功签发计费,吊销无论成败都不额外收费;但重复 404 可能提示你早已自动吊销,可忽略。

可以同时在多个组织复用同一个插件 ID 吗?

插件 ID 与组织绑定,跨组织无法复用;若搬迁仓库,需要在新组织重新注册。

网关 429 后会不会自动重试?

CLI 内置指数退避,最大 3 次;若仍失败需人工调高配额或降低并发。

收尾:下一步行动清单

读完本文,你已了解 SafeW 短期签名密钥注入的边界、成本与收益。建议先用非核心仓库做 1 周灰度,确认配额与并发无冲突后,再逐步将生产流水线切过来;切换前务必把旧永久密钥从变量库删除,防止“双轨”期误用。最后,把“故障排查速查表”贴到团队 Wiki,下次遇到 403/429 就能秒定位。未来版本若开放 RSA 或延长有效期,官方会在控制台 changelog 首条公告,记得打开“组织-消息通知”即可第一时间获得推送。