Skip to content

Commit 8a3d43c

Browse files
authored
feat: add VPC and subnet name support (#54)
## what * Adds support for specifying VPC and subnets by name in addition to existing ID-based configuration. * New variables: vpc_name and subnet_names for name-based resource selection * Data sources: Added aws_vpc and aws_subnet lookups with for_each for subnet names * Precedence logic: Names take precedence over IDs when both are provided * Validation: terraform_data preconditions halt execution if neither names nor IDs provided * Warnings: Non-blocking check blocks warn when both names and IDs are specified * Tests: Added focused tests for VPC/subnet input logic Also, * Bumps S3 bucket module + cuts arguments matching the defaults. * Addresses some trivy warnings. ## why - This way, root modules should not add extra data sources to retrieve VPC and Subnets IDs. ## references - N/A <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - New Features - Select VPC and subnets by name (Name tag) with automatic precedence over IDs. - Inputs for VPC/Subnets are now optional; module resolves from names or IDs. - Validation and warnings prevent mixing names and IDs. - Improvements - Upgraded logging bucket to stronger, KMS-based encryption. - Documentation - Added a complete example showing ID-based, name-based, and mixed configurations. - New example outputs for instance profile ARNs. - Tests - Added tests confirming name-based precedence and input warnings. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent b45ebcd commit 8a3d43c

File tree

8 files changed

+346
-79
lines changed

8 files changed

+346
-79
lines changed

data.tf

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,30 @@
11
data "aws_region" "current" {}
22
data "aws_caller_identity" "current" {}
33

4+
# VPC lookup by name (when vpc_name is provided)
5+
data "aws_vpc" "selected" {
6+
count = var.vpc_name != null ? 1 : 0
7+
8+
filter {
9+
name = "tag:Name"
10+
values = [var.vpc_name]
11+
}
12+
}
13+
14+
# Individual subnet lookup by name (when subnet_names are provided)
15+
data "aws_subnet" "selected" {
16+
for_each = toset(var.subnet_names)
17+
18+
filter {
19+
name = "tag:Name"
20+
values = [each.value]
21+
}
22+
filter {
23+
name = "vpc-id"
24+
values = [local.vpc_id]
25+
}
26+
}
27+
428
# Most recent Amazon Linux 2023 AMI
529
data "aws_ami" "amazon_linux_2023" {
630
most_recent = true
@@ -33,7 +57,7 @@ data "aws_ami" "amazon_linux_2023" {
3357
# This rule was introduced in the following PR:
3458
# https://github.com/masterpointio/terraform-aws-ssm-agent/pull/43.
3559
#
36-
# trunk-ignore(trivy/AVD-AWS-0344)
60+
# trivy:ignore:AVD-AWS-0344
3761
data "aws_ami" "instance" {
3862
count = length(var.ami) > 0 ? 1 : 0
3963

@@ -44,3 +68,69 @@ data "aws_ami" "instance" {
4468
values = [var.ami]
4569
}
4670
}
71+
72+
# IAM policy document for EC2 instances to assume the SSM Agent role
73+
data "aws_iam_policy_document" "default" {
74+
75+
statement {
76+
effect = "Allow"
77+
actions = ["sts:AssumeRole"]
78+
79+
principals {
80+
type = "Service"
81+
identifiers = ["ec2.amazonaws.com"]
82+
}
83+
}
84+
}
85+
86+
# https://docs.aws.amazon.com/systems-manager/latest/userguide/getting-started-create-iam-instance-profile.html#create-iam-instance-profile-ssn-logging
87+
data "aws_iam_policy_document" "session_logging" {
88+
count = var.session_logging_enabled ? 1 : 0
89+
90+
statement {
91+
sid = "SSMAgentSessionAllowS3Logging"
92+
effect = "Allow"
93+
actions = [
94+
"s3:PutObject"
95+
]
96+
resources = ["${local.session_logging_bucket_arn}/*"]
97+
}
98+
99+
statement {
100+
sid = "SSMAgentSessionAllowCloudWatchLogging"
101+
effect = "Allow"
102+
actions = [
103+
"logs:CreateLogStream",
104+
"logs:PutLogEvents"
105+
]
106+
resources = ["${local.session_logging_log_group_arn}:*"]
107+
}
108+
109+
statement {
110+
sid = "SSMAgentSessionAllowCloudWatchDescribe"
111+
effect = "Allow"
112+
actions = [
113+
"logs:DescribeLogGroups",
114+
"logs:DescribeLogStreams"
115+
]
116+
resources = [local.session_logging_log_group_arn]
117+
}
118+
119+
statement {
120+
sid = "SSMAgentSessionAllowGetEncryptionConfig"
121+
effect = "Allow"
122+
actions = [
123+
"s3:GetEncryptionConfiguration"
124+
]
125+
resources = [local.session_logging_bucket_arn]
126+
}
127+
128+
statement {
129+
sid = "SSMAgentSessionAllowKMSDataKey"
130+
effect = "Allow"
131+
actions = [
132+
"kms:GenerateDataKey"
133+
]
134+
resources = [local.session_logging_kms_key_arn]
135+
}
136+
}

examples/with-names/main.tf

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
provider "aws" {
2+
region = var.region
3+
}
4+
5+
# Create VPC and subnets with specific names for demonstration
6+
module "vpc" {
7+
source = "cloudposse/vpc/aws"
8+
version = "2.1.0"
9+
10+
namespace = var.namespace
11+
stage = var.stage
12+
name = "example-vpc"
13+
14+
ipv4_primary_cidr_block = "10.0.0.0/16"
15+
assign_generated_ipv6_cidr_block = true
16+
}
17+
18+
module "subnets" {
19+
source = "cloudposse/dynamic-subnets/aws"
20+
version = "2.3.0"
21+
namespace = var.namespace
22+
stage = var.stage
23+
name = "example-subnets"
24+
25+
availability_zones = var.availability_zones
26+
vpc_id = module.vpc.vpc_id
27+
igw_id = [module.vpc.igw_id]
28+
ipv4_cidr_block = [module.vpc.vpc_cidr_block]
29+
ipv6_enabled = var.ipv6_enabled
30+
}
31+
32+
# Example 1: Using VPC and subnet IDs (traditional approach)
33+
module "ssm_agent_with_ids" {
34+
source = "../../"
35+
stage = var.stage
36+
namespace = var.namespace
37+
name = "ssm-with-ids"
38+
39+
vpc_id = module.vpc.vpc_id
40+
subnet_ids = module.subnets.private_subnet_ids
41+
}
42+
43+
# Example 2: Using VPC and subnet names (new functionality)
44+
module "ssm_agent_with_names" {
45+
source = "../../"
46+
stage = var.stage
47+
namespace = var.namespace
48+
name = "ssm-with-names"
49+
50+
vpc_name = module.vpc.vpc_id_tag_name # This would be the Name tag value
51+
subnet_names = [
52+
# These would be the actual Name tag values of the subnets
53+
# In a real scenario, you'd know these names or get them from data sources
54+
"example-private-subnet-1",
55+
"example-private-subnet-2"
56+
]
57+
58+
depends_on = [module.subnets]
59+
}
60+
61+
# Example 3: Mixed configuration (VPC by ID, subnets by name)
62+
module "ssm_agent_mixed" {
63+
source = "../../"
64+
stage = var.stage
65+
namespace = var.namespace
66+
name = "ssm-mixed"
67+
68+
vpc_id = module.vpc.vpc_id
69+
subnet_names = [
70+
"example-private-subnet-1",
71+
"example-private-subnet-2"
72+
]
73+
74+
depends_on = [module.subnets]
75+
}

examples/with-names/variables.tf

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
variable "region" {
2+
type = string
3+
description = "AWS region"
4+
default = "us-east-1"
5+
}
6+
7+
variable "namespace" {
8+
type = string
9+
description = "Namespace for resource naming"
10+
default = "mp"
11+
}
12+
13+
variable "stage" {
14+
type = string
15+
description = "Stage for resource naming"
16+
default = "test"
17+
}
18+
19+
variable "availability_zones" {
20+
type = list(string)
21+
description = "List of availability zones"
22+
default = ["us-east-1a", "us-east-1b"]
23+
}

main.tf

Lines changed: 17 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ locals {
1414
length(var.ami) > 0 ? element(data.aws_ami.instance, 0).root_device_name : "/dev/xvda"
1515
)
1616

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

1923
resource "null_resource" "validate_instance_type" {
@@ -27,6 +31,7 @@ resource "null_resource" "validate_instance_type" {
2731
}
2832
}
2933

34+
3035
module "role_label" {
3136
source = "cloudposse/label/null"
3237
version = "0.25.0"
@@ -47,8 +52,10 @@ locals {
4752
region = coalesce(var.region, data.aws_region.current.region)
4853
account_id = data.aws_caller_identity.current.account_id
4954

50-
session_logging_bucket_name = try(coalesce(var.session_logging_bucket_name, module.logs_label.id), "")
51-
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}" : ""
5259

5360
logs_bucket_enabled = var.session_logging_enabled && length(var.session_logging_bucket_name) == 0
5461
}
@@ -57,68 +64,6 @@ locals {
5764
## SSM AGENT ROLE ##
5865
###################
5966

60-
data "aws_iam_policy_document" "default" {
61-
62-
statement {
63-
effect = "Allow"
64-
actions = ["sts:AssumeRole"]
65-
66-
principals {
67-
type = "Service"
68-
identifiers = ["ec2.amazonaws.com"]
69-
}
70-
}
71-
}
72-
73-
data "aws_s3_bucket" "logs_bucket" {
74-
count = var.session_logging_enabled ? 1 : 0
75-
bucket = try(coalesce(var.session_logging_bucket_name, module.logs_bucket.bucket_id), "")
76-
}
77-
78-
# https://docs.aws.amazon.com/systems-manager/latest/userguide/getting-started-create-iam-instance-profile.html#create-iam-instance-profile-ssn-logging
79-
data "aws_iam_policy_document" "session_logging" {
80-
count = var.session_logging_enabled ? 1 : 0
81-
82-
statement {
83-
sid = "SSMAgentSessionAllowS3Logging"
84-
effect = "Allow"
85-
actions = [
86-
"s3:PutObject"
87-
]
88-
resources = ["${join("", data.aws_s3_bucket.logs_bucket.*.arn)}/*"]
89-
}
90-
91-
statement {
92-
sid = "SSMAgentSessionAllowCloudWatchLogging"
93-
effect = "Allow"
94-
actions = [
95-
"logs:CreateLogStream",
96-
"logs:PutLogEvents",
97-
"logs:DescribeLogGroups",
98-
"logs:DescribeLogStreams"
99-
]
100-
resources = ["*"]
101-
}
102-
103-
statement {
104-
sid = "SSMAgentSessionAllowGetEncryptionConfig"
105-
effect = "Allow"
106-
actions = [
107-
"s3:GetEncryptionConfiguration"
108-
]
109-
resources = ["*"]
110-
}
111-
112-
statement {
113-
sid = "SSMAgentSessionAllowKMSDataKey"
114-
effect = "Allow"
115-
actions = [
116-
"kms:GenerateDataKey"
117-
]
118-
resources = ["*"]
119-
}
120-
}
121-
12267
resource "aws_iam_role" "default" {
12368
name = module.role_label.id
12469
assume_role_policy = data.aws_iam_policy_document.default.json
@@ -157,19 +102,21 @@ resource "aws_iam_instance_profile" "default" {
157102
###################
158103

159104
resource "aws_security_group" "default" {
160-
vpc_id = var.vpc_id
105+
vpc_id = local.vpc_id
161106
name = module.this.id
162107
description = "Allow ALL egress from SSM Agent."
163108
tags = module.this.tags
164109
}
165110

111+
# trivy:ignore:aws-vpc-no-public-egress-sgr SSM Agent requires broad egress to communicate with AWS services
166112
resource "aws_security_group_rule" "allow_all_egress" {
167113
type = "egress"
168114
from_port = 0
169115
to_port = 0
170116
protocol = "-1"
171117
cidr_blocks = ["0.0.0.0/0"]
172118
ipv6_cidr_blocks = ["::/0"]
119+
description = "Allow all outbound traffic for SSM Agent communication with AWS services"
173120
security_group_id = aws_security_group.default.id
174121
}
175122

@@ -246,21 +193,15 @@ DOC
246193

247194
module "logs_bucket" {
248195
source = "cloudposse/s3-bucket/aws"
249-
version = "3.1.2"
196+
version = "4.10.0"
250197

251198
enabled = local.logs_bucket_enabled
252199
context = module.logs_label.context
253200

254201
# Encryption / Security
255-
acl = "private"
256-
sse_algorithm = "aws:kms"
257-
kms_master_key_arn = local.session_logging_kms_key_arn
258-
allow_encrypted_uploads_only = false
259-
force_destroy = true
260-
261-
# Feature enablement
262-
user_enabled = false
263-
versioning_enabled = true
202+
sse_algorithm = "aws:kms"
203+
kms_master_key_arn = local.session_logging_kms_key_arn
204+
force_destroy = true
264205

265206
lifecycle_configuration_rules = [{
266207
enabled = true
@@ -385,7 +326,7 @@ resource "aws_autoscaling_group" "default" {
385326
# By default, we don't care to protect from scale in as we want to roll instances frequently
386327
protect_from_scale_in = var.protect_from_scale_in
387328

388-
vpc_zone_identifier = var.subnet_ids
329+
vpc_zone_identifier = local.subnet_ids
389330

390331
default_cooldown = 180
391332
health_check_grace_period = 180

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
}

0 commit comments

Comments
 (0)