How the two-role IAM model keeps a leaked deployer key from being root-equivalent.
dispatch uses a two-role model so the deployer's long-lived access key is never root-equivalent if it leaks:
dispatch-deployer— the human identity. Its attached policy (dispatch-deploy-policy.json) carries narrow powers: manage the CloudFormation stack, write/read SSM secrets, shell in, scale the service, debug the container instance, recover orphans during teardown, and manage one literal role (dispatch-cfn-exec).dispatch-cfn-exec— the CloudFormation service role. Its inline policy (dispatch-cfn-exec-policy.json, trustdispatch-cfn-exec-trust.json) carries the broad infrastructure-create lifecycle (EC2/ASG/ECS/IAM/KMS/logs) that CloudFormation assumes during every deploy and the stack delete.
The deployer identity only ever passes dispatch-cfn-exec to CloudFormation
(iam:PassRole conditioned to cloudformation.amazonaws.com); it never
touches infrastructure resources directly.
Because dispatch-cfn-exec is assumable only by
cloudformation.amazonaws.com (its trust policy) and the deployer's only
iam:PassRole for it is conditioned to that same service, none of the broad
powers are reachable by the human-held key — closing the privilege-escalation
chains a leaked deployer key otherwise opens.
The stack cannot create the role it assumes to create itself. It is therefore created out-of-band in setup Phase 0 and deleted last in teardown (after the stack is gone).
Resources whose ARNs use AWS-assigned IDs cannot be pinned by ARN prefix. The
policy pins what it can and uses tag conditions (ABAC) or read-only
Resource: "*" where AWS forces it:
| Statement type | Examples |
|---|---|
| ARN-pinned | CloudFormation (stack/dispatch/*), ManageCfnExecRole (role/dispatch-cfn-exec), PassRoleToCfn, ECSExec (cluster/dispatch, task/dispatch/*), ECSServiceManage, LogsRead, SSMSecrets (parameter/dispatch/*) |
| Tag-conditioned (ABAC) | EC2NetworkingManage (Name = dispatch*), EC2InstanceOps (ClawName = dispatch, ARN-scoped to instance/*), EC2DataVolumeManage (Name = dispatch-data), KMSUseKey (ResourceAliases = alias/dispatch-ssm) |
Read-only * (AWS-forced) |
ReadOnlyDescribe, ECSRead, SSMMessages, CloudFormationGlobalMeta |
Two deliberate omissions:
- No
iam:SimulatePrincipalPolicy— a leaked key should not be able to probe its own scope. Permission gaps surface as the exactis not authorized to performerror at deploy time. - No
sts:DecodeAuthorizationMessage— the deployer key deliberately cannot decode encoded denial messages; use a separate admin identity.
Shell-in (ecs:ExecuteCommand) permissions are on the deployer, scoped to the
agent's cluster and tasks. AWS additionally recommends denying
ssm:StartSession on ECS tasks (DenyDirectSSMSession): sessions via
ecs:ExecuteCommand are logged; direct SSM sessions bypass ECS Exec logging
and consume the session quota.
Secrets are SSM SecureStrings under /dispatch/, encrypted with the stack's
own KMS key. The deployer policy pins kms:Decrypt/kms:Encrypt to
alias/dispatch-ssm via kms:ResourceAliases, so the deployer can only
decrypt parameters this key encrypted. See
Concepts: secrets.