把 CI 里的长期凭证换成 OIDC,以推镜像到 Vercel Container Registry 为例
9 月 25 日 Vercel 加了一个新的 GitHub Action:vercel/vcr-action/login。它用 workflow 自带的 OIDC 令牌换一张短时 Vercel 访问令牌,拿这张令牌登录 vcr.vercel.com,job 结束时登出并吊销。
要做的就三件事,然后是一段能直接粘的 workflow。
第一步:在 Vercel 上建 OIDC 策略
进 Vercel 团队设置 → Build and Deployment,加一条 VCR 类型的 OIDC 策略。三个字段是关键:
- Repository:精确到
owner/repo,不要用通配符 - Workflow:精确到文件名,例如
push-image.yml - 权限:Container Registry 的 read-write
策略只匹配这一个仓库的这一个 workflow。别的仓库、别的 workflow 拿不到令牌,这是整套方案的收口点。
第二步:在 GitHub 存变量
这四个都不含密钥,存成 repo variable(Settings → Secrets and variables → Actions → Variables)就行:
| 变量名 | 说明 |
|---|---|
VERCEL_TEAM_ID | 团队 ID,形如 team_xxxxxxxx,不是团队 slug |
VERCEL_TEAM_SLUG | 团队 slug,用在镜像 tag 里 |
VERCEL_PROJECT_SLUG | 项目 slug |
VCR_REPOSITORY | 你在 VCR 里的仓库名 |
最常见的翻车点就是第一个:VERCEL_TEAM_ID 要 ID,镜像 tag 里要 slug,两个长得不一样,填反了会报莫名其妙的鉴权错。
第三步:workflow
name: Push to VCR
on:
push:
branches: [main]
permissions:
contents: read
id-token: write
jobs:
push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Log in to VCR
uses: vercel/vcr-action/login@v1
with:
team: ${{ vars.VERCEL_TEAM_ID }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64
provenance: false
tags: vcr.vercel.com/${{ vars.VERCEL_TEAM_SLUG }}/${{ vars.VERCEL_PROJECT_SLUG }}/${{ vars.VCR_REPOSITORY }}:latest
outputs: type=image,push=true,oci-mediatypes=true,compression=zstd,compression-level=3,force-compression=true
id-token: write 是必需的,没有它 GitHub 不会签发 OIDC 令牌,login 步骤直接失败。
provenance: false 别删。默认的 provenance attestation 会让镜像多一层 VCR 目前不认的附件,推上去之后拉不下来。
要用 Podman 或 Buildah 而不是 Docker,给 login 步骤加 engines 参数。
推完之后,这个镜像可以直接当 Vercel Sandbox 的自定义镜像用,同一个项目里引用 <repository>:<tag> 即可。
怎么知道自己做对了
这是这篇的重点。跑通一次不算做对,下面 5 条全过才算:
-
仓库里没有长期凭证。
gh api repos/OWNER/REPO/actions/secrets --jq '.secrets[].name'输出里不该出现VERCEL_TOKEN、VERCEL_ACCESS_TOKEN之类的东西。有就删掉,然后确认 workflow 还能跑通——能跑通才说明真的没在用它。 -
关掉 OIDC 权限后必须失败。 把
permissions.id-token临时改成read,重跑。login 步骤一定要失败。如果还成功,说明你还在靠某个 secret 兜底,第 1 条没查干净。 -
非白名单来源必须被拒。 从另一个仓库复制同一份 workflow 跑,或者从一个 feature 分支(策略里只放行
main的)跑。应当被 OIDC 策略拒绝,而不是静默成功。 -
策略的匹配范围收得住。 回 Vercel 后台看那条策略,repository 和 workflow 都应该是精确值。写成
owner/*或留空的,等于把写权限开给了整个组织。 -
令牌是短时的。 job 结束后 action 会登出并吊销。你可以在本地试着拿日志里出现过的令牌去调 Vercel API,应该已经失效。如果隔天还能用,说明吊销没生效,这个方案对你就没有意义。
几个坑
- 区域和团队是两回事。 镜像 tag 里的路径顺序固定是
vcr.vercel.com/<team-slug>/<project-slug>/<repository>,别把 team ID 塞进去。 - 只支持
linux/amd64。 Sandbox 镜像目前只接受这个架构,加arm64会推上去但用不了。 - fork 的 PR 拿不到你想要的令牌。 来自 fork 的 workflow 权限默认是只读的,
id-token: write不生效。如果合作方要从 fork 推镜像,走pull_request_target得额外评估,别默认开。 - variables 不是 secrets。 团队 slug 这类信息放变量没有问题,但别顺手把任何真实令牌也放进去——它们在日志里是明文可见的。
- 这是刚上线 3 天的 action。 版本号写死
@v1,不要用@main。
顺带一句
这套东西的价值不在 Vercel 这一家。OIDC 换短时令牌现在已经是各家 CI 的主流做法——AWS、GCP、Azure、Cloudflare 都支持。你在一个地方配顺了,剩下几个是同一套心智:策略精确到身份,令牌短时,仓库里不留长期凭证。
真正要改的不是配置文件,是习惯。一个人维护十几个仓库的时候,最容易出现的不是被黑,是某个仓库里躺着一个两年前生成、谁也记不清还有什么权限的令牌。OIDC 把这个可能性从源头删掉了。
原文:https://vercel.com/changelog/vcr-login-github-action VCR 文档:https://vercel.com/docs/container-registry GitHub Actions 指南:https://vercel.com/docs/container-registry/github-actions