If you’re an infrastructure engineer like I am, and if you use Terraform like I do, then your Terraform state files are some of the most important files you manage. If those files somehow disappeared, Terraform wouldn’t be able to know which resources it should manage, any subsequent terraform apply that you might run would probably fail (in the best case) and would maybe be actively destructive to your business (in the worst case). So you should be really careful to make sure your state files don’t disappear.
You probably host your state file on S3, which means it isn’t managed by version control (eg. git). It’s just a file on S3, and it could be vulnerable in the event that your AWS account gets hacked, it could be vulnerable to sabotage by a disgruntled employee, it could be vulnerable to an errant automation which is meant to clean up your S3 buckets, and so on…
Fortunately, S3 has a feature called S3 Object Lock which can protect your state file from deletion in any of those cases. In fact, it can protect your state file from deletion in every case, because when S3 Object Lock is enabled, it blocks all deletions, even from the root account and even from AWS Support. It can be configured to protect your state file (or anything) from deletion for X amount of time, where X is a user-configurable amount of days (including an infinite amount of days). This is a perfect use case for protecting your Terraform state file.
You can set Object Lock to protect all versions of your Terraform state file for X days (configurable), and then you can use an expiration policy to expire versions which are older than X + 1. If you used a 30 day retention period, for example, and you expect to run terraform apply as part of your normal course of work at least once every 30 days, then you would be able to notice any deletions of your Terraform state within that 30 day period and recover a deleted state file before it’s too late.
Here is how I like to set up my state buckets:
1locals {
2 environments = [
3 "development",
4 "staging",
5 "production",
6 ]
7}
8
9# This is the bucket where you will store your terraform state
10resource "aws_s3_bucket" "state" {
11 for_each = toset(local.environments)
12 bucket = "somecompany-terraform-state-${each.key}"
13}
14
15# Versioning is recommended in general for terraform state,
16# and versioning is required when using object locks
17resource "aws_s3_bucket_versioning" "state" {
18 for_each = aws_s3_bucket.state
19 bucket = each.value.bucket
20
21 versioning_configuration {
22 status = "Enabled"
23 }
24}
25
26# Nobody can delete versions of my state file for 30 days,
27# and that really means nobody. Not me, not root, not even AWS.
28resource "aws_s3_bucket_object_lock_configuration" "state" {
29 for_each = aws_s3_bucket.state
30 bucket = each.value.bucket
31
32 rule {
33 default_retention {
34 mode = "COMPLIANCE"
35 days = 30
36 }
37 }
38}
39
40# Delete non-current versions after 35 days,
41# and this number needs to be higher than the object lock period
42# for this to work as one would expect it to work.
43resource "aws_s3_bucket_lifecycle_configuration" "state" {
44 for_each = aws_s3_bucket.state
45 bucket = each.value.bucket
46
47 rule {
48 id = "delete-after-35-days"
49 status = "Enabled"
50
51 noncurrent_version_expiration {
52 noncurrent_days = 35
53 }
54 }
55}
56
57# Encryption is entirely unrelated to object locking,
58# but it's a best practice to enable it and it helps
59# with compliance.
60resource "aws_s3_bucket_server_side_encryption_configuration" "state" {
61 for_each = aws_s3_bucket.state
62 bucket = each.value.bucket
63
64 rule {
65 apply_server_side_encryption_by_default {
66 sse_algorithm = "AES256"
67 }
68 }
69}
70
71# Public access blocking is entirely unrelated to object locking,
72# but it's a best practice to enable it and it helps
73# with compliance.
74resource "aws_s3_bucket_public_access_block" "state" {
75 for_each = aws_s3_bucket.state
76 bucket = each.value.bucket
77
78 block_public_acls = true
79 block_public_policy = true
80 ignore_public_acls = true
81 restrict_public_buckets = true
82}
83
84# Ownership controls are entirely unrelated to object locking,
85# but it's a best practice to enable it and it helps
86# with compliance.
87resource "aws_s3_bucket_ownership_controls" "state" {
88 for_each = aws_s3_bucket.state
89 bucket = each.value.bucket
90
91 rule {
92 object_ownership = "BucketOwnerEnforced"
93 }
94}
When using S3 Object Lock, no other special configuration is needed when setting up your S3 backend config.