Skip to content

Commit 9537e93

Browse files
committed
chore: security best-practices + minor improvements
1 parent 2c0ec7b commit 9537e93

File tree

7 files changed

+48
-35
lines changed

7 files changed

+48
-35
lines changed

data.tf

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ data "aws_caller_identity" "current" {}
33

44
# VPC lookup by name (when vpc_name is provided)
55
data "aws_vpc" "selected" {
6-
count = length(var.vpc_name) > 0 ? 1 : 0
6+
count = var.vpc_name != null ? 1 : 0
77

88
filter {
99
name = "tag:Name"
@@ -19,6 +19,10 @@ data "aws_subnet" "selected" {
1919
name = "tag:Name"
2020
values = [each.value]
2121
}
22+
filter {
23+
name = "vpc-id"
24+
values = [local.vpc_id]
25+
}
2226
}
2327

2428
# Most recent Amazon Linux 2023 AMI
@@ -79,11 +83,7 @@ data "aws_iam_policy_document" "default" {
7983
}
8084
}
8185

82-
# S3 bucket for session logging (when using existing bucket)
83-
data "aws_s3_bucket" "logs_bucket" {
84-
count = var.session_logging_enabled ? 1 : 0
85-
bucket = try(coalesce(var.session_logging_bucket_name, module.logs_bucket.bucket_id), "")
86-
}
86+
8787

8888
# https://docs.aws.amazon.com/systems-manager/latest/userguide/getting-started-create-iam-instance-profile.html#create-iam-instance-profile-ssn-logging
8989
data "aws_iam_policy_document" "session_logging" {
@@ -95,19 +95,27 @@ data "aws_iam_policy_document" "session_logging" {
9595
actions = [
9696
"s3:PutObject"
9797
]
98-
resources = ["${join("", data.aws_s3_bucket.logs_bucket.*.arn)}/*"]
98+
resources = ["${local.session_logging_bucket_arn}/*"]
9999
}
100100

101101
statement {
102102
sid = "SSMAgentSessionAllowCloudWatchLogging"
103103
effect = "Allow"
104104
actions = [
105105
"logs:CreateLogStream",
106-
"logs:PutLogEvents",
106+
"logs:PutLogEvents"
107+
]
108+
resources = ["${local.session_logging_log_group_arn}:*"]
109+
}
110+
111+
statement {
112+
sid = "SSMAgentSessionAllowCloudWatchDescribe"
113+
effect = "Allow"
114+
actions = [
107115
"logs:DescribeLogGroups",
108116
"logs:DescribeLogStreams"
109117
]
110-
resources = ["*"]
118+
resources = [local.session_logging_log_group_arn]
111119
}
112120

113121
statement {
@@ -116,7 +124,7 @@ data "aws_iam_policy_document" "session_logging" {
116124
actions = [
117125
"s3:GetEncryptionConfiguration"
118126
]
119-
resources = ["*"]
127+
resources = [local.session_logging_bucket_arn]
120128
}
121129

122130
statement {
@@ -125,6 +133,6 @@ data "aws_iam_policy_document" "session_logging" {
125133
actions = [
126134
"kms:GenerateDataKey"
127135
]
128-
resources = ["*"]
136+
resources = [local.session_logging_kms_key_arn]
129137
}
130138
}

examples/complete/main.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ module "vpc" {
1111
name = var.name
1212

1313
ipv4_primary_cidr_block = "10.0.0.0/16"
14-
assign_generated_ipv6_cidr_block = true
14+
assign_generated_ipv6_cidr_block = var.ipv6_enabled
1515
}
1616

1717
module "subnets" {

examples/with-names/variables.tf

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,3 @@ variable "availability_zones" {
2121
description = "List of availability zones"
2222
default = ["us-east-1a", "us-east-1b"]
2323
}
24-
25-
variable "ipv6_enabled" {
26-
type = bool
27-
description = "Enable IPv6"
28-
default = false
29-
}

main.tf

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ locals {
1515
)
1616

1717
# VPC and Subnet ID resolution - names take precedence over IDs
18-
vpc_id = length(var.vpc_name) > 0 ? one(data.aws_vpc.selected[*].id) : var.vpc_id
18+
vpc_id = var.vpc_name != null ? one(data.aws_vpc.selected[*].id) : var.vpc_id
1919
subnet_ids = length(var.subnet_names) > 0 ? values(data.aws_subnet.selected)[*].id : var.subnet_ids
2020

2121
}
@@ -35,7 +35,7 @@ resource "null_resource" "validate_instance_type" {
3535
resource "terraform_data" "vpc_subnet_validation" {
3636
lifecycle {
3737
precondition {
38-
condition = length(var.vpc_name) > 0 || length(var.vpc_id) > 0
38+
condition = var.vpc_name != null || var.vpc_id != null
3939
error_message = "Either vpc_name or vpc_id must be provided."
4040
}
4141

@@ -49,7 +49,7 @@ resource "terraform_data" "vpc_subnet_validation" {
4949
# Warning checks for VPC and subnet configuration (non-blocking)
5050
check "vpc_subnet_warnings" {
5151
assert {
52-
condition = !(length(var.vpc_name) > 0 && length(var.vpc_id) > 0)
52+
condition = !(var.vpc_name != null && var.vpc_id != null)
5353
error_message = "Both vpc_name and vpc_id are provided. When vpc_name is specified, vpc_id will be ignored."
5454
}
5555

@@ -79,8 +79,10 @@ locals {
7979
region = coalesce(var.region, data.aws_region.current.region)
8080
account_id = data.aws_caller_identity.current.account_id
8181

82-
session_logging_bucket_name = try(coalesce(var.session_logging_bucket_name, module.logs_label.id), "")
83-
session_logging_kms_key_arn = try(coalesce(var.session_logging_kms_key_arn, module.kms_key.key_arn), "")
82+
session_logging_bucket_name = try(coalesce(var.session_logging_bucket_name, module.logs_label.id), "")
83+
session_logging_kms_key_arn = try(coalesce(var.session_logging_kms_key_arn, module.kms_key.key_arn), "")
84+
session_logging_bucket_arn = var.session_logging_enabled ? "arn:aws:s3:::${local.session_logging_bucket_name}" : ""
85+
session_logging_log_group_arn = var.session_logging_enabled ? "arn:aws:logs:${local.region}:${local.account_id}:log-group:${module.logs_label.id}" : ""
8486

8587
logs_bucket_enabled = var.session_logging_enabled && length(var.session_logging_bucket_name) == 0
8688
}

outputs.tf

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ output "role_id" {
2424
}
2525

2626
output "session_logging_bucket_id" {
27-
value = local.logs_bucket_enabled ? join("", data.aws_s3_bucket.logs_bucket.*.id) : ""
27+
value = var.session_logging_enabled ? local.session_logging_bucket_name : ""
2828
description = "The ID of the SSM Agent Session Logging S3 Bucket."
2929
}
3030

3131
output "session_logging_bucket_arn" {
32-
value = local.logs_bucket_enabled ? join("", data.aws_s3_bucket.logs_bucket.*.arn) : ""
32+
value = local.session_logging_bucket_arn
3333
description = "The ARN of the SSM Agent Session Logging S3 Bucket."
3434
}

tests/vpc-subnet-locals-only.tftest.hcl renamed to tests/vpc-subnet-locals.tftest.hcl

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ mock_provider "aws" {
2727

2828
mock_data "aws_ami" {
2929
defaults = {
30-
id = "ami-mock"
30+
id = "ami-mock"
3131
root_device_name = "/dev/xvda"
3232
}
3333
}
@@ -40,29 +40,29 @@ mock_provider "aws" {
4040

4141
mock_data "aws_s3_bucket" {
4242
defaults = {
43-
id = "mock-bucket"
43+
id = "mock-bucket"
4444
arn = "arn:aws:s3:::mock-bucket"
4545
}
4646
}
4747

4848
# Mock AWS resources that get created in the module
4949
mock_resource "aws_launch_template" {
5050
defaults = {
51-
id = "lt-mock123456"
51+
id = "lt-mock123456"
5252
latest_version = "1"
5353
}
5454
}
5555
}
5656

5757
# Test precedence - names over IDs
58-
run "test_precedence_local" {
58+
run "verify_vpc_subnet_name_precedence" {
5959
command = plan
6060

6161
variables {
62-
vpc_id = "vpc-should-be-ignored"
63-
vpc_name = "my-vpc"
64-
subnet_ids = ["subnet-should-be-ignored"]
65-
subnet_names = ["my-subnet"]
62+
vpc_id = "vpc-should-be-ignored"
63+
vpc_name = "my-vpc"
64+
subnet_ids = ["subnet-should-be-ignored"]
65+
subnet_names = ["my-subnet"]
6666
session_logging_enabled = false
6767

6868
# Add required context variables for proper naming

variables.tf

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
variable "vpc_id" {
22
type = string
33
description = "The ID of the VPC which the EC2 Instance will run in."
4-
default = ""
4+
default = null
55
}
66

77
variable "subnet_ids" {
@@ -13,7 +13,7 @@ variable "subnet_ids" {
1313
variable "vpc_name" {
1414
type = string
1515
description = "The name of the VPC which the EC2 Instance will run in. If provided, vpc_id will be ignored."
16-
default = ""
16+
default = null
1717
}
1818

1919
variable "subnet_names" {
@@ -172,6 +172,15 @@ variable "session_logging_bucket_name" {
172172
default = ""
173173
type = string
174174
description = "The name of the S3 Bucket to ship session logs to. This will remove creation of an independent session logging bucket. This is only relevant if the session_logging_enabled variable is `true`."
175+
176+
validation {
177+
condition = var.session_logging_bucket_name == "" || (
178+
can(regex("^[a-z0-9][a-z0-9.-]*[a-z0-9]$", var.session_logging_bucket_name)) &&
179+
length(var.session_logging_bucket_name) >= 3 &&
180+
length(var.session_logging_bucket_name) <= 63
181+
)
182+
error_message = "S3 bucket name must follow AWS naming conventions: 3-63 characters, lowercase letters, numbers, dots, and hyphens only, must start and end with letter or number."
183+
}
175184
}
176185

177186
variable "region" {

0 commit comments

Comments
 (0)