Skip to content

Commit

Permalink
fix(alb): complete example can be applied without errors (#150)
Browse files Browse the repository at this point in the history
* fix(example): complete example can be applied without errors

* fix initial apply w/o target

* Fix alb lookup

* fixed ecr login in example

* updated docs

* fixed race conditions and trivy errors

---------

Co-authored-by: Saef Taher <[email protected]>
  • Loading branch information
moritzzimmer and saefty committed Sep 19, 2024
1 parent 8b89520 commit 5a2bbef
Show file tree
Hide file tree
Showing 10 changed files with 111 additions and 113 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,8 @@ for example.
| <a name="output_cloudwatch_log_group"></a> [cloudwatch\_log\_group](#output\_cloudwatch\_log\_group) | Name of the CloudWatch log group for container logs. |
| <a name="output_container_definitions"></a> [container\_definitions](#output\_container\_definitions) | Container definitions used by this service including all sidecars. |
| <a name="output_ecr_repository_arn"></a> [ecr\_repository\_arn](#output\_ecr\_repository\_arn) | Full ARN of the ECR repository. |
| <a name="output_ecr_repository_url"></a> [ecr\_repository\_url](#output\_ecr\_repository\_url) | URL of the ECR repository. |
| <a name="output_ecr_repository_id"></a> [ecr\_repository\_id](#output\_ecr\_repository\_id) | The registry ID where the repository was created. |
| <a name="output_ecr_repository_url"></a> [ecr\_repository\_url](#output\_ecr\_repository\_url) | The URL of the repository (in the form `aws_account_id.dkr.ecr.region.amazonaws.com/repositoryName`) |
| <a name="output_task_execution_role_arn"></a> [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. |
| <a name="output_task_execution_role_name"></a> [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. |
| <a name="output_task_execution_role_unique_id"></a> [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. |
Expand Down
18 changes: 8 additions & 10 deletions examples/complete/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,39 +17,37 @@ Note that this example may create resources which cost money. Run `terraform des

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.0 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 4.9 |
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.3 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 5.32 |
| <a name="requirement_null"></a> [null](#requirement\_null) | >= 3.2 |
| <a name="requirement_random"></a> [random](#requirement\_random) | >= 3.4 |

## Providers

| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 4.9 |
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 5.32 |
| <a name="provider_null"></a> [null](#provider\_null) | >= 3.2 |
| <a name="provider_random"></a> [random](#provider\_random) | >= 3.4 |

## Modules

| Name | Source | Version |
|------|--------|---------|
| <a name="module_alb_security_group_public"></a> [alb\_security\_group\_public](#module\_alb\_security\_group\_public) | registry.terraform.io/terraform-aws-modules/security-group/aws | >= 4.17 |
| <a name="module_alb"></a> [alb](#module\_alb) | terraform-aws-modules/alb/aws | ~> 9.0 |
| <a name="module_service"></a> [service](#module\_service) | ../../ | n/a |
| <a name="module_vpc"></a> [vpc](#module\_vpc) | registry.terraform.io/terraform-aws-modules/vpc/aws | >= 4.0 |
| <a name="module_vpc_endpoints"></a> [vpc\_endpoints](#module\_vpc\_endpoints) | registry.terraform.io/terraform-aws-modules/vpc/aws//modules/vpc-endpoints | >= 4.0 |
| <a name="module_vpc"></a> [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

Expand All @@ -61,5 +59,5 @@ Note that this example may create resources which cost money. Run `terraform des

| Name | Description |
|------|-------------|
| <a name="output_alb_dns_name"></a> [alb\_dns\_name](#output\_alb\_dns\_name) | n/a |
| <a name="output_endpoint"></a> [endpoint](#output\_endpoint) | n/a |
<!-- END_TF_DOCS -->
7 changes: 2 additions & 5 deletions examples/complete/data.tf
Original file line number Diff line number Diff line change
@@ -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
}
155 changes: 72 additions & 83 deletions examples/complete/main.tf
Original file line number Diff line number Diff line change
@@ -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" {
Expand All @@ -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"
Expand All @@ -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 = {
Expand All @@ -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"
Expand All @@ -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 = {
Expand All @@ -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"
}
}
4 changes: 2 additions & 2 deletions examples/complete/outputs.tf
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
output "alb_dns_name" {
value = aws_lb.public.dns_name
output "endpoint" {
value = "http://${module.alb.dns_name}/"
}
4 changes: 2 additions & 2 deletions examples/complete/versions.tf
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
12 changes: 9 additions & 3 deletions examples/fixtures/context/Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
11 changes: 6 additions & 5 deletions main.tf
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
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(
[
[
{
# allow backend_port traffic
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"
}
],
Expand All @@ -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"
}
] : []
Expand Down Expand Up @@ -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"]
Expand Down
3 changes: 2 additions & 1 deletion modules/deployment/iam_code_pipeline.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading

0 comments on commit 5a2bbef

Please sign in to comment.