|

ECS – Prevent Services from Being Publicly Accessible

This FinOps and security policy requires that Amazon ECS services are not directly reachable from the public internet. Publicly accessible ECS services expand the attack surface, increase the risk of unauthorized access, and commonly violate compliance frameworks such as CIS AWS Foundations Benchmark and PCI-DSS. This policy corresponds to AWS Security Hub control ECS.2.

Why This Policy Matters

An ECS service becomes publicly accessible when its tasks are assigned public IP addresses or placed in public subnets without appropriate network controls. This configuration allows any external actor to attempt direct connections to running containers.

The risk is not theoretical. Exposed container workloads are actively scanned and targeted. Beyond security, publicly routing traffic to ECS tasks bypasses standard ingress patterns, making traffic harder to inspect, log, and control.

How It Helps Reduce Cloud Costs

Publicly accessible ECS services often signal architectural gaps that carry indirect cost consequences.

  • Unnecessary NAT and data transfer charges: When ECS tasks have public IPs, outbound traffic may route inefficiently, increasing data transfer costs.
  • Over-provisioned networking resources: Teams compensating for poor access controls often add redundant load balancers, WAFs, or extra security groups, each adding cost.
  • Incident response costs: A compromised container can result in unauthorized compute usage, data exfiltration fees, and engineering time spent on remediation.

Enforcing private-only ECS access aligns network topology with cost-efficient design patterns that route external traffic through a single, controlled ingress point.

Potential Savings

A team running 20 ECS Fargate tasks each with public IP addresses and uncontrolled outbound data transfer can easily accumulate $50–$200/month in unnecessary data transfer and networking overhead, depending on traffic volume. Consolidating ingress through an Application Load Balancer (ALB) in a public subnet, while keeping ECS tasks private, typically reduces this waste and simplifies security group management.

Preventing a single security incident—which can generate tens of thousands of dollars in compute abuse or forensic investigation costs—represents the largest potential saving from enforcing this policy.

Implementation Guide

Infrastructure-as-Code Example (Terraform)

The following examples show a non-compliant ECS service configuration and the corrected version.

Non-compliant configuration — ECS tasks assigned public IPs in a public subnet:

resource "aws_ecs_service" "example" {
  name            = "my-service"
  cluster         = aws_ecs_cluster.main.id
  task_definition = aws_ecs_task_definition.app.arn
  launch_type     = "FARGATE"
  desired_count   = 2

  network_configuration {
    subnets          = [aws_subnet.public_subnet.id]  # ❌ Public subnet
    security_groups  = [aws_security_group.ecs_sg.id]
    assign_public_ip = true  # ❌ Public IP assigned to tasks
  }
}

This configuration places ECS tasks directly on the internet. Any port open in the security group is reachable by external actors.

Compliant configuration — ECS tasks in private subnets, no public IP:

resource "aws_ecs_service" "example" {
  name            = "my-service"
  cluster         = aws_ecs_cluster.main.id
  task_definition = aws_ecs_task_definition.app.arn
  launch_type     = "FARGATE"
  desired_count   = 2

  network_configuration {
    subnets          = [aws_subnet.private_subnet.id]  # ✅ Private subnet
    security_groups  = [aws_security_group.ecs_sg.id]
    assign_public_ip = false  # ✅ No public IP assigned
  }

  load_balancer {
    target_group_arn = aws_lb_target_group.app.arn
    container_name   = "app"
    container_port   = 8080
  }
}

# ALB in public subnet handles external traffic
resource "aws_lb" "app" {
  name               = "app-alb"
  internal           = false
  load_balancer_type = "application"
  subnets            = [aws_subnet.public_subnet_a.id, aws_subnet.public_subnet_b.id]
  security_groups    = [aws_security_group.alb_sg.id]
}

resource "aws_lb_listener" "http" {
  load_balancer_arn = aws_lb.app.arn
  port              = 80
  protocol          = "HTTP"

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.app.arn
  }
}

resource "aws_lb_target_group" "app" {
  name        = "app-tg"
  port        = 8080
  protocol    = "HTTP"
  vpc_id      = aws_vpc.main.id
  target_type = "ip"
}

The ALB sits in the public subnet and accepts external traffic. ECS tasks remain in private subnets and are only reachable from the ALB’s security group.

Manual Step-by-Step Fix Instructions

  1. Identify affected services: Run the following AWS CLI command to list ECS services with assign_public_ip set to ENABLED:
aws ecs list-services --cluster <cluster-name> \
  --query 'serviceArns[]' --output text | \
  xargs -I {} aws ecs describe-services \
  --cluster <cluster-name> \
  --services {} \
  --query 'services[?networkConfiguration.awsvpcConfiguration.assignPublicIp==`ENABLED`].{Name:serviceName,PublicIP:networkConfiguration.awsvpcConfiguration.assignPublicIp}'
  1. Identify target subnets: Confirm which subnets are private (no route to an Internet Gateway).
  2. Update the Terraform configuration: Change assign_public_ip = false and replace public subnet IDs with private subnet IDs.
  3. Add a load balancer: If external access is required, deploy an ALB in a public subnet and configure a target group pointing to the ECS service.
  4. Update security groups: Restrict the ECS task security group to accept traffic only from the ALB security group on the required port.
  5. Apply the change: Run terraform plan to review changes, then terraform apply.
  6. Verify: Confirm tasks no longer have public IPs by describing running tasks:
aws ecs describe-tasks \
  --cluster <cluster-name> \
  --tasks <task-arn> \
  --query 'tasks[].attachments[].details[?name==`networkInterfaceId`]'
  1. Check AWS Security Hub: Confirm the ECS.2 finding is resolved in the AWS Security Hub console.

Best Practices

  • Always place ECS Fargate tasks in private subnets unless there is an explicit, documented reason for public placement.
  • Route all external traffic through an ALB or API Gateway, never directly to container IPs.
  • Restrict ECS task security groups to accept inbound traffic only from the ALB or internal VPC CIDR ranges.
  • Use VPC endpoints for AWS service calls (ECR, S3, Secrets Manager) from private subnets to avoid NAT Gateway charges.
  • Enforce this policy in CI/CD pipelines using Infracost policy checks before any infrastructure change is applied.
  • Tag non-compliant resources during detection so they can be tracked and prioritized for remediation.
  • Review ECS service configurations quarterly or on every infrastructure change.

Tools and Scripts to Help Implement

Infracost detects this policy violation automatically during infrastructure planning. When a Terraform plan includes an ECS service with assign_public_ip = true, Infracost flags it as a policy violation before the resource is deployed. This policy is available in Infracost, including in the free trial.

Infracost integrates directly into CI/CD pipelines (GitHub Actions, GitLab CI, Atlantis, and others). It evaluates each pull request that modifies infrastructure and blocks or warns on non-compliant ECS configurations before they reach production.

Teams can use Infracost to:

  • Detect violations automatically on every terraform plan run.
  • Prevent future violations by enforcing policy checks as a CI/CD gate.
  • Track remediation progress over time using the Infracost dashboard, enabling FinOps teams to measure burn-down of existing violations.
  • Prioritize fixes by reviewing which ECS services are flagged across all environments.

AWS Config can also enforce this using the ecs-task-definition-no-environment-variables managed rule and custom rules targeting awsvpcConfiguration.assignPublicIp.

OPA (Open Policy Agent) / Conftest can be used to write custom policy rules that reject Terraform plans containing assign_public_ip = true in ECS service resources.

Examples of Impact

Example 1: SaaS Application Running on ECS Fargate

A team runs 15 Fargate tasks across two environments with assign_public_ip = true. Security Hub flags ECS.2 violations across all services. After migrating tasks to private subnets and routing traffic through an existing ALB, the team eliminates direct internet exposure and reduces their monthly data transfer costs by approximately $80 due to more efficient routing through the VPC.

Example 2: Crypto-Mining Incident Prevention

A development team accidentally deploys a container image with an exposed management port on a publicly accessible ECS service. Without this policy enforced, an external actor could discover and exploit the open port within hours. Enforcing assign_public_ip = false in CI/CD via Infracost blocks the deployment before it reaches production, preventing a potential compute abuse incident that could cost thousands of dollars in unauthorized charges.

Considerations and Caveats

  • Legacy architectures: Some older ECS deployments using EC2 launch type (not Fargate) may have public IP assignment handled at the EC2 instance level rather than the task level. Verify the network topology for EC2-backed ECS clusters separately.
  • Internal services that require outbound internet access: ECS tasks in private subnets still require either a NAT Gateway or VPC endpoints to reach external services (e.g., ECR for image pulls, external APIs). NAT Gateway adds cost (~$0.045/hour per gateway plus data processing fees). Factor this into architecture decisions.
  • Development and testing environments: Some teams use public IP assignment in non-production environments for convenience. This policy should still apply to all environments, but enforcement urgency may be prioritized on production first.
  • Existing running services: Changing assign_public_ip requires a service update that causes task replacement. Plan for a brief disruption window or use blue/green deployment strategies.
  • ECS services that are intentionally public: In rare cases, a team may run a public-facing service without a load balancer. This should be explicitly documented, reviewed, and approved as an exception. It should not be the default.
  • Compliance scope: This policy directly maps to AWS Security Hub ECS.2, CIS AWS Foundations Benchmark, and is relevant for PCI-DSS scoped environments. Non-compliance in regulated environments may trigger audit findings.

Frequently Asked Questions (FAQs)

Yes. This policy is available in Infracost, including in the free trial. Infracost detects ECS services configured with assign_public_ip = true during terraform plan and flags them as violations before any infrastructure is deployed.

It requires a task replacement, which causes a brief interruption. If the service sits behind an ALB and uses target type ip, traffic can be shifted gracefully using blue/green or rolling deployment strategies. Plan the change during a maintenance window or use deployment circuit breakers.

Use VPC endpoints for ECR (com.amazonaws.<region>.ecr.dkr, com.amazonaws.<region>.ecr.api) and S3 (com.amazonaws.<region>.s3). This allows private subnet tasks to access AWS services without routing through a NAT Gateway, reducing cost and maintaining security.

Yes. Infracost policies can be scoped to specific environments, clusters, or teams. You can configure the policy to warn rather than block in lower environments, while enforcing a hard block in production.

The assign_public_ip setting in network_configuration applies only to Fargate tasks using awsvpc network mode. For EC2-backed ECS, public accessibility is controlled at the EC2 instance and security group level. Review those configurations separately.

Infracost provides a centralized view of all flagged policy violations across environments. FinOps teams can track which ECS services remain non-compliant over time and measure progress as issues are resolved, enabling a structured burn-down approach.

This check should run automatically on every infrastructure pull request via CI/CD. A manual review of all ECS services should occur at minimum quarterly, or following any significant infrastructure change.