当 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 自托管 Runner — Runner Group 与自托管机器注册细节
- GitHub Actions Environment 审批门禁 — 受保护环境与部署审批
- GitHub Actions OIDC 云认证 — 免密上云完整配置
- GitHub Actions 安全加固 — 权限最小化与第三方 action 风险
- GitHub Actions 成本优化 — 分钟数配额与缓存降本
- GitHub Actions 可复用工作流 — 跨仓库共享流水线
- DevOps 专题 — CI/CD 通用治理原则
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。