如何在 GitHub Actions 中使用环境变量文件?

111

我有多个环境(开发,测试,生产)并且我正在使用 .env 文件来存储机密信息等...现在我要切换到 GitHub Actions,并且我想使用我的 .env 文件并将它们声明到 github actions yml 的 env 部分中。

但是根据我迄今所见,似乎无法设置文件路径,必须手动重新声明所有变量。

作为最佳实践,我该怎么做?


3
嗨OP!这个问题有什么进展吗?我也在使用Github Actions时遇到了同样的问题 -- 尝试找出一种方法在不同的环境中存储秘密变量。请问您有解决方案吗? - Vic
1
基本上,您需要手动复制相应的.env文件(例如.env.stage.env.production)的内容到相应的GitHub Actions秘密变量(例如WEBSITE_ENV_STAGEWEBSITE_ENV_PRODUCTION)。然后,在GitHub Actions工作流脚本中,从所需的变量创建.env文件,如下所示:echo "${{ secrets.WEBSITE_ENV_STAGE }}" > .env,并在工作流程中使用它。 - Valentine Shi
我有另外两种方法可以让你在项目中使用.env文件。请查看我的回答 - Valentine Shi
14个回答

198

一个快速的解决方案是在需要用到.env文件之前手动创建它。

      - name: 'Create env file'
        run: |
          touch .env
          echo API_ENDPOINT="https://xxx.execute-api.us-west-2.amazonaws.com" >> .env
          echo API_KEY=${{ secrets.API_KEY }} >> .env
          cat .env

多变量的更好方法

如果您有许多环境变量,只需将整个文件粘贴到名为ENV_FILE的 GitHub 私密信息中,然后只需回显整个文件即可。

      - name: 'Create env file'
        run: |
          echo "${{ secrets.ENV_FILE }}" > .env

9
我们有相同的解决方案,但是这样做很繁琐,我需要编辑36个环境变量。 - aRtoo
2
@aRtoo 您可以将整个 env 文件粘贴到一个名为 ENV_FILE 的密钥中,然后只需执行 echo "${{ secrets.ENV_FILE }}" > .env。我更新了答案,提供了稍微更好的方法。 - DollarAkshay
2
注意:永远不要将结构化数据用作机密信息。 结构化数据可能导致日志内的机密信息删除失败,因为删除操作主要依靠查找特定机密值的精确匹配。例如,不要使用 JSON、XML、YAML(或类似格式)来封装机密值,因为这会大大降低正确删除机密信息的概率。相反,应为每个敏感值创建单独的机密信息。参考:https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-secrets - The Fool
2
注意:永远不要将结构化数据用作机密信息。 结构化数据可能导致日志中的机密信息删除失败,因为删除操作主要依赖于找到与特定机密值完全匹配的内容。例如,不要使用一大段JSON、XML或YAML(或类似的格式)来封装机密信息,因为这会大大降低机密信息被正确删除的概率。相反,应为每个敏感值创建单独的机密信息。参考链接:https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-secrets - undefined
1
@DollarAkshay,当你编辑时,不应该添加自己的内容,而是为了更清晰地更新语言。你的添加应该完全放在另一个答案中。 - undefined
显示剩余7条评论

39
我建议使用3种非常简单的方法来在GitHub Actions工作流中使用你的.env文件变量。这些方法根据你是将文件存储在仓库中(最差的做法)还是将其放在仓库之外(最佳做法)而有所不同。
  1. 你将.env文件保存在代码库中:

  2. (简单、手动,更新.env变量时有点麻烦) 你将文件保存在代码库之外(保留并维护更新.env.example):

    • 你手动将相应的.env文件(比如.env.stage.env.production)的内容复制到相应的GitHub Actions secret variables(比如WEBSITE_ENV_STAGEWEBSITE_ENV_PRODUCTION)中。

    • 然后在GitHub Actions的工作流脚本中,使用所需的变量创建.env文件,例如:echo "${{secrets.WEBSITE_ENV_STAGE }}" > .env,并在工作流中使用它。

  3. (稍微复杂一些,但只需准备一次,然后在本地机器上更改.env变量,然后一键同步到GitHub) 如第2项所述,文件不在代码库中。

    • 现在你可以使用GitHub Actions API来创建或更新secrets。在本地机器的dev环境中,编写调用API端点并将.env文件写入所需的GitHub Actions secret变量的NodeJS脚本(例如写入WEBSITE_ENV_STAGE或同时写入stage和production变量);
这是一种相当广泛的选择方式来使用工作流中的.env文件的变量。根据您的偏好和情况,可以使用任何一种方式。
只是提供信息,还有第四种方式,涉及到一些第三方服务,比如Dotenv VaultHasiCorp Vault(还有其他类似的服务),您可以将秘密变量保存在这些服务中,在构建时使用CI/CD流水线读取这些变量来创建.env文件。请阅读相关详细信息。

4
为什么这个回答被踩了?我觉得它是一个好回答。 - julian
2
这是最好的答案。 - Graham Billington
7
将您的.env文件保留在代码库中是不良实践,也可能存在安全问题,特别是当其中包含AWS凭据或任何云服务提供商凭据时。 - Jeff Gruenbaum
4
@JeffGruenbaum,我在我的答案中明确提到了这一点。尽管将“.env”文件放入版本控制系统是最糟糕的做法之一,但在现实中仍被许多人使用,就像许多其他最糟糕的做法一样。 - Valentine Shi
1
还有第四个选项。将环境上传到私有的S3存储桶中,在GitHub Action中从S3下载它。 - undefined
显示剩余6条评论

36

最简单的方法是将 .env 文件作为 GitHub 密钥创建,然后在您的操作中创建 .env 文件。
因此,第一步是将 .env 文件作为 base64 编码字符串创建为 GitHub 密钥:
openssl base64 -A -in qa.env -out qa.txt
或者
cat qa.env | base64 -w 0 > qa.txt
然后在您的操作中,您可以执行以下操作:

- name: Do Something with env files
  env:
    QA_ENV_FILE: ${{ secrets.QA_ENV_FILE }}
    PROD_ENV_FILE: ${{ secrets.PROD_ENV_FILE }}
  run: |
    [ "$YOUR_ENVIRONMENT" = qa ] && echo $QA_ENV_FILE | base64 --decode > .env
    [ "$YOUR_ENVIRONMENT" = prod ] && echo $PROD_ENV_FILE | base64 --decode > .env

确定 $YOUR_ENVIRONMENT 的方法有很多种,但通常可以从 GITHUB_REF 对象中提取。 您的应用程序应能根据需要读取 .env 文件。

确定$YOUR_ENVIRONMENT的方式有很多种,但通常可以从GITHUB_REF对象中提取。您的应用程序应该能够根据需要从.env文件中读取。


Github secrets已经被加密了,不是吗?你似乎在对一个已经被加密的文件进行加密,而Github已经解密了以读取secrets文件。 - Brettins
7
这里的 .env 文件可能包含很多密钥。将它们编码成字符串的目的是,你不需要在 GitHub secrets 中逐个声明它们。 - munsu
2
@brian,这似乎是我情况的完美解决方案,但是我对openssl命令感到困惑,我们需要在哪里运行此命令? - uneeb meer
@uneebmeer 你可以在终端中运行该命令生成base64版本的字符串。 - Zaffer
1
@munsu 的解决方案非常好。我们不需要通过你的方式设置每个环境变量键。 - Jeff Gu Kang
这可能不安全。您的秘密将在日志和其他地方被曝光。大部分的遮蔽依赖于找到特定秘密值的完全匹配。也就是说,如果您解码并在此处和那里使用这些值,GitHub将无法检测到。因为它们与编码字符串不匹配。参考链接:https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-secrets - The Fool

10

编辑: 你使用了Circleci 的Contexts,因此你拥有每个环境的一组密钥。我知道他们正在努力将密钥带到机构级别,也许是团队级别……没有信息表明他们会创建类似CCI中我们拥有的Contexts。

我考虑在yml文件中添加${env}_GITHUB_KEY作为暂时的变通方法,将env作为密钥名称的前缀,例如STAGE_GITHUB_KEY或INTEGRATION_GITHUB_KEY…您觉得如何?

--- 原始回答: 如果我理解正确,您已经在某个地方存储了dotenv文件,并且想要将所有这些密钥注入步骤中,而无需手动将它们添加到github secrets并在每个迁移工作流程中进行映射… 对吗?

有个人制作了一个操作,可以读取dotenv文件并将其值放入输出中,因此您可以在后续的步骤中使用它们。以下是链接:https://github.com/marketplace/actions/dotenv-action

.env文件中存在的任何内容都将被转换为输出变量。 例如,具有以下内容的.env文件:

VERSION=1.0
AUTHOR=Mickey Mouse

你需要做的是:

id: dotenv
uses: ./.github/actions/dotenv-action

然后,稍后您可以像这样引用Alpine版本 ${{ steps.dotenv.outputs.version }}


1
那个.env文件应该在哪里?由于它没有保存在版本库中,这也是拥有.env文件的意义所在。 - HRK44
看起来这是一个常见的模式(有一个被CI任务读取的.env文件--例如https://circleci.com/orbs/registry/orb/anilanar/dotenv)。如果您可以将变量作为任务的“路径”提供,那么这个答案似乎很适合您的问题。 - nilleb
1
好的,我明白了。您正在使用Circleci Contexts,因此您拥有每个环境的一组机密信息。我知道他们正在努力将机密信息带到组织级别,也许是团队级别...目前还没有信息表明他们会创建类似CCI中的上下文。我考虑在密钥名称前添加env作为前缀,例如STAGE_GITHUB_KEY或INTEGRATION_GITHUB_KEY,在yml中使用${env}_GITHUB_KEY作为解决方法...您觉得呢? - Ayo
@HRK44 如果我帮到了您,您介意选择我作为正确答案吗?这将有助于我开始在这里建立个人资料,因为有“最低声望”限制 :-) 谢谢 - Ayo
@Ayo 太棒了!!谢谢你!!那个解决方法帮了我大忙 :D - Vic
显示剩余4条评论

9
你也可以使用来自 GitHub Marketplace 的专用github action创建.env文件。
示例用法:
name: Create envfile

on: [push]

jobs:

  create-envfile:
 
    runs-on: ubuntu-18.04
 
    steps:
    - name: Make envfile
      uses: SpicyPizza/create-envfile@v1
      with:
        envkey_DEBUG: false
        envkey_SOME_API_KEY: "123456abcdef"
        envkey_SECRET_KEY: ${{ secrets.SECRET_KEY }}
        file_name: .env

根据您在 Github 存储库中定义的密钥值,这将创建一个像下面这样的 .env 文件。
DEBUG: false
SOME_API_KEY: "123456abcdef"
SECRET_KEY: password123

更多信息:https://github.com/marketplace/actions/create-env-file


这个解决方案更加简洁和简单。 - oracleruiz

4

您可以将所有的密钥导出为环境变量,并从脚本中执行。

我专门创建了一个操作,用于获取所有的密钥并将它们导出为环境变量。

以下是一个示例:

- run: echo "Value of MY_SECRET1: $MY_SECRET1"
  env:
    MY_SECRET1: ${{ secrets.MY_SECRET1 }}
    MY_SECRET2: ${{ secrets.MY_SECRET2 }}
    MY_SECRET3: ${{ secrets.MY_SECRET3 }}
    MY_SECRET4: ${{ secrets.MY_SECRET4 }}
    MY_SECRET5: ${{ secrets.MY_SECRET5 }}
    MY_SECRET6: ${{ secrets.MY_SECRET6 }}
    ...

您可以将其转换为:
- uses: oNaiPs/secrets-to-env-action@v1
  with:
    secrets: ${{ toJSON(secrets) }}
- run: echo "Value of MY_SECRET1: $MY_SECRET1"

链接到操作,其中包含有关配置的更多文档:https://github.com/oNaiPs/secrets-to-env-action


这会将所有键值对从env转换为json格式吗?还是它会返回一个“json:无法将字符串解组为类型为kvflag.FlagJSON的Go值”的错误信息? - askb
对我来说很好用,简洁明了。 - Courlu
这将所有的 GitHub 机密导出为环境变量。你在示例中看到的 JSON 是为了让这个自定义操作读取这些机密。 - undefined

3

另一种选择是使用github的Environments功能。虽然在免费计划的私有存储库中不可用。 您可以在存储库、配置文件/组织级别和环境中设置作用域变量。离存储库更近的配置变量优先于其他变量。


1

我尝试使用被接受的解决方案,但GitHub操作对Shell命令发出了投诉。我一直收到这个错误:line 3: unexpected EOF while looking for matching ``'

与其直接在Shell脚本中引用密码,我不得不单独传递它们。

  - name: Create env file
    run: |
      touch .env
      echo POSTGRES_USER=${POSTGRES_USER} >> .env
      echo POSTGRES_PASSWORD=${POSTGRES_PASSWORD} >> .env
      cat .env
    env: 
      POSTGRES_USER: ${{ secrets.POSTGRES_USER }} 
      POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} 

1
我认为他想要输入>>时,误打成了>。 - Felipe Valdes

1

另一种方法是按照https://docs.github.com/en/actions/security-guides/encrypted-secrets#limits-for-secrets中所述的方式进行操作。

基本上将您的.env文件视为“大秘密”。在这种情况下,加密的.env文件被提交到您的存储库中,这应该没问题。然后,在您的操作中添加一个步骤来解密.env文件。

这样做可以避免在Github Secrets中为每个单独的密钥创建开销。在这种情况下,唯一需要维护的Github Secret是加密密码。如果您有多个像 qa.envprod.env等的.env文件...我强烈建议为每个文件使用不同的加密密码,然后将每个加密密码作为"环境密钥"而不是"仓库密钥"存储在Github中(如果您使用Github环境,请参见 https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment)。

如果您不想将(加密的).env文件提交到您的代码库中,那么我建议使用https://dev59.com/wlIH5IYBdhLWcg3wUMB6#64452700中描述的base64方法(类似于https://docs.github.com/en/actions/security-guides/encrypted-secrets#storing-base64-binary-blobs-as-secrets),然后创建一个新的Github秘密来存储编码内容。

对于像我这样厌恶手动重复任务的人,Github秘密的创建现在可以很容易地通过Github CLI工具进行脚本化。请参见https://cli.github.com/manual/gh_secret_set。它还支持从env文件批量创建秘密(请参见-f--env-file标志)


1
顺便说一下,如果你有 GitHub 命令行工具,你也可以执行 gh secret set -f .env

感谢您对贡献Stack Overflow社区的兴趣。这个问题已经有相当多的答案了,其中一个答案已经得到社区的广泛验证。您确定您的方法之前没有被提出过吗?如果是这样,解释一下您的方法有何不同之处,什么情况下可能更可取,以及为什么您认为之前的答案不够。您可以编辑您的答案来提供解释吗? - undefined

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