diff --git a/README.md b/README.md
index 485e114..9f33f03 100644
--- a/README.md
+++ b/README.md
@@ -291,7 +291,8 @@ for example.
| [cloudwatch\_log\_group](#output\_cloudwatch\_log\_group) | Name of the CloudWatch log group for container logs. |
| [container\_definitions](#output\_container\_definitions) | Container definitions used by this service including all sidecars. |
| [ecr\_repository\_arn](#output\_ecr\_repository\_arn) | Full ARN of the ECR repository. |
-| [ecr\_repository\_url](#output\_ecr\_repository\_url) | URL of the ECR repository. |
+| [ecr\_repository\_id](#output\_ecr\_repository\_id) | The registry ID where the repository was created. |
+| [ecr\_repository\_url](#output\_ecr\_repository\_url) | The URL of the repository (in the form `aws_account_id.dkr.ecr.region.amazonaws.com/repositoryName`) |
| [task\_execution\_role\_arn](#output\_task\_execution\_role\_arn) | ARN of the task execution role that the Amazon ECS container agent and the Docker daemon can assume. |
| [task\_execution\_role\_name](#output\_task\_execution\_role\_name) | Friendly name of the task execution role that the Amazon ECS container agent and the Docker daemon can assume. |
| [task\_execution\_role\_unique\_id](#output\_task\_execution\_role\_unique\_id) | Stable and unique string identifying the IAM role that the Amazon ECS container agent and the Docker daemon can assume. |
diff --git a/examples/complete/README.md b/examples/complete/README.md
index 787b5f0..e3fd2e9 100644
--- a/examples/complete/README.md
+++ b/examples/complete/README.md
@@ -17,8 +17,8 @@ Note that this example may create resources which cost money. Run `terraform des
| Name | Version |
|------|---------|
-| [terraform](#requirement\_terraform) | >= 1.0 |
-| [aws](#requirement\_aws) | >= 4.9 |
+| [terraform](#requirement\_terraform) | >= 1.3 |
+| [aws](#requirement\_aws) | >= 5.32 |
| [null](#requirement\_null) | >= 3.2 |
| [random](#requirement\_random) | >= 3.4 |
@@ -26,7 +26,7 @@ Note that this example may create resources which cost money. Run `terraform des
| Name | Version |
|------|---------|
-| [aws](#provider\_aws) | >= 4.9 |
+| [aws](#provider\_aws) | >= 5.32 |
| [null](#provider\_null) | >= 3.2 |
| [random](#provider\_random) | >= 3.4 |
@@ -34,22 +34,20 @@ Note that this example may create resources which cost money. Run `terraform des
| Name | Source | Version |
|------|--------|---------|
-| [alb\_security\_group\_public](#module\_alb\_security\_group\_public) | registry.terraform.io/terraform-aws-modules/security-group/aws | >= 4.17 |
+| [alb](#module\_alb) | terraform-aws-modules/alb/aws | ~> 9.0 |
| [service](#module\_service) | ../../ | n/a |
-| [vpc](#module\_vpc) | registry.terraform.io/terraform-aws-modules/vpc/aws | >= 4.0 |
-| [vpc\_endpoints](#module\_vpc\_endpoints) | registry.terraform.io/terraform-aws-modules/vpc/aws//modules/vpc-endpoints | >= 4.0 |
+| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 |
## Resources
| Name | Type |
|------|------|
| [aws_ecs_cluster.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_cluster) | resource |
-| [aws_lb.public](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb) | resource |
-| [aws_lb_listener.http](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener) | resource |
+| [aws_security_group.egress_all](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
| [null_resource.initial_image](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource |
| [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource |
| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source |
-| [aws_security_group.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/security_group) | data source |
+| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
## Inputs
@@ -61,5 +59,5 @@ Note that this example may create resources which cost money. Run `terraform des
| Name | Description |
|------|-------------|
-| [alb\_dns\_name](#output\_alb\_dns\_name) | n/a |
+| [endpoint](#output\_endpoint) | n/a |
diff --git a/examples/complete/data.tf b/examples/complete/data.tf
index f288dba..edba410 100644
--- a/examples/complete/data.tf
+++ b/examples/complete/data.tf
@@ -1,8 +1,5 @@
+data "aws_caller_identity" "current" {}
+
data "aws_availability_zones" "available" {
state = "available"
}
-
-data "aws_security_group" "default" {
- name = "default"
- vpc_id = module.vpc.vpc_id
-}
diff --git a/examples/complete/main.tf b/examples/complete/main.tf
index aa08ee7..bcacdd6 100644
--- a/examples/complete/main.tf
+++ b/examples/complete/main.tf
@@ -1,6 +1,9 @@
locals {
container_port = 8000
image_tag = "production"
+
+ vpc_cidr = "10.0.0.0/16"
+ azs = slice(data.aws_availability_zones.available.names, 0, 3)
}
resource "random_pet" "this" {
@@ -12,15 +15,17 @@ resource "aws_ecs_cluster" "this" {
}
module "vpc" {
- source = "registry.terraform.io/terraform-aws-modules/vpc/aws"
- version = ">= 4.0"
+ source = "terraform-aws-modules/vpc/aws"
+ version = "~> 5.0"
+
+ azs = local.azs
+ cidr = local.vpc_cidr
+ name = random_pet.this.id
+ private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)]
+ public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)]
- azs = slice(data.aws_availability_zones.available.names, 0, 3)
- cidr = "10.0.0.0/16"
- enable_dns_hostnames = true
- name = random_pet.this.id
- private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
- public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
+ enable_nat_gateway = true
+ single_nat_gateway = true
public_subnet_tags = {
Tier = "public"
@@ -31,95 +36,63 @@ module "vpc" {
}
}
-// see https://docs.aws.amazon.com/AmazonECR/latest/userguide/vpc-endpoints.html for necessary endpoints to run Fargate tasks
-module "vpc_endpoints" {
- source = "registry.terraform.io/terraform-aws-modules/vpc/aws//modules/vpc-endpoints"
- version = ">= 4.0"
-
- security_group_ids = [data.aws_security_group.default.id]
- vpc_id = module.vpc.vpc_id
-
- endpoints = {
- ecr_api = {
- service = "ecr.api"
- private_dns_enabled = true
- subnet_ids = module.vpc.private_subnets
- }
-
- ecr_dkr = {
- service = "ecr.dkr"
- private_dns_enabled = true
- subnet_ids = module.vpc.private_subnets
- }
-
- logs = {
- service = "logs"
- private_dns_enabled = true
- subnet_ids = module.vpc.private_subnets
- }
-
- s3 = {
- service = "s3"
- service_type = "Gateway"
- route_table_ids = flatten([module.vpc.private_route_table_ids, module.vpc.public_route_table_ids])
- }
- }
-}
-
-module "alb_security_group_public" {
- source = "registry.terraform.io/terraform-aws-modules/security-group/aws"
- version = ">= 4.17"
- name = "fargate-allow-alb-traffic"
- use_name_prefix = false
- description = "Security group for example usage with ALB"
- vpc_id = module.vpc.vpc_id
-
- ingress_cidr_blocks = ["0.0.0.0/0"]
- ingress_ipv6_cidr_blocks = ["::/0"]
- ingress_rules = ["http-80-tcp"]
- egress_rules = ["all-all"]
-}
+module "alb" {
+ source = "terraform-aws-modules/alb/aws"
+ version = "~> 9.0"
-#tfsec:ignore:aws-elb-alb-not-public
-resource "aws_lb" "public" {
- drop_invalid_header_fields = true
+ enable_deletion_protection = false
load_balancer_type = "application"
name = random_pet.this.id
- security_groups = [module.vpc.default_security_group_id, module.alb_security_group_public.security_group_id]
subnets = module.vpc.public_subnets
-}
-
-#tfsec:ignore:aws-elb-http-not-used
-resource "aws_lb_listener" "http" {
- load_balancer_arn = aws_lb.public.arn
- port = 80
+ vpc_id = module.vpc.vpc_id
+
+ security_group_ingress_rules = {
+ all_http = {
+ from_port = 80
+ to_port = 80
+ ip_protocol = "tcp"
+ cidr_ipv4 = "0.0.0.0/0"
+ }
+ }
+ security_group_egress_rules = {
+ all = {
+ ip_protocol = "-1"
+ cidr_ipv4 = module.vpc.vpc_cidr_block
+ }
+ }
- default_action {
- type = "fixed-response"
+ listeners = {
+ http = {
+ port = 80
+ protocol = "HTTP"
- fixed_response {
- content_type = "text/plain"
- message_body = "Request was not routed."
- status_code = 400
+ fixed_response = {
+ content_type = "text/plain"
+ message_body = "Request was not routed."
+ status_code = 404
+ }
}
}
}
module "service" {
- source = "../../"
+ source = "../../"
+ depends_on = [module.vpc]
cpu = 256
+ cpu_architecture = "ARM64"
cluster_id = aws_ecs_cluster.this.id
container_port = local.container_port
- create_ingress_security_group = false
+ create_ingress_security_group = true
create_deployment_pipeline = false
desired_count = 1
ecr_force_delete = true
+ ecr_image_tag = local.image_tag
memory = 512
service_name = random_pet.this.id
+ security_groups = [aws_security_group.egress_all.id]
vpc_id = module.vpc.vpc_id
- ecr_image_tag = local.image_tag
// configure autoscaling for this service
appautoscaling_settings = {
@@ -136,7 +109,7 @@ module "service" {
// add listener rules that determine how the load balancer routes requests to its registered targets.
https_listener_rules = [{
- listener_arn = aws_lb_listener.http.arn
+ listener_arn = module.alb.listeners["http"].arn
actions = [{
type = "forward"
@@ -154,7 +127,7 @@ module "service" {
name_prefix = "${substr(random_pet.this.id, 0, 5)}-"
backend_protocol = "HTTP"
backend_port = local.container_port
- load_balancer_arn = aws_lb_listener.http.load_balancer_arn
+ load_balancer_arn = module.alb.arn
target_type = "ip"
health_check = {
@@ -166,18 +139,34 @@ module "service" {
]
}
-resource "null_resource" "initial_image" {
- provisioner "local-exec" {
- command = "aws ecr get-login-password --region ${var.region} | docker login --username AWS --password-stdin ${module.service.ecr_repository_url}"
+resource "aws_security_group" "egress_all" {
+ name_prefix = "${random_pet.this.id}-egress-all-"
+ description = "Allow all outbound traffic"
+ vpc_id = module.vpc.vpc_id
+
+ # make sure to secure traffic in production environments
+ # see https://avd.aquasec.com/misconfig/aws/ec2/avd-aws-0104/#Terraform
+ #trivy:ignore:AVD-AWS-0104
+ egress {
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_blocks = ["0.0.0.0/0"]
+ ipv6_cidr_blocks = ["::/0"]
+ }
+
+ lifecycle {
+ create_before_destroy = true
}
+}
+resource "null_resource" "initial_image" {
provisioner "local-exec" {
- command = "docker build --tag ${module.service.ecr_repository_url}:${local.image_tag} ."
- working_dir = "${path.module}/../fixtures/context"
+ command = "aws ecr get-login-password --region ${var.region} | docker login --username AWS --password-stdin ${data.aws_caller_identity.current.account_id}.dkr.ecr.${var.region}.amazonaws.com"
}
provisioner "local-exec" {
- command = "docker push --all-tags ${module.service.ecr_repository_url}"
+ command = "docker buildx build --tag ${module.service.ecr_repository_url}:${local.image_tag} --platform linux/amd64,linux/arm64 --push ."
working_dir = "${path.module}/../fixtures/context"
}
}
diff --git a/examples/complete/outputs.tf b/examples/complete/outputs.tf
index 0953589..521a1b9 100644
--- a/examples/complete/outputs.tf
+++ b/examples/complete/outputs.tf
@@ -1,3 +1,3 @@
-output "alb_dns_name" {
- value = aws_lb.public.dns_name
+output "endpoint" {
+ value = "http://${module.alb.dns_name}/"
}
diff --git a/examples/complete/versions.tf b/examples/complete/versions.tf
index 96995bf..0b4c16e 100644
--- a/examples/complete/versions.tf
+++ b/examples/complete/versions.tf
@@ -1,10 +1,10 @@
terraform {
- required_version = ">= 1.0"
+ required_version = ">= 1.3"
required_providers {
aws = {
source = "hashicorp/aws"
- version = ">= 4.9"
+ version = ">= 5.32"
}
random = {
source = "hashicorp/random"
diff --git a/examples/fixtures/context/Dockerfile b/examples/fixtures/context/Dockerfile
index 78f2701..b348627 100644
--- a/examples/fixtures/context/Dockerfile
+++ b/examples/fixtures/context/Dockerfile
@@ -1,9 +1,15 @@
-FROM python:3.9-alpine
+FROM python:3.12-alpine
-ADD index.html index.html
-ADD server.py server.py
+RUN addgroup -S app && adduser -S app -G app
+WORKDIR /home/app
+
+ADD index.html /home/app/index.html
+ADD server.py /home/app/server.py
+
+RUN chown -R app:app /home/app
USER app
+
EXPOSE 8000
ENTRYPOINT ["python3", "server.py"]
diff --git a/main.tf b/main.tf
index 68db84a..f654c33 100644
--- a/main.tf
+++ b/main.tf
@@ -1,12 +1,12 @@
data "aws_lb" "public" {
- for_each = var.create_ingress_security_group ? toset([for target in var.target_groups : lookup(target, "load_balancer_arn", "")]) : []
+ for_each = var.create_ingress_security_group ? { for idx, target in var.target_groups : idx => lookup(target, "load_balancer_arn", "") } : {}
arn = each.value
}
locals {
ingress_targets = flatten(
[
- for target in var.target_groups : flatten(
+ for idx, target in var.target_groups : flatten(
[
[
{
@@ -14,7 +14,7 @@ locals {
from_port = lookup(target, "backend_port", null)
to_port = lookup(target, "backend_port", null)
protocol = "tcp"
- source_security_group_id = tolist(data.aws_lb.public[lookup(target, "load_balancer_arn", null)].security_groups)[0]
+ source_security_group_id = tolist(data.aws_lb.public[idx].security_groups)[0]
prefix = "backend_port"
}
],
@@ -27,7 +27,7 @@ locals {
from_port = target["health_check"]["port"]
to_port = target["health_check"]["port"]
protocol = "tcp"
- source_security_group_id = tolist(data.aws_lb.public[lookup(target, "load_balancer_arn", null)].security_groups)[0]
+ source_security_group_id = tolist(data.aws_lb.public[idx].security_groups)[0]
prefix = "health_check_port"
}
] : []
@@ -64,7 +64,8 @@ module "sg" {
}
resource "aws_security_group_rule" "trusted_egress_attachment" {
- for_each = { for route in local.ingress_targets : "${route["prefix"]}-${route["source_security_group_id"]}" => route }
+ depends_on = [data.aws_lb.public]
+ for_each = { for route in local.ingress_targets : "${route["prefix"]}-${route["protocol"]}-${route["from_port"]}-${route["to_port"]}" => route }
type = "egress"
from_port = each.value["from_port"]
to_port = each.value["to_port"]
diff --git a/modules/deployment/iam_code_pipeline.tf b/modules/deployment/iam_code_pipeline.tf
index d2971ae..5338bb9 100644
--- a/modules/deployment/iam_code_pipeline.tf
+++ b/modules/deployment/iam_code_pipeline.tf
@@ -69,9 +69,10 @@ data "aws_iam_policy_document" "code_pipepline_permissions" {
resources = [aws_codebuild_project.this.arn]
}
+ # cloudtrail reports that codepipeline actually requires access to `*`
+ #trivy:ignore:AVD-AWS-0057
statement {
actions = [
- # cloudtrail reports that codepipeline actually requires access to `*`
"ecs:DescribeTaskDefinition",
"ecs:RegisterTaskDefinition",
"ecs:TagResource"
diff --git a/outputs.tf b/outputs.tf
index 9de82a0..16fdebe 100644
--- a/outputs.tf
+++ b/outputs.tf
@@ -19,8 +19,13 @@ output "ecr_repository_arn" {
value = join("", module.ecr[*].arn)
}
+output "ecr_repository_id" {
+ description = "The registry ID where the repository was created."
+ value = join("", module.ecr[*].registry_id)
+}
+
output "ecr_repository_url" {
- description = "URL of the ECR repository."
+ description = "The URL of the repository (in the form `aws_account_id.dkr.ecr.region.amazonaws.com/repositoryName`)"
value = join("", module.ecr[*].repository_url)
}