GitHub Actions 密钥允许你在仓库设置中安全存储敏感数据——API 密钥、密码、令牌、证书——并在工作流中使用它们,而无需在代码中暴露。本指南涵盖从基本密钥使用到高级模式(如 OIDC 联合认证、环境范围密钥和自动轮换)的所有内容。
基本密钥使用
密钥在仓库设置(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)时,密钥必须显式传递。它们不会自动继承。
# .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 }}自动密钥轮换
长期密钥是安全风险。使用计划工作流设置自动轮换。
# 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 |
|---|---|---|---|---|---|
| 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 个密钥,每个组织最多 1,000 个。每个密钥最大 64 KB。对于较大的密钥(如证书),可以考虑存储 base64 编码版本并在工作流中解码。
存储后我可以读取密钥值吗?
不能。一旦创建密钥,其值就无法通过 GitHub UI 或 API 读取。你只能更新或删除它。这是设计使然——如果你丢失了密钥,创建新的并轮换凭证。
如何在工作流中的作业之间传递密钥?
密钥不能直接作为作业输出在作业之间传递(输出在日志中可见)。相反,在每个需要它的作业中使用 secrets.MY_SECRET 重新引用密钥,或者在一个作业中生成临时凭证,并通过使用 GPG 的加密构件存储传递。