Encrypting private data in HashiCorp Terraform is an important aspect of working with this tool. There are methods that allow securely storing sensitive data in encrypted form and transferring it in a safe way when needed.
In this article, we will look at how to securely store secrets in Terraform, and encryption methods that help improve security when working with cloud infrastructure.
Terraform users sometimes face the need to handle sensitive information—for example, API keys or a user’s login and password for a database.
Here is an example of code for creating a database in Hostman (a more detailed guide on working with Terraform and Hostman is available on GitHub):
resource "hm_db_mysql_8" "my_db" {
name = "mysql_8_database"
# Username and password
login = <To be decided>
password = <To be decided>
preset_id = data.hm_db_preset.example-db-preset.id
}
To make this code work, the variables username and password must contain actual credentials. Our task is to properly handle these credentials and prevent their accidental disclosure.
The simplest option is to immediately assign text values to these variables:
resource "hm_db_mysql_8" "my_db" {
name = "mysql_8_database"
# Username and password
login = "root"
password = "admin"
preset_id = data.hm_db_preset.example-db-preset.id
}
But this is a bad practice that harms the security of the whole system—even if you use a private Git repository to store the project. Anyone with access to the version control system would also have access to the secrets.
Additionally, many tools that access the repository, such as Jenkins, CircleCI, or GitLab, keep a local copy of the repo before building the code. If sensitive information is stored as plain text, other programs on your computer may also access it, and therefore gain access to the secrets.
In general, storing secrets as plain text greatly simplifies access to them, which could be exploited by attackers. This problem is relevant not only to Terraform but to any tool. Therefore, the main rule of secret encryption in Infrastructure as Code is: never store sensitive information as plain text.
Risks of unprotected information:
Terraform has a flaw that reduces system security. Each time Terraform is used to deploy infrastructure, it saves a large amount of information about it, including database connection parameters, inside the terraform.tfstate
file—in plain text. This file is stored in the same directory where the apply command is run.
This means that even if you use one of the encryption methods described in this article, sensitive data will still appear in plain text in the state file.
This problem has been known for several years, but there is still no universal solution. There are temporary fixes that remove secrets from state files, but they are not reliable and may break compatibility after updates.
Therefore, at present, regardless of the encryption method, the most important aspect of data security is securing the state file. It is not recommended to keep it locally or in a repoÑŽ Instead, use storage systems that support encryption, such as Amazon S3 with access control.
Terraform supports reading environment variables, and this can be used for storing keys securely.
First, create a file variables.tf
with variables:
variable "username" {
description = "Username"
type = string
sensitive = true
}
variable "password" {
description = "User password"
type = string
sensitive = true
}
The type
parameter sets the variable type, while sensitive = true
marks it as sensitive so its value won’t be shown in logs, including during plan
and apply
.
Now replace credentials in your resources with variables:
resource "hm_db_mysql_8" "my_db" {
name = "mysql_8_database"
# Username and password
login = var.username
password = var.password
preset_id = data.hm_db_preset.example-db-preset.id
}
Then set the values via terminal. Prefix each variable name with TF_VAR_
:
export TF_VAR_username="root"
export TF_VAR_password="admin"
When you run terraform apply
, Terraform will use these environment variable values.
Important: Bash commands are saved in history. To prevent passwords and logins from being stored, set the environment variable HISTCONTROL=ignorespace
. Then, any command starting with a space won’t be saved:
export HISTCONTROL=ignorespace
Using environment variables prevents secrets from appearing in Terraform code, but it doesn’t fully solve the problem, it only shifts it from Terraform to the OS, where the data must also be protected.
A popular approach is to store sensitive data in GPG-encrypted files. You can use plain GPG, or a password manager that uses the same logic—for example, Pass.
In Pass, secrets are stored as GPG-encrypted files organized into a directory hierarchy. They can be copied between devices and managed with standard terminal commands.
To install Pass on Ubuntu:
sudo apt update
sudo apt install pass
You’ll also need GPG. Install and generate a key:
sudo apt install gpg
gpg --full-generate-key
Choose the key type (e.g., “RSA and RSA”) and size, and provide details. Once generated, copy the GPG key and initialize Pass:
pass init <GPG-key>
Encrypt credentials:
pass insert username
pass insert password
To access secrets from the command line, use the pass
command and the secret’s name:
pass username
Enter the passphrase you specified when generating the GPG key, and the secret will be displayed in the console as plain text.
Now, to use the encrypted data, set them as environment variables:
export TF_VAR_username=$(pass username)
export TF_VAR_password=$(pass password)
Pros and cons of using environment variables for secrets:
Pros |
Cons |
Secrets remain outside the code, which means they are not stored in the repository. |
The infrastructure is not fully described in Terraform code, which complicates its maintenance and reduces readability. |
Easy to use: you don’t need advanced qualifications to start working with it. |
Requires additional steps to work with this solution. |
Environment variables can be integrated with many password managers, such as in the example with Pass. |
Since all work with secrets takes place outside of Terraform, Terraform’s built-in security mechanisms do not apply to them. |
Suitable for test runs: dummy values can easily be set as environment variables. |
Vault is an open-source external storage system for sensitive information. Like Terraform, it was developed by HashiCorp.
Vault allows you to implement centralized encrypted storage for secrets. Here are its key features:
Vault is an external storage system, and interaction with it is carried out over the network. Therefore, it can be installed either on a local device (accessed via localhost) or on a remote server.
In this material, we’ll show how to install it on Ubuntu.
Update package indexes and install GPG:
sudo apt update && sudo apt install gpg
Download the GPG key:
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
Install Vault:
sudo apt update && sudo apt install vault
To make sure the installation was successful, check the version of the software:
vault version
If installation was successful, the terminal will display the latest version of the key storage system.
After installation, the tool must be configured. We will use it in server mode. Start it with:
vault server -dev
Important: here we are running the server in development mode. This means that in this mode it stores all data, including keys, in RAM. When the server restarts, all data is lost. Therefore, in production it is better to use the standard server mode. Development mode is suitable for learning purposes, like in this material.
During execution, the terminal will display details of the process and, afterwards, the URL where the server is running and a token for authorization. You can even connect to it through a browser by entering the server’s URL in the address bar.
For further work with the server in development mode, create an environment variable with its URL. If you installed Vault on your local machine, the command will look like this:
export VAULT_ADDR='http://127.0.0.1:8200'
Check the storage status as follows:
vault status
The command will return information about it: creation date, software version, and more.
To store sensitive data, we’ll use a key-value store. First, enable it:
vault secrets enable -path=db_data kv
Then, add secrets into the storage:
vault kv put db_data/secret_tf username=root password=admin
You can check the result directly in your browser.
In the main Terraform file, specify Vault as a provider:
terraform {
required_providers {
vault = {
source = "hashicorp/vault"
version = "3.23.0"
}
hm = {
source = "hostman-cloud/hostman"
}
}
required_version = ">= 0.13"
}
In the Terraform configuration file, you also need to declare Vault as a provider:
provider "vault" {
address = "http://127.0.0.1:8200"
token = "Vault Token"
}
Then, implement a method for reading data from the storage:
data "vault_generic_secret" "secret_credentials" {
path = "db_data/secret_tf"
}
Now you can use the retrieved secrets in a resource, for example when creating a MySQL database:
resource "hm_db_mysql_8" "my_db" {
name = "mysql_8_database"
# Username and password
login = data.vault_generic_secret.secret_credentials.data["username"]
password = data.vault_generic_secret.secret_credentials.data["password"]
preset_id = data.hm_db_preset.example-db-preset.id
}
Secret encryption in Terraform can be automated to ensure a scalable and secure process for managing sensitive data. Below are some methods of automation:
Scripts. They can be used to automatically pass secrets into Terraform. This can be implemented using GPG or OpenSSL.
CI/CD tools. Many CI/CD tools, such as GitLab CI/CD or Jenkins, have built-in encryption that can be used automatically together with Terraform.
In this article, we examined the issue of handling sensitive information, discussed the risks of storing it in an unprotected form, explained why it is important to carefully protect the state file, and provided examples of encrypting secrets in Terraform using HashiCorp Vault, environment variables, and GPG.