How to Encrypt Terraform State with OpenTofu

Securing your Infrastructure: Encrypting Terraform State with OpenTofu

This tutorial shows how to use the new state encryption in OpenTofu 1.7.0 Alpha 1.

How to Encrypt Terraform State

  1. Prerequisites

    Install Terraform and OpenTofu and read State and Plan Encryption for OpenTofu 1.7.0-alpha1.

  2. Terraform State File Encryption Options

    Terraform state files should be encrypted to protect sensitive information like credentials, API keys, and infrastructure details from unauthorized access or tampering. OpenTofu has implemented state file encryption using PBKDF2 and AWS KMS.

  3. Encrypting Terraform State File with AWS KMS

    Terraform State file encryption using AWS KMS requires an existing KMS key. Use Terraform resource "aws_kms_key" to create an encryption Key and configure the key_provider, method and state configuration to be used in a local or remote backend. An S3 example is shown.

  4. Removing Terraform State File Encryption

    Move the line defining the encryption method to the fallback section and run tofu apply. The updated state file will be stored unencrypted.

Prerequisites

Terraform State file encryption is a work in progress and shouldn't be used in production until released under general availability.

Terraform State File Encryption Options

Terraform state files should be encrypted to protect sensitive information like credentials, API keys, and infrastructure details from unauthorized access or tampering. Encryption adds a layer of security, preventing unauthorized users from easily extracting or modifying sensitive data stored within the state files.

OpenTofu has implemented state file encryption using two different Key Providers:

  • PBKDF2: Password-Based Key Derivation Function 2. It's a key derivation function that's commonly used to strengthen passwords before they're hashed. PBKDF2 applies a cryptographic hash function (such as SHA-1 or SHA-256) repeatedly to the input password along with a salt value to generate a derived key. This process makes it more computationally intensive for attackers to guess or crack passwords, thereby enhancing security.
  • AWS KMS: AWS KMS (Key Management Service) is a managed service offered by Amazon Web Services (AWS) that allows the creation and control of the encryption keys used to encrypt data. It provides a highly secure and scalable solution for managing cryptographic keys and integrating encryption into applications and services running on AWS.

This tutorial uses AWS KMS for state file encryption and S3 as a backend for remote state file storage.

ITWL Tutorials Terraform Encryption Security Groups RDS Design

Encrypting Terraform State File with AWS KMS

Setup KMS

Terraform State file encryption using AWS KMS requires an existing KMS key. This Key should be part of a Company Terraform plan global infrastructure. An S3 bucket or directory inside a bucket should be created for each Terraform plan. We will be reusing an S3 bucket named ditwl_infradmin.

Use Terraform resource "aws_kms_key" to create an encryption Key.

Create a setup directory for setup and add a Terraform plan file named terraform_state_encryption_setup.tf and add the following configuration:

# Copyright (C) 2018 - 2024 IT Wonder Lab (https://www.itwonderlab.com)
#-----------------------------------------
# Define Terrraform Providers and Backend
#-----------------------------------------
terraform {
  required_version = ">= 1.7"

  required_providers {
    aws = {
      source  = "aws"
      version = "~> 5.0"
    }
  }
}

#-----------------------------------------
# Provider: AWS
#-----------------------------------------
provider "aws" {
  profile = "ditwl_infradmin"
}

# KMS Secrets Manager (Terraform State file Encryption)
resource "aws_kms_key" "ditwl-kms-tf-state-key" {
  description = "Terraform Key"
   tags = {
     "Name"         = "ditwl-kms-tf-state-key"  
  }
}

# KMS Alias for Terraform State file Encryption (optional)
resource "aws_kms_alias" "ditwl-kmsalias-tf-state-key" {
  name          = "alias/ditwl-kms-tf-state-key"
  target_key_id = aws_kms_key.ditwl-kms-tf-state-key.key_id
}

# Output the KMS ID for Terraform State file Encryption
output "ditwl-kms-tf-state-key-key_id" {
  value = aws_kms_key.ditwl-kms-tf-state-key.key_id
}

Apply the Terraform plan and take note of the ditwl-kms-tf-state-key-key_id value that corresponds to the KMS Key ID created to encrypt/decrypt the Terraform state file.

$ tofu apply

OpenTofu used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
  + create

OpenTofu will perform the following actions:

  # aws_kms_alias.ditwl-kmsalias-tf-state-key will be created
  + resource "aws_kms_alias" "ditwl-kmsalias-tf-state-key" {
      + name           = "alias/ditwl-kms-tf-state-key"
     ...
    }

  # aws_kms_key.ditwl-kms-tf-state-key will be created
  + resource "aws_kms_key" "ditwl-kms-tf-state-key" {
      + key_usage                          = "ENCRYPT_DECRYPT"
      + tags_all                           = {
          + "Name" = "ditwl-kms-tf-state-key"
        }
     ...
    }

Plan: 2 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + ditwl-kms-tf-state-key-key_id = (known after apply)

Do you want to perform these actions?
  OpenTofu will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_kms_key.ditwl-kms-tf-state-key: Creating...
aws_kms_key.ditwl-kms-tf-state-key: Creation complete after 7s [id=66bc03c1-8ca7-4f6d-af9d-fb89552ac2b5]
aws_kms_alias.ditwl-kmsalias-tf-state-key: Creating...
aws_kms_alias.ditwl-kmsalias-tf-state-key: Creation complete after 1s [id=alias/ditwl-kms-tf-state-key]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

Outputs:

ditwl-kms-tf-state-key-key_id = "66bc03c1-8ca7-4f6d-af9d-fb89552ac2b5"

Encrypt a Terraform Plan

Now create a new example directory with a Terraform plan named terraform_state_encryption.tf. This plan state file will be encrypted.

Set the value of the kms_key_id inside the key_provider to the KMS Key ID value extracted from the output ditwl-kms-tf-state-key-key_id = "66bc03c1-8ca7-4f6d-af9d-fb89552ac2b5".

The State file will be encrypted and stored in the S3 backend bucket (located at ditwl_infradmin/terraform_state_encryption/terraform):

terraform {
  required_version = ">= 1.7"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }

  # Define State file encryption
  encryption {
    key_provider "aws_kms" "ditwl-kp-tf-state-key" {
      kms_key_id = "66bc03c1-8ca7-4f6d-af9d-fb89552ac2b5"
      region     = "us-east-1"
      key_spec   = "AES_256"
    }
    method "aes_gcm" "ditwl-encryp-aes" {
      keys = key_provider.aws_kms.ditwl-kp-tf-state-key
    }
    state {
      method = method.aes_gcm.ditwl-encryp-aes
      fallback { # The empty fallback block allows reading unencrypted state files.
      }
    }
  }

  # Stores the state as a given key in a given bucket on Amazon S3.
  backend "s3" {
    bucket                   = "ditwonderlab"
    key                      = "ditwl_infradmin/terraform_state_encryption/terraform"
    profile                  = "ditwl_infradmin"
    region                   = "us-east-1"
    shared_credentials_files = ["~/.aws/credentials"] # See change 690. Before shared_credentials_file = "~/.aws/credentials"
  }
}

#-----------------------------------------
# Default provider: AWS
#-----------------------------------------
provider "aws" {
  shared_credentials_files = ["~/.aws/credentials"]
  profile                  = "ditwl_infradmin"
  region                   = "us-east-1" //See BUG https://github.com/hashicorp/terraform-provider-aws/issues/30488
}

# VPC
resource "aws_vpc" "ditlw-vpc" {
  cidr_block = "172.21.0.0/19" #172.21.0.0 - 172.21.31.254
  tags = {
    Name = "ditlw-vpc"
    Ver  = "OpenTofu 1.7.0-alpha1"
  }
}

# Subnet
resource "aws_subnet" "ditwl-sn-za-pro-pub-00" {
  vpc_id     = aws_vpc.ditlw-vpc.id
  cidr_block = "172.21.0.0/23" #172.21.0.0 - 172.21.1.255
  tags = {
    Name = "ditwl-sn-za-pro-pub-00"
    Ver  = "OpenTofu 1.7.0-alpha1"
  }
}

Run the Terraform plan:

$ tofu apply

OpenTofu used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
  + create

OpenTofu will perform the following actions:

  # aws_subnet.ditwl-sn-za-pro-pub-00 will be created
  + resource "aws_subnet" "ditwl-sn-za-pro-pub-00" {
    ...
    }

  # aws_vpc.ditlw-vpc will be created
  + resource "aws_vpc" "ditlw-vpc" {
    ...
    }

Plan: 2 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  OpenTofu will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_vpc.ditlw-vpc: Creating...
aws_vpc.ditlw-vpc: Creation complete after 4s [id=vpc-018e8526ec9a27bf7]
aws_subnet.ditwl-sn-za-pro-pub-00: Creating...
aws_subnet.ditwl-sn-za-pro-pri-02: Creating...
aws_subnet.ditwl-sn-za-pro-pub-00: Creation complete after 2s [id=subnet-032d57c695801bd8a]
aws_subnet.ditwl-sn-za-pro-pri-02: Creation complete after 2s [id=subnet-0bbb8a6997ba34041]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Check that the State file has been stored encrypted.

Download the state file using AWS Console S3:

ditwonderlab S3 bucket S3 Global

Examine the content of the state file ditwl_infradmin/terraform_state_encryption/terraform

{
   "meta":{
      "key_provider.aws_kms.ditwl-kp-tf-state-key":"eyJjaXBoZXJ0ZXh0X2Jsb2IiOiJBUUlEQUhpUTNkNnBWT....9PSJ9"
   },
   "encrypted_data":"QuHhNo8cgVifZ0qMhdjoUjWap1y2r....JeIMUgBw==",
   "encryption_version":"v0"
}

Warning

Avoid changing directories or renaming Terraform key providers or encryption methods. This feature is in alpha and shouldn't be used for production. Create an unencrypted backup of the state file.

Export the unencrypted state file using the OpenTofu CLI command state pull:

$ tofu state pull > state_backup.tfstate

Store it in a safe place, and delete its local copy.

Removing Terraform State File Encryption

Move the line defining the encryption method (method = method.aes_gcm.ditwl-encryp-aes) to the fallback section and run tofu apply. The updated state file will be stored unencrypted. Move back the method line to encrypt again.

  # Define State file encryption
  encryption {
    key_provider "aws_kms" "ditwl-kp-tf-state-key" {
      kms_key_id = "66bc03c1-8ca7-4f6d-af9d-fb89552ac2b5"
      region     = "us-east-1"
      key_spec   = "AES_256"
    }
    method "aes_gcm" "ditwl-encryp-aes" {
      keys = key_provider.aws_kms.ditwl-kp-tf-state-key
    }
    state {
      method = method.aes_gcm.ditwl-encryp-aes
      fallback { # The empty fallback block allows reading unencrypted state files.
      }
    }
  }
  # Define State file encryption
  encryption {
    key_provider "aws_kms" "ditwl-kp-tf-state-key" {
      kms_key_id = "66bc03c1-8ca7-4f6d-af9d-fb89552ac2b5"
      region     = "us-east-1"
      key_spec   = "AES_256"
    }
    method "aes_gcm" "ditwl-encryp-aes" {
      keys = key_provider.aws_kms.ditwl-kp-tf-state-key
    }
    state {
      fallback { # The empty fallback block allows reading unencrypted state files.
        method = method.aes_gcm.ditwl-encryp-aes
      }
    }

Destroy the infrastructure using OpenTofu

AWS will bill the time that the infrastructure is running.

To reduce the cost during Terraform development or when the infrastructure is not needed anymore run tofu destroy in the two directories used:

├── example
│   └── terraform_state_encryption.tf
└── setup
    ├── terraform_state_encryption_setup.tf
    └── terraform.tfstate

Leave a Reply

Your email address will not be published. Required fields are marked *


Related Cloud Tutorials

Using the Terraform aws_route53_delegation_set, aws_route53_zone, and aws_route53_record resource blocks to configure DNS in AWS.
Using the Terraform aws_db_instance resource block to configure, launch, and secure RDS instances.
How to use the Terraform aws_instance resource block to configure, launch, and secure EC2 instances.
How to configure and use the Terraform aws_ami data source block to find and use AWS AMIs as templates (root volume snapshot with operating system and applications) for EC2 instances.
How to configure and use the Terraform aws_key_pair resource block to create and manage AWS Key Pairs for performing SSH Public Key Authentication into EC2 instances.
Javier Ruiz Cloud and SaaS Expert

Javier Ruiz

IT Wonder Lab tutorials are based on the diverse experience of Javier Ruiz, who founded and bootstrapped a SaaS company in the energy sector. His company, later acquired by a NASDAQ traded company, managed over €2 billion per year of electricity for prominent energy producers across Europe and America. Javier has over 25 years of experience in building and managing IT companies, developing cloud infrastructure, leading cross-functional teams, and transitioning his own company from on-premises, consulting, and custom software development to a successful SaaS model that scaled globally.

Are you looking for cloud automation best practices tailored to your company?

linkedin facebook pinterest youtube rss twitter instagram facebook-blank rss-blank linkedin-blank pinterest youtube twitter instagram