Docker 现已支持为 GitHub Actions 配置 OpenID Connect(OIDC)连接。符合条件的 Docker Team、Docker Business 或 Docker Hardened Images 组织,可以让工作流使用按次签发的短期令牌完成认证,不再把 PAT 或 OAT 长期保存在 GitHub Secrets 中。
这项变化解决的不只是“少配置一个 Secret”。它把 CI/CD 的身份模型从“持有一串长期密码”改成“在满足仓库、分支和运行环境等条件时,为当前任务临时签发身份”。
认证链路发生了什么变化
传统 Docker 发布流程通常在 GitHub 仓库或组织中保存访问令牌:
- name: Log in to Docker
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
这种方式简单,但长期凭证会带来几个持续成本:
- 令牌需要创建、分发和定期轮换。
- Secret 一旦被错误打印、传递给不可信步骤或从依赖链泄漏,攻击者可能在令牌失效前反复使用。
- 多仓库共用一个令牌时,很难准确判断一次推送来自哪个工作流。
- 删除成员或迁移项目时,还要追踪遗留凭证。
采用 OIDC 后,GitHub Actions 在每次运行中向 GitHub 请求一个带签名的身份令牌。Docker 根据组织中配置的信任关系验证令牌声明,再授予短期、仅用于当前运行的访问能力。工作流结束或令牌过期后,即使令牌曾被截获,可利用窗口也会明显缩短。
OIDC 降低的是长期凭证风险,并不意味着工作流天然安全。第三方 Action、过宽的触发条件和不受保护的发布环境,仍可能借助合法签发的短期令牌执行未授权操作。
信任条件要比“允许这个仓库”更精确
OIDC 令牌通常包含仓库、组织、分支、Tag、环境和工作流等身份声明。实际可配置字段应以 Docker 组织控制台展示的连接规则为准,但生产环境可以按以下思路收紧:
- 只信任明确的 GitHub 组织和仓库,不使用宽泛通配符。
- 发布镜像时只允许受保护的
main分支或版本 Tag。 - 将生产推送绑定到 GitHub Environment,并启用审批或分支保护。
- 区分测试镜像与正式镜像,避免同一连接拥有不必要的仓库写权限。
- 固定第三方 Action 的版本,敏感场景可进一步固定到提交 SHA。
例如,一个合理的策略语义可以是:只有 acme/api 仓库中,由 v* Tag 触发并进入 production Environment 的任务,才能向正式镜像仓库推送。即使普通分支中的脚本被修改,也无法满足这组身份条件。
可以这样改造 GitHub Actions 工作流
下面的 YAML 展示 OIDC 工作流需要具备的核心结构。id-token: write 允许任务向 GitHub 请求 OIDC 令牌;它不是 Docker 仓库的直接写权限。
由于来源摘要没有给出 Docker 官方 Action 的具体输入参数,示例中的“令牌交换”步骤是一个明确标注的接入骨架:请用 Docker 组织控制台提供的官方 Action 或交换方式替换该步骤,不要自行猜测生产端点和请求格式。
name: Publish container image
on:
push:
tags:
- "v*"
permissions:
contents: read
id-token: write
jobs:
publish:
runs-on: ubuntu-latest
environment: production
steps:
- name: Check out source
uses: actions/checkout@v4
- name: Request GitHub OIDC token
id: github-oidc
shell: bash
run: |
set -euo pipefail
response="$(curl --fail --silent --show-error \
-H "Authorization: bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \
"${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=docker")"
token="$(jq -r '.value' <<<"${response}")"
test -n "${token}"
test "${token}" != "null"
echo "::add-mask::${token}"
echo "token=${token}" >> "${GITHUB_OUTPUT}"
# 接入假设:用 Docker 官方文档指定的 Action 或交换步骤替换这里。
# 不要把 OIDC JWT 写入日志、Artifact、缓存或普通环境文件。
- name: Exchange identity and log in to Docker
shell: bash
env:
GITHUB_OIDC_TOKEN: ${{ steps.github-oidc.outputs.token }}
run: |
set -euo pipefail
echo "Replace this step with the Docker-provided OIDC authentication method."
test -n "${GITHUB_OIDC_TOKEN}"
- name: Build image
run: docker build --tag docker.io/acme/api:${GITHUB_REF_NAME} .
# 完成 Docker OIDC 登录后再启用推送。
# - name: Push image
# run: docker push docker.io/acme/api:${GITHUB_REF_NAME}
运行前需要修改 docker.io/acme/api,并在 Docker 组织中创建与该 GitHub 仓库、Tag 和 Environment 相匹配的 OIDC 连接。完成官方登录步骤的接入后,再取消 docker push 的注释。
这里没有把 GitHub OIDC JWT 当作 Docker 密码直接传给 docker login。OIDC JWT 是身份断言,通常必须由信任方验证并交换为目标服务接受的短期凭证;把它直接当成 registry password 使用,既可能失败,也会混淆两个令牌的安全边界。
迁移时不要立即删除旧令牌
可以按低风险顺序推进迁移:
- 盘点使用 Docker PAT、OAT 或共享账号密码的仓库和工作流。
- 在 Docker 组织中建立 GitHub OIDC 连接,并从单个非关键镜像开始验证。
- 限制仓库、分支、Tag、Environment 和镜像仓库权限。
- 确认分支构建、外部 Pull Request 和未授权 Tag 无法获得推送能力。
- 观察一段时间的认证与发布记录,再撤销旧 PAT 或 OAT。
- 删除 GitHub Secrets 中的旧凭证,并检查可复用工作流、组织 Secret 和自托管 Runner 是否仍有副本。
Docker OIDC 的主要收益是缩短凭证寿命并减少人工轮换,但它不能替代最小权限、分支保护、Environment 审批和依赖审查。真正稳健的配置应让攻击者即使控制了普通分支,也无法构造一个满足生产信任条件的工作流运行。