GitHub Actions 企业级治理:组织级 Secrets、Runner Groups 与合规策略

GitHub Actions 企业级治理实战:仓库级、组织级、企业级三级 Secrets 与可见性优先级、组织级 Variables 与 vars、Environment 审批门禁、Runner Groups 与自托管调度、actions 白名单与 GITHUB_TOKEN 默认权限、OIDC 免密上云、audit log 审计、Terraform 策略即代码与 Actions 用量成本治理

当 GitHub Actions 从「个人项目跑测试」走向「企业几百个仓库共用一套流水线」,治理问题就会盖过技术问题:密钥散落在每个仓库里轮换不动、第三方 action 直接引用 @master、GITHUB_TOKEN 默认写权限能推代码、自托管 runner 谁都能调度、审计时拿不出谁在什么时候改了 workflow。本文从三级 Secrets 层级讲到 Runner Groups、组织策略、OIDC 免密上云与审计合规,给出一套可以直接落地的企业级治理方案。


一、三级 Secrets 层级与可见性

1.1 三个层级

GitHub 的 Secrets 分为仓库级、组织级、企业级三层,作用范围与权限要求逐级上升。

仓库级 Repository secrets
  范围:仅当前仓库
  权限:仓库 Admin
  适用:单仓库独有的部署密钥、第三方 token

组织级 Organization secrets
  范围:按可见性分发给组织内仓库
  权限:组织 Owner
  适用:跨仓库共享的通用凭证(如 Slack webhook、云账号只读 token)

企业级 Enterprise secrets
  范围:企业账户下所有组织
  权限:企业 Owner
  适用:集团级统一凭证、合规要求的集中管控

1.2 组织级 Secret 的可见性策略

组织级 Secret 创建时必须选择可见性,这是最容易配错的一环。

All repositories       所有仓库(含未来新建)可用
Private repositories   仅私有仓库可用(公开仓库读不到)
Selected repositories  手动勾选白名单仓库

用 gh api 批量创建并限定可见性:

# 创建组织级 secret,仅对白名单仓库可见
gh api --method PUT \
  /orgs/my-org/actions/secrets/SLACK_WEBHOOK \
  -f encrypted_value="$(echo -n 'https://hooks.slack.com/xxx' | base64)" \
  -f key_id="$(gh api /repos/my-org/my-repo/actions/secrets/public-key -q .key_id)" \
  -f visibility="selected" \
  -f 'selected_repository_ids[]=123456'

# 查看某 secret 的可见性
gh api /orgs/my-org/actions/secrets/SLACK_WEBHOOK -q '.visibility'

注意:encrypted_value 必须用仓库公钥做 libsodium sealed box 加密,上面用 base64 只是占位示意,真实场景要用 gh secret set 或 SDK 加密。

1.3 同名覆盖优先级

三层出现同名 Secret 时,就近覆盖:

仓库级 > 组织级 > 企业级

即 workflow 里写 ${{ secrets.API_TOKEN }} 时,
若仓库定义了 API_TOKEN,组织/企业级同名值被完全遮蔽。

踩坑提示:排查「为什么 CI 用的 token 是旧的」时,第一件事就是确认仓库级是否残留了一个同名 Secret 把组织级的新值盖住了。GitHub UI 不会提示遮蔽关系。


二、Variables 与 vars 上下文

2.1 Secrets 与 Variables 的差异

很多人把所有配置都塞进 Secrets,其实非敏感配置应该用 Variables。

Secrets                         Variables
写入后不可读回(只显示名称)      明文可见、可读回
日志中自动脱敏 ****              日志原样输出
引用 ${{ secrets.NAME }}       引用 ${{ vars.NAME }}
不适合放非敏感配置              适合放区域、环境名、集群名

Variables 同样支持仓库级、组织级、企业级三层,可见性与 Secrets 一致。

2.2 使用 vars 上下文

name: Deploy
on:
  workflow_dispatch:

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      AWS_REGION: ${{ vars.AWS_REGION }}
      CLUSTER_NAME: ${{ vars.CLUSTER_NAME }}
    steps:
      - name: Show target
        run: |
          echo "region=$AWS_REGION cluster=$CLUSTER_NAME"

      - name: Conditional by variable
        if: vars.ENABLE_CANARY == 'true'
        run: echo "canary enabled"

2.3 关键差异与坑

1) vars 可以在 if 中直接使用(secrets 不行,见第七章踩坑)
2) Variables 有 48KB 上限,Secrets 单个 48KB、单仓库 100 个
3) 组织级 Variable 的可见性同样分 all / private / selected
4) Environment 级也可以定义 vars 与 secrets,优先级高于组织级

三、Environment 与生产发布门禁

3.1 定义受保护环境

Environment 是企业治理中最有效的一道闸门:把生产发布绑定到一个需要人工审批的环境上。

jobs:
  deploy-prod:
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://app.example.com
    steps:
      - uses: actions/checkout@v4
      - name: Deploy
        run: ./scripts/deploy.sh
        env:
          DEPLOY_KEY: ${{ secrets.PROD_DEPLOY_KEY }}

在仓库 Settings 的 Environments 中为 production 配置:

Required reviewers    指定 1~6 名审批人,job 进入 waiting 状态等待批准
Wait timer            延迟 N 分钟(0~43200)后才允许部署
Deployment branches   仅允许 main 分支或指定 tag 触发该环境
Environment secrets   仅在该 environment 的 job 中可见

3.2 用 gh api 检查环境配置

# 列出仓库所有 environment
gh api /repos/my-org/my-repo/environments -q '.environments[].name'

# 查看 production 的审批人
gh api /repos/my-org/my-repo/environments/production \
  -q '.protection_rules[] | select(.type=="required_reviewers") | .reviewers[].login'

3.3 组合策略

开发环境 dev      无审批,允许任意分支
预发环境 staging  等待计时器 5 分钟,允许 main
生产环境 prod     必需审批 + 仅 main/tag + environment secret 隔离

Environment secret 只在引用该 environment 的 job 中注入,未绑定 environment 的 job 拿不到 PROD_DEPLOY_KEY,这是比「把密钥放仓库级再靠 if 判断」更硬的隔离。


四、Runner Groups 与自托管调度

4.1 创建 Runner Group

Runner Group 是自托管 runner 的访问控制单元,控制哪些仓库能调度到哪批机器。

# 在组织下创建 runner group,仅白名单仓库可见
gh api --method POST /orgs/my-org/actions/runner-groups \
  -f name="gpu-builders" \
  -f visibility="selected" \
  -f 'selected_repository_ids[]=111111' \
  -f 'selected_repository_ids[]=222222'

# 查看组
gh api /orgs/my-org/actions/runner-groups -q '.runner_groups[] | "\(.id) \(.name) \(.visibility)"'

可见性三档与企业级对应:

enterprise    企业内所有组织可见(企业级 runner group)
organization  组织内所有仓库可见
selected      仅指定仓库可见

4.2 runs-on 语法

jobs:
  # 单组 + 标签
  gpu:
    runs-on:
      group: gpu-builders
      labels: [self-hosted, linux, x64, cuda]

  # 组内任意可用 runner
  generic:
    runs-on:
      group: general-pool

  # 表达式动态选组
  dynamic:
    runs-on:
      group: ${{ github.ref == 'refs/heads/main' && 'prod-pool' || 'dev-pool' }}

  # 数组形式(旧写法,等价于标签交集)
  legacy:
    runs-on: [self-hosted, linux, gpu]

4.3 组合与踩坑

1) group + labels 是「与」关系:必须同时满足组归属与全部标签
2) 未授权仓库写 group: xxx → job 一直 queued,不报错
3) runner 必须先在组内注册,标签是 runner 注册时自带的
4) 默认组 default 不可删除,新 runner 不指定组会进 default
5) 企业级 runner group 需要企业 Owner 创建,组织 Owner 无权查看

排查 job 卡在 queued:先确认 runner group 可见性是否包含该仓库,再看标签是否与在线 runner 完全匹配(标签大小写敏感)。


五、组织与企业级策略管控

5.1 限制可用的 Actions

企业/组织 Settings 下的 Actions permissions 决定能引用哪些 action。

Allow all actions and reusable workflows
  放开一切,最方便也最危险

Allow enterprise actions and reusable workflows, plus select
  仅允许企业内 + 白名单第三方

Allow select actions and reusable workflows
  只允许指定 action 与 reusable workflow
  可勾选 Allow actions created by GitHub
  可勾选 Allow Marketplace actions by verified creators

推荐的保守配置:

[x] Allow actions created by GitHub
[x] Allow Marketplace actions by verified creators
[x] Allow specified actions and reusable workflows
    额外白名单:
      actions/*@*
      docker/build-push-action@*
      hashicorp/setup-terraform@*
      my-org/shared-workflows/.github/workflows/*@*

5.2 允许 reusable workflow 的来源

组织级 reusable workflow 必须显式放行来源仓库:
  Settings → Actions → General → Allow select actions
  → 追加 "my-org/ci-workflows/.github/workflows/deploy.yml@main"
否则下游仓库调用时报 "workflow is not allowed to be used"

5.3 GITHUB_TOKEN 默认权限

默认(历史):read and write,几乎全开
推荐:Read repository contents and packages permissions(只读)

组织级 Settings → Actions → General → Workflow permissions
  ○ Read and write permissions
  ● Read repository contents and packages permissions
  [ ] Allow GitHub Actions to create and approve pull requests

工作流里再按需最小化提权:

permissions:
  contents: read

jobs:
  release:
    runs-on: ubuntu-latest
    permissions:
      contents: write      # 仅该 job 需要写
      packages: write
    steps:
      - uses: actions/checkout@v4

5.4 策略对比表

维度             宽松默认          企业治理推荐
action 来源      任意              白名单 + verified creators
GITHUB_TOKEN     读写全开          默认只读,job 级提权
PR 审批机器人    允许              关闭
fork PR secret   可用              不可用(GitHub 强制)

六、OIDC 免密上云与合规审计

6.1 用 OIDC 替代长期密钥

把云厂商的长期 AccessKey 放进 Secret 是企业里最大的合规风险点。OIDC 让 workflow 换取临时凭证,Secret 里不再有任何静态密钥。

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write     # 必需,否则拿不到 OIDC token
      contents: read
    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/gha-deploy
          aws-region: ap-northeast-1

      - name: Deploy
        run: aws s3 sync ./dist s3://my-bucket --delete

AWS 侧的信任策略限定到具体仓库与分支:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
        },
        "StringLike": {
          "token.actions.githubusercontent.com:sub": "repo:my-org/my-repo:ref:refs/heads/main"
        }
      }
    }
  ]
}

sub 条件必须收紧,否则任意仓库的任意 workflow 都能 AssumeRole。

6.2 审计日志

# 拉取组织最近 7 天的 workflow / secret 相关审计事件
gh api "/orgs/my-org/audit-log?phrase=action:workflows&per_page=100" \
  -q '.[] | "\(.created_at) \(.actor) \(.action) \(.repo)"'

# 追踪 secret 的创建与更新
gh api "/orgs/my-org/audit-log?phrase=action:secrets" \
  -q '.[] | select(.action | test("secret")) | "\(.created_at) \(.actor) \(.action)"'

# 追踪自托管 runner 注册
gh api "/orgs/my-org/audit-log?phrase=action:runners" -q '.[].action' | sort -u

企业级可用 /enterprises/{enterprise}/audit-log 拉全企业事件,导出到 SIEM 做长期留存。

6.3 保留期与 IP 白名单

Artifact 保留期   仓库级 1~90 天(默认 90),组织/企业可设上限
Log 保留期        默认 90 天,企业可统一调整
IP allow list     企业设置 → 仅允许受信 IP 段访问,自托管 runner 需放行
                  注意:GitHub 托管 runner 的出口 IP 是动态的,需用 meta API 拉取

拉取 GitHub Actions 当前 IP 段:

gh api /meta -q '.actions[] | "\(.start_address) - \(.end_address)"'

6.4 策略即代码

企业级策略可以交给 Terraform 管理,避免手工点 UI 造成的配置漂移。

resource "github_actions_organization_secret" "slack" {
  secret_name             = "SLACK_WEBHOOK"
  visibility              = "selected"
  plaintext_value         = var.slack_webhook
  selected_repository_ids = [111111, 222222]
}

resource "github_actions_organization_permissions" "this" {
  allowed_actions          = "selected"
  enabled_repositories     = "all"
  allowed_actions_config {
    github_owned_allowed   = true
    verified_allowed       = true
    patterns_allowed       = ["actions/*@*", "my-org/shared-workflows/*@*"]
  }
}

resource "github_actions_repository_permissions" "repo" {
  repository      = "my-repo"
  allowed_actions = "selected"
  allowed_actions_config {
    github_owned_allowed = true
  }
}

6.5 成本治理

# 组织级 Actions 用量(需 admin:org 权限)
gh api /orgs/my-org/settings/billing/actions \
  -q '"included=\(.included_minutes) used=\(.total_minutes_used) paid=\(.total_paid_minutes_used)"'

# 单仓库用量
gh api /repos/my-org/my-repo/actions/runs -q '.workflow_runs | length'
治理手段:
  1) 组织级设置 included minutes 上限,超额需审批
  2) 用 larger runners 时按 vCPU 倍率计费,注意倍率而非分钟数
  3) 对高消耗仓库设置并发上限
  4) 用缓存与 concurrency 取消旧运行降低分钟数

七、治理速查与踩坑

7.1 层级速查表

配置项            仓库级   组织级   企业级   覆盖优先级
Secrets           有       有       有       仓库 > 组织 > 企业
Variables         有       有       有       仓库 > 组织 > 企业
Environment       有       -        -       environment 级最高
Runner Groups     -        有       有       job 显式指定
Actions 白名单    -        有       有       组织级收窄企业级
GITHUB_TOKEN      -        有       有       组织级为默认值

7.2 常见踩坑清单

1) fork PR 拿不到组织级 secret
   GitHub 对来自 fork 的 pull_request 事件不下发任何 secret,
   即使可见性选了 all repositories 也一样。
   解法:改用 pull_request_target(注意安全)或 worktrig 分离可信/不可信流程。

2) secrets 不能直接在 if 中使用
   错误:if: ${{ secrets.FOO != '' }}
   正确:env: { HAS_FOO: ${{ secrets.FOO != '' }} }  然后 if: env.HAS_FOO == 'true'

3) runner group 未授权导致 job 一直 queued
   不报错,只卡住。检查 group 可见性是否包含该仓库、标签是否与在线 runner 完全一致。

4) GITHUB_TOKEN 权限不足
   默认只读后,推 tag、发 release、推镜像都会 403。
   解法:在 job 级显式声明 permissions: contents: write / packages: write。

5) 组织级 secret 同名被仓库级遮蔽
   UI 不提示,排查时先看仓库级是否有同名项。

6) reusable workflow 未被放行
   报 "workflow is not allowed to be used",
   需在组织级 Actions permissions 里加白名单来源。

7) environment 未绑定就引用 secrets.PROD_KEY
   取到空值,部署脚本可能静默失败。

7.3 最小治理清单

必做:
  [ ] 组织级 GITHUB_TOKEN 默认设为只读
  [ ] Actions 白名单 + verified creators,禁 Allow all
  [ ] 生产环境绑定 required reviewers + deployment branches
  [ ] 云凭证改 OIDC,清空长期 AccessKey secret
  [ ] 自托管 runner 全部进显式 runner group,不用 default

建议:
  [ ] 组织级 secret 用 selected 可见性而非 all
  [ ] audit log 定期导出留存
  [ ] 策略用 Terraform 管理,禁止手工改 UI
  [ ] 设置 included minutes 与并发上限

总结

GitHub Actions 企业级治理的核心是把「散落在各仓库的自由配置」收敛为「层级化的集中策略」。Secrets 与 Variables 走仓库、组织、企业三级,记住就近覆盖的优先级;生产发布用 Environment 绑定审批人与分支限制,用 environment secret 做硬隔离;自托管机器用 Runner Groups 加可见性控制调度范围;Actions 来源用白名单收紧,GITHUB_TOKEN 默认只读、按 job 提权;云凭证用 OIDC 换取临时 token 替代长期密钥;最后用 audit log、保留期、IP 白名单和 Terraform 策略即代码兜住合规与漂移。这套组合落地后,几百个仓库的 CI 才能既有自由度又不失控。

延伸阅读:

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「github-actions」更多文章

  1. GitHub Actions 本地调试与排错:act、workflow_dispatch、日志与重跑
  2. GitHub Actions 工作流性能与并发控制:concurrency、超时与分钟数优化
  3. GitHub Actions 容器作业与 Service 容器:job container、健康检查与集成测试