Most Amazon ECS security problems come down to confusing two IAM roles: the task execution role that the platform uses to start a task, and the task role that the application uses at runtime, and then giving one or both far more access than the workload needs. Add secrets passed as environment variables, ECS Exec left open on production and containers on EC2 hosts that can reach the instance profile, and a single container compromise becomes an account compromise. This guide covers ECS identity end to end on both Fargate and the EC2 launch type: the two roles, iam:PassRole, secrets injection, ECS Exec, logging, runtime monitoring and a hardening baseline. It complements the broader Container Security Guide and is written for platform engineers and cloud security teams running ECS.
Key takeaways
- Two roles, two audiences. The task execution role is used by the ECS and Fargate agents and is not accessible to containers; the task role is what your code gets. Never merge them.
- PassRole is the escalation path. Anyone who can register a task definition and run a task, and can pass a powerful role, can run code with that role's permissions.
- Secrets are injected once. Values from Secrets Manager or Parameter Store arrive as environment variables at container start and are not refreshed on rotation.
- Fargate isolates tasks; EC2 does not. AWS states containers are not a security boundary; on EC2 hosts a container may reach other tasks' credentials and the instance role unless you block it.
- ECS Exec is a shell as root. Gate
ecs:ExecuteCommandwith conditions, log sessions, and deny directssm:StartSessionon tasks.
How identity and access work in Amazon ECS
| Identity | Used by | Typical permissions | Reachable from the container? |
|---|---|---|---|
| Task execution role | ECS container agent and Fargate agent | Pull images from ECR, write awslogs to CloudWatch Logs, fetch referenced secrets and parameters, private registry credentials, Runtime Monitoring | No |
| Task role | Application code in the task, through the credentials endpoint at 169.254.170.2 | Whatever the app calls: S3, DynamoDB, SQS and so on | Yes |
| Container instance role (EC2 launch type) | The ECS agent on the host | Register the instance, pull tasks | Yes, through instance metadata, unless blocked |
| Service-linked role | Amazon ECS service | Manage load balancer targets, ENIs | No |
| Human and pipeline principals | Engineers and CI/CD | ecs:* actions, iam:PassRole, ecs:ExecuteCommand | Not applicable |
Task role
- Trust policy: allow the
ecs-tasks.amazonaws.comservice principal, withaws:SourceAccountand anArnLikeaws:SourceArncondition such asarn:aws:ecs:us-west-2:111122223333:*to prevent confused deputy use. AWS notesaws:SourceArndoes not yet support scoping to a single cluster. - Granularity: the task role page recommends a separate role per task definition or service with only the permissions it needs. AWS's ECS IAM best practices also suggest treating the cluster as an administrative boundary to avoid unmanageable role sprawl. A workable compromise is one role per service in production and per cluster in development.
- Auditability: task credentials carry the
taskArnin the session context, so CloudTrail shows which task used them. See the AWS CloudTrail Guide. - SDK behaviour: containers using a task role do not inherit instance permissions; the SDKs read credentials from the container credential provider endpoint.
Task execution role
Start from the managed policy AmazonECSTaskExecutionRolePolicy (ECR pull and CloudWatch Logs), then add only the secret retrieval you need: ssm:GetParameters, secretsmanager:GetSecretValue and kms:Decrypt scoped to the specific parameter, secret and key ARNs. Restrict image pulls to your VPC endpoint with aws:SourceVpce, and when the role reads configuration files from S3, require aws:ResourceAccount so a recreated bucket in another account cannot serve them.
Admin roles and privileged access
- Control iam:PassRole: registering a task definition or running a task that names a role requires
iam:PassRoleon it. Allow it only on specific role ARNs and addiam:PassedToServiceequal toecs-tasks.amazonaws.com. A developer withecs:RegisterTaskDefinition,ecs:RunTaskand PassRole on*effectively holds every role in the account. See the AWS IAM Policies and Roles Guide. - Deploy through pipelines: AWS recommends automated pipelines create, update and delete tasks so end users do not need direct ECS API access in production.
- Use conditions: require MFA for destructive actions such as
ecs:DeleteClusterand restrictRunTask,StartTaskandStopTaskby resource tags (service tags propagate to tasks). - Gate ECS Exec: allow
ecs:ExecuteCommandonly with conditions such asecs:cluster,ecs:container-nameorecs:ResourceTag/environment, and deny it for production containers by default. Grant time-bound access through your privileged access process. See the Cloud PAM and CIEM Guide.
ECS Exec in detail
ECS Exec uses Systems Manager Session Manager: the agent bind-mounts the SSM agent into the container and the task role needs Session Manager (ssmmessages) permissions. Points that matter for security:
- Commands run as
root, even if the container defines a non-root user. - It cannot be combined with
readonlyRootFilesystem, so enabling it weakens that control. - It must be enabled with
--enable-execute-commandwhen a task starts, so a policy that blocks the flag in production prevents it entirely (condition keyecs:enable-execute-command). - Session logging is configured on the cluster in
executeCommandConfiguration(NONE,DEFAULTorOVERRIDEto CloudWatch Logs or S3, optionally KMS encrypted). The image needsscriptandcatfor logging to work. - Sessions started directly with
ssm:StartSessionbypass ECS Exec logging; denyssm:StartSessionon task resources.
Integrations, API tokens and service accounts
Secrets injection options
| Method | How it works | Exposure | Use when |
|---|---|---|---|
Plaintext environment | Value written in the task definition | Visible to anyone who can describe task definitions; flagged by Security Hub ECS.8 | Never for secrets |
secrets with valueFrom | Execution role fetches from Secrets Manager or Parameter Store at start; injected as environment variable | Can appear in logs and docker inspect; not refreshed on rotation | Default for most services |
| Sidecar to shared volume | A sidecar reads the secret and writes it to a volume before the app starts | File on task storage, deleted when the task stops | High-sensitivity values |
| Application reads at runtime with task role | App calls Secrets Manager directly using its task role | Only in process memory; supports rotation without restart | Rotating database credentials |
- JSON keys and versions:
valueFromacceptssecret-name:json-key:version-stage:version-id, so inject only the field you need rather than a whole JSON document. - Rotation: after rotating an injected secret, force a new deployment of the service or replace standalone tasks. Build that into the rotation runbook. See the Cloud Secrets Managers Guide.
- Logging drivers and registries: use
secretOptionsinlogConfigurationfor log platform tokens andrepositoryCredentialsfor private registries rather than embedding them. - Images: never bake secrets into images. The Docker Hub secrets leak found 10,456 public images exposing secrets, and about three quarters of secrets removed from images were still active. See the Secrets in Container Images and Build Artefacts Guide.
Machine identities beyond AWS
Tasks often hold third-party API keys and database passwords as well as AWS roles. Give each a named owner, store it in Secrets Manager with a resource policy limited to the task's execution role or task role, and track it in your non-human identity inventory. Where the third party supports workload identity federation, prefer it over static keys. See the Cloud Workload Identity Guide.
Fargate versus the EC2 launch type
| Aspect | Fargate | EC2 launch type |
|---|---|---|
| Task isolation | Each task in its own isolated environment; no shared ENI, ephemeral storage, CPU or memory with other tasks | No task isolation; co-located tasks may reach each other's credentials and data |
| Instance role exposure | No instance profile available to containers | Reachable via instance metadata unless blocked |
| Privileged containers | Not available; only SYS_PTRACE can be added | Possible; must be blocked by policy (Security Hub ECS.4) |
| Host access | Neither customers nor AWS operators can connect to the host | You patch and secure the host OS |
| Patching | Platform versions; keep services on the latest (ECS.10) | AMI and agent updates are your responsibility |
On the EC2 launch type, block metadata access from tasks: set ECS_AWSVPC_BLOCK_IMDS=true in the agent configuration for awsvpc tasks, add an iptables rule in the DOCKER-USER chain dropping traffic to 169.254.169.254 for bridge tasks, and keep the instance role minimal. Avoid host network mode (ECS.17), which bypasses both. See the Cloud Instance Metadata Services Compared Guide.
Choose Fargate where workloads of different trust levels or tenants share a cluster, or where the team cannot run a host patching programme. Choose EC2 where you need GPUs, privileged daemons or host-level agents, and then separate trust levels onto different capacity providers or clusters.
Logging and detection
- CloudTrail control plane: alert on
RegisterTaskDefinitionintroducing a new task role,RunTaskoutside pipelines,UpdateServiceenabling execute command,ExecuteCommandin production and IAM changes to task roles. - Task role use: CloudTrail events from task credentials carry the task ARN; alert when a task role is used from an IP outside your VPC NAT ranges, which suggests stolen credentials. GuardDuty's credential exfiltration findings cover EC2 instance credentials; for task roles, build your own detection on source IP and VPC endpoint.
- ECS Exec sessions: ship session logs to a protected log group or bucket.
- Container logs: every task definition should have a log configuration (ECS.9); Container Insights (ECS.12) adds metrics.
- Runtime: GuardDuty Runtime Monitoring for Fargate attaches a managed sidecar agent at task launch, covering new tasks only. Clusters tagged
GuardDutyManaged:falseare excluded, so protect that tag with SCPs. See the Amazon GuardDuty Guide.
Metrics
- Percentage of task definitions with a dedicated task role and no wildcard actions.
- Number of principals with
iam:PassRoleon*and ECS run permissions (target: zero). - Number of task definitions with secrets in plaintext
environment(ECS.8 failures). - Percentage of production services with ECS Exec disabled.
- GuardDuty Runtime Monitoring coverage of running Fargate tasks.
Hardening baseline
- Inventory: list task definitions, their task and execution roles, policies attached, secrets referenced and whether execute command is enabled. Turn on Security Hub ECS controls to score them.
- Split and scope roles: create per-service task roles with confused deputy conditions, slim execution roles to named secret and key ARNs, and remove any task that uses the execution role as its task role.
- Fix PassRole: restrict PassRole to named roles with
iam:PassedToService, and move production deployments to pipelines. - Move secrets: replace plaintext environment values with
secretsreferences, and use runtime retrieval for credentials that rotate. Test in a staging service, then roll forward with a new deployment; roll back by redeploying the previous task definition revision. - Harden containers: non-root users (ECS.20), read-only root filesystem where ECS Exec is not needed (ECS.5), no privileged mode, no host network or PID namespace (ECS.3), no automatic public IPs (ECS.2).
- Lock down Exec: deny execute command in production by SCP or IAM, enable session logging in non-production, and deny direct
ssm:StartSessionon tasks. - Monitor: Runtime Monitoring, CloudTrail alerts above and Container Insights.
Small teams with a handful of services usually get most of the benefit from steps 2 to 4 and from running everything on Fargate. Large organisations with many teams sharing clusters should enforce the same controls centrally: SCPs that deny execute command and privileged task definitions in production accounts, a pipeline that rejects task definitions failing Security Hub ECS controls, and a role-naming convention that makes task role ownership obvious in CloudTrail.
Common misconfigurations
- One role for both jobs: the execution role doubles as the task role, so application code can read every secret the platform fetches for every service using it.
- AdministratorAccess on a task role: added "temporarily" to unblock a deployment and never removed.
- Secrets in plaintext environment: visible to anyone with
ecs:DescribeTaskDefinition, and retained in every revision. - Rotation without redeploy: the secret is rotated, running tasks keep the old value, and either the app breaks or the old credential is never actually retired.
- Instance metadata reachable from tasks on EC2: a server-side request forgery bug yields the container instance role.
- ECS Exec open in production: anyone with the API permission gets a root shell next to production data.
Framework mapping
| Framework | Reference | ECS control |
|---|---|---|
| NIST SP 800-53 Rev. 5 | AC-6 | Per-service task roles, scoped execution roles, PassRole limits |
| NIST SP 800-53 Rev. 5 | IA-5 | Secrets Manager references, rotation with redeploy |
| NIST SP 800-53 Rev. 5 | CM-6, CM-7 | Non-root, read-only root filesystem, no privileged or host mode |
| NIST SP 800-53 Rev. 5 | AU-2, SI-4 | CloudTrail, Exec session logs, Runtime Monitoring |
| NIST CSF 2.0 | PR.AA-05, PR.PS-01, DE.CM-09 | Least privilege, configuration management, monitoring |
| MITRE ATT&CK | T1552.005, T1610, T1078.004 | Cloud instance metadata API, deploy container, cloud accounts |
Practitioner checklist
- Keep task roles and task execution roles separate for every task definition.
- Add
aws:SourceAccountandaws:SourceArnconditions to task role trust policies. - Restrict
iam:PassRoleto named roles withiam:PassedToService. - Scope execution role secret access to specific secret, parameter and key ARNs.
- Remove secrets from plaintext environment variables and baked images.
- Redeploy services after every secret rotation, or read secrets at runtime.
- Block instance metadata from tasks on the EC2 launch type.
- Disable ECS Exec in production and log it everywhere else.
- Enable GuardDuty Runtime Monitoring and protect the GuardDutyManaged tag.
- Track Security Hub ECS controls across all accounts.
Standards and references
- AWS: Amazon ECS task IAM role
- AWS: Amazon ECS task execution IAM role
- AWS: Pass sensitive data to an Amazon ECS container
- AWS: Monitor Amazon ECS containers with ECS Exec
- AWS: Fargate security considerations
- AWS: Amazon ECS IAM best practices
- AWS: Security Hub controls for Amazon ECS
- AWS: How Runtime Monitoring works with Fargate (Amazon ECS only)
Related NHI Mgmt Group resources: Container Security Guide · EKS Identity Hardening Guide · AWS STS and AssumeRole Guide · Container Runtime Security Guide · AWS Security Guide