Managing GitHub Actions Secrets via Git + Terraform
Tags: githubactions, terraform, devops, tutorial
Published: 2023-03-21 09:09:18

[GitHub Actions Secrets](https://docs.github.com/en/actions/security-guides/encrypted-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](https://libsodium.gitbook.io/doc/public-key_cryptography/sealed_boxes) to encrypt secrets, and there is absolutely no risk in storing encrypted secrets in the repo itself.

## Prerequisites

1. GitHub repository
2. [Terraform](https://developer.hashicorp.com/terraform/downloads) (version 1.3.7 was tested)
3. [GitHub CLI](https://cli.github.com/) a.k.a. `gh` (version 2.23.0 was tested)

## Create Terraform stack

`in-vars.tf`:

```terraform
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, like `torvalds`.
- `github_repository` will be your GitHub repository name, like `linux`.

This example uses [Terraform's GitHub](https://registry.terraform.io/providers/integrations/github/latest) integration to manage GitHub resources via Terraform. 

`providers.tf`:

```terraform
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:

```terraform
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`:

```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:

![GitHub Personal Access Token](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ld99nk8eh0j8a0r7khnb.png)

## Create Environments

[Environments](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment) 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:

![GitHub Environments](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x7tuhgiyjeca222wnt7t.png) 

## Generate Secrets

Encrypt secrets for each environment:

```bash
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`:

```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).

```bash
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:

```bash
terraform apply
```

```terraform
[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:

![GitHub Environments with Secrets](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xdztnzis48a3a5tk8zl2.png)

![GitHub Action Secrets](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0a0pkohaqhn9g4k23bht.png)

That is it. Now you can manage all GitHub Secrets in GitHub repo.

## Notes Regarding Terraform State

Terraform is [stateful](https://developer.hashicorp.com/terraform/language/state) and keeps information about all created resources in `terraform.tfstate` by default:


```json
    {
      "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](https://libsodium.gitbook.io/doc/public-key_cryptography/sealed_boxes). 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.

## Links

- [GitHub repository for this tutorial](https://github.com/rtsisyk/github-actions-secrets-terraform/)
- [GitHub Secrets documentation](https://docs.github.com/en/actions/security-guides/encrypted-secrets)
- [GitHub Environments documentation](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment)
- [Terraform->GitHub integration overview](https://registry.terraform.io/providers/integrations/github/latest/docs)