2023 年,某知名开源项目的 npm 发布 token 因泄露被恶意利用,攻击者在供应链中植入了后门代码。这类事件的根源几乎总是同一个:CI/CD 系统中存储的长期云凭证。GitHub Actions 在 2021 年底推出的 OIDC(OpenID Connect)认证机制,彻底改变了这一局面——通过短期、自动轮换的 token 替代静态密钥,实现了「零长期凭证」的云资源访问架构。本文将系统讲解 OIDC 的原理、三大云平台的配置实战,以及落地过程中的安全加固策略。
一、为什么传统密钥认证不再安全
1.1 长期凭证的根本风险
# ❌ 传统方式:将 AWS 密钥硬编码在仓库 secrets 中
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
| 风险点 | 说明 | 发生频率 |
|---|---|---|
| 密钥泄露 | 被意外提交到代码、日志或第三方服务 | 高频 |
| 权限过大 | 为了方便,密钥通常授予过度权限 | 极高 |
| 轮换困难 | 手动轮换密钥容易遗漏某些系统 | 中频 |
| 审计盲区 | 无法追踪「谁在何时使用密钥做了什么」 | 极高 |
| 供应链传递 | fork 的 PR 可能意外读取 secrets | 中频 |
1.2 OIDC 如何解决这些问题
OIDC 的核心思想是:GitHub Actions 临时生成一个 JWT token,云平台验证其签名后授予短期权限。
传统模式:
开发者 ──生成──► 长期 AWS 密钥 ──存储──► GitHub Secrets ──注入──► Workflow
│
泄露 = 永久风险
OIDC 模式:
GitHub ──签发──► 短期 JWT (15分钟) ──交换──► 云平台临时 token ──访问──► 资源
│
└── 自动过期,无需存储,每次 job 独立
优势对比:
| 维度 | 传统密钥 | OIDC |
|---|---|---|
| 有效期 | 永久(直到手动删除) | 最短 5 分钟,最长 1 小时 |
| 存储位置 | GitHub Secrets / 配置文件 | 无需存储,运行时生成 |
| 权限粒度 | 粗粒度(关联到 IAM User) | 细粒度(关联到 IAM Role + 条件判断) |
| 审计追踪 | 模糊(只知道密钥 ID) | 精确(知道仓库、workflow、job、commit) |
| 泄露影响 | 永久、全局 | 限定在 token 有效期内 |
二、OIDC 认证机制详解
2.1 JWT Token 的结构
GitHub Actions 在每个 job 开始时,向 GitHub OIDC Provider 请求一个 JWT:
{
"typ": "JWT",
"alg": "RS256",
"kid": "12345678"
}
.
{
"sub": "repo:my-org/my-repo:ref:refs/heads/main",
"aud": "https://github.com/my-org",
"repository": "my-org/my-repo",
"repository_owner": "my-org",
"workflow": "deploy",
"run_id": "1234567890",
"run_attempt": "1",
"actor": "username",
"head_ref": "",
"base_ref": "",
"event_name": "push",
"ref": "refs/heads/main",
"ref_type": "branch",
"job_workflow_ref": "my-org/my-repo/.github/workflows/deploy.yml@refs/heads/main",
"iss": "https://token.actions.githubusercontent.com",
"nbf": 1699999999,
"exp": 1700000599,
"iat": 1699999999
}
关键字段:
| 字段 | 含义 | 信任条件示例 |
|---|---|---|
sub | 主体标识 | 只信任特定仓库的分支 |
repository | 来源仓库 | 限制为白名单仓库 |
workflow | 触发 workflow 名称 | 只允许 deploy.yml |
ref | Git ref(分支/tag) | 只允许 refs/heads/main |
iss | 签发者 | 必须是 GitHub 官方 |
aud | 受众 | 限制为特定组织 |
2.2 认证流程
Workflow Job 启动
│
▼
GitHub OIDC Provider ──签发──► JWT Token
│ │
│ │ 携带 JWT 调用云 STS API
│ ▼
│ AWS/Azure/GCP
│ └── 验证 JWT 签名(通过 GitHub 公钥)
│ └── 检查 trust policy(sub/aud/ref 匹配)
│ └── 颁发临时凭证(15-60 分钟)
│ │
│ ▼
│ Workflow 使用临时凭证访问云资源
│
▼
Job 结束 ──token 自动失效──► 无残留凭证
三、AWS 配置实战
3.1 创建 OIDC Identity Provider
# 在 AWS IAM 控制台或使用 CLI 创建
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--thumbprint-list 6938fd4e98bab03faadb97b34396831e3780aea1 \
--client-id-list sts.amazonaws.com
3.2 创建信任 GitHub 的 IAM Role
{
"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:*"
}
}
}
]
}
条件说明:
| 条件 | 作用 | 严格程度 |
|---|---|---|
StringLike + repo:my-org/my-repo:* | 信任该仓库的所有分支和 tag | 中等 |
StringEquals + ref:refs/heads/main | 只允许 main 分支 | 高 |
StringEquals + workflow:deploy.yml | 只允许特定 workflow 文件 | 极高 |
3.3 GitHub Actions Workflow
name: Deploy to AWS
on:
push:
branches: [main]
permissions:
id-token: write # 必需:请求 OIDC JWT token
contents: read # 必需:检出代码
jobs:
deploy:
runs-on: ubuntu-latest
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/GitHubActionsDeployRole
aws-region: us-east-1
- name: Deploy to S3
run: |
aws s3 sync ./dist s3://my-bucket/
关键:permissions: id-token: write 是获取 JWT 的前提。如果省略,workflow 会失败。
3.4 IAM Role 的最小权限策略
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::my-bucket/*"
}
]
}
原则:OIDC Role 的权限应比传统 IAM User 更严格——因为你能精确控制「哪个仓库的哪个 workflow 的哪个分支」才能获取凭证。
四、Azure 配置实战
4.1 创建 App Registration 和 Federated Credential
# 1. 创建 App Registration
az ad app create --display-name "GitHubActionsOIDC"
# 2. 创建 Service Principal
az ad sp create --id <app-id>
# 3. 添加 Federated Credential(信任 GitHub)
az ad app federated-credential create \
--id <app-id> \
--parameters '{
"name": "github-main-branch",
"issuer": "https://token.actions.githubusercontent.com",
"subject": "repo:my-org/my-repo:ref:refs/heads/main",
"audiences": ["api://AzureADTokenExchange"]
}'
4.2 GitHub Actions Workflow
name: Deploy to Azure
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Azure Login
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Deploy to App Service
run: az webapp deploy --resource-group my-rg --name my-app --src-path ./dist
Azure 的 OIDC 实现需要显式指定 client-id、tenant-id 和 subscription-id,但这些不再是敏感密钥,而是公开标识符——真正的认证由 OIDC 流程完成。
五、GCP 配置实战
5.1 配置 Workload Identity Federation
# 1. 创建 Workload Identity Pool
gcloud iam workload-identity-pools create "github-pool" \
--location="global" \
--display-name="GitHub Actions Pool"
# 2. 创建 OIDC Provider
gcloud iam workload-identity-pools providers create-oidc "github-provider" \
--location="global" \
--workload-identity-pool="github-pool" \
--issuer-uri="https://token.actions.githubusercontent.com" \
--allowed-audiences="https://github.com/my-org" \
--attribute-mapping="google.subject=assertion.sub"
# 3. 绑定 Service Account
gcloud iam service-accounts add-iam-policy-binding \
deployer@my-project.iam.gserviceaccount.com \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/projects/my-project/locations/global/workloadIdentityPools/github-pool/subject/repo:my-org/my-repo:ref:refs/heads/main"
5.2 GitHub Actions Workflow
name: Deploy to GCP
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Authenticate to GCP
uses: google-github-actions/auth@v2
with:
workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/github-pool/providers/github-provider'
service_account: 'deployer@my-project.iam.gserviceaccount.com'
- name: Deploy to Cloud Run
uses: google-github-actions/deploy-cloudrun@v2
with:
service: my-service
source: ./
六、跨云平台统一策略
6.1 多云部署的权限设计
当项目同时部署到 AWS、Azure、GCP 时,推荐为每个云平台创建独立的 IAM Role,而非使用一个超级凭证:
jobs:
deploy-aws:
permissions:
id-token: write
contents: read
steps:
- uses: aws-actions/configure-aws-credentials@v4
with: { role-to-assume: arn:aws:iam::...:role/GitHubActionsAWSRole }
deploy-azure:
permissions:
id-token: write
contents: read
steps:
- uses: azure/login@v2
with: { client-id: ..., tenant-id: ..., subscription-id: ... }
deploy-gcp:
permissions:
id-token: write
contents: read
steps:
- uses: google-github-actions/auth@v2
with: { workload_identity_provider: ..., service_account: ... }
6.2 环境隔离策略
jobs:
deploy-staging:
environment: staging
permissions:
id-token: write
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::STAGING-ACCOUNT:role/DeployRole
deploy-production:
environment: production # 触发审批
permissions:
id-token: write
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::PROD-ACCOUNT:role/DeployRole
七、安全加固与审计
7.1 Trust Policy 的精细化配置
最宽松的配置(不推荐):
"Condition": {
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:my-org/*:*"
}
}
最严格的配置(推荐):
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:my-org/my-repo:ref:refs/heads/main",
"token.actions.githubusercontent.com:workflow": "deploy"
}
}
7.2 CloudTrail / Activity Log 审计
AWS CloudTrail 会记录每次 AssumeRoleWithWebIdentity 调用:
{
"eventName": "AssumeRoleWithWebIdentity",
"requestParameters": {
"roleArn": "arn:aws:iam::...:role/GitHubActionsDeployRole",
"roleSessionName": "GitHubActions"
},
"responseElements": {
"subject": "repo:my-org/my-repo:ref:refs/heads/main",
"issuer": "https://token.actions.githubusercontent.com"
}
}
这意味着你可以精确审计:哪个仓库的哪次 workflow 在哪个时间获取了云凭证。
八、常见问题解答(FAQ)
Q1: OIDC token 在 workflow 中如何手动获取?
curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=sts.amazonaws.com"
通常不需要手动获取,aws-actions/configure-aws-credentials 等官方 action 会自动处理。
Q2: 私有仓库是否支持 OIDC?
完全支持。OIDC 与仓库公开性无关,只与 workflow 的 permissions: id-token: write 有关。
Q3: 自托管 Runner 是否支持 OIDC?
支持,但需要确保 Runner 能访问 https://token.actions.githubusercontent.com。在完全离线的内网 Runner 上,OIDC 不可用。
Q4: 迁移现有密钥到 OIDC 的步骤?
- 创建 OIDC Identity Provider 和 IAM Role
- 在 workflow 中添加
permissions: id-token: write - 替换
secrets.AWS_ACCESS_KEY_ID为role-to-assume - 在测试环境验证权限边界
- 删除旧的 IAM User 和 secrets
总结
OIDC 不是「另一个认证选项」,而是 CI/CD 云资源访问的安全范式转变。从「长期密钥永不失效」到「短期 token 每次生成、每次过期」,从「模糊审计」到「精确的仓库-workflow-分支-提交级追踪」,OIDC 为现代软件供应链安全提供了基础设施级的保障。
实施检查清单:
- 所有 GitHub → 云的访问已迁移到 OIDC
- IAM Role 的 trust policy 包含细粒度条件(仓库、分支、workflow)
- IAM Role 的权限策略遵循最小权限原则
- 生产部署启用了 Environment 审批保护
- CloudTrail / Activity Log 已启用并定期检查
- 旧的 IAM User 和静态密钥已清理删除
完成以上检查后,你的 CI/CD 基础设施将达到当前业界最高安全水位。
延伸阅读:
- GitHub Actions 发布自动化 — 结合 OIDC 实现安全的 npm/Docker 发布
- GitHub Actions 安全加固 — secrets 管理与供应链防护全景
- 信息安全与云安全专题 — 纵深防御体系与零信任网络
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。