自托管 Runner 上的云凭据迟早会漏:用 GitHub Actions OIDC 换掉长期 AK/SK,信任边界还剩哪些
有一次我们把 AWS 的长期 access key 塞进 GitHub Actions 的 workflow secrets,供自托管 runner 上的部署脚本调用 S3。结果某天日志里完整打出了 AK/SK——一个 print 环境变量的调试分支被推到 develop 触发。key 轮换只要几分钟,但真正的问题是自托管 runner 长期挂着这套凭据:runner 一旦被攻破,或者工作流被恶意 PR 触发,仓库能碰到的凭据就全白给了。后来我们把所有云凭据从 runner 撤走,改用 OIDC 联合身份,云访问变成几分钟内自动过期的短时令牌。
GITHUB_TOKEN 与凭据泄漏的根因
GitHub Actions 里每个 job 默认拿到的 GITHUB_TOKEN 是临时、仓库级、默认只读的,并受 workflow 的 permissions 块约束。问题通常出在两处:
- 把长期凭据(PAT / 云 access key)塞进 secret;
- 把敏感 action 用的 secret 设成整个 workflow 共享。
凡是 job 能读到的 env/secret,等价于能运行该 job 的任意代码都能读到。对仓库有 push 权限、或能开 PR 触发 workflow 的人,在自托管 runner 上就是任意代码执行。
OIDC 怎么把凭据变短时
做法是让 workflow 的 job 声明 permissions: id-token: write,GitHub 为该 job 签发一个 JWT。action 通过 ACTIONS_ID_TOKEN_REQUEST_URL 与 ACTIONS_ID_TOKEN_REQUEST_TOKEN 两个环境变量去换令牌。云侧(AWS/GCP/Azure/Vault 等)把自己的 IdP 配置指向 GitHub 的 JWKS,并校验 JWT 里的 claims:repository、repository_owner、ref、job_workflow_ref 等。
这样「哪个仓库、哪个分支、跑哪个 workflow 才能 assume 哪个角色」的判定放在云侧。凭据本身是 5 分钟量级、随 job 消失,runner 上不需要持久存放任何长期 key。
AWS 最小配置
workflow 侧:
permissions:
id-token: write
contents: read
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/my-deploy-role
aws-region: ap-northeast-1
aws-actions/configure-aws-credentials 会自动换取 OIDC 令牌并 assume role。云侧在 role 的 trust policy 里限定:
{
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:acme/web:ref:refs/heads/main"
}
}
}
几个要点:
aud和sub都要锁。只锁aud,等于任何该 IdP 账号下的角色都能 assume。sub用StringLike限定到仓库与 ref 级别,不要用宽泛的*。
Vault 的思路类似:配置 role 时把 bound_audience 设为 actions.github.com,并绑 bound_claims 的 repository/ref。
边界与不适用
OIDC 只替换云凭据,不保护 runner 本身。
- runner 仍是工作流代码的执行环境。恶意或被攻破的 job 照样能读 workflow 里暴露的其它 secret、能拿到 job 的
GITHUB_TOKEN。OIDC 解决的是「长期云 key 常驻 runner」的问题,不解决「不该信的任务跑在信得过的 runner 上」的问题。 - 信任边界要单独收。不要拿自托管 runner 跑不受信的公共仓库或开放 PR:任何能开 PR 的人都能触发 workflow 在你机器上跑任意代码。规范做法是按信任等级分开 runner 池(受信 repo / 半受信 / 不受信隔离),不受信仓库直接用 GitHub-hosted runner。
- 自托管 runner 需要能出网到
tokens.actions.githubusercontent.com换令牌。纯内网、断外网的 runner 用不了这套,只能退回受管 secret 或自建 OIDC broker。 - 仍然不要把任何 secret 设成 workflow 级全局可见。能放 step/env 级就缩小范围,
permissions按最小集给contents/packages权限。 - 审计要对齐。OIDC assume 与 runner job 都要留日志,把「谁在哪个 repo/ref 以哪个角色动了云资源」串起来。否则短时令牌只是把泄漏窗口从「永久」缩到「几分钟」,追责仍要靠云侧 CloudTrail/审计日志。
官方文档见 About security hardening with OpenID Connect。机制与 claims 字段以官方为准,具体版本行为需在自建 runner 上实测。