Skip to content

Commit 802d5f1

Browse files
authored
feat: Add support for creating a security group for VPC endpoint(s) (#962)
1 parent 3770660 commit 802d5f1

File tree

8 files changed

+156
-72
lines changed

8 files changed

+156
-72
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
repos:
22
- repo: https://github.com/antonbabenko/pre-commit-terraform
3-
rev: v1.79.1
3+
rev: v1.81.0
44
hooks:
55
- id: terraform_fmt
66
- id: terraform_validate

examples/complete/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,10 @@ Note that this example may create resources which can cost money (AWS Elastic IP
4242

4343
| Name | Type |
4444
|------|------|
45-
| [aws_security_group.vpc_tls](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
45+
| [aws_security_group.rds](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
4646
| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source |
4747
| [aws_iam_policy_document.dynamodb_endpoint_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
4848
| [aws_iam_policy_document.generic_endpoint_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
49-
| [aws_security_group.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/security_group) | data source |
5049

5150
## Inputs
5251

@@ -153,6 +152,8 @@ No inputs.
153152
| <a name="output_vpc_enable_dns_hostnames"></a> [vpc\_enable\_dns\_hostnames](#output\_vpc\_enable\_dns\_hostnames) | Whether or not the VPC has DNS hostname support |
154153
| <a name="output_vpc_enable_dns_support"></a> [vpc\_enable\_dns\_support](#output\_vpc\_enable\_dns\_support) | Whether or not the VPC has DNS support |
155154
| <a name="output_vpc_endpoints"></a> [vpc\_endpoints](#output\_vpc\_endpoints) | Array containing the full resource object and attributes for all endpoints created |
155+
| <a name="output_vpc_endpoints_security_group_arn"></a> [vpc\_endpoints\_security\_group\_arn](#output\_vpc\_endpoints\_security\_group\_arn) | Amazon Resource Name (ARN) of the security group |
156+
| <a name="output_vpc_endpoints_security_group_id"></a> [vpc\_endpoints\_security\_group\_id](#output\_vpc\_endpoints\_security\_group\_id) | ID of the security group |
156157
| <a name="output_vpc_flow_log_cloudwatch_iam_role_arn"></a> [vpc\_flow\_log\_cloudwatch\_iam\_role\_arn](#output\_vpc\_flow\_log\_cloudwatch\_iam\_role\_arn) | The ARN of the IAM role used when pushing logs to Cloudwatch log group |
157158
| <a name="output_vpc_flow_log_destination_arn"></a> [vpc\_flow\_log\_destination\_arn](#output\_vpc\_flow\_log\_destination\_arn) | The ARN of the destination for VPC Flow Logs |
158159
| <a name="output_vpc_flow_log_destination_type"></a> [vpc\_flow\_log\_destination\_type](#output\_vpc\_flow\_log\_destination\_type) | The type of the destination for VPC Flow Logs |

examples/complete/main.tf

Lines changed: 20 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,17 @@ module "vpc" {
8888
module "vpc_endpoints" {
8989
source = "../../modules/vpc-endpoints"
9090

91-
vpc_id = module.vpc.vpc_id
92-
security_group_ids = [data.aws_security_group.default.id]
91+
vpc_id = module.vpc.vpc_id
92+
93+
create_security_group = true
94+
security_group_name_prefix = "${local.name}-vpc-endpoints-"
95+
security_group_description = "VPC endpoint security group"
96+
security_group_rules = {
97+
ingress_https = {
98+
description = "HTTPS from VPC"
99+
cidr_blocks = [module.vpc.vpc_cidr_block]
100+
}
101+
}
93102

94103
endpoints = {
95104
s3 = {
@@ -103,23 +112,6 @@ module "vpc_endpoints" {
103112
policy = data.aws_iam_policy_document.dynamodb_endpoint_policy.json
104113
tags = { Name = "dynamodb-vpc-endpoint" }
105114
},
106-
ssm = {
107-
service = "ssm"
108-
private_dns_enabled = true
109-
subnet_ids = module.vpc.private_subnets
110-
security_group_ids = [aws_security_group.vpc_tls.id]
111-
},
112-
ssmmessages = {
113-
service = "ssmmessages"
114-
private_dns_enabled = true
115-
subnet_ids = module.vpc.private_subnets
116-
security_group_ids = [aws_security_group.vpc_tls.id]
117-
},
118-
lambda = {
119-
service = "lambda"
120-
private_dns_enabled = true
121-
subnet_ids = module.vpc.private_subnets
122-
},
123115
ecs = {
124116
service = "ecs"
125117
private_dns_enabled = true
@@ -131,18 +123,6 @@ module "vpc_endpoints" {
131123
private_dns_enabled = true
132124
subnet_ids = module.vpc.private_subnets
133125
},
134-
ec2 = {
135-
service = "ec2"
136-
private_dns_enabled = true
137-
subnet_ids = module.vpc.private_subnets
138-
security_group_ids = [aws_security_group.vpc_tls.id]
139-
},
140-
ec2messages = {
141-
service = "ec2messages"
142-
private_dns_enabled = true
143-
subnet_ids = module.vpc.private_subnets
144-
security_group_ids = [aws_security_group.vpc_tls.id]
145-
},
146126
ecr_api = {
147127
service = "ecr.api"
148128
private_dns_enabled = true
@@ -155,21 +135,11 @@ module "vpc_endpoints" {
155135
subnet_ids = module.vpc.private_subnets
156136
policy = data.aws_iam_policy_document.generic_endpoint_policy.json
157137
},
158-
kms = {
159-
service = "kms"
160-
private_dns_enabled = true
161-
subnet_ids = module.vpc.private_subnets
162-
security_group_ids = [aws_security_group.vpc_tls.id]
163-
},
164-
codedeploy = {
165-
service = "codedeploy"
166-
private_dns_enabled = true
167-
subnet_ids = module.vpc.private_subnets
168-
},
169-
codedeploy_commands_secure = {
170-
service = "codedeploy-commands-secure"
138+
rds = {
139+
service = "rds"
171140
private_dns_enabled = true
172141
subnet_ids = module.vpc.private_subnets
142+
security_group_ids = [aws_security_group.rds.id]
173143
},
174144
}
175145

@@ -189,11 +159,6 @@ module "vpc_endpoints_nocreate" {
189159
# Supporting Resources
190160
################################################################################
191161

192-
data "aws_security_group" "default" {
193-
name = "default"
194-
vpc_id = module.vpc.vpc_id
195-
}
196-
197162
data "aws_iam_policy_document" "dynamodb_endpoint_policy" {
198163
statement {
199164
effect = "Deny"
@@ -207,7 +172,7 @@ data "aws_iam_policy_document" "dynamodb_endpoint_policy" {
207172

208173
condition {
209174
test = "StringNotEquals"
210-
variable = "aws:sourceVpce"
175+
variable = "aws:sourceVpc"
211176

212177
values = [module.vpc.vpc_id]
213178
}
@@ -234,15 +199,15 @@ data "aws_iam_policy_document" "generic_endpoint_policy" {
234199
}
235200
}
236201

237-
resource "aws_security_group" "vpc_tls" {
238-
name_prefix = "${local.name}-vpc_tls"
239-
description = "Allow TLS inbound traffic"
202+
resource "aws_security_group" "rds" {
203+
name_prefix = "${local.name}-rds"
204+
description = "Allow PostgreSQL inbound traffic"
240205
vpc_id = module.vpc.vpc_id
241206

242207
ingress {
243208
description = "TLS from VPC"
244-
from_port = 443
245-
to_port = 443
209+
from_port = 5432
210+
to_port = 5432
246211
protocol = "tcp"
247212
cidr_blocks = [module.vpc.vpc_cidr_block]
248213
}

examples/complete/outputs.tf

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,3 +539,13 @@ output "vpc_endpoints" {
539539
description = "Array containing the full resource object and attributes for all endpoints created"
540540
value = module.vpc_endpoints.endpoints
541541
}
542+
543+
output "vpc_endpoints_security_group_arn" {
544+
description = "Amazon Resource Name (ARN) of the security group"
545+
value = module.vpc_endpoints.security_group_arn
546+
}
547+
548+
output "vpc_endpoints_security_group_id" {
549+
description = "ID of the security group"
550+
value = module.vpc_endpoints.security_group_id
551+
}

modules/vpc-endpoints/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ No modules.
7272

7373
| Name | Type |
7474
|------|------|
75+
| [aws_security_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
76+
| [aws_security_group_rule.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
7577
| [aws_vpc_endpoint.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_endpoint) | resource |
7678
| [aws_vpc_endpoint_service.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/vpc_endpoint_service) | data source |
7779

@@ -80,8 +82,14 @@ No modules.
8082
| Name | Description | Type | Default | Required |
8183
|------|-------------|------|---------|:--------:|
8284
| <a name="input_create"></a> [create](#input\_create) | Determines whether resources will be created | `bool` | `true` | no |
85+
| <a name="input_create_security_group"></a> [create\_security\_group](#input\_create\_security\_group) | Determines if a security group is created | `bool` | `false` | no |
8386
| <a name="input_endpoints"></a> [endpoints](#input\_endpoints) | A map of interface and/or gateway endpoints containing their properties and configurations | `any` | `{}` | no |
87+
| <a name="input_security_group_description"></a> [security\_group\_description](#input\_security\_group\_description) | Description of the security group created | `string` | `null` | no |
8488
| <a name="input_security_group_ids"></a> [security\_group\_ids](#input\_security\_group\_ids) | Default security group IDs to associate with the VPC endpoints | `list(string)` | `[]` | no |
89+
| <a name="input_security_group_name"></a> [security\_group\_name](#input\_security\_group\_name) | Name to use on security group created. Conflicts with `security_group_name_prefix` | `string` | `null` | no |
90+
| <a name="input_security_group_name_prefix"></a> [security\_group\_name\_prefix](#input\_security\_group\_name\_prefix) | Name prefix to use on security group created. Conflicts with `security_group_name` | `string` | `null` | no |
91+
| <a name="input_security_group_rules"></a> [security\_group\_rules](#input\_security\_group\_rules) | Security group rules to add to the security group created | `any` | `{}` | no |
92+
| <a name="input_security_group_tags"></a> [security\_group\_tags](#input\_security\_group\_tags) | A map of additional tags to add to the security group created | `map(string)` | `{}` | no |
8593
| <a name="input_subnet_ids"></a> [subnet\_ids](#input\_subnet\_ids) | Default subnets IDs to associate with the VPC endpoints | `list(string)` | `[]` | no |
8694
| <a name="input_tags"></a> [tags](#input\_tags) | A map of tags to use on all resources | `map(string)` | `{}` | no |
8795
| <a name="input_timeouts"></a> [timeouts](#input\_timeouts) | Define maximum timeout for creating, updating, and deleting VPC endpoint resources | `map(string)` | `{}` | no |
@@ -92,4 +100,6 @@ No modules.
92100
| Name | Description |
93101
|------|-------------|
94102
| <a name="output_endpoints"></a> [endpoints](#output\_endpoints) | Array containing the full resource object and attributes for all endpoints created |
103+
| <a name="output_security_group_arn"></a> [security\_group\_arn](#output\_security\_group\_arn) | Amazon Resource Name (ARN) of the security group |
104+
| <a name="output_security_group_id"></a> [security\_group\_id](#output\_security\_group\_id) | ID of the security group |
95105
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->

modules/vpc-endpoints/main.tf

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,19 @@
44

55
locals {
66
endpoints = { for k, v in var.endpoints : k => v if var.create && try(v.create, true) }
7+
8+
security_group_ids = var.create && var.create_security_group ? concat(var.security_group_ids, [aws_security_group.this[0].id]) : var.security_group_ids
79
}
810

911
data "aws_vpc_endpoint_service" "this" {
1012
for_each = local.endpoints
1113

12-
service = lookup(each.value, "service", null)
13-
service_name = lookup(each.value, "service_name", null)
14+
service = try(each.value.service, null)
15+
service_name = try(each.value.service_name, null)
1416

1517
filter {
1618
name = "service-type"
17-
values = [lookup(each.value, "service_type", "Interface")]
19+
values = [try(each.value.service_type, "Interface")]
1820
}
1921
}
2022

@@ -23,20 +25,62 @@ resource "aws_vpc_endpoint" "this" {
2325

2426
vpc_id = var.vpc_id
2527
service_name = data.aws_vpc_endpoint_service.this[each.key].service_name
26-
vpc_endpoint_type = lookup(each.value, "service_type", "Interface")
27-
auto_accept = lookup(each.value, "auto_accept", null)
28+
vpc_endpoint_type = try(each.value.service_type, "Interface")
29+
auto_accept = try(each.value.auto_accept, null)
2830

29-
security_group_ids = lookup(each.value, "service_type", "Interface") == "Interface" ? length(distinct(concat(var.security_group_ids, lookup(each.value, "security_group_ids", [])))) > 0 ? distinct(concat(var.security_group_ids, lookup(each.value, "security_group_ids", []))) : null : null
30-
subnet_ids = lookup(each.value, "service_type", "Interface") == "Interface" ? distinct(concat(var.subnet_ids, lookup(each.value, "subnet_ids", []))) : null
31-
route_table_ids = lookup(each.value, "service_type", "Interface") == "Gateway" ? lookup(each.value, "route_table_ids", null) : null
32-
policy = lookup(each.value, "policy", null)
33-
private_dns_enabled = lookup(each.value, "service_type", "Interface") == "Interface" ? lookup(each.value, "private_dns_enabled", null) : null
31+
security_group_ids = try(each.value.service_type, "Interface") == "Interface" ? length(distinct(concat(local.security_group_ids, lookup(each.value, "security_group_ids", [])))) > 0 ? distinct(concat(local.security_group_ids, lookup(each.value, "security_group_ids", []))) : null : null
32+
subnet_ids = try(each.value.service_type, "Interface") == "Interface" ? distinct(concat(var.subnet_ids, lookup(each.value, "subnet_ids", []))) : null
33+
route_table_ids = try(each.value.service_type, "Interface") == "Gateway" ? lookup(each.value, "route_table_ids", null) : null
34+
policy = try(each.value.policy, null)
35+
private_dns_enabled = try(each.value.service_type, "Interface") == "Interface" ? try(each.value.private_dns_enabled, null) : null
3436

35-
tags = merge(var.tags, lookup(each.value, "tags", {}))
37+
tags = merge(var.tags, try(each.value.tags, {}))
3638

3739
timeouts {
38-
create = lookup(var.timeouts, "create", "10m")
39-
update = lookup(var.timeouts, "update", "10m")
40-
delete = lookup(var.timeouts, "delete", "10m")
40+
create = try(var.timeouts.create, "10m")
41+
update = try(var.timeouts.update, "10m")
42+
delete = try(var.timeouts.delete, "10m")
43+
}
44+
}
45+
46+
################################################################################
47+
# Security Group
48+
################################################################################
49+
50+
resource "aws_security_group" "this" {
51+
count = var.create && var.create_security_group ? 1 : 0
52+
53+
name = var.security_group_name
54+
name_prefix = var.security_group_name_prefix
55+
description = var.security_group_description
56+
vpc_id = var.vpc_id
57+
58+
tags = merge(
59+
var.tags,
60+
var.security_group_tags,
61+
{ "Name" = try(coalesce(var.security_group_name, var.security_group_name_prefix), "") },
62+
)
63+
64+
lifecycle {
65+
create_before_destroy = true
4166
}
4267
}
68+
69+
resource "aws_security_group_rule" "this" {
70+
for_each = { for k, v in var.security_group_rules : k => v if var.create && var.create_security_group }
71+
72+
# Required
73+
security_group_id = aws_security_group.this[0].id
74+
protocol = try(each.value.protocol, "tcp")
75+
from_port = try(each.value.from_port, 443)
76+
to_port = try(each.value.to_port, 443)
77+
type = try(each.value.type, "ingress")
78+
79+
# Optional
80+
description = try(each.value.description, null)
81+
cidr_blocks = lookup(each.value, "cidr_blocks", null)
82+
ipv6_cidr_blocks = lookup(each.value, "ipv6_cidr_blocks", null)
83+
prefix_list_ids = lookup(each.value, "prefix_list_ids", null)
84+
self = try(each.value.self, null)
85+
source_security_group_id = lookup(each.value, "source_security_group_id", null)
86+
}

modules/vpc-endpoints/outputs.tf

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,17 @@ output "endpoints" {
22
description = "Array containing the full resource object and attributes for all endpoints created"
33
value = aws_vpc_endpoint.this
44
}
5+
6+
################################################################################
7+
# Security Group
8+
################################################################################
9+
10+
output "security_group_arn" {
11+
description = "Amazon Resource Name (ARN) of the security group"
12+
value = try(aws_security_group.this[0].arn, null)
13+
}
14+
15+
output "security_group_id" {
16+
description = "ID of the security group"
17+
value = try(aws_security_group.this[0].id, null)
18+
}

modules/vpc-endpoints/variables.tf

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,43 @@ variable "timeouts" {
3939
type = map(string)
4040
default = {}
4141
}
42+
43+
################################################################################
44+
# Security Group
45+
################################################################################
46+
47+
variable "create_security_group" {
48+
description = "Determines if a security group is created"
49+
type = bool
50+
default = false
51+
}
52+
53+
variable "security_group_name" {
54+
description = "Name to use on security group created. Conflicts with `security_group_name_prefix`"
55+
type = string
56+
default = null
57+
}
58+
59+
variable "security_group_name_prefix" {
60+
description = "Name prefix to use on security group created. Conflicts with `security_group_name`"
61+
type = string
62+
default = null
63+
}
64+
65+
variable "security_group_description" {
66+
description = "Description of the security group created"
67+
type = string
68+
default = null
69+
}
70+
71+
variable "security_group_rules" {
72+
description = "Security group rules to add to the security group created"
73+
type = any
74+
default = {}
75+
}
76+
77+
variable "security_group_tags" {
78+
description = "A map of additional tags to add to the security group created"
79+
type = map(string)
80+
default = {}
81+
}

0 commit comments

Comments
 (0)