Skip to main content
This page covers skill access to AWS services (Redshift, Athena, S3, EKS). It does not cover model access: Amazon Bedrock is called with the pods’ own credentials, so bedrock:InvokeModel* and bedrock:Converse* belong on the role bound directly to the pods, not on a skill role behind the base role’s sts:AssumeRole chain. See Amazon Bedrock without a key and, for Hybrid, Amazon Bedrock: keyless IAM or API key.

Architecture Overview

xpander self-hosted uses a three-tier IAM role architecture for skill authentication:

Why Three Tiers?

  1. Least privilege - Each skill only has access to its specific service. The Redshift skill can’t access EKS and vice versa.
  2. Auditability - CloudTrail shows exactly which skill role accessed which resource. No shared credentials.
  3. Independent lifecycle - Add or remove skills without touching other roles. Revoke one skill’s access without affecting others.
  4. Role chaining - The agent-controller’s skill relay (agent-controller) assumes the appropriate skill role per request. This is standard AWS role chaining, fully supported by xpander.
If you’re using a single IAM role for all skills (the setup from AWS Operator Setup), the instructions on this page show how to split that into per-skill 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.
Attach the assume permissions to the xpander-base-role role, as an inline policy named AssumeConnectorRoles (update the Resource list as you add skills):
Associate with the xpander service account via Pod Identity:

Step 2: Skill Roles

Each skill role needs a trust policy with four statements:

Trust Policy Template

The SelfAssume and AllowFromBaseRole statements must not have an ExternalId condition. The skill relay’s internal AssumeRole calls do not pass an external ID. If you add an ExternalId condition to these statements, the skill will fail with AccessDenied.

Permission Policies Per Skill

Each skill role needs its own service permissions plus self-assume.

Self-Assume + Session Tagging (required for all skill roles)

The EKS skill also needs Kubernetes RBAC:

Step 3: Configure in xpander UI

In the skill’s connection settings, set the IAM Role ARN to the skill-specific role (not the base role):

Adding a New Skill

When adding a new AWS skill (e.g., S3, DynamoDB, Athena):
1

Create the skill 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 skill’s IAM Role ARN.
5

Restart the skill relay

No Pod Identity changes needed - the base role association stays the same.

Role Creation Order

IAM roles can’t reference themselves during creation. Follow this order:
  1. Create the role with only PodIdentity + CrossAccountWithExternalId trust statements
  2. Wait 10 seconds for IAM propagation
  3. Update the trust policy to add SelfAssume + AllowFromBaseRole statements

Troubleshooting

Cause: Missing sts:TagSession in either the trust policy or the permission policy.Fix: Ensure both:
  • Trust policy allows sts:TagSession from the caller
  • Permission policy includes sts:TagSession on the role’s own ARN
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 skill relay does not pass an ExternalId during internal role operations.
Cause: Base role doesn’t have permission to assume the skill role, or the skill role’s trust policy doesn’t list the base role.Fix: Check both sides:
  • Base role permission policy lists the skill role ARN
  • Skill role trust policy has AllowFromBaseRole statement
Cause: In cloud mode, xpander assumes the role directly with the ExternalId. In self-hosted mode, the skill relay chains through the base role without an ExternalId.Fix: Add the AllowFromBaseRole trust statement to the skill role.

Security Recommendations

  1. Scope resources - Use specific ARNs in each skill role’s permission policy, not "Resource": "*" where possible
  2. Separate roles per data source - Don’t combine Redshift + EKS permissions in one role
  3. Rotate ExternalId - The organization ID is used as ExternalId. If it is compromised, contact xpander to have it rotated
  4. Monitor with CloudTrail - Each skill role appears separately in CloudTrail, making it easy to audit which skill accessed which resource
  5. Tag roles - Tag all roles with Environment, Project, ManagedBy for governance
  6. Restrict base role - The base role should ONLY have sts:AssumeRole + sts:TagSession. Never attach service permissions directly to it