fix(ecs): vend task-role sessions from the ECS credentials endpoint

Merged
VI
18d ago
+1709-184

Summary

Closes #2601.

  • GET /_fakecloud/ecs/creds/{task_id} (new fakecloud-server ecs_creds module) returns an STS session for the task's taskRoleArn, session name = task id (assumed-role/<role>/<task-id>), minted via the same mint_session_credentials path as Lambda (#2562), so it verifies under --verify-sigv4 and 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 taskRoleArn resolves in the task's account. RunTask overrides.taskRoleArn / executionRoleArn now actually apply (they were validated but ignored).
  • fakecloud_ecs::validate_task_role shared by RegisterTaskDefinition, RunTask overrides and CFN AWS::ECS::TaskDefinition: trust policy must name ecs-tasks.amazonaws.com (looked up in the role's owning account); cross-account role refused under --iam strict, logged under soft; error is ECS's ClientException ("ECS was unable to assume the role '...'").
  • --imds-link-local's 169.254.170.2 listener 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.rs and iam_pass_role.rs for the new behavior; ecs, ecs_enforce, cloudformation_ecs, cloudformation_ecs_backing, ecs_metadata_endpoint, instance_credentials, imds e2e 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 InvalidIdInRequest response, and the fallback to a made-up default role is gone.
  • RunTask overrides.taskRoleArn/executionRoleArn now apply, and bare role names resolve in the task's account.
  • RegisterTaskDefinition, RunTask overrides, and CloudFormation AWS::ECS::TaskDefinition share role validation: the trust policy must name ecs-tasks.amazonaws.com, cross-account roles are refused under --iam strict, both with ECS's ClientException.
  • --imds-link-local's 169.254.170.2 listener 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_URI host (host.docker.internal or 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.