Skip to content

Commit 21f6285

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

File tree

8 files changed

+71
-74
lines changed

8 files changed

+71
-74
lines changed

data.tf

Lines changed: 18 additions & 12 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,12 +83,6 @@ 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-
}
87-
8886
# https://docs.aws.amazon.com/systems-manager/latest/userguide/getting-started-create-iam-instance-profile.html#create-iam-instance-profile-ssn-logging
8987
data "aws_iam_policy_document" "session_logging" {
9088
count = var.session_logging_enabled ? 1 : 0
@@ -95,19 +93,27 @@ data "aws_iam_policy_document" "session_logging" {
9593
actions = [
9694
"s3:PutObject"
9795
]
98-
resources = ["${join("", data.aws_s3_bucket.logs_bucket.*.arn)}/*"]
96+
resources = ["${local.session_logging_bucket_arn}/*"]
9997
}
10098

10199
statement {
102100
sid = "SSMAgentSessionAllowCloudWatchLogging"
103101
effect = "Allow"
104102
actions = [
105103
"logs:CreateLogStream",
106-
"logs:PutLogEvents",
104+
"logs:PutLogEvents"
105+
]
106+
resources = ["${local.session_logging_log_group_arn}:*"]
107+
}
108+
109+
statement {
110+
sid = "SSMAgentSessionAllowCloudWatchDescribe"
111+
effect = "Allow"
112+
actions = [
107113
"logs:DescribeLogGroups",
108114
"logs:DescribeLogStreams"
109115
]
110-
resources = ["*"]
116+
resources = [local.session_logging_log_group_arn]
111117
}
112118

113119
statement {
@@ -116,7 +122,7 @@ data "aws_iam_policy_document" "session_logging" {
116122
actions = [
117123
"s3:GetEncryptionConfiguration"
118124
]
119-
resources = ["*"]
125+
resources = [local.session_logging_bucket_arn]
120126
}
121127

122128
statement {
@@ -125,6 +131,6 @@ data "aws_iam_policy_document" "session_logging" {
125131
actions = [
126132
"kms:GenerateDataKey"
127133
]
128-
resources = ["*"]
134+
resources = [local.session_logging_kms_key_arn]
129135
}
130136
}

examples/with-names/outputs.tf

Lines changed: 0 additions & 14 deletions
This file was deleted.

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: 5 additions & 30 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
}
@@ -31,33 +31,6 @@ resource "null_resource" "validate_instance_type" {
3131
}
3232
}
3333

34-
# Validation using terraform_data to halt execution if requirements aren't met
35-
resource "terraform_data" "vpc_subnet_validation" {
36-
lifecycle {
37-
precondition {
38-
condition = length(var.vpc_name) > 0 || length(var.vpc_id) > 0
39-
error_message = "Either vpc_name or vpc_id must be provided."
40-
}
41-
42-
precondition {
43-
condition = length(var.subnet_names) > 0 || length(var.subnet_ids) > 0
44-
error_message = "Either subnet_names or subnet_ids must be provided."
45-
}
46-
}
47-
}
48-
49-
# Warning checks for VPC and subnet configuration (non-blocking)
50-
check "vpc_subnet_warnings" {
51-
assert {
52-
condition = !(length(var.vpc_name) > 0 && length(var.vpc_id) > 0)
53-
error_message = "Both vpc_name and vpc_id are provided. When vpc_name is specified, vpc_id will be ignored."
54-
}
55-
56-
assert {
57-
condition = !(length(var.subnet_names) > 0 && length(var.subnet_ids) > 0)
58-
error_message = "Both subnet_names and subnet_ids are provided. When subnet_names are specified, subnet_ids will be ignored."
59-
}
60-
}
6134

6235
module "role_label" {
6336
source = "cloudposse/label/null"
@@ -79,8 +52,10 @@ locals {
7952
region = coalesce(var.region, data.aws_region.current.region)
8053
account_id = data.aws_caller_identity.current.account_id
8154

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), "")
55+
session_logging_bucket_name = try(coalesce(var.session_logging_bucket_name, module.logs_label.id), "")
56+
session_logging_kms_key_arn = try(coalesce(var.session_logging_kms_key_arn, module.kms_key.key_arn), "")
57+
session_logging_bucket_arn = var.session_logging_enabled ? "arn:aws:s3:::${local.session_logging_bucket_name}" : ""
58+
session_logging_log_group_arn = var.session_logging_enabled ? "arn:aws:logs:${local.region}:${local.account_id}:log-group:${module.logs_label.id}" : ""
8459

8560
logs_bucket_enabled = var.session_logging_enabled && length(var.session_logging_bucket_name) == 0
8661
}

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

validations.tf

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Validation using terraform_data to halt execution if requirements aren't met
2+
resource "terraform_data" "vpc_subnet_validation" {
3+
lifecycle {
4+
precondition {
5+
condition = var.vpc_name != null || var.vpc_id != null
6+
error_message = "Either vpc_name or vpc_id must be provided."
7+
}
8+
9+
precondition {
10+
condition = length(var.subnet_names) > 0 || length(var.subnet_ids) > 0
11+
error_message = "Either subnet_names or subnet_ids must be provided."
12+
}
13+
}
14+
}
15+
16+
# Warning checks for VPC and subnet configuration (non-blocking)
17+
check "vpc_subnet_warnings" {
18+
assert {
19+
condition = !(var.vpc_name != null && var.vpc_id != null)
20+
error_message = "Both vpc_name and vpc_id are provided. When vpc_name is specified, vpc_id will be ignored."
21+
}
22+
23+
assert {
24+
condition = !(length(var.subnet_names) > 0 && length(var.subnet_ids) > 0)
25+
error_message = "Both subnet_names and subnet_ids are provided. When subnet_names are specified, subnet_ids will be ignored."
26+
}
27+
}

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)