Terraform remote state backend on AWS, using discovery-resistant naming patterns and ephemeral local states.
- Single file ignition.
- Globally addressable resource names (S3) are fully randomized.
- State store and lock table reside in home region, and are encrypted with customer managed multi-region KMS keys.
- State, lock, and keys are replicated to secondary region.
Keys are additionally replicated to a "keystore region". - Resulting identifiers are stored in KMS encrypted SSM parameters.
- State and replica have distinct log buckets in their respective regions.
Logs are encrypted and replicated cross-region. - State version history and log history are maintained through lifecycle management.
Note
All resources are locked down through resource-based policies, as applicable. For S3 buckets:
- Require state requests to originate from MFA-authenticated sessions.
- Require all requests to use TLS v1.3 (or better).
- Prevent all PUT and DELETE requests on replication targets.
- Require all objects to be encrypted exclusively with our key.
- The primary state store additionally requires MFA-authenticated sessions to not be older than 1 hour.
Out of scope (for now):
- Custom key store
- Custom key material
- Multiple state replication regions
- Cross-account replication
- History object lock (in planning)
Create an AWS CLI SSO profile for your account, or whatever you have to do to commandeer the account
terraform init
terraform apply
terraform output seed
# Optional: Display a backend configuration.
./display-backend.tf.sh
Important
Take note of the seed
output. This is crucial to restore the state later. Keep it safe.
To generate the seed outside of the first infrastructure plan generation, -target
the resource.
terraform init
terraform apply -refresh=false -target=random_id.seed
terraform apply
Restore the state by providing the seed that was used to create it.
cd import
terraform init
# Ensure AWS_PROFILE and AWS_REGION are set appropriately.
# Prefix command with space to prevent history entry.
terraform import random_id.seed oCxD1aYEn4eSQXIObCAQZd6KpN_5-82G8_7PGYvXvmo
terraform apply
Expect success
# Note seed.
terraform output -json seed | jq --raw-output '.id'
# Delete state.
rm *.tfstate*
terraform import random_id.seed oCxD1aYEn4eSQXIObCAQZd6KpN_5-82G8_7PGYvXvmo
terraform apply
Expect success
# Write flag to state bucket
echo "$(date) $(whoami)@$(hostname):$PWD" | aws s3 cp - s3://$(terraform output -json s3 | jq --raw-output '.state.id')/flag.txt --sse=aws:kms --sse-kms-key-id=$(terraform output -json kms | jq --raw-output '.state.id')
# Verify
aws s3 cp s3://$(terraform output -json s3 | jq --raw-output '.state.id')/flag.txt -
Expect success
# Write flag to state bucket
echo "$(date) $(whoami)@$(hostname):$PWD" | aws s3 cp - s3://$(terraform output -json s3 | jq --raw-output '.state.id')/flag.txt --sse=aws:kms --sse-kms-key-id=$(terraform output -json kms | jq --raw-output '.state.id')
# Verify on replica
aws s3 cp s3://$(terraform output -json s3 | jq --raw-output '.replica.id')/flag.txt -
Expect failure
# Write flag to replica bucket
echo "$(date) $(whoami)@$(hostname):$PWD" | aws s3 cp - s3://$(terraform output -json s3 | jq --raw-output '.replica.id')/flag.txt --sse=aws:kms
# Replace flag with own
echo "$(date) CAPTURE" | aws s3 cp - s3://$(terraform output -json s3 | jq --raw-output '.replica.id')/flag.txt --sse=aws:kms
# Delete flag
aws s3 rm s3://$(terraform output -json s3 | jq --raw-output '.replica.id')/flag.txt --sse=aws:kms
Name | Version |
---|---|
terraform | >=1.5.9 |
aws | ~>5.84.0 |
random | ~>3.6.3 |
Name | Version |
---|---|
aws | 5.84.0 |
aws.keystore | 5.84.0 |
aws.replica | 5.84.0 |
random | 3.6.3 |
No modules.
Name | Description | Type | Default | Required |
---|---|---|---|---|
force_namespace | We expect that only a single IaC state is used per AWS account, as anything else just raises potential for conflicts. Thus, account-local resources are created with a fixed name to make them easier to discover. If you absolutely require multiple IaC states in one account, you can set this variable to true to have all resources namespaced. |
bool |
false |
no |
Name | Description |
---|---|
dynamodb | Details about the created DynamoDB state lock table. |
iam | ARN of the IAM policy that allows management access to the state resources. |
kms | Details about Server-Side-Encryption keys for created resources. |
s3 | Details about all created S3 buckets. |
seed | Cryptographic seed of this backend deployment. You need this to recover the state at a later point in time. |
ssm | Details about SSM parameters in the account, which hold the names of the created resources. |