Commit 802d5f14 authored by Bryant Biggs's avatar Bryant Biggs Committed by GitHub

feat: Add support for creating a security group for VPC endpoint(s) (#962)

parent 37706604
repos: repos:
- repo: https://github.com/antonbabenko/pre-commit-terraform - repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.79.1 rev: v1.81.0
hooks: hooks:
- id: terraform_fmt - id: terraform_fmt
- id: terraform_validate - id: terraform_validate
......
...@@ -42,11 +42,10 @@ Note that this example may create resources which can cost money (AWS Elastic IP ...@@ -42,11 +42,10 @@ Note that this example may create resources which can cost money (AWS Elastic IP
| Name | Type | | 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_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.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_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 ## Inputs
...@@ -153,6 +152,8 @@ No inputs. ...@@ -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_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_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"></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_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_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 | | <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 |
......
...@@ -88,8 +88,17 @@ module "vpc" { ...@@ -88,8 +88,17 @@ module "vpc" {
module "vpc_endpoints" { module "vpc_endpoints" {
source = "../../modules/vpc-endpoints" source = "../../modules/vpc-endpoints"
vpc_id = module.vpc.vpc_id vpc_id = module.vpc.vpc_id
security_group_ids = [data.aws_security_group.default.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 = { endpoints = {
s3 = { s3 = {
...@@ -103,23 +112,6 @@ module "vpc_endpoints" { ...@@ -103,23 +112,6 @@ module "vpc_endpoints" {
policy = data.aws_iam_policy_document.dynamodb_endpoint_policy.json policy = data.aws_iam_policy_document.dynamodb_endpoint_policy.json
tags = { Name = "dynamodb-vpc-endpoint" } 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 = { ecs = {
service = "ecs" service = "ecs"
private_dns_enabled = true private_dns_enabled = true
...@@ -131,18 +123,6 @@ module "vpc_endpoints" { ...@@ -131,18 +123,6 @@ module "vpc_endpoints" {
private_dns_enabled = true private_dns_enabled = true
subnet_ids = module.vpc.private_subnets 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 = { ecr_api = {
service = "ecr.api" service = "ecr.api"
private_dns_enabled = true private_dns_enabled = true
...@@ -155,21 +135,11 @@ module "vpc_endpoints" { ...@@ -155,21 +135,11 @@ module "vpc_endpoints" {
subnet_ids = module.vpc.private_subnets subnet_ids = module.vpc.private_subnets
policy = data.aws_iam_policy_document.generic_endpoint_policy.json policy = data.aws_iam_policy_document.generic_endpoint_policy.json
}, },
kms = { rds = {
service = "kms" service = "rds"
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"
private_dns_enabled = true private_dns_enabled = true
subnet_ids = module.vpc.private_subnets subnet_ids = module.vpc.private_subnets
security_group_ids = [aws_security_group.rds.id]
}, },
} }
...@@ -189,11 +159,6 @@ module "vpc_endpoints_nocreate" { ...@@ -189,11 +159,6 @@ module "vpc_endpoints_nocreate" {
# Supporting Resources # Supporting Resources
################################################################################ ################################################################################
data "aws_security_group" "default" {
name = "default"
vpc_id = module.vpc.vpc_id
}
data "aws_iam_policy_document" "dynamodb_endpoint_policy" { data "aws_iam_policy_document" "dynamodb_endpoint_policy" {
statement { statement {
effect = "Deny" effect = "Deny"
...@@ -207,7 +172,7 @@ data "aws_iam_policy_document" "dynamodb_endpoint_policy" { ...@@ -207,7 +172,7 @@ data "aws_iam_policy_document" "dynamodb_endpoint_policy" {
condition { condition {
test = "StringNotEquals" test = "StringNotEquals"
variable = "aws:sourceVpce" variable = "aws:sourceVpc"
values = [module.vpc.vpc_id] values = [module.vpc.vpc_id]
} }
...@@ -234,15 +199,15 @@ data "aws_iam_policy_document" "generic_endpoint_policy" { ...@@ -234,15 +199,15 @@ data "aws_iam_policy_document" "generic_endpoint_policy" {
} }
} }
resource "aws_security_group" "vpc_tls" { resource "aws_security_group" "rds" {
name_prefix = "${local.name}-vpc_tls" name_prefix = "${local.name}-rds"
description = "Allow TLS inbound traffic" description = "Allow PostgreSQL inbound traffic"
vpc_id = module.vpc.vpc_id vpc_id = module.vpc.vpc_id
ingress { ingress {
description = "TLS from VPC" description = "TLS from VPC"
from_port = 443 from_port = 5432
to_port = 443 to_port = 5432
protocol = "tcp" protocol = "tcp"
cidr_blocks = [module.vpc.vpc_cidr_block] cidr_blocks = [module.vpc.vpc_cidr_block]
} }
......
...@@ -539,3 +539,13 @@ output "vpc_endpoints" { ...@@ -539,3 +539,13 @@ output "vpc_endpoints" {
description = "Array containing the full resource object and attributes for all endpoints created" description = "Array containing the full resource object and attributes for all endpoints created"
value = module.vpc_endpoints.endpoints 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
}
...@@ -72,6 +72,8 @@ No modules. ...@@ -72,6 +72,8 @@ No modules.
| Name | Type | | 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.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 | | [aws_vpc_endpoint_service.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/vpc_endpoint_service) | data source |
...@@ -80,8 +82,14 @@ No modules. ...@@ -80,8 +82,14 @@ No modules.
| Name | Description | Type | Default | Required | | 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"></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_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_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_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_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 | | <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. ...@@ -92,4 +100,6 @@ No modules.
| Name | Description | | 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_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 --> <!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
...@@ -4,17 +4,19 @@ ...@@ -4,17 +4,19 @@
locals { locals {
endpoints = { for k, v in var.endpoints : k => v if var.create && try(v.create, true) } 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" { data "aws_vpc_endpoint_service" "this" {
for_each = local.endpoints for_each = local.endpoints
service = lookup(each.value, "service", null) service = try(each.value.service, null)
service_name = lookup(each.value, "service_name", null) service_name = try(each.value.service_name, null)
filter { filter {
name = "service-type" name = "service-type"
values = [lookup(each.value, "service_type", "Interface")] values = [try(each.value.service_type, "Interface")]
} }
} }
...@@ -23,20 +25,62 @@ resource "aws_vpc_endpoint" "this" { ...@@ -23,20 +25,62 @@ resource "aws_vpc_endpoint" "this" {
vpc_id = var.vpc_id vpc_id = var.vpc_id
service_name = data.aws_vpc_endpoint_service.this[each.key].service_name service_name = data.aws_vpc_endpoint_service.this[each.key].service_name
vpc_endpoint_type = lookup(each.value, "service_type", "Interface") vpc_endpoint_type = try(each.value.service_type, "Interface")
auto_accept = lookup(each.value, "auto_accept", null) 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 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 = lookup(each.value, "service_type", "Interface") == "Interface" ? distinct(concat(var.subnet_ids, lookup(each.value, "subnet_ids", []))) : null subnet_ids = try(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 route_table_ids = try(each.value.service_type, "Interface") == "Gateway" ? lookup(each.value, "route_table_ids", null) : null
policy = lookup(each.value, "policy", null) policy = try(each.value.policy, null)
private_dns_enabled = lookup(each.value, "service_type", "Interface") == "Interface" ? lookup(each.value, "private_dns_enabled", null) : 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 { timeouts {
create = lookup(var.timeouts, "create", "10m") create = try(var.timeouts.create, "10m")
update = lookup(var.timeouts, "update", "10m") update = try(var.timeouts.update, "10m")
delete = lookup(var.timeouts, "delete", "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)
}
...@@ -2,3 +2,17 @@ output "endpoints" { ...@@ -2,3 +2,17 @@ output "endpoints" {
description = "Array containing the full resource object and attributes for all endpoints created" description = "Array containing the full resource object and attributes for all endpoints created"
value = aws_vpc_endpoint.this 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)
}
...@@ -39,3 +39,43 @@ variable "timeouts" { ...@@ -39,3 +39,43 @@ variable "timeouts" {
type = map(string) type = map(string)
default = {} 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 = {}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment