Architecture Overview
Xpander self-hosted uses a three-tier IAM role architecture for connector authentication:Why Three Tiers?
- Least privilege — Each connector only has access to its specific service. A Redshift connector can’t access EKS and vice versa.
- Auditability — CloudTrail shows exactly which connector role accessed which resource. No shared credentials.
- Independent lifecycle — Add or remove connectors without touching other roles. Revoke one connector’s access without affecting others.
- Role chaining — The AI Gateway assumes the appropriate connector role per request. This is standard AWS role chaining, fully supported by xpander.
If you’re using a single IAM role for all connectors (the setup from AWS Operator Setup), the instructions on this page show how to split that into per-connector roles for better isolation and auditability.
Step 1: Base Role
The base role is the only role attached to pods. It has no service permissions — it can only assume other roles.Step 2: Connector Roles
Each connector role needs a trust policy with four statements:Trust Policy Template
Permission Policies Per Connector
Each connector role needs its own service permissions plus self-assume.Self-Assume + Session Tagging (required for all connector roles)
Redshift Connector
Redshift Connector
EKS Connector
EKS Connector
Step 3: Configure in xpander UI
In the connector settings, set the IAM Role ARN to the connector-specific role (not the base role):Adding a New Connector
When adding a new AWS connector (e.g., S3, DynamoDB, Athena):1
Create the connector role
Use the trust policy template above.
2
Attach service-specific permissions
Add the service permissions + the self-assume policy.
3
Update the base role
Add the new role ARN to the
AssumeConnectorRoles policy on the base role.4
Configure in xpander UI
Set the connector’s IAM Role ARN.
5
Restart the AI Gateway
Role Creation Order
IAM roles can’t reference themselves during creation. Follow this order:- Create the role with only
PodIdentity+CrossAccountWithExternalIdtrust statements - Wait 10 seconds for IAM propagation
- Update the trust policy to add
SelfAssume+AllowFromBaseRolestatements
Troubleshooting
AccessDenied: sts:AssumeRole (self-assume)
AccessDenied: sts:AssumeRole (self-assume)
Cause: The
SelfAssume trust statement has an ExternalId condition, or the self-assume permission policy is missing.Fix: The SelfAssume and AllowFromBaseRole statements must NOT have conditions. The AI Gateway does not pass an ExternalId during internal role operations.AccessDenied: sts:AssumeRole (from base role)
AccessDenied: sts:AssumeRole (from base role)
Cause: Base role doesn’t have permission to assume the connector role, or the connector role’s trust policy doesn’t list the base role.Fix: Check both sides:
- Base role permission policy lists the connector role ARN
- Connector role trust policy has
AllowFromBaseRolestatement
Connector works in cloud but not self-hosted
Connector works in cloud but not self-hosted
Cause: In cloud mode, the xpander platform assumes the role directly with the ExternalId. In self-hosted mode, the AI Gateway chains through the base role without an ExternalId.Fix: Add the
AllowFromBaseRole trust statement to the connector role.Security Recommendations
- Scope resources — Use specific ARNs in permission policies, not
"Resource": "*"where possible - Separate roles per data source — Don’t combine Redshift + EKS permissions in one role
- Rotate ExternalId — The organization ID is used as ExternalId. If compromised, rotate it in the xpander platform
- Monitor with CloudTrail — Each connector role appears separately in CloudTrail, making it easy to audit which connector accessed which resource
- Tag roles — Tag all roles with
Environment,Project,ManagedByfor governance - Restrict base role — The base role should ONLY have
sts:AssumeRole+sts:TagSession. Never attach service permissions directly to it

