Using Secrets in Terraform¶
Terraform secret management involves securely storing and accessing sensitive information, such as API keys or passwords, within Terraform configurations and workflows.
Provide Terraform credentials to manage resources¶
As described in my document about using an Azure SPN as an environment variable, the secret "could" be used in the provider section of a Terraform configuration, but it is highly recommended that you never do this as per the first rule of fight club, that is to never store secrets in plan text.
Workstation environments (humans)
One of the most used methods for users authenticating their workstation environment with their cloud provider is to use environment variables. In essence, the secrets are saved as variables to the local workstation with pre-fixes that Terraform looks for when it requires and loads if they are present, if they are not then Terraform moves onto alternate authentication methods. This is detailed in the Azure SPN link from the previous paragraph.
Automation solutions (machines)
In most companies a CI/CD platform will likely be used to automate your Terraform workflows. This requires the "machine" to have access to your target cloud platform with credentials that can CRUD infrastructure. In these situations you still need a set of credentials such as an Azure SPN or AWS Credentials with the limited permissions required for your environment. The difference in most automation systems is that the credentials are saved as secrets
, variables
or contexts
which the automation system is configured to call when required. This means your user or developer does not need to know the credentials or even have access to the target Cloud platform.
Terraform Cloud is a HashiCorp platform that runs a Terraform pipeline and stores Terraform state files.
GitHub Actions is a widely used version control system that works with Git. It has a CI/CD pipeline engine called GitHub Actions that can be configured to connect with various platforms such as Azure as part of the pipeline. The authentication can be handled via either an SPN (username/password) or preferably using OpenID Connect (OIDC).
Providing credentials for resources, such as databases¶
Aside from configuring Terraform with credentials it can use to access the target platform to manage resources, you may also need to provide resource specific secrets such as credentials to access a database.
Manually using temporary environment variables¶
When Terraform executes it supports reading data via environment variables. So if you have a database and you need to provide the credentials you can declare the variables values.
{{% alert title="INFO" color="info" %}} The sensitive argument set to true means the values of these variables will be redacted from the CLI output to prevent exposure of sensitive information. {{% /alert %}}
In the variables.tf
file you create the variables.
variable "db_username" {
description = "The database username"
type = string
sensitive = true
}
variable "db_password" {
description = "The database password"
type = string
sensitive = true
}
In the resource block in the main.tf
file you define the inputs as the var.db_username
and var.db_password
values.
On the system running the Terraform code, you can define the variable values themselves with a specific pre-fix of TF_VAR_*
.
So to declare the username and password values you would run the following commands:
{{% alert title="INFO" color="info" %}} The environment variables only exist for the lifetime of the shell from where they are created {{% /alert %}}
This is a pragmatic approach and just requires the operator to access the secrets from their secure storage first then manually set them as environment variables and then run the code. At scale this is inefficient and also means your operator still needs to access the secrets and somehow ensure they are secure whilst being used. Plus this approach is not going to work for CI/CD pipelines as the automation advantage is lost if a human needs to be involved. There are better but more complicated methods such as taking advantage of API access to the secure secret management tools listed at the start of this document.
Automatically using a managed service and an API call¶
Instead of providing the database secrets manually as temporary environment variables you can store them in a managed service like AWS Secrets Manager and then call them from there when needed from within the Terraform configuration.
As an example, let's say we create the database credentials as secrets in our AWS Secrets Manager. The name of the secret is database00-creds
and the credentials are saved as a basic key:value pair.
In the Terraform data sources section we first retrieve the secret from the AWS Secrets Manager via an API call.
The secrets are stored in JSON format so you can decode the retrieved data value using the jsondecode
function and set then a local values.
locals {
database00-creds = jsondecode(
data.aws_secretsManager_secret_version.credentials.secret_string
)
}
Finally, in the resource configuration itself you can refer to the database credentials from the values set in locals
.
Be aware of secrets in Terraform State and Plan files¶
It's very important to understand that regardless of which method you use to provide a resource with credentials they are still written to the Terraform State and Plan files as plain text. This is not the case for credentials used by Terraform itself to access the target platform as part of the provider configuration.
This is a security concern because Terraform stores the state as plain text. The sensitive = true
flag only redacts the output from the CLI, but it doesn't secure the data in the state file.
You need to take extra steps to secure your state. Here are some best practices:
Secure your backend: Use a secure and encrypted backend to store your state file. For instance, if you're using AWS, you can store the state file in an S3 bucket with server-side encryption enabled. You can also enable versioning for your S3 bucket so you can track and revert changes, if necessary.
Use remote state with strict access controls: Only allow access to the state files to the necessary entities, whether they are users or machines. Use proper IAM roles and policies to control access.
Never print secrets as output: Although the sensitive
flag will redact the output from the CLI, don't create an output value for sensitive data to avoid any potential mishap.
Consider using a tool like Vault: You can use HashiCorp's Vault service to fetch secrets at runtime. This can be integrated with Terraform via the Vault provider. In this way, the secret data doesn't need to be stored in the state file.
However, there's no full-proof method to completely avoid storing sensitive data in the Terraform state when it's required by resources. These steps are just precautions to mitigate risks.