Skip to content

Commit b9dab4b

Browse files
committed
feat(modules): s3 sub-module
1 parent b623ba5 commit b9dab4b

File tree

6 files changed

+345
-0
lines changed

6 files changed

+345
-0
lines changed

modules/s3-bucket/.header.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# s3-bucket
2+
3+
This sub-module creates an S3 bucket with optional S3 bucket policies to attach.
4+
5+
## Presets
6+
7+
### S3 Bucket
8+
9+
- The `force_destroy` is set to `false` as the default option (which prevents the deletion of the S3 bucket if it has objects in it), and can be overridden to be `true`.
10+
11+
### S3 Bucket Policy
12+
13+
- The `effect` under `statement` in the `aws_iam_policy_document.this` data source is set to `Allow` as the default option (which grants the principal the defined permissions), and can be overridden to be `Deny`.
14+
15+
## Notes
16+
17+
- The S3 bucket policies are attached only if at least one policy is specified. Otherwise, no bucket policies are attached.

modules/s3-bucket/main.tf

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
locals {
2+
# S3 Bucket Policy
3+
create_s3_bucket_policy = length(var.bucket_policies) > 0
4+
}
5+
6+
################################################################################
7+
# S3 Bucket
8+
################################################################################
9+
10+
resource "aws_s3_bucket" "this" {
11+
bucket = var.bucket
12+
force_destroy = var.bucket_force_destroy
13+
object_lock_enabled = var.bucket_object_lock_enabled
14+
15+
tags = var.tags
16+
}
17+
18+
################################################################################
19+
# S3 Bucket Policy
20+
################################################################################
21+
22+
data "aws_iam_policy_document" "this" {
23+
for_each = local.create_s3_bucket_policy ? var.bucket_policies : {}
24+
25+
policy_id = each.value.id
26+
version = each.value.version
27+
28+
dynamic "statement" {
29+
for_each = each.value.statements
30+
31+
content {
32+
actions = statement.value.actions
33+
effect = statement.value.effect
34+
resources = statement.value.resources
35+
36+
dynamic "principals" {
37+
for_each = statement.value.principals
38+
iterator = principal
39+
40+
content {
41+
identifiers = principal.value.identifiers
42+
type = principal.value.type
43+
}
44+
}
45+
}
46+
}
47+
}
48+
49+
resource "aws_s3_bucket_policy" "this" {
50+
for_each = local.create_s3_bucket_policy ? data.aws_iam_policy_document.this : {}
51+
52+
bucket = aws_s3_bucket.this.id
53+
policy = each.value.json
54+
}

modules/s3-bucket/outputs.tf

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
################################################################################
2+
# S3 Bucket
3+
################################################################################
4+
5+
output "bucket_id" {
6+
description = "Name of the bucket."
7+
value = aws_s3_bucket.this.id
8+
}
9+
10+
output "bucket_arn" {
11+
description = "ARN of the bucket."
12+
value = aws_s3_bucket.this.arn
13+
}

modules/s3-bucket/variables.tf

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
################################################################################
2+
# S3 Bucket
3+
################################################################################
4+
5+
variable "bucket" {
6+
description = "(Optional, Forces new resource) Name of the bucket."
7+
type = string
8+
default = null
9+
}
10+
11+
variable "bucket_force_destroy" {
12+
description = "(Optional, Default:false) Boolean that indicates all objects (including any locked objects) should be deleted from the bucket when the bucket is destroyed so that the bucket can be destroyed without error."
13+
type = bool
14+
nullable = false
15+
default = false
16+
}
17+
18+
variable "bucket_object_lock_enabled" {
19+
description = "(Optional, Forces new resource) Indicates whether this bucket has an Object Lock configuration enabled."
20+
type = bool
21+
nullable = false
22+
default = false
23+
}
24+
25+
variable "tags" {
26+
description = "(Optional) Map of tags to assign to the bucket."
27+
type = map(string)
28+
nullable = false
29+
default = {}
30+
}
31+
32+
################################################################################
33+
# S3 Bucket Policy
34+
################################################################################
35+
36+
variable "bucket_policies" {
37+
description = "(Optional) Map of bucket policies to attach to the S3 bucket."
38+
type = map(object({
39+
id = optional(string, null)
40+
version = optional(string, null)
41+
statements = optional(list(object({
42+
actions = optional(set(string), [])
43+
effect = optional(string, "Allow")
44+
resources = optional(set(string), [])
45+
principals = optional(list(object({
46+
identifiers = set(string)
47+
type = string
48+
})), [])
49+
})), [])
50+
}))
51+
nullable = false
52+
default = {}
53+
}

modules/s3-bucket/version.tf

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
terraform {
2+
required_version = ">= 1.6.0"
3+
}
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
provider "aws" {
2+
region = "ap-south-1"
3+
}
4+
5+
################################################################################
6+
# S3 Bucket
7+
################################################################################
8+
9+
run "s3_bucket_attributes_match" {
10+
command = plan
11+
12+
module {
13+
source = "./modules/s3-bucket"
14+
}
15+
16+
variables {
17+
bucket = "example-bucket"
18+
bucket_force_destroy = true
19+
bucket_object_lock_enabled = true
20+
21+
tags = {
22+
Example = "Tag"
23+
}
24+
}
25+
26+
assert {
27+
condition = aws_s3_bucket.this.bucket == var.bucket
28+
error_message = "Bucket mismatch"
29+
}
30+
31+
assert {
32+
condition = aws_s3_bucket.this.force_destroy == var.bucket_force_destroy
33+
error_message = "Force destroy mismatch"
34+
}
35+
36+
assert {
37+
condition = aws_s3_bucket.this.object_lock_enabled == var.bucket_object_lock_enabled
38+
error_message = "Object lock status mismatch"
39+
}
40+
41+
assert {
42+
condition = aws_s3_bucket.this.tags == var.tags
43+
error_message = "Tags mismatch"
44+
}
45+
}
46+
47+
################################################################################
48+
# IAM Policy Document
49+
################################################################################
50+
51+
run "does_not_create_iam_policy_document_check" {
52+
command = plan
53+
54+
module {
55+
source = "./modules/s3-bucket"
56+
}
57+
58+
variables {}
59+
60+
assert {
61+
condition = length(data.aws_iam_policy_document.this) == 0
62+
error_message = "IAM policy document data source was created"
63+
}
64+
}
65+
66+
run "iam_policy_document_attributes_match" {
67+
command = plan
68+
69+
module {
70+
source = "./modules/s3-bucket"
71+
}
72+
73+
variables {
74+
bucket_policies = {
75+
example-policy = {
76+
id = "example-id"
77+
version = "2012-10-17"
78+
79+
statements = [
80+
{
81+
actions = [
82+
"s3:PutObject"
83+
]
84+
effect = "Allow"
85+
resources = [
86+
"example/*"
87+
]
88+
89+
principals = [
90+
{
91+
identifiers = ["delivery.logs.amazonaws.com"]
92+
type = "Service"
93+
}
94+
]
95+
}
96+
]
97+
}
98+
}
99+
}
100+
101+
assert {
102+
condition = length(data.aws_iam_policy_document.this) == 1
103+
error_message = "IAM policy document data source was not created"
104+
}
105+
106+
assert {
107+
condition = data.aws_iam_policy_document.this["example-policy"].policy_id == var.bucket_policies["example-policy"].id
108+
error_message = "Policy id mismatch"
109+
}
110+
111+
assert {
112+
condition = data.aws_iam_policy_document.this["example-policy"].version == var.bucket_policies["example-policy"].version
113+
error_message = "Version mismatch"
114+
}
115+
116+
assert {
117+
condition = length(data.aws_iam_policy_document.this["example-policy"].statement) == 1
118+
error_message = "Statement count mismatch"
119+
}
120+
121+
assert {
122+
condition = data.aws_iam_policy_document.this["example-policy"].statement[0].actions == var.bucket_policies["example-policy"].statements[0].actions
123+
error_message = "Statement actions mismatch"
124+
}
125+
126+
assert {
127+
condition = data.aws_iam_policy_document.this["example-policy"].statement[0].effect == var.bucket_policies["example-policy"].statements[0].effect
128+
error_message = "Statement effect mismatch"
129+
}
130+
131+
assert {
132+
condition = data.aws_iam_policy_document.this["example-policy"].statement[0].resources == var.bucket_policies["example-policy"].statements[0].resources
133+
error_message = "Statement resources mismatch"
134+
}
135+
136+
assert {
137+
condition = data.aws_iam_policy_document.this["example-policy"].statement[0].principals == toset(var.bucket_policies["example-policy"].statements[0].principals)
138+
error_message = "Statement principals mismatch"
139+
}
140+
}
141+
142+
################################################################################
143+
# S3 Bucket Policy
144+
################################################################################
145+
146+
run "does_not_create_s3_bucket_policy_check" {
147+
command = plan
148+
149+
module {
150+
source = "./modules/s3-bucket"
151+
}
152+
153+
variables {}
154+
155+
assert {
156+
condition = length(aws_s3_bucket_policy.this) == 0
157+
error_message = "S3 bucket policy was created"
158+
}
159+
}
160+
161+
run "s3_bucket_policy_attributes_match" {
162+
command = plan
163+
164+
module {
165+
source = "./modules/s3-bucket"
166+
}
167+
168+
variables {
169+
bucket_policies = {
170+
example-policy = {
171+
id = "example-id"
172+
version = "2012-10-17"
173+
174+
statements = [
175+
{
176+
actions = [
177+
"s3:PutObject"
178+
]
179+
effect = "Allow"
180+
resources = [
181+
"example/*"
182+
]
183+
184+
principals = [
185+
{
186+
identifiers = ["delivery.logs.amazonaws.com"]
187+
type = "Service"
188+
}
189+
]
190+
}
191+
]
192+
}
193+
}
194+
}
195+
196+
assert {
197+
condition = length(aws_s3_bucket_policy.this) == 1
198+
error_message = "S3 bucket policy was not created"
199+
}
200+
201+
assert {
202+
condition = jsondecode(aws_s3_bucket_policy.this["example-policy"].policy) == jsondecode(data.aws_iam_policy_document.this["example-policy"].json)
203+
error_message = "Policy mismatch"
204+
}
205+
}

0 commit comments

Comments
 (0)