GitHub Actions 密钥允许你在仓库设置中安全存储敏感数据——API 密钥、密码、令牌、证书——并在工作流中使用它们,而无需在代码中暴露。本指南涵盖从基本密钥使用到高级模式(如 OIDC 联合认证、环境范围密钥和自动轮换)的所有内容。
快速答案:GitHub Actions Secrets、GITHUB_TOKEN 与 gh secret set
当你需要快速、安全地配置 GitHub Actions 密钥时,先按这份简短决策表处理。
- Repository secrets 只对单个仓库的工作流可用。可以在 Settings > Secrets and variables > Actions 中创建,也可以用 gh secret set API_TOKEN 创建。
- Environment secrets 绑定到 production 等命名环境。作业必须选择该 environment 后才能拿到这些密钥,环境保护规则还可以要求审核者或限制部署分支。
- Organization secrets 可以跨仓库共享,但除非凭证确实是全局通用,否则组织管理员应限制到选定仓库。
- GITHUB_TOKEN 会为每次 workflow run 自动生成。你可以用 secrets.GITHUB_TOKEN 引用它,但 action 也可能通过 github.token 访问它,因此应始终设置最小化 permissions。
- Secrets 不能直接写在 if: 条件中。未设置的 secret 会返回空字符串;需要先把布尔值或 secret 值放到 env,再用 env 判断。
- 除 GITHUB_TOKEN 外,secrets 默认不会传给 fork 仓库触发的工作流。pull_request_target 是特权场景,不能让不可信 fork 代码带着 secrets 运行。
# Create or update a repository secret
gh secret set API_TOKEN --body "$API_TOKEN"
# Create an environment-scoped secret for production deploys
gh secret set DATABASE_URL --env production --body "$DATABASE_URL"
# Create an organization secret and limit it to selected repositories
gh secret set SENTRY_AUTH_TOKEN \
--org ORG_NAME \
--repos web-app,api-service \
--body "$SENTRY_AUTH_TOKEN"
# List configured secret names without revealing values
gh secret list
gh secret list --env production
gh secret list --org ORG_NAME# Use the automatic GITHUB_TOKEN with least privilege
permissions:
contents: read
pull-requests: write
jobs:
comment:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: 'CI completed.'
})# Secrets cannot be referenced directly in if:
# Move the value into an env var, then test the env var.
jobs:
deploy:
runs-on: ubuntu-latest
env:
HAS_DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN != '' }}
steps:
- name: Deploy only when the secret exists
if: env.HAS_DEPLOY_TOKEN == 'true'
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
run: ./deploy.sh基本密钥使用
密钥在仓库设置(Settings > Secrets and variables > Actions)中定义,并在工作流中使用 secrets 上下文引用。它们在工作流日志中自动被遮蔽。
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to server
env:
# Reference a repository secret
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
API_TOKEN: ${{ secrets.API_TOKEN }}
run: |
echo "Deploying with secure credentials..."
./deploy.sh环境范围的密钥
GitHub Environments 允许你定义特定于部署环境(staging、production)的密钥,并添加保护规则,如必要的审查者。
# Environment-specific secrets (staging vs production)
name: Deploy
on:
push:
branches: [main, staging]
jobs:
deploy:
runs-on: ubuntu-latest
# Pick environment based on branch
environment: ${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}
steps:
- uses: actions/checkout@v4
- name: Deploy
env:
# These come from the selected environment's secrets
DATABASE_URL: ${{ secrets.DATABASE_URL }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: ./deploy.sh --env ${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}OIDC 联合认证:无需长期密钥
2026 年的最佳实践是使用 OIDC(OpenID Connect)联合认证来与云提供商进行身份验证,而不是存储长期访问密钥。GitHub Actions 可以将 JWT 令牌交换为临时云凭证。
# OIDC: No long-lived credentials needed!
name: Deploy to AWS (OIDC)
on:
push:
branches: [main]
permissions:
id-token: write # Required for OIDC
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
aws-region: us-east-1
# No ACCESS_KEY or SECRET_KEY secrets needed!
- name: Deploy to S3
run: aws s3 sync ./dist s3://my-bucket --delete密钥遮蔽和安全使用
GitHub 自动在日志中遮蔽密钥值,但有一些重要的注意事项需要了解以避免意外暴露。
# Secrets are automatically masked in logs
# But be careful with derived values!
jobs:
careful:
runs-on: ubuntu-latest
steps:
- name: Use secret safely
env:
MY_SECRET: ${{ secrets.MY_SECRET }}
run: |
# This output will be masked: ***
echo "Secret value: $MY_SECRET"
# DANGER: This may expose the secret!
# If MY_SECRET="abc", base64 gives "YWJj" which is NOT masked
echo $MY_SECRET | base64
# Safe: always use secrets directly, not derived forms
curl -H "Authorization: Bearer $MY_SECRET" https://api.example.com/endpoint
- name: Add to mask (for dynamic secrets)
run: |
# Manually mask a dynamically generated value
TOKEN=$(generate-token.sh)
echo "::add-mask::${TOKEN}"
echo "TOKEN=${TOKEN}" >> $GITHUB_ENV可复用工作流中的密钥
使用可复用工作流(workflow_call)时,密钥必须通过 secrets 映射显式传递,或用 secrets: inherit 有意转发。不要假设被调用工作流会自动拿到凭证。
# .github/workflows/reusable-deploy.yml
name: Reusable Deploy Workflow
on:
workflow_call:
secrets:
DEPLOY_KEY:
required: true
DATABASE_URL:
required: true
SLACK_WEBHOOK:
required: false
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy
env:
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: ./deploy.sh
- name: Notify Slack
if: ${{ secrets.SLACK_WEBHOOK != '' }}
run: |
curl -X POST -H 'Content-type: application/json' \
--data '{"text":"Deployment complete!"}' \
${{ secrets.SLACK_WEBHOOK }}
# .github/workflows/production.yml — caller
name: Production Deploy
on:
push:
branches: [main]
jobs:
deploy:
uses: ./.github/workflows/reusable-deploy.yml
secrets:
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
DATABASE_URL: ${{ secrets.PROD_DATABASE_URL }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
# Alternative for trusted same-organization workflows:
# deploy:
# uses: org/reusable/.github/workflows/deploy.yml@main
# secrets: inherit自动密钥轮换
长期密钥是安全风险。使用计划工作流设置自动轮换。
# Automated secret rotation pattern
name: Rotate API Token
on:
schedule:
- cron: '0 0 1 * *' # Monthly rotation
workflow_dispatch: # Manual trigger
jobs:
rotate:
runs-on: ubuntu-latest
steps:
- name: Generate new token
id: generate
env:
OLD_TOKEN: ${{ secrets.API_TOKEN }}
run: |
# Call API to generate new token using old token
NEW_TOKEN=$(curl -s -X POST \
-H "Authorization: Bearer $OLD_TOKEN" \
https://api.example.com/tokens/rotate | jq -r '.token')
echo "::add-mask::${NEW_TOKEN}"
echo "new_token=${NEW_TOKEN}" >> $GITHUB_OUTPUT
- name: Update GitHub secret
uses: gliech/create-github-secret-action@v1
with:
name: API_TOKEN
value: ${{ steps.generate.outputs.new_token }}
github_token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}密钥存储选项对比
| Option | Scope | Visibility | Rotation | Cost | Best For |
|---|---|---|---|---|---|
| GITHUB_TOKEN | Current workflow run | Automatic | Per run | Free | GitHub API automation |
| Repository Secrets | Single repo | Repo admins | Manual | Free | Simple projects |
| Environment Secrets | Single repo + env | Repo admins | Manual | Free | Multi-stage deploys |
| Organization Secrets | Multiple repos | Org admins | Manual | Free | Shared credentials |
| OIDC Federation | Single repo | No stored secret | Automatic | Free | Cloud deployments |
| HashiCorp Vault | Any | Configurable | Automatic | Paid | Enterprise |
安全最佳实践
- 尽可能对云提供商(AWS、GCP、Azure)使用 OIDC 联合认证,而不是静态访问密钥。
- 将密钥限定在环境范围内。生产密钥应需要环境保护规则(必要的审查者、部署分支)。
- 永远不要在工作流步骤中打印密钥,即使是用于调试。GitHub 遮蔽确切的字符串,但编码/转换会绕过遮蔽。
- 定期轮换密钥。为 API 密钥设置日历提醒或使用自动轮换工作流。
- 使用所需的最小权限。专门为 GitHub Actions 创建具有狭窄范围的服务账户。
- 通过 GitHub 的审计日志审计密钥访问。检查哪些工作流在使用哪些密钥。
常见问题
分叉的仓库可以访问密钥吗?
不能。默认情况下,密钥不会传递给由分叉触发的拉取请求工作流。这可以防止分叉仓库窃取你的密钥。对于公共仓库,你可以配置哪些事件允许外部贡献者使用密钥,但这需要明确批准。
仓库密钥和组织密钥有什么区别?
仓库密钥只对该特定仓库中的工作流可用。组织密钥可以在多个仓库之间共享,并有策略设置来控制哪些仓库可以访问它们(所有仓库、选定仓库或仅私有仓库)。
我可以存储多少密钥?
GitHub 允许每个仓库最多 100 个 secrets、每个 environment 最多 100 个 secrets、每个组织最多 1,000 个 secrets。每个 secret 大小上限为 48 KB。如果仓库可访问超过 100 个组织级 secrets,workflow 只能使用按名称字母序排列的前 100 个。
存储后我可以读取密钥值吗?
不能。一旦创建密钥,其值就无法通过 GitHub UI 或 API 读取。你只能更新或删除它。这是设计使然——如果你丢失了密钥,创建新的并轮换凭证。
如何在工作流中的作业之间传递密钥?
密钥不能直接作为作业输出在作业之间传递(输出在日志中可见)。相反,在每个需要它的作业中使用 secrets.MY_SECRET 重新引用密钥,或者在一个作业中生成临时凭证,并通过使用 GPG 的加密构件存储传递。
secrets.GITHUB_TOKEN 和 GITHUB_TOKEN 是同一个东西吗?
自动 GITHUB_TOKEN 会为每次 workflow run 生成,并可通过 secrets.GITHUB_TOKEN 引用。一些 action 即使没有显式传入,也能通过 github.token 上下文访问它,因此要用 permissions 块限制权限。
为什么 GitHub Actions secret 在 if 条件里是空的?
GitHub Actions 不允许在 if: 条件中直接引用 secrets。未设置的 secret 在表达式中会解析为空字符串。应先把检查结果或值放入环境变量,再在条件中判断 env.MY_FLAG 或 env.MY_SECRET。