Skip to main content

State and Plan Encryption

OpenTofu supports encrypting state and plan files at rest, both for local storage and when using a backend. In addition, you can also use encryption with the terraform_remote_state data source. This page explains how to set up encryption and what encryption method is suitable for which use case.

General guidance and pitfalls (please read)

When you enable encryption, your state and plan files become unrecoverable without the appropriate encryption key. Please make sure you read this section carefully before enabling encryption.

What does encryption protect against?

When you enable encryption, OpenTofu will encrypt state data at rest. If an attacker were to gain access to your state file, they should not be able to read it and use the sensitive values (e.g. access keys) contained in the state file.

However, encryption does not protect against data loss (your state file getting damaged) and it also does not protect against replay attack (an attacker using an older state or plan file and tricking you into running it). Additionally, OpenTofu does not and cannot protect the sensitive values in the state file from the person running the tofu command.

What precautions do I need to take?

When you enable encryption, consider who needs access to your state file directly. If you have more than a very small number of people with access needs, you may want to consider running your production plan and apply runs from a continuous integration system to protect both the encryption key and the sensitive values in your state.

You will also need to decide what kind of key you would like to use based on your security requirements. You can either opt for a static passphrase or you can choose a key management system. If you opt for a key management system, it is imperative to configure automatic key rotation for some encryption methods. This is particularly crucial if the encryption algorithm you choose has the potential to reach a point of 'key saturation', where the maximum safe usage limit of the key is approached, such as AES-GCM. You can find more information about this in the encryption methods section below.

Finally, before enabling encryption, please exercise your disaster recovery plan and make a temporary backup of your unencrypted state file. Also, make sure you have backups of your keys. Once you enable encryption, OpenTofu cannot read your state file without the correct key.

Migrating from an unencrypted state/plan

If you have a pre-existing state file and want to enable encryption, simply enabling encryption is not enough as OpenTofu will refuse to read plain text data. This is a protection mechanism to prevent OpenTofu from reading manipulated, unencrypted data. Please see the initial setup section below for detailed migration instructions.

Compatibility guarantee

Research in cryptography can change the state of the art quickly. We will support all key providers and methods as documented for +1 minor version, but may introduce new versions of the same key providers and methods (e.g. aes_gcm_v2), or new key providers and methods in any minor version. If we deprecate a key provider or method, you will receive a warning on the console when running tofu plan or tofu apply. If you receive such a warning, please switch before upgrading to the next version.

Configuration

You can configure encryption in OpenTofu either by specifying the configuration in the OpenTofu code, or using the TF_ENCRYPTION environment variable. Both solutions are equivalent and if you use both, OpenTofu will merge the two configurations, overriding any code-based settings with the environment ones.

The basic configuration structure looks as follows:

Code Block
terraform {
encryption {
key_provider "some_key_provider" "some_name" {
# Key provider options here
}

method "some_method" "some_method_name" {
# Method options here
keys = key_provider.some_key_provider.some_name
}

state {
# Encryption/decryption for state data
method = method.some_method.some_method_name
}

plan {
# Encryption/decryption for plan data
method = method.some_method.some_method_name
}

remote_state_data_sources {
# See below
}
}
}

Key and method rollover

In some cases, you may want to change your encryption configuration. This can include renaming a key provider or method, changing a passphrase for a key provider, or switching key-management systems. OpenTofu supports an automatic rollover of your encryption configuration if you provide your old configuration in a fallback block:

Code Block
terraform {
encryption {
# Methods and key providers here.

state {
method = method.some_method.new_method
fallback {
method = method.some_method.old_method
}
}

plan {
method = method.some_method.new_method
fallback {
method = method.some_method.old_method
}
}
}
}

If OpenTofu fails to read your state or plan file with the new method, it will automatically try the fallback method. When OpenTofu saves your state or plan file, it will always use the new method and not the fallback.

Initial setup

New project

If you are setting up a new project and do not yet have a state file, this sample configuration will get you started with passphrase-based encryption:

Code Block
variable "passphrase" {
# Change passphrase to be at least 16 characters long:
default = "changeme!"
}

terraform {
encryption {
## Step 1: Add the desired key provider:
key_provider "pbkdf2" "mykey" {
passphrase = var.passphrase
}
## Step 2: Set up your encryption method:
method "aes_gcm" "new_method" {
keys = key_provider.pbkdf2.mykey
}

state {
## Step 3: Link the desired encryption method:
method = method.aes_gcm.new_method

## Step 4: Run "tofu apply".

## Step 5: Consider adding the "enforced" option:
# enforced = true
}

## Step 6: Repeat steps 3-5 for plan{} if needed.
}
}

Pre-existing project

When you first configure encryption on an existing project, your state and plan files are unencrypted. OpenTofu, by default, refuses to read them because they could have been manipulated. To enable reading unencrypted data, you have to specify an unencrypted method:

Code Block
variable "passphrase" {
# Change passphrase to be at least 16 characters long:
default = "changeme!"
}

terraform {
encryption {
## Step 1: Add the unencrypted method:
method "unencrypted" "migrate" {}

## Step 2: Add the desired key provider:
key_provider "pbkdf2" "mykey" {
passphrase = var.passphrase
}

## Step 3: Add the desired encryption method:
method "aes_gcm" "new_method" {
keys = key_provider.pbkdf2.mykey
}

state {
## Step 4: Link the desired encryption method:
method = method.aes_gcm.new_method

## Step 5: Add the "fallback" block referencing the
## "unencrypted" method.
fallback {
method = method.unencrypted.migrate
}

## Step 6: Run "tofu apply".

## Step 7: Remove the "fallback" block above and
## consider adding the "enforced" option:
# enforced = true
}

## Step 8: Repeat steps 4-8 for plan{} if needed.
}
}

Rolling back encryption

Similar to the initial setup above, migrating to unencrypted state and plan files is also possible by using the unencrypted method as follows:

Code Block
terraform {
encryption {
## Step 1: Leave the original encryption method unchanged:
method "some_method" "old_method" {
## Parameters for the old method here.
}

# Step 2: Add the unencrypted method here:
method "unencrypted" "migrate" {}

state {
## Step 3: Disable or remove the "enforced" option:
enforced = false

## Step 4: Move the original encryption method into the "fallback" block:
fallback {
method = method.some_method.old_method
}

## Step 5: Reference the unencrypted method as your primary "encryption" method.
method = method.unencrypted.migrate
}

## Step 6: Run "tofu apply".

## Step 7: Remove the "state" block once the migration is complete.

## Step 8: Repeat steps 3-7 for plan{} if needed.
}
}

Remote state data sources

You can also configure an encryption setup for projects using the terraform_remote_state data source. This can be the same encryption setup as your main configuration, but you can also define a separate set of keys and methods. The configuration syntax is as follows:

Code Block
terraform {
encryption {
# Key provider and method configuration here

remote_state_data_sources {
default {
method = method.my_method.my_name
}
remote_state_data_source "my_state" {
method = method.my_method.my_other_name
}
}
}
}

data "terraform_remote_state" "my_state" {
# ...
}

For specific remote states, you can use the following syntax:

  • myname to target a data source in the main project with the given name.
  • mymodule.myname to target a data source in the specified module with the given name.
  • mymodule.myname[0] to target the first data source in the specified module with the given name.

In some cases key names between projects can conflict and you will need to use a different name for the key provider in one project than the other. In this case, you should use the encrypted_metadata_alias option to set a fixed metadata key in order to ensure the encryption works.

For example, you may create certificates in project "A" and want to reference them in project "B". In project "A", you could create the following setup:

Code Block
terraform {
encryption {
key_provider "pbkdf2" "mykey" {
passphrase = "OpenTofu has encryption"
# Note the fixed encrypted_metadata_alias here:
encrypted_metadata_alias = "certificates"
}
method "aes_gcm" "mymethod" {
keys = key_provider.pbkdf2.mykey
}
state {
method = method.aes_gcm.mymethod
}
}
}

resource "tls_private_key" "webserver" {
algorithm = "ED25519"
}

resource "tls_self_signed_cert" "webserver" {
private_key_pem = tls_private_key.webserver.private_key_pem

subject {
common_name = "someserver.opentofu.org"
organization = "OpenTofu"
}

validity_period_hours = 24*365*10

allowed_uses = [
"key_encipherment",
"digital_signature",
"server_auth",
]
}

output "cert_pem" {
value = tls_self_signed_cert.webserver.cert_pem
}

Then you can reference it in project "B" as follows:

Code Block
terraform {
encryption {
# Note that the name of the key here is different:
key_provider "pbkdf2" "mykeyrenamed" {
passphrase = "OpenTofu has encryption"
# Note the fixed encrypted_metadata_alias here:
encrypted_metadata_alias = "certificates"
}
method "aes_gcm" "mymethod" {
keys = key_provider.pbkdf2.mykeyrenamed
}
remote_state_data_sources {
default {
method = method.aes_gcm.mymethod
}
}
}
}


data "terraform_remote_state" "cert" {
backend = "local"
config = {
# Refer to the other project here:
path = "../a/terraform.tfstate"
}
}

output "cert" {
# Use data from the other project by referencing it as follows:
value = data.terraform_remote_state.cert.outputs.cert_pem
}

Key providers

PBKDF2

The PBKDF2 key provider allows you to use a long passphrase as to generate a key for an encryption method such as AES-GCM. You can configure it as follows:

Code Block
terraform {
encryption {
key_provider "pbkdf2" "foo" {
# Specify a long / complex passphrase (min. 16 characters)
passphrase = "correct-horse-battery-staple"

# Adjust the key length to the encryption method (default: 32)
key_length = 32

# Specify the number of iterations (min. 200.000, default: 600.000)
iterations = 600000

# Specify the salt length in bytes (default: 32)
salt_length = 32

# Specify the hash function (sha256 or sha512, default: sha512)
hash_function = "sha512"
}
}
}
OptionDescriptionMin.Default
passphrase (required)Enter a long and complex passphrase.16 chars.-
key_lengthNumber of bytes to generate as a key.132
iterationsNumber of iterations. See this document for recommendations.200.000600.000
salt_lengthLength of the salt for the key derivation.132
hash_functionSpecify either sha256 or sha512 to use as a hash function. sha1 is not supported.N/Asha512
encrypted_metadata_aliasOptional identifier to store metadata in the encrypted state/plan files under. Specify this to allow changing the name of a key provider.-derived from the key provider name

AWS KMS

This key provider uses the Amazon Web Servers Key Management Service to generate keys. The authentication options are identical to the S3 backend excluding any deprecated options. In addition, please provide the following options:

OptionDescriptionMin.Default
kms_key_idKey ID for AWS KMS.1-
key_specKey spec for AWS KMS. Adapt this to your encryption method (e.g. AES_256).1-
encrypted_metadata_aliasOptional identifier to store metadata in the encrypted state/plan files under. Specify this to allow changing the name of a key provider.-derived from the key provider name

The following example illustrates a minimal configuration:

Code Block
terraform {
encryption {
key_provider "aws_kms" "basic" {
kms_key_id = "a4f791e1-0d46-4c8e-b489-917e0bec05ef"
region = "us-east-1"
key_spec = "AES_256"
}
}
}

GCP KMS

This key provider uses the Google Cloud Key Management Service to generate keys. The authentication options are identical to the GCS backend excluding any deprecated options. In addition, please provide the following options:

OptionDescriptionMin.Default
kms_encryption_key (required)Key ID for GCP KMS.N/A-
key_length (required)Number of bytes to generate as a key. Must be in range from 1 to 1024 bytes.1-
encrypted_metadata_aliasOptional identifier to store metadata in the encrypted state/plan files under. Specify this to allow changing the name of a key provider.-derived from the key provider name

The following example illustrates a minimal configuration:

Code Block
terraform {
encryption {
key_provider "gcp_kms" "basic" {
kms_encryption_key = "projects/local-vehicle-id/locations/global/keyRings/ringid/cryptoKeys/keyid"
key_length = 32
}
}
}

OpenBao (experimental)

This key provider uses the OpenBao Transit Secret Engine to generate data keys. You can configure it as follows:

OptionDescriptionMin.Default
key_name (required)Name of the transit encryption key to use to encrypt/decrypt the datakey. Pre-configure it in your in OpenBao server.N/A-
tokenAuthorization Token to use when accessing OpenBao API. OpenTofu can read it from the BAO_TOKEN environment variable as well.N/A-
addressOpenBao server address to access the API. OpenTofu can read it from the BAO_ADDR environment variable as well. Your system must trust the TLS certificate of the server.N/Ahttps://127.0.0.1:8200
transit_engine_pathPath at which the Transit Secret Engine is enabled in OpenBao. Customize this if you changed the transit engine path.N/A/transit
key_lengthNumber of bytes to generate as a key. Available options are 16, 32 or 64 bytes.1632
encrypted_metadata_aliasOptional identifier to store metadata in the encrypted state/plan files under. Specify this to allow changing the name of a key provider.-derived from the key provider name

The following example illustrates a possible configuration:

Code Block
terraform {
encryption {
key_provider "openbao" "my_bao" {

# Required. Name of the transit encryption key
# to use to encrypt/decrypt the data key.
key_name = "test-key"

# Optional. Authorization Token to use when accessing OpenBao API.
# You can also set this in the BAO_TOKEN environment variable.
token = "s.Fg8wA4nDrP08TirpjEXkrTmt"

# Optional. OpenBao server address to access the API on.
# You can also set this using the BAO_ADDR environment variable.
address = "http://127.0.0.1:8200"

# Optional. You can customize this if you mounted the
# transit engine on a different path. Default: /transit
transit_engine_path = "/my-org/transit"

# Optional. Number of bytes to generate as a key. Default: 32
key_length = 16
}
}
}

Methods

AES-GCM

The only currently supported encryption method is AES-GCM. You can configure it in the following way:

Code Block
terraform {
encryption {
# Key provider configuration here

method "aes_gcm" "yourname" {
keys = key_provider.yourkeyprovider.yourname
}
}
}

Unencrypted

The unencrypted method is used to provide an explicit migration path to and from encryption. It takes no configuration and can be seen in use above in the Initial Setup block.