ECS Services Do Not Have Public IPs Assigned
Overview
This check ensures that your Amazon ECS (Elastic Container Service) services do not automatically assign public IP addresses to their tasks. When tasks receive public IPs, they become directly accessible from the internet, which significantly increases your security exposure.
Risk
When ECS tasks have public IP addresses, they are directly reachable from the internet. This creates several security risks:
- Direct attack surface: Attackers can scan and probe your container ports
- Data exfiltration: Compromised containers can easily send data out
- Lateral movement: Attackers who breach one task may pivot to other resources in your VPC
- Compliance violations: Many security frameworks prohibit direct internet exposure of workloads
Tasks should communicate through load balancers, NAT gateways, or VPC endpoints instead.
Remediation Steps
Prerequisites
- Access to the AWS Console with permissions to modify ECS services
- Knowledge of which ECS cluster and service need to be updated
- Your service must be in a VPC with private subnets
Required IAM permissions
You need the following IAM permissions:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecs:UpdateService",
"ecs:DescribeServices",
"ecs:DescribeClusters"
],
"Resource": "*"
}
]
}
AWS Console Method
- Open the Amazon ECS console
- In the left navigation, click Clusters
- Select the cluster containing your service
- Click the Services tab
- Select the service you want to update
- Click Update in the upper right
- Scroll down to the Networking section
- Under Public IP, select Turned off (or uncheck "Assign public IP")
- Click Update to save changes
The service will deploy new tasks without public IPs. Existing tasks will be replaced according to your deployment configuration.
Important: Before disabling public IPs, ensure your tasks can still reach the internet (for pulling images, calling APIs, etc.) through a NAT gateway or VPC endpoints.
AWS CLI
Use the update-service command with the network configuration set to disable public IP assignment:
aws ecs update-service \
--cluster <your-cluster-name> \
--service <your-service-name> \
--network-configuration "awsvpcConfiguration={subnets=[<subnet-id-1>,<subnet-id-2>],securityGroups=[<security-group-id>],assignPublicIp=DISABLED}" \
--region us-east-1
Replace the placeholders:
<your-cluster-name>: Your ECS cluster name<your-service-name>: Your ECS service name<subnet-id-1>,<subnet-id-2>: Your private subnet IDs<security-group-id>: Your security group ID
Example with real values:
aws ecs update-service \
--cluster my-production-cluster \
--service my-web-service \
--network-configuration "awsvpcConfiguration={subnets=[subnet-0123456789abcdef0,subnet-0fedcba9876543210],securityGroups=[sg-0123456789abcdef0],assignPublicIp=DISABLED}" \
--region us-east-1
CloudFormation
When defining an ECS service in CloudFormation, set AssignPublicIp to DISABLED in the network configuration:
AWSTemplateFormatVersion: '2010-09-09'
Description: ECS Service with private IP addressing (no public IP)
Parameters:
ClusterName:
Type: String
Description: Name of the ECS cluster
ServiceName:
Type: String
Description: Name of the ECS service
TaskDefinitionArn:
Type: String
Description: ARN of the task definition to use
SubnetIds:
Type: List<AWS::EC2::Subnet::Id>
Description: Subnet IDs for the service (must be private subnets)
SecurityGroupIds:
Type: List<AWS::EC2::SecurityGroup::Id>
Description: Security group IDs for the service
Resources:
ECSService:
Type: AWS::ECS::Service
Properties:
Cluster: !Ref ClusterName
ServiceName: !Ref ServiceName
TaskDefinition: !Ref TaskDefinitionArn
DesiredCount: 1
LaunchType: FARGATE
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: DISABLED
Subnets: !Ref SubnetIds
SecurityGroups: !Ref SecurityGroupIds
Outputs:
ServiceArn:
Description: ARN of the ECS service
Value: !Ref ECSService
Deploy the stack:
aws cloudformation deploy \
--template-file template.yaml \
--stack-name my-ecs-service-stack \
--parameter-overrides \
ClusterName=my-cluster \
ServiceName=my-service \
TaskDefinitionArn=arn:aws:ecs:us-east-1:123456789012:task-definition/my-task:1 \
SubnetIds=subnet-0123456789abcdef0,subnet-0fedcba9876543210 \
SecurityGroupIds=sg-0123456789abcdef0 \
--region us-east-1
Terraform
Set assign_public_ip = false in the network_configuration block:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
variable "cluster_name" {
description = "Name of the ECS cluster"
type = string
}
variable "service_name" {
description = "Name of the ECS service"
type = string
}
variable "task_definition_arn" {
description = "ARN of the task definition"
type = string
}
variable "subnet_ids" {
description = "List of private subnet IDs"
type = list(string)
}
variable "security_group_ids" {
description = "List of security group IDs"
type = list(string)
}
resource "aws_ecs_service" "main" {
name = var.service_name
cluster = var.cluster_name
task_definition = var.task_definition_arn
desired_count = 1
launch_type = "FARGATE"
network_configuration {
subnets = var.subnet_ids
security_groups = var.security_group_ids
assign_public_ip = false
}
}
output "service_arn" {
description = "ARN of the ECS service"
value = aws_ecs_service.main.id
}
Apply the configuration:
terraform init
terraform plan
terraform apply
Verification
After making changes, verify that your ECS service no longer assigns public IPs:
- Go to ECS > Clusters > your cluster > Services
- Select your service and click the Configuration tab
- Under Network configuration, confirm "Auto-assign public IP" shows DISABLED
CLI verification
aws ecs describe-services \
--cluster <your-cluster-name> \
--services <your-service-name> \
--query 'services[0].networkConfiguration.awsvpcConfiguration.assignPublicIp' \
--region us-east-1
The output should be:
"DISABLED"
To check running tasks for public IPs:
# Get task ARNs
TASK_ARNS=$(aws ecs list-tasks \
--cluster <your-cluster-name> \
--service-name <your-service-name> \
--query 'taskArns' \
--output text \
--region us-east-1)
# Describe tasks to check for public IPs
aws ecs describe-tasks \
--cluster <your-cluster-name> \
--tasks $TASK_ARNS \
--query 'tasks[*].attachments[*].details[?name==`networkInterfaceId`].value' \
--region us-east-1
Then check if the ENI has a public IP:
aws ec2 describe-network-interfaces \
--network-interface-ids <eni-id> \
--query 'NetworkInterfaces[0].Association.PublicIp' \
--region us-east-1
If the result is null, the task has no public IP.
Additional Resources
Notes
- Outbound connectivity: Tasks without public IPs need a NAT gateway or VPC endpoints to reach the internet (for pulling container images, calling external APIs, etc.)
- Private subnets required: Deploy tasks in private subnets that route internet traffic through a NAT gateway
- Load balancers: For inbound traffic, use an Application Load Balancer or Network Load Balancer in public subnets to route traffic to your private tasks
- Deployment impact: Updating the network configuration triggers a new deployment, replacing existing tasks with new ones
- Service interruption: Plan the update during a maintenance window if your service cannot tolerate brief interruptions during task replacement