Skip to content

Commit

Permalink
feat: Add support for creating a security group for VPC endpoint(s) (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
bryantbiggs authored Jul 15, 2023
1 parent 3770660 commit 802d5f1
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 72 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.79.1
rev: v1.81.0
hooks:
- id: terraform_fmt
- id: terraform_validate
Expand Down
5 changes: 3 additions & 2 deletions examples/complete/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,10 @@ Note that this example may create resources which can cost money (AWS Elastic IP

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

## Inputs

Expand Down Expand Up @@ -153,6 +152,8 @@ No inputs.
| <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 |
| <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 |
| <a name="output_vpc_endpoints"></a> [vpc\_endpoints](#output\_vpc\_endpoints) | Array containing the full resource object and attributes for all endpoints created |
| <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 |
| <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 |
| <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 |
| <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 |
| <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 |
Expand Down
75 changes: 20 additions & 55 deletions examples/complete/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,17 @@ module "vpc" {
module "vpc_endpoints" {
source = "../../modules/vpc-endpoints"

vpc_id = module.vpc.vpc_id
security_group_ids = [data.aws_security_group.default.id]
vpc_id = module.vpc.vpc_id

create_security_group = true
security_group_name_prefix = "${local.name}-vpc-endpoints-"
security_group_description = "VPC endpoint security group"
security_group_rules = {
ingress_https = {
description = "HTTPS from VPC"
cidr_blocks = [module.vpc.vpc_cidr_block]
}
}

endpoints = {
s3 = {
Expand All @@ -103,23 +112,6 @@ module "vpc_endpoints" {
policy = data.aws_iam_policy_document.dynamodb_endpoint_policy.json
tags = { Name = "dynamodb-vpc-endpoint" }
},
ssm = {
service = "ssm"
private_dns_enabled = true
subnet_ids = module.vpc.private_subnets
security_group_ids = [aws_security_group.vpc_tls.id]
},
ssmmessages = {
service = "ssmmessages"
private_dns_enabled = true
subnet_ids = module.vpc.private_subnets
security_group_ids = [aws_security_group.vpc_tls.id]
},
lambda = {
service = "lambda"
private_dns_enabled = true
subnet_ids = module.vpc.private_subnets
},
ecs = {
service = "ecs"
private_dns_enabled = true
Expand All @@ -131,18 +123,6 @@ module "vpc_endpoints" {
private_dns_enabled = true
subnet_ids = module.vpc.private_subnets
},
ec2 = {
service = "ec2"
private_dns_enabled = true
subnet_ids = module.vpc.private_subnets
security_group_ids = [aws_security_group.vpc_tls.id]
},
ec2messages = {
service = "ec2messages"
private_dns_enabled = true
subnet_ids = module.vpc.private_subnets
security_group_ids = [aws_security_group.vpc_tls.id]
},
ecr_api = {
service = "ecr.api"
private_dns_enabled = true
Expand All @@ -155,21 +135,11 @@ module "vpc_endpoints" {
subnet_ids = module.vpc.private_subnets
policy = data.aws_iam_policy_document.generic_endpoint_policy.json
},
kms = {
service = "kms"
private_dns_enabled = true
subnet_ids = module.vpc.private_subnets
security_group_ids = [aws_security_group.vpc_tls.id]
},
codedeploy = {
service = "codedeploy"
private_dns_enabled = true
subnet_ids = module.vpc.private_subnets
},
codedeploy_commands_secure = {
service = "codedeploy-commands-secure"
rds = {
service = "rds"
private_dns_enabled = true
subnet_ids = module.vpc.private_subnets
security_group_ids = [aws_security_group.rds.id]
},
}

Expand All @@ -189,11 +159,6 @@ module "vpc_endpoints_nocreate" {
# Supporting Resources
################################################################################

data "aws_security_group" "default" {
name = "default"
vpc_id = module.vpc.vpc_id
}

data "aws_iam_policy_document" "dynamodb_endpoint_policy" {
statement {
effect = "Deny"
Expand All @@ -207,7 +172,7 @@ data "aws_iam_policy_document" "dynamodb_endpoint_policy" {

condition {
test = "StringNotEquals"
variable = "aws:sourceVpce"
variable = "aws:sourceVpc"

values = [module.vpc.vpc_id]
}
Expand All @@ -234,15 +199,15 @@ data "aws_iam_policy_document" "generic_endpoint_policy" {
}
}

resource "aws_security_group" "vpc_tls" {
name_prefix = "${local.name}-vpc_tls"
description = "Allow TLS inbound traffic"
resource "aws_security_group" "rds" {
name_prefix = "${local.name}-rds"
description = "Allow PostgreSQL inbound traffic"
vpc_id = module.vpc.vpc_id

ingress {
description = "TLS from VPC"
from_port = 443
to_port = 443
from_port = 5432
to_port = 5432
protocol = "tcp"
cidr_blocks = [module.vpc.vpc_cidr_block]
}
Expand Down
10 changes: 10 additions & 0 deletions examples/complete/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -539,3 +539,13 @@ output "vpc_endpoints" {
description = "Array containing the full resource object and attributes for all endpoints created"
value = module.vpc_endpoints.endpoints
}

output "vpc_endpoints_security_group_arn" {
description = "Amazon Resource Name (ARN) of the security group"
value = module.vpc_endpoints.security_group_arn
}

output "vpc_endpoints_security_group_id" {
description = "ID of the security group"
value = module.vpc_endpoints.security_group_id
}
10 changes: 10 additions & 0 deletions modules/vpc-endpoints/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ No modules.

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

Expand All @@ -80,8 +82,14 @@ No modules.
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_create"></a> [create](#input\_create) | Determines whether resources will be created | `bool` | `true` | no |
| <a name="input_create_security_group"></a> [create\_security\_group](#input\_create\_security\_group) | Determines if a security group is created | `bool` | `false` | no |
| <a name="input_endpoints"></a> [endpoints](#input\_endpoints) | A map of interface and/or gateway endpoints containing their properties and configurations | `any` | `{}` | no |
| <a name="input_security_group_description"></a> [security\_group\_description](#input\_security\_group\_description) | Description of the security group created | `string` | `null` | no |
| <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 |
| <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 |
| <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 |
| <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 |
| <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 |
| <a name="input_subnet_ids"></a> [subnet\_ids](#input\_subnet\_ids) | Default subnets IDs to associate with the VPC endpoints | `list(string)` | `[]` | no |
| <a name="input_tags"></a> [tags](#input\_tags) | A map of tags to use on all resources | `map(string)` | `{}` | no |
| <a name="input_timeouts"></a> [timeouts](#input\_timeouts) | Define maximum timeout for creating, updating, and deleting VPC endpoint resources | `map(string)` | `{}` | no |
Expand All @@ -92,4 +100,6 @@ No modules.
| Name | Description |
|------|-------------|
| <a name="output_endpoints"></a> [endpoints](#output\_endpoints) | Array containing the full resource object and attributes for all endpoints created |
| <a name="output_security_group_arn"></a> [security\_group\_arn](#output\_security\_group\_arn) | Amazon Resource Name (ARN) of the security group |
| <a name="output_security_group_id"></a> [security\_group\_id](#output\_security\_group\_id) | ID of the security group |
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
72 changes: 58 additions & 14 deletions modules/vpc-endpoints/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@

locals {
endpoints = { for k, v in var.endpoints : k => v if var.create && try(v.create, true) }

security_group_ids = var.create && var.create_security_group ? concat(var.security_group_ids, [aws_security_group.this[0].id]) : var.security_group_ids
}

data "aws_vpc_endpoint_service" "this" {
for_each = local.endpoints

service = lookup(each.value, "service", null)
service_name = lookup(each.value, "service_name", null)
service = try(each.value.service, null)
service_name = try(each.value.service_name, null)

filter {
name = "service-type"
values = [lookup(each.value, "service_type", "Interface")]
values = [try(each.value.service_type, "Interface")]
}
}

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

vpc_id = var.vpc_id
service_name = data.aws_vpc_endpoint_service.this[each.key].service_name
vpc_endpoint_type = lookup(each.value, "service_type", "Interface")
auto_accept = lookup(each.value, "auto_accept", null)
vpc_endpoint_type = try(each.value.service_type, "Interface")
auto_accept = try(each.value.auto_accept, null)

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
subnet_ids = lookup(each.value, "service_type", "Interface") == "Interface" ? distinct(concat(var.subnet_ids, lookup(each.value, "subnet_ids", []))) : null
route_table_ids = lookup(each.value, "service_type", "Interface") == "Gateway" ? lookup(each.value, "route_table_ids", null) : null
policy = lookup(each.value, "policy", null)
private_dns_enabled = lookup(each.value, "service_type", "Interface") == "Interface" ? lookup(each.value, "private_dns_enabled", null) : null
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
subnet_ids = try(each.value.service_type, "Interface") == "Interface" ? distinct(concat(var.subnet_ids, lookup(each.value, "subnet_ids", []))) : null
route_table_ids = try(each.value.service_type, "Interface") == "Gateway" ? lookup(each.value, "route_table_ids", null) : null
policy = try(each.value.policy, null)
private_dns_enabled = try(each.value.service_type, "Interface") == "Interface" ? try(each.value.private_dns_enabled, null) : null

tags = merge(var.tags, lookup(each.value, "tags", {}))
tags = merge(var.tags, try(each.value.tags, {}))

timeouts {
create = lookup(var.timeouts, "create", "10m")
update = lookup(var.timeouts, "update", "10m")
delete = lookup(var.timeouts, "delete", "10m")
create = try(var.timeouts.create, "10m")
update = try(var.timeouts.update, "10m")
delete = try(var.timeouts.delete, "10m")
}
}

################################################################################
# Security Group
################################################################################

resource "aws_security_group" "this" {
count = var.create && var.create_security_group ? 1 : 0

name = var.security_group_name
name_prefix = var.security_group_name_prefix
description = var.security_group_description
vpc_id = var.vpc_id

tags = merge(
var.tags,
var.security_group_tags,
{ "Name" = try(coalesce(var.security_group_name, var.security_group_name_prefix), "") },
)

lifecycle {
create_before_destroy = true
}
}

resource "aws_security_group_rule" "this" {
for_each = { for k, v in var.security_group_rules : k => v if var.create && var.create_security_group }

# Required
security_group_id = aws_security_group.this[0].id
protocol = try(each.value.protocol, "tcp")
from_port = try(each.value.from_port, 443)
to_port = try(each.value.to_port, 443)
type = try(each.value.type, "ingress")

# Optional
description = try(each.value.description, null)
cidr_blocks = lookup(each.value, "cidr_blocks", null)
ipv6_cidr_blocks = lookup(each.value, "ipv6_cidr_blocks", null)
prefix_list_ids = lookup(each.value, "prefix_list_ids", null)
self = try(each.value.self, null)
source_security_group_id = lookup(each.value, "source_security_group_id", null)
}
14 changes: 14 additions & 0 deletions modules/vpc-endpoints/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,17 @@ output "endpoints" {
description = "Array containing the full resource object and attributes for all endpoints created"
value = aws_vpc_endpoint.this
}

################################################################################
# Security Group
################################################################################

output "security_group_arn" {
description = "Amazon Resource Name (ARN) of the security group"
value = try(aws_security_group.this[0].arn, null)
}

output "security_group_id" {
description = "ID of the security group"
value = try(aws_security_group.this[0].id, null)
}
40 changes: 40 additions & 0 deletions modules/vpc-endpoints/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,43 @@ variable "timeouts" {
type = map(string)
default = {}
}

################################################################################
# Security Group
################################################################################

variable "create_security_group" {
description = "Determines if a security group is created"
type = bool
default = false
}

variable "security_group_name" {
description = "Name to use on security group created. Conflicts with `security_group_name_prefix`"
type = string
default = null
}

variable "security_group_name_prefix" {
description = "Name prefix to use on security group created. Conflicts with `security_group_name`"
type = string
default = null
}

variable "security_group_description" {
description = "Description of the security group created"
type = string
default = null
}

variable "security_group_rules" {
description = "Security group rules to add to the security group created"
type = any
default = {}
}

variable "security_group_tags" {
description = "A map of additional tags to add to the security group created"
type = map(string)
default = {}
}

0 comments on commit 802d5f1

Please sign in to comment.