用 Docker OIDC 取代 GitHub Actions 中的长期凭证

2026-08-01 25 预计阅读时间: 1 分钟
来源: docker.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:8 分钟

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 使用,既可能失败,也会混淆两个令牌的安全边界。

迁移时不要立即删除旧令牌

可以按低风险顺序推进迁移:

  1. 盘点使用 Docker PAT、OAT 或共享账号密码的仓库和工作流。
  2. 在 Docker 组织中建立 GitHub OIDC 连接,并从单个非关键镜像开始验证。
  3. 限制仓库、分支、Tag、Environment 和镜像仓库权限。
  4. 确认分支构建、外部 Pull Request 和未授权 Tag 无法获得推送能力。
  5. 观察一段时间的认证与发布记录,再撤销旧 PAT 或 OAT。
  6. 删除 GitHub Secrets 中的旧凭证,并检查可复用工作流、组织 Secret 和自托管 Runner 是否仍有副本。

Docker OIDC 的主要收益是缩短凭证寿命并减少人工轮换,但它不能替代最小权限、分支保护、Environment 审批和依赖审查。真正稳健的配置应让攻击者即使控制了普通分支,也无法构造一个满足生产信任条件的工作流运行。


相关推荐