Instruction for the Fargate v1.4.0.

  1. Configure gateway endpoint for S3: com.amazonaws.<region>.s3 and connect it to the route table
resource "aws_vpc_endpoint" "s3" {
  vpc_id            = aws_vpc.antivirus.id
  service_name      = "com.amazonaws.${local.aws_region}.s3"
  vpc_endpoint_type = "Gateway"
  route_table_ids   = [aws_route_table.private1.id]
}
  1. Configure interface endpoints, connect it to the subnets and security groups:
    1. com.amazonaws.<region>.ecr.dkr and com.amazonaws.<region>.ecr.api - to pull an image from ECR.
    2. com.amazonaws.<region>.ecr.logs - for AWS CloudWatch logs. Private DNS Name should be enabled. It was the last thing I missed Example:
resource "aws_vpc_endpoint" "ecr_dkr" {
  vpc_id              = aws_vpc.antivirus.id
  service_name        = "com.amazonaws.${local.aws_region}.ecr.dkr"
  vpc_endpoint_type   = "Interface"
  private_dns_enabled = true
  subnet_ids          = [aws_subnet.private1.id]
  security_group_ids  = [aws_security_group.scanner.id]
}
  1. Security group need to allow inbound access to port 443 (SSH) from the private subnet
resource "aws_security_group" "scanner" {
  vpc_id = aws_vpc.antivirus.id
  name   = "${local.name_prefix}-scanner"
 
  # Allow SSH access from the private subnet on port 443
  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = [aws_subnet.private1.cidr_block]
  }
 
  # Allow all outbound traffic
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

Working example:

# network.tf
resource "aws_vpc" "antivirus" {
  cidr_block = "10.0.0.0/16"
 
  enable_dns_support   = true
  enable_dns_hostnames = true
 
  tags = {
    Name = "${local.name_prefix}-vpc"
  }
}
 
resource "aws_subnet" "private1" {
  vpc_id            = aws_vpc.antivirus.id
  cidr_block        = "10.0.1.0/24"
  availability_zone = "${local.aws_region}a"
 
  tags = {
    Name = "${local.name_prefix}-subnet-private1-${local.aws_region}a"
  }
}
 
resource "aws_route_table" "private1" {
  vpc_id = aws_vpc.antivirus.id
 
  tags = {
    Name = "${local.name_prefix}-rtb-private1-${local.aws_region}a"
  }
}
 
resource "aws_route_table_association" "private1" {
  subnet_id      = aws_subnet.private1.id
  route_table_id = aws_route_table.private1.id
}
 
resource "aws_security_group" "scanner" {
  vpc_id = aws_vpc.antivirus.id
  name   = "${local.name_prefix}-scanner"
 
  # Allow SSH access from the private subnet on port 443
  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = [aws_subnet.private1.cidr_block]
  }
 
  # Allow all outbound traffic
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
 
resource "aws_vpc_endpoint" "s3" {
  vpc_id            = aws_vpc.antivirus.id
  service_name      = "com.amazonaws.${local.aws_region}.s3"
  vpc_endpoint_type = "Gateway"
  route_table_ids   = [aws_route_table.private1.id]
}
 
resource "aws_vpc_endpoint" "ecr_dkr" {
  vpc_id              = aws_vpc.antivirus.id
  service_name        = "com.amazonaws.${local.aws_region}.ecr.dkr"
  vpc_endpoint_type   = "Interface"
  private_dns_enabled = true
  subnet_ids          = [aws_subnet.private1.id]
  security_group_ids  = [aws_security_group.scanner.id]
}
 
resource "aws_vpc_endpoint" "ecr_api" {
  vpc_id              = aws_vpc.antivirus.id
  service_name        = "com.amazonaws.${local.aws_region}.ecr.api"
  vpc_endpoint_type   = "Interface"
  private_dns_enabled = true
  subnet_ids          = [aws_subnet.private1.id]
  security_group_ids  = [aws_security_group.scanner.id]
}
 
resource "aws_vpc_endpoint" "logs" {
  vpc_id              = aws_vpc.antivirus.id
  service_name        = "com.amazonaws.${local.aws_region}.logs"
  vpc_endpoint_type   = "Interface"
  private_dns_enabled = true
  subnet_ids          = [aws_subnet.private1.id]
  security_group_ids  = [aws_security_group.scanner.id]
}
 
resource "aws_vpc_endpoint" "sqs" {
  vpc_id              = aws_vpc.antivirus.id
  service_name        = "com.amazonaws.${local.aws_region}.sqs"
  vpc_endpoint_type   = "Interface"
  private_dns_enabled = true
  subnet_ids          = [aws_subnet.private1.id]
  security_group_ids  = [aws_security_group.scanner.id]
}

ECS task was called in a AWS Lambda function like this:

# main.py
from __future__ import annotations
 
import json
import os
from typing import TYPE_CHECKING
 
import boto3
 
if TYPE_CHECKING:
    from aws_lambda_powertools.utilities.typing import LambdaContext
 
 
ECS_TASK_NAME = os.environ.get("ECS_TASK_NAME", "")
ECS_CLUSTER_NAME = os.environ.get("ECS_CLUSTER_NAME", "")
AWS_VPC_SUBNET_IDS = json.loads(os.environ.get("AWS_VPC_SUBNET_IDS", "[]"))
AWS_VPC_SECURITY_GROUP_IDS = json.loads(
    os.environ.get("AWS_VPC_SECURITY_GROUP_IDS", "[]"),
)
 
ecs_client = boto3.client("ecs")
 
 
def lambda_handler(event: dict, context: LambdaContext) -> None:
    ecs_client.run_task(
        taskDefinition=ECS_TASK_NAME,
        launchType="FARGATE",
        cluster=ECS_CLUSTER_NAME,
        networkConfiguration={
            "awsvpcConfiguration": {
                "securityGroups": AWS_VPC_SECURITY_GROUP_IDS,
                "subnets": AWS_VPC_SUBNET_IDS,
            },
        },
    )

References