How to input secret values to Terraform in Github Actions

How to input secret values to Terraform in Github Actions

There are times where we have secret values to configure in our cloud resources for our system to work properly. For example, we might need to add an Api Key to a deployed application that communicates with a third party Api, or we have to connect to a SQL Server that only supports traditional user and password authentication. In this blog post we are going to explain how to pass secret values to an Infrastructure as Code pipeline in a secure way using Github Actions and Terraform.

Secret variables in Terraform

If we want to set secret values in Terraform, first we need to declare them as variables and reference them in each resource that needs that secret. The problem with the default type of variables is that the value of these variables usually appear in clear text in the console output and in logs. This is a security concern because our keys might leak every time we execute Terraform.

Terraform has a way to avoid showing the value of a variable, we need to declare them the following way:

variable "mssql_login_pwd" {
  type      = string
  sensitive = true
}

The sensitive = true attribute tells Terraform that the variable is a secret and, because of this, its value should not be shown in logs or outputs. This variable can then be used to create the resources that are in need of those secrets. For example, creating a SQL Server with the admin login and password:

resource "azurerm_mssql_server" "mssql" {
  name                         = "mssqlserver"
  resource_group_name          = data.azurerm_resource_group.rg-core.name
  location                     = data.azurerm_resource_group.rg-core.location
  version                      = "12.0"
  administrator_login          = var.mssql_login
  administrator_login_password = var.mssql_login_pwd
  minimum_tls_version          = "1.2"

  lifecycle {
    ignore_changes = [
      tags
    ]
  }
}

Now that Terraform knows that the variable is a secret we need to find a way to input the value to that variable.

Input secret values to Terraform

The standard way to input values to variables in Terraform is setting them up in a .tfvars file, but we cannot use that approach because we write the secret in a file that is managed by git, leaking the value.

Another option in having a file secrets.tfvars that is in .gitignore, but this means that the file is not in the repository and using it in a pipeline suddenly becomes way more challenging. We must have it stored in a secure way somewhere and the pipeline runner has to authenticate to that service to download the file.

Finally, inputting the values interactively is impossible since we are running an automatic pipeline.

There is a feature in Terraform that is reading values from environment variables. The environment variables must follow the syntax TF_VAR_{var-name}. var-name should be the exact same name as stated in the Terraform files. In our previous example with mssql_login_pwd, to input a value by environment that variable has to be named TF_VAR_mssql_login_pwd. On execution, Terraform will use that value to populate the variable.

With this mechanism, the secret values implicitly exist in the environment variables of the runner operating system. We avoid having to input them in any form.

To complete the solution, we need to let the pipeline runner know what its environment variables are.

Set up in a Github Action Workflow

Our CI/CD provider should always have a way to store secret values and pass them to the runners.

In Github, first we need to set up an environment in our repository. Its always a good idea to create at least two environments (Production and Development), that are linked to two particular branches in the repository (master and develop). This way we can test our deployments first in ‘Development’ before merging to ‘Production’, which is the real application that is running for our users.

alt Github Environments

We then can add different secrets values to every environment. Github Actions Workflows are smart enough to get the proper secret depending on the environment we are working with. In this case, we add the user and password for the connection to a SQL Server to our ‘Development’ environment.

alt Github Environment Secrets

Once we have the secrets securely saved in the Github environment, then we can reference them in the workflows as follows:

jobs:
  IaC:
    name: "Infrastructure as Code"
    runs-on: ubuntu-latest
    env:
      TF_VAR_mssql_login: ${{ secrets.MSSQL_LOGIN }}
      TF_VAR_mssql_login_pwd: ${{ secrets.MSSQL_LOGIN_PWD }}

Now the runner in ubuntu will have two environment variables that are set up directly from the Github secrets. Terraform executions in this runner will take those values to fill the values mssql_login and mssql_login_pwd.

Conclusion

With this method we have created a secure way to transmit static secrets to the Github Actions Workflows that deploy our infrastructure. The values are considered a secret in Github and in Terraform. Furthermore, since the pipeline runners are ephemeral, these values stay in the environment variables only the time it takes the job to finish.

You can check a complete example on the repository for the DevSecOps talk in the .NET Bern User group meetup on June 14th that I participated in.