This guide walks you through connecting an AWS account using cross-account IAM role with external ID — the AWS-standard pattern for letting a SaaS act on your behalf without exposing long-lived access keys.
AssumeRoleWithWebIdentity against
the orchestrator’s OIDC issuer is on the roadmap (parallels the
GCP WIF setup). When that ships, this guide will gain
a Step 0 covering the IAM identity provider creation. For now,
cross-account role with external ID is the supported pattern.What this does
After setup, the orchestrator can deploy resources into your AWS
account by calling sts:AssumeRole against a role you create.
Authentication flow:
- Orchestrator calls
sts:AssumeRolewith your role ARN + a secret external ID (shown only to you) - AWS STS validates: the orchestrator’s IAM principal is allowed to assume the role, AND the supplied external ID matches the role’s trust policy condition
- AWS returns temporary credentials (15-min to 1-hour TTL)
- The orchestrator uses those credentials to call AWS APIs for your deployment
The orchestrator never stores AWS access keys. The external ID is how AWS prevents confused-deputy attacks — even if another customer of the orchestrator knew your role ARN, without your external ID they can’t assume the role.
Prerequisites
- An AWS account with IAM administrative permissions (to create roles and trust policies)
awsCLI installed and authenticated to that account- 5-10 minutes for the first account setup
Step 1 — Get the orchestrator’s IAM principal + your external ID
In the orchestrator UI: /cloud-accounts → Connect cloud → AWS.
The form shows two read-only values you’ll need:
- Orchestrator IAM principal: the AWS ARN of the role that
will assume into your account (e.g.,
arn:aws:iam::123456789012:role/orchestrator-runtime) - Your unique external ID: a UUID generated specifically for
your org (e.g.,
ext-7a3e9d2c-1f4b-4a8c-9e1d-3b7f8c2a5d6e)
Copy both — you’ll paste them into the role’s trust policy.
Step 2 — Create the IAM role in your AWS account
Save this trust policy to a file (substitute your real external ID and the orchestrator’s actual principal ARN from Step 1):
cat > trust-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/orchestrator-runtime"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "ext-7a3e9d2c-1f4b-4a8c-9e1d-3b7f8c2a5d6e"
}
}
}
]
}
EOFCreate the role:
aws iam create-role \
--role-name OrchestratorDeployer \
--assume-role-policy-document file://trust-policy.json \
--description "AI Orchestrator cross-account access"Note the role’s ARN from the output:
arn:aws:iam::987654321098:role/OrchestratorDeployerYou’ll paste this into the orchestrator’s connect form.
Step 3 — Grant the role permissions for your deployments
Start narrow — give the role only what your initial deployments need. You can expand later:
# Common minimal set for cloud-resource-orchestration use cases
for policy in \
arn:aws:iam::aws:policy/AmazonRDSFullAccess \
arn:aws:iam::aws:policy/AmazonEC2FullAccess \
arn:aws:iam::aws:policy/AmazonS3FullAccess \
arn:aws:iam::aws:policy/AmazonEKSClusterPolicy; do
aws iam attach-role-policy \
--role-name OrchestratorDeployer \
--policy-arn "$policy"
doneAmazonRDSFullAccess,
etc.) are convenient but over-broad. For production, write a
custom policy that grants only the specific actions on the
specific resources the orchestrator’s agents will provision.For application deployments (as opposed to databases), the agents additionally use: ECS/Fargate, ECR, CodeBuild (zip-source deploys build the container image inside your account), Elastic Load Balancing, Auto Scaling, Lambda, and CloudWatch Logs — grant the matching policies for the topologies you’ll actually deploy.
Step 4 — Paste the values into the orchestrator UI
Back in /cloud-accounts → Connect cloud → AWS, fill in:
| Field | Value |
|---|---|
| Account label | Free-text, e.g. prod-aws |
| AWS account ID | Your 12-digit account ID (e.g. 987654321098) |
| Role ARN | From Step 2 output (arn:aws:iam::987654321098:role/OrchestratorDeployer) |
| External ID | The one shown in Step 1 (already filled in for you) |
| Default region | The AWS region your deployments target (e.g. us-east-1) |
Click Connect. The orchestrator runs the probe:
- Calls
sts:AssumeRolewith your role ARN + external ID - On success: reads a small bit of account metadata (e.g.,
sts:GetCallerIdentity) as the smoke test - Marks the cloud account
connected(green badge)
Verify
After the green badge appears, you can sanity-check from the command line:
# As an AWS admin in the customer account:
aws iam get-role --role-name OrchestratorDeployer \
--query 'Role.AssumeRolePolicyDocument'Output should show your external ID in the Condition.StringEquals.sts:ExternalId.
Troubleshooting
”AccessDenied — User is not authorized to perform sts:AssumeRole”
The orchestrator’s principal isn’t allowed by your role’s trust
policy. Check Step 2 — make sure the Principal.AWS in trust-policy.json
matches the exact ARN shown in the orchestrator UI’s “Orchestrator
IAM principal” field.
”AccessDenied — Wrong external ID”
The external ID in the form doesn’t match the one in your role’s trust policy. The form’s external ID is generated per-org and is immutable. Re-create the role with the correct external ID from Step 1.
”AccessDenied — once role is assumed, specific action denied”
The role was assumed successfully but lacks permissions for a
specific AWS action (e.g., rds:CreateDBInstance). Attach the
relevant managed policy or write a custom one with the missing
action.
”Role does not exist”
Typo in the role ARN, or the role wasn’t created in the AWS account matching the AWS account ID field. Verify with:
aws iam get-role --role-name OrchestratorDeployerIf “NoSuchEntity”: re-run Step 2.
Connect succeeds but deployments hit quota limits
New AWS accounts commonly start with 5 VPCs and 5 Elastic IPs per region and low On-Demand/Fargate vCPU quotas. The platform checks headroom at preflight (fast failure with the limit named), but raising the limits is yours:
aws service-quotas request-service-quota-increase \
--service-code vpc --quota-code L-F678F1CE --desired-value 15 # VPCs/regionFull deployment-time failure patterns — including which ones the platform self-heals — are on Troubleshooting AWS deployments.
Connect succeeds but deployments fail with API-not-enabled errors
Some AWS services require explicit opt-in per region (e.g., some ML services, some regions for newer services). Enable them in the AWS Console under the relevant service.
Removing an AWS connection
# 1. Remove the cloud-account row from the orchestrator UI
# (Cloud Accounts → click the account → Remove)
# 2. Delete the IAM role in your AWS account
aws iam list-attached-role-policies --role-name OrchestratorDeployer \
--query 'AttachedPolicies[].PolicyArn' --output text | \
xargs -n1 aws iam detach-role-policy --role-name OrchestratorDeployer --policy-arn
aws iam delete-role --role-name OrchestratorDeployerAfter this, even if someone had your external ID, the role doesn’t exist — no path back into your account.
How this works under the hood
┌─────────────────────────┐
│ Orchestrator backend │
│ (in our AWS account) │
│ runtime principal: │
│ arn:aws:iam::123... │
└───────────┬─────────────┘
│ sts:AssumeRole(roleArn=987...:role/OrchestratorDeployer,
│ externalId="ext-7a3e9...")
▼
┌─────────────────────────┐
│ AWS STS │
│ - validates principal │
│ - validates ext ID │
│ - returns 1h creds │
└───────────┬─────────────┘
│ AccessKey + SecretKey + SessionToken (15min-1h TTL)
▼
┌─────────────────────────┐
│ Orchestrator uses creds │
│ to call AWS APIs: │
│ rds.create_db_instance│
│ ec2.run_instances │
│ ... │
└─────────────────────────┘The orchestrator’s runtime IAM principal lives in our AWS account
(separate from yours). Your role’s trust policy says “this specific
principal in their account can assume my role, but only if they
present the right external ID”. The external ID is the
defense-in-depth: it prevents another customer (whose role might
share the same Principal block) from accidentally being able to
assume yours due to the confused deputy
problem.
Comparison with GCP / Azure
| AWS | GCP | Azure | |
|---|---|---|---|
| Mechanism | Cross-account IAM role + external ID | Workload Identity Federation | Service Principal w/ federated credentials |
| Credential lifetime | 15min-1h (STS-issued) | 1h (impersonation token) | 1h (federated assertion) |
| Customer-side primitive | IAM role w/ trust policy | WIF Pool + Provider + binding | App registration + federated cred |
| Orchestrator stores | Role ARN + external ID | Provider resource path | App ID + tenant + cert thumbprint |
All three converge on short-lived federated credentials with no long-lived secrets at the orchestrator. The customer-side setup differs because each cloud has its own primitives, but the security posture is equivalent.
See also
- Connect GCP — parallel guide for GCP
- Connect Azure — parallel guide for Azure
- Concepts — Security model — why federated credentials matter
- Troubleshooting AWS deployments — post-connect failure patterns