Skip to content

Commit 2c0ec7b

Browse files
committed
feat: allow passing VPC and Subnet names
1 parent b45ebcd commit 2c0ec7b

File tree

7 files changed

+345
-75
lines changed

7 files changed

+345
-75
lines changed

data.tf

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,26 @@
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 = length(var.vpc_name) > 0 ? 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+
}
23+
424
# Most recent Amazon Linux 2023 AMI
525
data "aws_ami" "amazon_linux_2023" {
626
most_recent = true
@@ -33,7 +53,7 @@ data "aws_ami" "amazon_linux_2023" {
3353
# This rule was introduced in the following PR:
3454
# https://github.com/masterpointio/terraform-aws-ssm-agent/pull/43.
3555
#
36-
# trunk-ignore(trivy/AVD-AWS-0344)
56+
# trivy:ignore:AVD-AWS-0344
3757
data "aws_ami" "instance" {
3858
count = length(var.ami) > 0 ? 1 : 0
3959

@@ -44,3 +64,67 @@ data "aws_ami" "instance" {
4464
values = [var.ami]
4565
}
4666
}
67+
68+
# IAM policy document for EC2 instances to assume the SSM Agent role
69+
data "aws_iam_policy_document" "default" {
70+
71+
statement {
72+
effect = "Allow"
73+
actions = ["sts:AssumeRole"]
74+
75+
principals {
76+
type = "Service"
77+
identifiers = ["ec2.amazonaws.com"]
78+
}
79+
}
80+
}
81+
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+
88+
# https://docs.aws.amazon.com/systems-manager/latest/userguide/getting-started-create-iam-instance-profile.html#create-iam-instance-profile-ssn-logging
89+
data "aws_iam_policy_document" "session_logging" {
90+
count = var.session_logging_enabled ? 1 : 0
91+
92+
statement {
93+
sid = "SSMAgentSessionAllowS3Logging"
94+
effect = "Allow"
95+
actions = [
96+
"s3:PutObject"
97+
]
98+
resources = ["${join("", data.aws_s3_bucket.logs_bucket.*.arn)}/*"]
99+
}
100+
101+
statement {
102+
sid = "SSMAgentSessionAllowCloudWatchLogging"
103+
effect = "Allow"
104+
actions = [
105+
"logs:CreateLogStream",
106+
"logs:PutLogEvents",
107+
"logs:DescribeLogGroups",
108+
"logs:DescribeLogStreams"
109+
]
110+
resources = ["*"]
111+
}
112+
113+
statement {
114+
sid = "SSMAgentSessionAllowGetEncryptionConfig"
115+
effect = "Allow"
116+
actions = [
117+
"s3:GetEncryptionConfiguration"
118+
]
119+
resources = ["*"]
120+
}
121+
122+
statement {
123+
sid = "SSMAgentSessionAllowKMSDataKey"
124+
effect = "Allow"
125+
actions = [
126+
"kms:GenerateDataKey"
127+
]
128+
resources = ["*"]
129+
}
130+
}

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/outputs.tf

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
output "ssm_agent_with_ids_instance_profile_arn" {
2+
description = "ARN of the IAM instance profile for SSM agent (using IDs)"
3+
value = module.ssm_agent_with_ids.iam_instance_profile_arn
4+
}
5+
6+
output "ssm_agent_with_names_instance_profile_arn" {
7+
description = "ARN of the IAM instance profile for SSM agent (using names)"
8+
value = module.ssm_agent_with_names.iam_instance_profile_arn
9+
}
10+
11+
output "ssm_agent_mixed_instance_profile_arn" {
12+
description = "ARN of the IAM instance profile for SSM agent (mixed approach)"
13+
value = module.ssm_agent_mixed.iam_instance_profile_arn
14+
}

examples/with-names/variables.tf

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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+
}
24+
25+
variable "ipv6_enabled" {
26+
type = bool
27+
description = "Enable IPv6"
28+
default = false
29+
}

main.tf

Lines changed: 40 additions & 74 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 = length(var.vpc_name) > 0 ? 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,34 @@ resource "null_resource" "validate_instance_type" {
2731
}
2832
}
2933

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+
}
61+
3062
module "role_label" {
3163
source = "cloudposse/label/null"
3264
version = "0.25.0"
@@ -57,68 +89,6 @@ locals {
5789
## SSM AGENT ROLE ##
5890
###################
5991

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-
12292
resource "aws_iam_role" "default" {
12393
name = module.role_label.id
12494
assume_role_policy = data.aws_iam_policy_document.default.json
@@ -157,19 +127,21 @@ resource "aws_iam_instance_profile" "default" {
157127
###################
158128

159129
resource "aws_security_group" "default" {
160-
vpc_id = var.vpc_id
130+
vpc_id = local.vpc_id
161131
name = module.this.id
162132
description = "Allow ALL egress from SSM Agent."
163133
tags = module.this.tags
164134
}
165135

136+
# trivy:ignore:aws-vpc-no-public-egress-sgr SSM Agent requires broad egress to communicate with AWS services
166137
resource "aws_security_group_rule" "allow_all_egress" {
167138
type = "egress"
168139
from_port = 0
169140
to_port = 0
170141
protocol = "-1"
171142
cidr_blocks = ["0.0.0.0/0"]
172143
ipv6_cidr_blocks = ["::/0"]
144+
description = "Allow all outbound traffic for SSM Agent communication with AWS services"
173145
security_group_id = aws_security_group.default.id
174146
}
175147

@@ -246,21 +218,15 @@ DOC
246218

247219
module "logs_bucket" {
248220
source = "cloudposse/s3-bucket/aws"
249-
version = "3.1.2"
221+
version = "4.10.0"
250222

251223
enabled = local.logs_bucket_enabled
252224
context = module.logs_label.context
253225

254226
# 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
227+
sse_algorithm = "aws:kms"
228+
kms_master_key_arn = local.session_logging_kms_key_arn
229+
force_destroy = true
264230

265231
lifecycle_configuration_rules = [{
266232
enabled = true
@@ -385,7 +351,7 @@ resource "aws_autoscaling_group" "default" {
385351
# By default, we don't care to protect from scale in as we want to roll instances frequently
386352
protect_from_scale_in = var.protect_from_scale_in
387353

388-
vpc_zone_identifier = var.subnet_ids
354+
vpc_zone_identifier = local.subnet_ids
389355

390356
default_cooldown = 180
391357
health_check_grace_period = 180

0 commit comments

Comments
 (0)