动态获取GitHub Actions秘密

51
我正在尝试在运行时使用GitHub Actions动态地拉回GitHub秘密。假设我有两个GitHub Secrets:SECRET_ORANGES:“这是一个橙色的秘密”和SECRET_APPLES:“这是一个苹果秘密”。在我的GitHub Action中,我还有另一个环境变量,它会因分支而异。
env:
  FRUIT_NAME: APPLES

本质上我想找到一种方法进行某种变量替换,以获取正确的密钥。因此,在我的其中一个子作业中,我想要做如下操作:

env:
  FRUIT_SECRET: {{ 'SECRET_' + env.FRUIT_NAME }}

我尝试了以下方法,但都没有成功:

secrets['SECRET_$FRUIT_NAME'] }}

我甚至尝试了一种更简单的方法,没有使用拼接来尝试使其工作。


secrets['$FRUIT_NAME'] }}

{{ secrets.$FRUIT_NAME }}

以上方法均未奏效。

如果我解释得不够清楚,还请谅解。我试图尽可能简单地举例说明。

有人有任何想法可以实现这个吗?

或者,我正在尝试按分支存储秘密信息。

例如:

customer1代码分支中: SECRET_CREDENTIAL="abc123"

customer2代码分支中: SECRET_CREDENTIAL="def456"

然后,我可以根据自己所在的分支访问SECRET_CREDENTIAL的正确值。

谢谢!

更新:我离实现目标又近了一步:

name: Test

env:
  CUSTOMER: CUSTOMER1

jobs:
  build:
    runs-on: ubuntu-latest
    env:
      AWS_ACCESS_KEY_ID: ${{ env.CUSTOMER }}_AWS_ACCESS_KEY_ID
    steps:
    - uses: actions/checkout@v2
    - run: |
        AWS_ACCESS_KEY_ID=${{ secrets[env.AWS_ACCESS_KEY_ID] }}
        echo "AWS_ACCESS_KEY_ID = $AWS_ACCESS_KEY_ID"

在这里大声思考一下,既然语法是 {{ secrets.SECRET_NAME }},也许你可以尝试:{{ secrets['$FRUIT_NAME'] }} 或者 {{ secrets.$FRUIT_NAME }}。如果两者都不起作用,我建议你编辑你的问题并提到你已经尝试了这两种方法。 - Meir Gabay
谢谢您的建议,但是当我这样做并将其输出时,它是空的。看起来它没有正确解析$FRUIT_NAME作为密钥的值。 - Mark McKim
1
{{ secrets[$FRUIT_NAME] }} 是什么意思? - Meir Gabay
很遗憾,刚刚尝试了一下,得到了 Unexpected symbol: '$FRUIT_NAME'。该符号位于表达式中第9个位置:secrets[$FRUIT_NAME] - Mark McKim
1
显然是可行的,回答了一个解决方案。我真的很惊讶知道这是可能的,感谢您让我通过GitHub Actions表达式加强我的知识 :) - Meir Gabay
7个回答

57

使用格式化函数可以更轻松地实现此目的。

假设有两个密钥 DEV_A 和 TEST_A,以下两个作业将使用这两个密钥:

name: Secrets

on: [push]

jobs:

  dev:
    name: dev
    runs-on: ubuntu-18.04
    env:
      ENVIRONMENT: DEV
    steps:
      - run: echo ${{ secrets[format('{0}_A', env.ENVIRONMENT)] }}

  test:
    name: test
    runs-on: ubuntu-18.04
    env:
      ENVIRONMENT: TEST
    steps:
      - run: echo ${{ secrets[format('{0}_A', env.ENVIRONMENT)] }}

这也适用于通过手动工作流提供的输入(workflow_dispatch事件):

name: Secrets

on:
  workflow_dispatch:
    inputs:
      env:
        description: "Environment to deploy to"
        required: true

jobs:
  secrets:
    name: secrets
    runs-on: ubuntu-18.04
    steps:
      - run: echo ${{ secrets[format('{0}_A', github.event.inputs.env)] }}

24

更新 - 2021年7月

我找到了一种更好的方法来准备动态密钥,并将这些密钥作为环境变量在其他作业中使用。

以下是在GitHub Actions中的实现方式。

假设每个密钥应根据分支名称获取。 我使用此操作rlespinasse/github-slug-action获取分支名称。

请查看内联注释以了解它们如何共同工作。

name: Dynamic Secret Names

# Assumption:
# You've created the following GitHub secrets in your repository:
# AWS_ACCESS_KEY_ID_master
# AWS_SECRET_ACCESS_KEY_master

on:
  push:

env:
  AWS_REGION: "eu-west-1"

jobs:
  prepare:
    name: Prepare
    runs-on: ubuntu-20.04
    steps:
      - uses: actions/checkout@v2
      - name: Inject slug/short variables
        uses: rlespinasse/github-slug-action@v3.x
      - name: Prepare Outputs
        id: prepare-step
        # Sets this step's outputs, that later on will be exported as the job's outputs
        run: |
          echo "::set-output name=aws_access_key_id_name::AWS_ACCESS_KEY_ID_${GITHUB_REF_SLUG}";
          echo "::set-output name=aws_secret_access_key_name::AWS_SECRET_ACCESS_KEY_${GITHUB_REF_SLUG}";
    # Sets this job's, that will be consumed by other jobs
    # https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idoutputs
    outputs:
      aws_access_key_id_name: ${{ steps.prepare-step.outputs.aws_access_key_id_name }}
      aws_secret_access_key_name: ${{ steps.prepare-step.outputs.aws_secret_access_key_name }}

  test:
    name: Test
    # Must wait for `prepare` to complete so it can use `${{ needs.prepare.outputs.{output_name} }}`
    # https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#needs-context
    needs:
      - prepare
    runs-on: ubuntu-20.04
    env:
      # Get secret names
      AWS_ACCESS_KEY_ID_NAME: ${{ needs.prepare.outputs.aws_access_key_id_name }}
      AWS_SECRET_ACCESS_KEY_NAME: ${{ needs.prepare.outputs.aws_secret_access_key_name }}
    steps:
      - uses: actions/checkout@v2
      - name: Test Application
        env:
          # Inject secret values to environment variables
          AWS_ACCESS_KEY_ID: ${{ secrets[env.AWS_ACCESS_KEY_ID_NAME] }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets[env.AWS_SECRET_ACCESS_KEY_NAME] }}
        run: |
          printenv | grep AWS_
          aws s3 ls

更新 - 2020年8月

通过对这个项目 terraform-monorepo 进行一些实际操作,以下是我如何动态使用秘密名称的示例。

  1. 秘密名称与环境名称和分支名称相对应 - developmentstagingproduction
  2. $GITHUB_REF_SLUG 来自于 Slug GitHub Action,该操作获取分支名称
  3. 执行解析的命令为
      - name: set-aws-credentials
        run: |
          echo "::set-env name=AWS_ACCESS_KEY_ID_SECRET_NAME::AWS_ACCESS_KEY_ID_${GITHUB_REF_SLUG}"
          echo "::set-env name=AWS_SECRET_ACCESS_KEY_SECRET_NAME::AWS_SECRET_ACCESS_KEY_${GITHUB_REF_SLUG}"
      - name: terraform-apply
        run: |
          export AWS_ACCESS_KEY_ID=${{ secrets[env.AWS_ACCESS_KEY_ID_SECRET_NAME] }}
          export AWS_SECRET_ACCESS_KEY=${{ secrets[env.AWS_SECRET_ACCESS_KEY_SECRET_NAME] }}

完整示例

name: pipeline

on:
  push:
    branches: [development, staging, production]
    paths-ignore:
      - "README.md"

jobs:
  terraform:
    runs-on: ubuntu-latest

    env:
      ### -----------------------
      ### Available in all steps, change app_name to your app_name
      TF_VAR_app_name: tfmonorepo
      ### -----------------------

    steps:
      - uses: actions/checkout@v2
      - name: Inject slug/short variables
        uses: rlespinasse/github-slug-action@v2.x
      - name: prepare-files-folders
        run: |
          mkdir -p ${GITHUB_REF_SLUG}/
          cp live/*.${GITHUB_REF_SLUG} ${GITHUB_REF_SLUG}/
          cp live/*.tf ${GITHUB_REF_SLUG}/
          cp live/*.tpl ${GITHUB_REF_SLUG}/ 2>/dev/null || true
          mv ${GITHUB_REF_SLUG}/backend.tf.${GITHUB_REF_SLUG} ${GITHUB_REF_SLUG}/backend.tf
      - name: install-terraform
        uses: little-core-labs/install-terraform@v1
        with:
          version: 0.12.28
      - name: set-aws-credentials
        run: |
          echo "::set-env name=AWS_ACCESS_KEY_ID_SECRET_NAME::AWS_ACCESS_KEY_ID_${GITHUB_REF_SLUG}"
          echo "::set-env name=AWS_SECRET_ACCESS_KEY_SECRET_NAME::AWS_SECRET_ACCESS_KEY_${GITHUB_REF_SLUG}"
      - name: terraform-apply
        run: |
          export AWS_ACCESS_KEY_ID=${{ secrets[env.AWS_ACCESS_KEY_ID_SECRET_NAME] }}
          export AWS_SECRET_ACCESS_KEY=${{ secrets[env.AWS_SECRET_ACCESS_KEY_SECRET_NAME] }}
          cd ${GITHUB_REF_SLUG}/
          terraform version
          rm -rf .terraform
          terraform init -input=false
          terraform get
          terraform validate
          terraform plan -out=plan.tfout -var environment=${GITHUB_REF_SLUG}
          terraform apply -auto-approve plan.tfout 
          rm -rf .terraform

阅读完GitHub Actions中上下文和表达式语法,特别关注env对象后,我发现:

在表达式的一部分中,您可以使用以下两种语法之一访问上下文信息:

索引语法:github['sha']

属性引用语法:github.sha

因此,对于secrets也适用相同的语法,您可以执行secrets[secret_name],因此您可以执行以下操作:

    - name: Run a multi-line script
      env:
        SECRET_NAME: A_FRUIT_NAME
      run: |
        echo "SECRET_NAME = $SECRET_NAME"
        echo "SECRET_NAME = ${{ env.SECRET_NAME }}"
        SECRET_VALUE=${{ secrets[env.SECRET_NAME] }}
        echo "SECRET_VALUE = $SECRET_VALUE"

导致的结果是

SECRET_NAME = A_FRUIT_NAME
SECRET_NAME = A_FRUIT_NAME
SECRET_VALUE = ***

由于SECRET_VALUE已被删除,我们可以假设真正的秘密已经被获取。

我学到的东西 -

  1. 您无法从另一个env引用env因此这不起作用

env:
  SECRET_PREFIX: A
  SECRET_NAME: ${{ env.SECRET_PREFIX }}_FRUIT_NAME

SECRET_NAME 的结果是 _FRUIT_NAME,不太好

您可以在代码中使用上下文表达式,不仅限于 env,您可以看到在 SECRET_VALUE=${{ secrets[env.SECRET_NAME] }} 中,这很酷

当然 - 这是我测试过的工作流程 - https://github.com/unfor19/gha-play/runs/595345435?check_suite_focus=true - 请检查 Run a multi-line script 步骤


1
太棒了!这真是太好了。我已经玩了几个小时,但还是一无所获。非常感谢。如果我需要字符串连接,那么水果名称应该如何处理呢:APPLE_SECRET1 APPLE_SECRET2 ORANGE_SECRET1 ORANGE_SECRET2所以我想将 A_FRUIT_NAME 设置为 APPLE 或 ORANGE,然后在脚本的其他地方使用该占位符来获取 SECRET1 和 SECRET2? - Mark McKim
好的,非常感谢您的帮助。我认为您的答案已经非常接近我想要实现的目标了。我试图避免在我的YML文件中到处使用硬编码变量,而是有一个单一的变量来更新每次为客户创建新分支时,但我认为我所尝试的可能过于复杂,收益微薄。我想我可以只在每个分支中硬编码更改。无论如何,还是非常感谢您的帮助! - Mark McKim
我认为我已经成功使用工作流名称实现了我的目标!我将在下面回答自己的问题。 - Mark McKim
我认为你在滥用git,在同一个repo中为每个客户创建一个分支似乎是一种不好的方法 :/ 我不确定你想要实现什么,但这听起来不可管理和不可扩展。 - Meir Gabay
我知道这已经偏离了原始问题,但你会如何解决上述问题?我有一个示例应用程序,我会为多个客户自定义各种风味(颜色、文本更新、联系方式)。只有我可以访问代码(客户本身不访问代码)。分支模式是为了让我能够对代码的主要副本进行更改,然后将更改/增强功能拉入包含其特定调整的客户特定分支中。你会如何完成这个任务? - Mark McKim
显示剩余8条评论

13

如果这有所帮助的话,经过阅读上述真正有用的答案后,我决定采用以下方式来存储我的机密信息:

  • DB_USER_MASTER
  • DB_PASSWORD_MASTER
  • DB_USER_TEST
  • DB_PASSWORD_TEST

其中MASTER是生产环境的主分支,TEST是测试环境的测试分支。

然后,使用此线程中建议的解决方案,关键是动态生成secrets变量的键。 这些键通过一个中间步骤(在下面的示例中称为vars)使用outputs生成:

name: Pulumi up
on:
  push:
    branches:
      - master
      - test
jobs:
  up:
    name: Update
    runs-on: ubuntu-latest
    steps:
      - name: Create variables
        id: vars 
        run: |
          branch=${GITHUB_REF##*/} 
          echo "::set-output name=DB_USER::DB_USER_${branch^^}"
          echo "::set-output name=DB_PASSWORD::DB_PASSWORD_${branch^^}"
      - uses: actions/checkout@v2
        with:
          fetch-depth: 1
      - uses: docker://pulumi/actions
        with:
          args: up -s ${GITHUB_REF##*/} -y
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }}
          PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}  
          DB_USER: ${{ secrets[steps.vars.outputs.DB_USER] }}  
          DB_PASSWORD: ${{ secrets[steps.vars.outputs.DB_PASSWORD] }}  
请注意使用大写字母获取分支的技巧:${branch^^}。这是必需的,因为GitHub强制将密钥变成大写字母。

1
做得好!我不熟悉步骤引用(vars),感谢分享。 - Meir Gabay

4

2020年12月起新增解决方案

如果您正在阅读这篇文章,是因为您需要根据部署的环境使用不同的秘密值,GitHub Actions现在有一个名为“Environments”的新功能处于测试版:https://docs.github.com/en/free-pro-team@latest/actions/reference/environments

这允许我们定义环境机密,并允许只分配给该环境的作业访问它们。这不仅能为开发者提供更好的用户体验,还能提高不同部署作业之间的安全性和隔离性。

以下是一个示例,演示如何根据分支名称动态确定应使用的环境:

jobs:
  get-environment-name:
    name: "Extract environment name"
    runs-on: ubuntu-latest
    outputs:
      environment: ${{ steps.extract.outputs.environment }}
    steps:
      - id: extract
        # You can run any logic you want here to map refs to environment names.
        # The GITHUB_REF will look like this: refs/heads/my-branchname
        # The example logic here simply removes "refs/heads/deploy-" from the beginning,
        # so a branch name deploy-prod would be mapped to the environment "prod"
        run: echo "::set-output name=environment::$(echo $GITHUB_REF | sed -e '/^refs\/heads\/deploy-\(.*\)$/!d;s//\1/')"
      - env:
          EXTRACTED: ${{ steps.extract.outputs.environment }}
        run: 'echo "Extracted environment name: $EXTRACTED"'

  deploy:
    name: "Deploy"
    if: ${{ github.event_name == 'push' && needs.get-environment-name.outputs.environment }}
    needs:
      - get-environment-name
#     - unit-tests
#     - frontend-tests
#     ... add your unit test jobs here, so they are executed before deploying anything
    environment: ${{ needs.get-environment-name.outputs.environment }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
#     ... Run your deployment actions here, with full access to the environment's secrets

请注意,在部署作业的if:子句中,无法使用任何环境变量或bash脚本。因此,在当前时刻,使用从分支名称提取环境名称的先前作业是我能做到最简单的方法。

太棒了!这是个好消息!谢谢,Carlo! - Mark McKim
2
我认为当前测试版中的这个功能并没有解决问题。环境特性允许您将整个作业锁定到单个环境配置,但不能帮助您在多个环境中运行同一作业,并具有特定于环境的参数。 - Tom Manterfield
2
我认为只要您可以动态分配环境,它就可以实现。因此,如果您可以获取分支的名称并使用它来匹配您的环境,问题就解决了。 - Jim Jimson
我终于在一些项目中实现了这个功能。它确实有效,尽管仅在部署分支上时触发部署工作有点棘手。为了参考,我添加了示例工作流配置。 - Carlo Beltrame

4

我能够通过将工作流名称用作分支特定变量来实现这一点。

对于我创建的每个分支,我只需在YML文件顶部更新此单个值,然后添加与工作流名称匹配的GitHub Secrets:

name: CUSTOMER1

jobs:
  build:
    runs-on: ubuntu-latest
    env:
      AWS_ACCESS_KEY_ID: ${{ github.workflow }}_AWS_ACCESS_KEY_ID
    steps:
    - uses: actions/checkout@v2
    - run: echo "::set-env name=AWS_ACCESS_KEY_ID::${{ secrets[env.AWS_ACCESS_KEY_ID] }}"
    - run: echo $AWS_ACCESS_KEY_ID


4

不要使用 ::set-env,它已被弃用

请使用以下替代方法

echo "env_key=env_value" >> $GITHUB_ENV

你可以通过设置环境变量来基于分支进行设置,就像这个例子中设置的那样。
假设你的仓库中至少有两个具有不同前缀的密钥,就像这样:(DEV_SERVER_IP,OTHER_SERVER_IP)
我使用的是Github上提供的'format'和'$GITHUB_ENV',它们是工作流命令和函数。
- name: Set develop env
  if: ${{ github.ref == 'refs/heads/develop' }}
  run: echo "branch_name=DEVELOP" >> $GITHUB_ENV
- name: Set other env
  if: ${{ github.ref == 'refs/heads/other' }}
  run: echo "branch_name=OTHER" >> $GITHUB_ENV
- name: SSH Test
  env:
    SERVER_IP: ${{ secrets[format('{0}_SERVER_IP', env.branch_name)] }}
  run: ssh -T user@$SERVER_IP

我落入了这个陷阱。在GitHub Enterprise上,set-outputset-env并没有过时,更重要的是,GITHUB_ENVGITHUB_OUTPUT在你的runner上不存在,而你的runner将无法选择比它的Enterprise服务器推荐的更新版本。因此,以上命令失败了!它们可能很快会被弃用,但是在我撰写本文时,3.6和3.7(最新版本)都不支持重定向选项。 - Martin

3
当我尝试为Github action实现基于环境的秘密选择时,遇到了这个问题。
这个variable-mapper动作(https://github.com/marketplace/actions/variable-mapper)实现了将一个关键变量或环境名称映射到预定义值或其他秘密的所需概念。
它的示例用法如下:
on: [push]
name: Export variables corresponding to regular expression-matched keys
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: kanga333/variable-mapper@v1 
      with:
        key: ${{GITHUB_REF#refs/heads/}}
        map: |
          {
            "master": {
              "environment": "production",
              "AWS_ACCESS_KEY_ID": ${{ secrets.PROD_AWS_ACCESS_KEY_ID }},
              "AWS_SECRET_ACCESS_KEY": ${{ secrets.PROD_AWS_ACCESS_KEY_ID }}
            },
            "staging": {
              "environment": "staging",
              "AWS_ACCESS_KEY_ID": ${{ secrets.STG_AWS_ACCESS_KEY_ID }},
              "AWS_SECRET_ACCESS_KEY": ${{ secrets.STG_AWS_ACCESS_KEY_ID }}
            },
            ".*": {
              "environment": "development",
              "AWS_ACCESS_KEY_ID": ${{ secrets.DEV_AWS_ACCESS_KEY_ID }},
              "AWS_SECRET_ACCESS_KEY": ${{ secrets.DEV_AWS_ACCESS_KEY_ID }}
            }
          }
    - name: Echo environment
      run: echo ${{ env.environment }}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接