GitHub Actions Secrets are encrypted environment variables that you create in an organization or repository on GitHub. Secrets can be used to store things like API keys, access tokens, passwords, and other sensitive information. Secrets can be managed through the GitHub UI or API.
Unfortunately, GitHub doesn't provide any way to manage encrypted secrets via git
. Yes, this sounds really strange that you need something other than git
on GitHub, but it is. This article closes this gap by leveraging Terraform to populate GitHub Actions Secrets directly from the GitHub repository. GitHub uses a libsodium sealed box to encrypt secrets, and there is absolutely no risk in storing encrypted secrets in the repo itself.
Prerequisites
- GitHub repository
- Terraform (version 1.3.7 was tested)
-
GitHub CLI a.k.a.
gh
(version 2.23.0 was tested)
Create Terraform stack
in-vars.tf
:
variable "github_owner" {
description = "GitHub user name"
type = string
}
variable "github_repository" {
description = "GitHub repository name"
type = string
}
-
github_owner
will be your GitHub account name, liketorvalds
. -
github_repository
will be your GitHub repository name, likelinux
.
This example uses Terraform's GitHub integration to manage GitHub resources via Terraform.
providers.tf
:
terraform {
required_version = "~> 1.0"
required_providers {
github = {
source = "integrations/github"
version = "~> 5.18"
}
}
}
provider "github" {
owner = var.github_owner
}
The next file is the primary Terraform configuration file that instructs Terraform to update GitHub Actions Secrets from secrets.yaml
file:
data "github_user" "current" {
username = ""
}
data "github_repository" "example" {
full_name = "${var.github_owner}/${var.github_repository}"
}
resource "github_actions_environment_secret" "example" {
for_each = merge([for environment, secrets in yamldecode(file("secrets.yaml"))["secrets"] : {
for name, value in secrets : "${environment}:${name}" =>
{
environment = environment
name = name
value = value
}
}]...)
repository = data.github_repository.example.name
environment = each.value.environment
secret_name = each.value.name
encrypted_value = each.value.value
}
All secrets will be stored encrypted in secrets.yaml
file:
secrets.yaml
:
#
# This file contains encrypted secrets for GitHub Actions
#
# All values in this file are encrypted by using public-key cryptography.
# GitHub uses different public key per each environment per each repository.
# To add a new secret, please run `gh secret set --no-store -e dev|stage|prod` in the root of this repo.
# Please make ensure that you are in the right repository and have the right value of `-e` flag.
#
# To apply values to GitHub, please run `terraform apply`.
#
secrets:
# dev:
# HELLO: <encrypted value>
# WORLD: <encrypted value>
dev: []
stage: []
prod: []
Keep an empty list ([]
) for each environment for now.
Generate Personal Access Token (PAT)
Go to https://github.com/settings/tokens and generate a new Personal Access Token (PAT) to access GitHub via API. At the moment of writing (2023-03-21), new fine-grained scoped tokens didn't work properly for managing Secrets.
Please grant repo
(Full control of private repositories) permission to your token:
Create Environments
Environments are needed to protect your secrets. In this tutorial we will create dev
, stage
and prod
environments. Only GitHub Workflows with dev
, stage
and prod
tags will be able to access Secrets in corresponding environments. GitHub Environments can be restricted by branch names.
Go to your repository → Settings → Environments and add dev
, stage
, prod
environments:
Generate Secrets
Encrypt secrets for each environment:
gh secret set --no-store -e dev
Type secret value into the command line prompt. This command together with the --no-store
option encrypts all data locally by using a public key from the specified GitHub Environment (i.e. dev
, stage
, prod
). Sensitive data is not transmitted outside your machine. Save generated value into secrets.yaml
:
secrets:
dev:
HELLO: QQYIZqerFfiarG+9VyGeDDqz/cMNL7OABaNjWMPOOD7qDO7IHUr+1RND8p5oilV8VaIgbso=
WORLD: oxSzP+MNstOknjv/1Q3/m9q0fQAbaxny91DNd0tf2TV+pOBpgfqURA/0UrG1+V8OR3RHD7Y=
stage:
HELLO: d4gfSmhZolZSh/zWq3gh/hTMUiBm5bZyeJuHmKIkW3jl3pHX3E565vTpDBjHBVzydr8bMFw=
WORLD: vVwxkPZY4Z6HYZKmfmy5PrPe7GPCqXav+59yPB7WzHe6dZ3B3VLxvei4PnO+hqIL4OoDlsQ=
prod:
HELLO: +DsS2fbRzLIDmUqqUC0+dZlq9tgYO2jufUGj3vGxpk/1pFgjLaPk03a6uLOGgha43nfNLxU=
WORLD: 1Pxp9C8BK4L9Q1COBQe+3MjaJR7iyajM3iF5gkmBvxEg/9d38VBvJbLf3Zt+40qexk87ZnY=
Apply Secrets
Now it is time to apply all the secrets to GitHub by using Terraform. Export environment variables to specify your GitHub name, repository and Personal Access Token (PAT).
export TF_VAR_github_owner=rtsisyk
export TF_VAR_github_repository=github-actions-secrets-terraform
export GITHUB_TOKEN=<Personal Access Token generated previously>
Now run Terraform:
terraform apply
[REDACTED]
Terraform will perform the following actions:
# github_actions_environment_secret.example["dev:HELLO"] will be created
+ resource "github_actions_environment_secret" "example" {
+ created_at = (known after apply)
+ encrypted_value = (sensitive value)
+ environment = "dev"
+ id = (known after apply)
+ repository = "github-actions-secrets-terraform"
+ secret_name = "HELLO"
+ updated_at = (known after apply)
}
[REDACTED]
Terraform will apply all secrets to GitHub:
That is it. Now you can manage all GitHub Secrets in GitHub repo.
Notes Regarding Terraform State
Terraform is stateful and keeps information about all created resources in terraform.tfstate
by default:
{
"mode": "managed",
"type": "github_actions_environment_secret",
"name": "example",
"provider": "provider[\"registry.terraform.io/integrations/github\"]",
"instances": [
{
"index_key": "dev:HELLO",
"schema_version": 0,
"attributes": {
"created_at": "2023-03-20 09:29:43 +0000 UTC",
"encrypted_value": "QQYIZqerFfiarG+9VyGeDDqz/cMNL7OABaNjWMPOOD7qDO7IHUr+1RND8p5oilV8VaIgbso=",
"environment": "dev",
"id": "github-actions-secrets-terraform:dev:HELLO",
"plaintext_value": "",
"repository": "github-actions-secrets-terraform",
"secret_name": "HELLO",
"updated_at": "2023-03-20 09:29:43 +0000 UTC"
},
"sensitive_attributes": [],
"private": "bnVsbA==",
"dependencies": [
"data.github_repository.example"
]
},
All Secrets generated in this tutorial are encrypted by using libsodium sealed box. There is absolutely no risk in storing these encrypted strings in a git repository and having them in the Terraform state.
For github_actions_environment_secret
resource in particular, you can actually drop Terraform state and Terraform will correctly update GitHub Actions Secrets on every run without any issues.
Top comments (2)
An improvement would be to create the envs in tf too. You can do that with something like this:
Sweet, thanks for the post. It helped me sort out libsodium for the encrypted action secrets. I normally use sops for secret encryption. Interesting GH went with libsodium.