State file encryption using tofu & terragrunt.

Thu Jun 26 12:51:30 2025

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.

Step 1: Create a KMS Key for State Encryption
Firstly we need to create our KMS key for state encryption in the code snippet below we will create our KMS key and attach resource policy to it with specify who can has access working with this KMS key, here we include root file which has configuration regarding how we can store our remote states, we include aws provider here as well which configure how we can communicate with AWS.

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
        }
      ]
    }
  ]
}
Step 2: Configure Terragrunt for State Encryption
After we have created our KMS key we can start using it with our remote state, secondly we need to configure our terragrunt root file where we can specify when we can use encryption. Here I specified a trigger remote_state_encryption if it's true then please use the following KMS key if it disable please don't, I did this way because intially when we creates KMS key for the state encryption we don't wanna to encrypt this state do not self locked out, in below example there a little bit more than state encryption but again this is a working code snippet example

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"]
  }
}
Recommendations:


Comments
To leave a comment please login via github

Powered by Golang net/http package