DevToolBox免费
博客广告合作

GitHub Actions Secrets 指南:GITHUB_TOKEN、gh secret set、OIDC 与 if 条件

12 分钟作者 DevToolBox

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 }}

密钥存储选项对比

OptionScopeVisibilityRotationCostBest For
GITHUB_TOKENCurrent workflow runAutomaticPer runFreeGitHub API automation
Repository SecretsSingle repoRepo adminsManualFreeSimple projects
Environment SecretsSingle repo + envRepo adminsManualFreeMulti-stage deploys
Organization SecretsMultiple reposOrg adminsManualFreeShared credentials
OIDC FederationSingle repoNo stored secretAutomaticFreeCloud deployments
HashiCorp VaultAnyConfigurableAutomaticPaidEnterprise

安全最佳实践

  1. 尽可能对云提供商(AWS、GCP、Azure)使用 OIDC 联合认证,而不是静态访问密钥。
  2. 将密钥限定在环境范围内。生产密钥应需要环境保护规则(必要的审查者、部署分支)。
  3. 永远不要在工作流步骤中打印密钥,即使是用于调试。GitHub 遮蔽确切的字符串,但编码/转换会绕过遮蔽。
  4. 定期轮换密钥。为 API 密钥设置日历提醒或使用自动轮换工作流。
  5. 使用所需的最小权限。专门为 GitHub Actions 创建具有狭窄范围的服务账户。
  6. 通过 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。

这篇文章有帮助吗?

保持更新

获取每周开发技巧和新工具通知。

无垃圾邮件,随时退订。

合作推荐

赞助这篇文章

把你的产品放到这个开发者主题旁边,并追踪点击效果。

咨询文章赞助

本站使用 Cookie 进行流量分析与广告展示。继续浏览即视为同意。 隐私政策