Skip to content

Commit 73e86c7

Browse files
authored
Support AWS provider version 4 (#129)
1 parent 8c02473 commit 73e86c7

14 files changed

+960
-329
lines changed

README.md

+28-23
Large diffs are not rendered by default.

docs/terraform.md

+28-23
Large diffs are not rendered by default.

examples/complete/fixtures.us-east-2.tfvars

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
enabled = true
2-
31
region = "us-east-2"
42

53
namespace = "eg"

examples/complete/main.tf

+15
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ provider "aws" {
22
region = var.region
33
}
44

5+
resource "aws_s3_bucket" "default" {
6+
count = module.this.enabled ? 1 : 0
7+
8+
bucket = "${module.this.id}-logs"
9+
}
10+
511
module "tfstate_backend" {
612
source = "../../"
713

@@ -10,5 +16,14 @@ module "tfstate_backend" {
1016
bucket_enabled = var.bucket_enabled
1117
dynamodb_enabled = var.dynamodb_enabled
1218

19+
logging = [
20+
{
21+
target_bucket = one(aws_s3_bucket.default[*].id)
22+
target_prefix = "tfstate/"
23+
}
24+
]
25+
26+
bucket_ownership_enforced_enabled = true
27+
1328
context = module.this.context
1429
}

examples/complete/outputs.tf

+5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ output "s3_bucket_id" {
33
description = "S3 bucket ID"
44
}
55

6+
output "s3_replication_role_arn" {
7+
value = module.tfstate_backend.s3_replication_role_arn
8+
description = "The ARN of the IAM Role created for replication, if enabled."
9+
}
10+
611
output "dynamodb_table_name" {
712
value = module.tfstate_backend.dynamodb_table_name
813
description = "DynamoDB table name"

examples/complete/versions.tf

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
terraform {
2-
required_version = ">= 0.13.0"
2+
required_version = ">= 1.1.0"
33

44
required_providers {
55
aws = {

main.tf

+88-103
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ locals {
66

77
dynamodb_table_name = local.dynamodb_enabled ? coalesce(var.dynamodb_table_name, module.dynamodb_table_label.id) : ""
88

9-
prevent_unencrypted_uploads = local.enabled && var.prevent_unencrypted_uploads && var.enable_server_side_encryption
9+
prevent_unencrypted_uploads = local.enabled && var.prevent_unencrypted_uploads
1010

1111
policy = local.prevent_unencrypted_uploads ? join(
1212
"",
@@ -23,36 +23,39 @@ locals {
2323

2424
terraform_backend_config_content = templatefile(local.terraform_backend_config_template_file, {
2525
region = data.aws_region.current.name
26-
bucket = join("", aws_s3_bucket.default.*.id)
27-
28-
dynamodb_table = local.dynamodb_enabled ? element(
29-
coalescelist(
30-
aws_dynamodb_table.with_server_side_encryption.*.name,
31-
aws_dynamodb_table.without_server_side_encryption.*.name
32-
),
33-
0
34-
) : ""
35-
36-
encrypt = var.enable_server_side_encryption ? "true" : "false"
37-
role_arn = var.role_arn
38-
profile = var.profile
39-
terraform_version = var.terraform_version
40-
terraform_state_file = var.terraform_state_file
41-
namespace = var.namespace
42-
stage = var.stage
43-
environment = var.environment
44-
name = var.name
26+
# Template file inputs cannot be null, so we use empty string if the variable is null
27+
bucket = try(aws_s3_bucket.default[0].id, "")
28+
29+
dynamodb_table = try(aws_dynamodb_table.with_server_side_encryption[0].name, "")
30+
31+
encrypt = "true"
32+
role_arn = var.role_arn == null ? "" : var.role_arn
33+
profile = var.profile == null ? "" : var.profile
34+
terraform_version = var.terraform_version == null ? "" : var.terraform_version
35+
terraform_state_file = var.terraform_state_file == null ? "" : var.terraform_state_file
36+
namespace = var.namespace == null ? "" : var.namespace
37+
stage = var.stage == null ? "" : var.stage
38+
environment = var.environment == null ? "" : var.environment
39+
name = var.name == null ? "" : var.name
4540
})
4641

47-
bucket_name = var.s3_bucket_name != "" ? var.s3_bucket_name : module.this.id
42+
labels_enabled = local.enabled && (var.s3_bucket_name == "" || var.s3_bucket_name == null)
4843

49-
logging_bucket_enabled = local.bucket_enabled && var.logging_bucket_enabled
50-
logging_bucket_name_default = try(var.logging["bucket_name"], "${local.bucket_name}-logs")
51-
logging_prefix_default = try(var.logging["prefix"], "logs/")
52-
logging_bucket_name = local.logging_bucket_enabled ? module.log_storage.bucket_id : local.logging_bucket_name_default
53-
logging_prefix = local.logging_bucket_enabled ? module.log_storage.prefix : local.logging_prefix_default
44+
bucket_name = local.labels_enabled ? module.bucket_label.id : var.s3_bucket_name
5445
}
5546

47+
module "bucket_label" {
48+
source = "cloudposse/label/null"
49+
version = "0.25.0"
50+
51+
enabled = local.labels_enabled
52+
id_length_limit = 63
53+
54+
context = module.this.context
55+
}
56+
57+
data "aws_region" "current" {}
58+
5659
data "aws_iam_policy_document" "prevent_unencrypted_uploads" {
5760
count = local.prevent_unencrypted_uploads ? 1 : 0
5861

@@ -138,82 +141,88 @@ data "aws_iam_policy_document" "prevent_unencrypted_uploads" {
138141
}
139142
}
140143

141-
module "log_storage" {
142-
source = "cloudposse/s3-log-storage/aws"
143-
version = "0.26.0"
144-
145-
enabled = local.logging_bucket_enabled
146-
access_log_bucket_prefix = local.logging_prefix_default
147-
acl = "log-delivery-write"
148-
expiration_days = var.logging_bucket_expiration_days
149-
glacier_transition_days = var.logging_bucket_glacier_transition_days
150-
name = local.logging_bucket_name_default
151-
standard_transition_days = var.logging_bucket_standard_transition_days
152-
153-
context = module.this.context
154-
}
155-
156144
resource "aws_s3_bucket" "default" {
157145
count = local.bucket_enabled ? 1 : 0
158146

159147
#bridgecrew:skip=BC_AWS_S3_13:Skipping `Enable S3 Bucket Logging` check until Bridgecrew will support dynamic blocks (https://github.com/bridgecrewio/checkov/issues/776).
160148
#bridgecrew:skip=CKV_AWS_52:Skipping `Ensure S3 bucket has MFA delete enabled` check due to issues operating with `mfa_delete` in terraform
161149
bucket = substr(local.bucket_name, 0, 63)
162-
acl = var.acl
163150
force_destroy = var.force_destroy
164-
policy = local.policy
165151

166-
versioning {
167-
enabled = true
168-
mfa_delete = var.mfa_delete
169-
}
152+
tags = module.this.tags
153+
}
170154

171-
server_side_encryption_configuration {
172-
rule {
173-
apply_server_side_encryption_by_default {
174-
sse_algorithm = "AES256"
175-
}
176-
}
177-
}
155+
resource "aws_s3_bucket_policy" "default" {
156+
count = local.bucket_enabled ? 1 : 0
178157

179-
dynamic "replication_configuration" {
180-
for_each = var.s3_replication_enabled ? toset([var.s3_replica_bucket_arn]) : []
181-
content {
182-
role = aws_iam_role.replication[0].arn
183-
184-
rules {
185-
id = module.this.id
186-
prefix = ""
187-
status = "Enabled"
188-
189-
destination {
190-
bucket = var.s3_replica_bucket_arn
191-
storage_class = "STANDARD"
192-
}
193-
}
194-
}
158+
bucket = one(aws_s3_bucket.default.*.id)
159+
policy = local.policy
160+
}
161+
162+
resource "aws_s3_bucket_acl" "default" {
163+
count = local.bucket_enabled && !var.bucket_ownership_enforced_enabled ? 1 : 0
164+
165+
bucket = one(aws_s3_bucket.default.*.id)
166+
acl = var.acl
167+
168+
# Default "bucket ownership controls" for new S3 buckets is "BucketOwnerEnforced", which disables ACLs.
169+
# So, we need to wait until we change bucket ownership to "BucketOwnerPreferred" before we can set ACLs.
170+
depends_on = [aws_s3_bucket_ownership_controls.default]
171+
}
172+
173+
resource "aws_s3_bucket_versioning" "default" {
174+
count = local.bucket_enabled ? 1 : 0
175+
176+
bucket = one(aws_s3_bucket.default.*.id)
177+
178+
versioning_configuration {
179+
status = "Enabled"
180+
mfa_delete = var.mfa_delete ? "Enabled" : "Disabled"
195181
}
182+
}
183+
184+
resource "aws_s3_bucket_server_side_encryption_configuration" "default" {
185+
count = local.bucket_enabled ? 1 : 0
186+
187+
bucket = one(aws_s3_bucket.default.*.id)
196188

197-
dynamic "logging" {
198-
for_each = var.logging == null ? [] : [1]
199-
content {
200-
target_bucket = local.logging_bucket_name
201-
target_prefix = local.logging_prefix
189+
rule {
190+
apply_server_side_encryption_by_default {
191+
sse_algorithm = "AES256"
202192
}
203193
}
194+
}
204195

205-
tags = module.this.tags
196+
resource "aws_s3_bucket_logging" "default" {
197+
count = local.bucket_enabled && length(var.logging) > 0 ? 1 : 0
198+
199+
bucket = one(aws_s3_bucket.default.*.id)
200+
201+
target_bucket = var.logging[0].target_bucket
202+
target_prefix = var.logging[0].target_prefix
206203
}
207204

208205
resource "aws_s3_bucket_public_access_block" "default" {
209-
count = local.bucket_enabled && var.enable_public_access_block ? 1 : 0
210-
bucket = join("", aws_s3_bucket.default.*.id)
206+
count = local.bucket_enabled && var.enable_public_access_block ? 1 : 0
207+
208+
bucket = one(aws_s3_bucket.default.*.id)
211209
block_public_acls = var.block_public_acls
212210
ignore_public_acls = var.ignore_public_acls
213211
block_public_policy = var.block_public_policy
214212
restrict_public_buckets = var.restrict_public_buckets
215213
}
216214

215+
# After you apply the bucket owner enforced setting for Object Ownership, ACLs are disabled for the bucket.
216+
# See https://docs.aws.amazon.com/AmazonS3/latest/userguide/about-object-ownership.html
217+
resource "aws_s3_bucket_ownership_controls" "default" {
218+
count = local.bucket_enabled ? 1 : 0
219+
bucket = one(aws_s3_bucket.default.*.id)
220+
221+
rule {
222+
object_ownership = var.bucket_ownership_enforced_enabled ? "BucketOwnerEnforced" : "BucketOwnerPreferred"
223+
}
224+
}
225+
217226
module "dynamodb_table_label" {
218227
source = "cloudposse/label/null"
219228
version = "0.25.0"
@@ -223,7 +232,7 @@ module "dynamodb_table_label" {
223232
}
224233

225234
resource "aws_dynamodb_table" "with_server_side_encryption" {
226-
count = local.dynamodb_enabled && var.enable_server_side_encryption ? 1 : 0
235+
count = local.dynamodb_enabled ? 1 : 0
227236
name = local.dynamodb_table_name
228237
billing_mode = var.billing_mode
229238
read_capacity = var.billing_mode == "PROVISIONED" ? var.read_capacity : null
@@ -248,30 +257,6 @@ resource "aws_dynamodb_table" "with_server_side_encryption" {
248257
tags = module.dynamodb_table_label.tags
249258
}
250259

251-
resource "aws_dynamodb_table" "without_server_side_encryption" {
252-
count = local.dynamodb_enabled && ! var.enable_server_side_encryption ? 1 : 0
253-
name = local.dynamodb_table_name
254-
billing_mode = var.billing_mode
255-
read_capacity = var.billing_mode == "PROVISIONED" ? var.read_capacity : null
256-
write_capacity = var.billing_mode == "PROVISIONED" ? var.write_capacity : null
257-
258-
# https://www.terraform.io/docs/backends/types/s3.html#dynamodb_table
259-
hash_key = "LockID"
260-
261-
point_in_time_recovery {
262-
enabled = var.enable_point_in_time_recovery
263-
}
264-
265-
attribute {
266-
name = "LockID"
267-
type = "S"
268-
}
269-
270-
tags = module.dynamodb_table_label.tags
271-
}
272-
273-
data "aws_region" "current" {}
274-
275260
resource "local_file" "terraform_backend_config" {
276261
count = local.enabled && var.terraform_backend_config_file_path != "" ? 1 : 0
277262
content = local.terraform_backend_config_content

outputs.tf

+11-27
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,35 @@
11
output "s3_bucket_domain_name" {
2-
value = join("", aws_s3_bucket.default.*.bucket_domain_name)
2+
value = one(aws_s3_bucket.default[*].bucket_domain_name)
33
description = "S3 bucket domain name"
44
}
55

66
output "s3_bucket_id" {
7-
value = join("", aws_s3_bucket.default.*.id)
7+
value = one(aws_s3_bucket.default[*].id)
88
description = "S3 bucket ID"
99
}
1010

1111
output "s3_bucket_arn" {
12-
value = join("", aws_s3_bucket.default.*.arn)
12+
value = one(aws_s3_bucket.default[*].arn)
1313
description = "S3 bucket ARN"
1414
}
1515

16+
output "s3_replication_role_arn" {
17+
value = one(aws_iam_role.replication[*].arn)
18+
description = "The ARN of the IAM Role created for replication, if enabled."
19+
}
20+
1621
output "dynamodb_table_name" {
17-
value = element(
18-
coalescelist(
19-
aws_dynamodb_table.with_server_side_encryption.*.name,
20-
aws_dynamodb_table.without_server_side_encryption.*.name,
21-
[""]
22-
),
23-
0
24-
)
22+
value = one(aws_dynamodb_table.with_server_side_encryption.*.name)
2523
description = "DynamoDB table name"
2624
}
2725

2826
output "dynamodb_table_id" {
29-
value = element(
30-
coalescelist(
31-
aws_dynamodb_table.with_server_side_encryption.*.id,
32-
aws_dynamodb_table.without_server_side_encryption.*.id,
33-
[""]
34-
),
35-
0
36-
)
27+
value = one(aws_dynamodb_table.with_server_side_encryption.*.id)
3728
description = "DynamoDB table ID"
3829
}
3930

4031
output "dynamodb_table_arn" {
41-
value = element(
42-
coalescelist(
43-
aws_dynamodb_table.with_server_side_encryption.*.arn,
44-
aws_dynamodb_table.without_server_side_encryption.*.arn,
45-
[""]
46-
),
47-
0
48-
)
32+
value = one(aws_dynamodb_table.with_server_side_encryption.*.arn)
4933
description = "DynamoDB table ARN"
5034
}
5135

0 commit comments

Comments
 (0)