Skip to main content
Connect Amazon Redshift to xpander using IAM authentication. This guide covers connecting a private Redshift cluster accessible only from the Kubernetes cluster — no public access required.
For self-hosted deployments, ensure the AWS Operator is configured with IAM role and Pod Identity before following this guide.

Prerequisites

  • A running xpander environment (cloud or self-hosted)
  • Your xpander Organization ID
  • AWS CLI access to the account

1. Create Redshift Subnet Group

Place Redshift in the same private subnets as your EKS nodes:
aws redshift create-cluster-subnet-group \
  --cluster-subnet-group-name xpander-redshift \
  --description "xpander Redshift private subnets" \
  --subnet-ids <PRIVATE_SUBNET_A_ID> <PRIVATE_SUBNET_B_ID> \
  --region <REGION> --profile <PROFILE>

2. Create Security Group

Lock access to Redshift port 5439 — only allow traffic from the EKS cluster security group:
# Get the EKS cluster security group
EKS_SG=$(aws eks describe-cluster --name <CLUSTER_NAME> \
  --region <REGION> --profile <PROFILE> \
  --query 'cluster.resourcesVpcConfig.clusterSecurityGroupId' --output text)

# Create Redshift security group
REDSHIFT_SG=$(aws ec2 create-security-group \
  --group-name xpander-redshift-sg \
  --description "Redshift SG - EKS cluster access only" \
  --vpc-id <VPC_ID> \
  --region <REGION> --profile <PROFILE> \
  --query 'GroupId' --output text)

# Allow port 5439 only from EKS
aws ec2 authorize-security-group-ingress \
  --group-id $REDSHIFT_SG \
  --protocol tcp --port 5439 \
  --source-group $EKS_SG \
  --region <REGION> --profile <PROFILE>

3. Create Redshift Cluster

aws redshift create-cluster \
  --cluster-identifier xpander-redshift \
  --cluster-type single-node \
  --node-type ra3.xlplus \
  --master-username xpander_admin \
  --master-user-password '<STRONG_PASSWORD>' \
  --db-name xpander_demo \
  --cluster-subnet-group-name xpander-redshift \
  --vpc-security-group-ids $REDSHIFT_SG \
  --no-publicly-accessible \
  --encrypted \
  --region <REGION> --profile <PROFILE>
Wait for the cluster (~5-10 minutes):
aws redshift wait cluster-available \
  --cluster-identifier xpander-redshift \
  --region <REGION> --profile <PROFILE>
Node types: dc2.large (0.250.33/hr)ischeapestbutnotavailableinallregions.Usera3.xlplus(0.25-0.33/hr) is cheapest but not available in all regions. Use `ra3.xlplus` (1.08-1.20/hr) as fallback. Check availability:
aws redshift describe-orderable-cluster-options \
  --region <REGION> --profile <PROFILE> \
  --query 'OrderableClusterOptions[*].NodeType' --output json | \
  python3 -c "import json,sys; print(sorted(set(json.load(sys.stdin))))"
Get the endpoint:
aws redshift describe-clusters \
  --cluster-identifier xpander-redshift \
  --region <REGION> --profile <PROFILE> \
  --query 'Clusters[0].Endpoint.{Address:Address,Port:Port}' --output table

4. Create IAM Role

The IAM role needs three trust principals:
PrincipalPurpose
pods.eks.amazonaws.comEKS Pod Identity — lets xpander pods assume the role
arn:aws:iam::<ACCOUNT_ID>:rootCross-account assume with External ID for the xpander platform
arn:aws:iam::<ACCOUNT_ID>:role/xpander-redshift-accessSelf-assume — the AI gateway re-assumes its own role to get Redshift credentials
cat <<TRUST > /tmp/redshift-trust.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PodIdentity",
      "Effect": "Allow",
      "Principal": { "Service": "pods.eks.amazonaws.com" },
      "Action": ["sts:AssumeRole", "sts:TagSession"]
    },
    {
      "Sid": "CrossAccountWithExternalId",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::<ACCOUNT_ID>:root"
      },
      "Action": ["sts:AssumeRole", "sts:TagSession"],
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "<ORGANIZATION_ID>"
        }
      }
    },
    {
      "Sid": "SelfAssume",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::<ACCOUNT_ID>:role/xpander-redshift-access"
      },
      "Action": ["sts:AssumeRole", "sts:TagSession"]
    }
  ]
}
TRUST

aws iam create-role \
  --role-name xpander-redshift-access \
  --assume-role-policy-document file:///tmp/redshift-trust.json \
  --profile <PROFILE>
The SelfAssume statement must NOT have an ExternalId condition. The AI gateway’s internal code calls sts:AssumeRole on its own role without passing an external ID. If this statement is missing or has a condition, you’ll get AccessDenied errors.

5. Attach Permission Policies

The role needs three permission policies.

Redshift Credential Access

aws iam put-role-policy \
  --role-name xpander-redshift-access \
  --policy-name RedshiftCredentials \
  --policy-document '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Action": [
        "redshift:GetClusterCredentials",
        "redshift:GetClusterCredentialsWithIAM",
        "redshift:DescribeClusters"
      ],
      "Resource": [
        "arn:aws:redshift:<REGION>:<ACCOUNT_ID>:cluster:<REDSHIFT_CLUSTER_ID>",
        "arn:aws:redshift:<REGION>:<ACCOUNT_ID>:dbname:<REDSHIFT_CLUSTER_ID>/<DB_NAME>",
        "arn:aws:redshift:<REGION>:<ACCOUNT_ID>:dbuser:<REDSHIFT_CLUSTER_ID>/*"
      ]
    }]
  }' \
  --profile <PROFILE>

Redshift Data API

aws iam put-role-policy \
  --role-name xpander-redshift-access \
  --policy-name RedshiftDataAPI \
  --policy-document '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Action": [
        "redshift-data:ExecuteStatement",
        "redshift-data:GetStatementResult",
        "redshift-data:DescribeStatement",
        "redshift-data:ListStatements",
        "redshift-data:CancelStatement",
        "redshift-data:BatchExecuteStatement",
        "redshift-data:ListDatabases",
        "redshift-data:ListSchemas",
        "redshift-data:ListTables",
        "redshift-data:DescribeTable"
      ],
      "Resource": "*"
    }]
  }' \
  --profile <PROFILE>

Self-Assume and Session Tagging

aws iam put-role-policy \
  --role-name xpander-redshift-access \
  --policy-name SelfAssumeAndTagSession \
  --policy-document '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Action": ["sts:AssumeRole", "sts:TagSession"],
      "Resource": "arn:aws:iam::<ACCOUNT_ID>:role/xpander-redshift-access"
    }]
  }' \
  --profile <PROFILE>

6. Create IAM-Mapped Redshift User

The username IAMR:<role-name> is a Redshift convention that maps the IAM role to a database user.
kubectl run redshift-iam-setup --restart=Never --image=postgres:15-alpine -n xpander \
  --env="PGPASSWORD=<MASTER_PASSWORD>" \
  --command -- psql -h <REDSHIFT_ENDPOINT> -p 5439 -U <MASTER_USER> -d <DB_NAME> -c "
    CREATE USER \"IAMR:xpander-redshift-access\" PASSWORD DISABLE;
    GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"IAMR:xpander-redshift-access\";
    ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO \"IAMR:xpander-redshift-access\";"

sleep 15 && kubectl logs redshift-iam-setup -n xpander
kubectl delete pod redshift-iam-setup -n xpander

7. Associate Role with Service Account

aws eks create-pod-identity-association \
  --cluster-name <CLUSTER_NAME> \
  --namespace xpander \
  --service-account xpander \
  --role-arn arn:aws:iam::<ACCOUNT_ID>:role/xpander-redshift-access \
  --region <REGION> --profile <PROFILE>

8. Configure in xpander UI

In the xpander connector configuration, set:
FieldValue
ClusterNamePathYour Redshift cluster identifier (e.g., xpander-redshift)
DatabaseNamePathYour database name (e.g., xpander_demo)
CatalogNamePathpg_catalog.svv_all_columns (recommended)
IAM Role ARNarn:aws:iam::<ACCOUNT_ID>:role/xpander-redshift-access
RegionYour AWS region (e.g., us-west-1)
Auth MethodIAM

9. Verify Connection

Ask your xpander agent to validate the Redshift connection. Expected result: all checks pass (path config, database, target, AWS credentials, Data API client, auth method, query execution).

Troubleshooting

Add sts:TagSession to both the trust policy AND as a permission policy on the role.
The AI gateway re-assumes its own role internally. The trust policy needs a SelfAssume statement for the role’s own ARN without an ExternalId condition. Also add sts:AssumeRole as a permission policy.
Add the Redshift Data API permissions policy (see step 5).
Check the Redshift security group allows port 5439 from the EKS cluster security group.
The connector path parameters (ClusterNamePath, DatabaseNamePath, CatalogNamePath) must be configured in the xpander UI with the correct values.