fix(ecs): vend task-role sessions from the ECS credentials endpoint
Summary
Closes #2601.
GET /_fakecloud/ecs/creds/{task_id}(newfakecloud-serverecs_credsmodule) returns an STS session for the task'staskRoleArn, session name = task id (assumed-role/<role>/<task-id>), minted via the samemint_session_credentialspath as Lambda (#2562), so it verifies under--verify-sigv4and acts as the role under--iam.- Cached per task (new
WorkloadCredentialCache, shared rotation logic) and re-minted near expiry; revoked when the task stops (1s sweep, task lookup held through the mint so a stopping task can't get a fresh session); sessions left over from before a restart are revoked at startup. - Stopped / unknown / role-less task -> the ECS agent's exact response: HTTP 400
{"code":"InvalidIdInRequest","message":"CredentialsV2Request: Credentials not found","HTTPErrorCode":400}; empty id ->NoIdInRequest. The fallback to a made-up default role is gone. - Bare role name as
taskRoleArnresolves in the task's account.RunTaskoverrides.taskRoleArn/executionRoleArnnow actually apply (they were validated but ignored). fakecloud_ecs::validate_task_roleshared by RegisterTaskDefinition, RunTask overrides and CFNAWS::ECS::TaskDefinition: trust policy must nameecs-tasks.amazonaws.com(looked up in the role's owning account); cross-account role refused under--iam strict, logged under soft; error is ECS'sClientException("ECS was unable to assume the role '...'").--imds-link-local's169.254.170.2listener also serves the agent path/v2/credentials/<task-id>.
Known pre-existing limitation (documented, not changed here): the injected AWS_CONTAINER_CREDENTIALS_FULL_URI host is host.docker.internal (or the in-cluster URL), which AWS SDKs reject for plain HTTP (only loopback / 169.254.170.2 allowed). HTTP clients get the credentials; SDK default chains inside the container don't. docs/services/ecs.md no longer claims it works out of the box.
Surfaces: docs/services/ecs.md, docs/reference/introspection.md, docs/guides/instance-credentials.md, docs/reference/configuration.md, --imds-link-local help text, AGENTS.md service notes, doc comments in all seven SDKs (no code change).
Test plan
- Unit: ECS / IAM / server / CloudFormation crates (129/526/133/565) incl. 11 server tests for the endpoint and link-local route.
- New e2e
ecs_task_role_credentials.rs(3): running task under--verify-sigv4+--iam strict->GetCallerIdentity=assumed-role/app-task-role/<task-id>, allowed SQS call works, disallowed denied, container fetched creds itself (wget), after StopTask endpoint 400 and old keys stop authenticating; role-less and unknown task -> agent error; RegisterTaskDefinition rejects untrusted and cross-account roles. - Updated
ecs_task_role_secrets.rsandiam_pass_role.rsfor the new behavior;ecs,ecs_enforce,cloudformation_ecs,cloudformation_ecs_backing,ecs_metadata_endpoint,instance_credentials,imdse2e pass. - fmt + workspace clippy clean; local /code-review medium x5, last clean (fixed: sweep race, cross-account trust lookup, SDK docs, post-restart sessions, stop/mint race, bare role account).
Summary by cubic
GET /_fakecloud/ecs/creds/{task_id} now vends a real STS session for the task's taskRoleArn (session name = task ID) instead of static credentials, minted and registered like an AssumeRole session so it verifies under --verify-sigv4 and acts as the role under --iam. Closes #2601.
New Features
- Sessions are cached per task, rotated near expiry, and revoked once the task stops; stopped, unknown, or role-less tasks get the ECS agent's exact HTTP 400
InvalidIdInRequestresponse, and the fallback to a made-up default role is gone. RunTaskoverrides.taskRoleArn/executionRoleArnnow apply, and bare role names resolve in the task's account.- RegisterTaskDefinition, RunTask overrides, and CloudFormation
AWS::ECS::TaskDefinitionshare role validation: the trust policy must nameecs-tasks.amazonaws.com, cross-account roles are refused under--iam strict, both with ECS'sClientException. --imds-link-local's169.254.170.2listener also serves the agent path/v2/credentials/<task-id>.- Sessions left registered by a previous restart are revoked at startup.
Migration
- The injected
AWS_CONTAINER_CREDENTIALS_FULL_URIhost (host.docker.internalor the in-cluster URL) is still rejected by AWS SDKs for plain HTTP; HTTP clients get credentials, SDK default chains in containers don't, and the docs no longer claim it works out of the box.