State file encryption using tofu & terragrunt. When provisioning infrastructure in AWS using tofu (version 1.7.0 or newer), you may store state files in an S3 bucket. These state files often contain sensitive data like database credentials, tokens, and passwords. Encrypting these files with AWS KMS ensures that even if your bucket accidentally becomes public, no one can read the stored data. Here is a straightforward example using terragrunt as a wrapper for tofu.
include "root" {
path = find_in_parent_folders()
}
include "aws" {
path = find_in_parent_folders("aws.hcl")
}
terraform {
source = "tfr:///terraform-aws-modules/kms/aws?version=${local.version}"
}
locals {
version = read_terragrunt_config(find_in_parent_folders("versions.hcl")).locals.terraform.kms
config = jsondecode(read_tfvars_file(find_in_parent_folders("config.tfvars")))
management_account = local.config.accounts[index(local.config.accounts.*.name, "management-services")].id
users = [
#This is example which allows to use this KMS key from Identity center
"arn:aws:sts:::assumed-role/AWSReservedSSO_AdministratorAccess_7a967555579ca9s2/",
]
}
inputs = {
aliases = ["remote-state"]
description = "This is a KMS key to encrypt remote state s3 bucket, to secure any credentials passed in."
key_usage = "ENCRYPT_DECRYPT"
enable_default_policy = false
enable_key_rotation = false
key_statements = [
{
sid = "AllowAccessForKeyAdministrators"
actions = [
"kms:Create*",
"kms:Describe*",
"kms:Enable*",
"kms:List*",
"kms:Put*",
"kms:Update*",
"kms:Revoke*",
"kms:Disable*",
"kms:Get*",
"kms:Delete*",
"kms:TagResource",
"kms:UntagResource",
"kms:ScheduleKeyDeletion",
"kms:CancelKeyDeletion",
"kms:RotateKeyOnDemand"
]
effect = "Allow"
resources = ["*"]
principals = [
{
type = "AWS"
identifiers = local.users
}
]
},
{
sid = "AllowRegularUsage"
actions = [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey"
]
effect = "Allow"
resources = ["*"]
principals = [
{
type = "AWS"
identifiers = local.users
}
]
}
]
}
locals {
config_file = "config.tfvars"
config = try(jsondecode(read_tfvars_file((local.config_file))), jsondecode(read_tfvars_file(find_in_parent_folders(local.config_file))))
}
generate "remote_state_encryption_config" {
path = "encryption.tf"
if_exists = "overwrite"
contents = <<-EOF
terraform {
required_version = ">= 1.7.0"
%{if local.config.remote_state_encryption}
encryption {
key_provider "aws_kms" "kms_key" {
kms_key_id = ""
key_spec = "AES_256"
region = "eu-central-1"
profile = "management-profile"
}
method "aes_gcm" "secure_method" {
keys = key_provider.aws_kms.kms_key
}
state {
method = method.aes_gcm.secure_method
}
}
%{endif}
}
EOF
}
remote_state {
backend = "s3"
generate = {
path = "backend.tf"
if_exists = "overwrite_terragrunt"
}
config = {
profile = "management-profile"
bucket = "remote-state"
key = "${path_relative_to_include()}/terraform.tfstate"
region = "eu-central-1"
encrypt = true
dynamodb_table = "remote-state"
disable_bucket_update = true
}
}
terraform {
before_hook "tflint" {
commands = ["apply", "plan"]
execute = ["tflint", "--terragrunt-external-tflint", "--minimum-failure-severity=error", "--config", find_in_parent_folders(".tflint.hcl")]
}
#Force Terraform to keep trying to acquire a lock for up to 20 minutes if someone else already has the lock
extra_arguments "retry_lock" {
commands = get_terraform_commands_that_need_locking()
arguments = ["-lock-timeout=20m"]
}
}
Powered by Golang net/http package