> ## Documentation Index
> Fetch the complete documentation index at: https://docs.xpander.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Amazon Redshift

> Connect Amazon Redshift to xpander using IAM authentication

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.

<Note>
  For self-hosted deployments, ensure the [AWS Operator](/self-hosted/aws-operator) is configured with IAM role and Pod Identity before following this guide.
</Note>

## Prerequisites

* A running xpander environment (cloud or [self-hosted](/self-hosted/deployment))
* 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:

```bash theme={"dark"}
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:

```bash theme={"dark"}
# 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

```bash theme={"dark"}
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):

```bash theme={"dark"}
aws redshift wait cluster-available \
  --cluster-identifier xpander-redshift \
  --region <REGION> --profile <PROFILE>
```

<Tip>
  **Node types:** `dc2.large` ($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:

  ```bash theme={"dark"}
  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))))"
  ```
</Tip>

Get the endpoint:

```bash theme={"dark"}
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:

| Principal                                                | Purpose                                                                          |
| -------------------------------------------------------- | -------------------------------------------------------------------------------- |
| `pods.eks.amazonaws.com`                                 | EKS Pod Identity — lets xpander pods assume the role                             |
| `arn:aws:iam::<ACCOUNT_ID>:root`                         | Cross-account assume with External ID for the xpander platform                   |
| `arn:aws:iam::<ACCOUNT_ID>:role/xpander-redshift-access` | Self-assume — the AI gateway re-assumes its own role to get Redshift credentials |

```bash theme={"dark"}
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>
```

<Warning>
  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.
</Warning>

## 5. Attach Permission Policies

The role needs three permission policies.

### Redshift Credential Access

```bash theme={"dark"}
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

```bash theme={"dark"}
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

```bash theme={"dark"}
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

<Info>
  The username `IAMR:<role-name>` is a Redshift convention that maps the IAM role to a database user.
</Info>

```bash theme={"dark"}
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

```bash theme={"dark"}
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:

| Field                | Value                                                       |
| -------------------- | ----------------------------------------------------------- |
| **ClusterNamePath**  | Your Redshift cluster identifier (e.g., `xpander-redshift`) |
| **DatabaseNamePath** | Your database name (e.g., `xpander_demo`)                   |
| **CatalogNamePath**  | `pg_catalog.svv_all_columns` (recommended)                  |
| **IAM Role ARN**     | `arn:aws:iam::<ACCOUNT_ID>:role/xpander-redshift-access`    |
| **Region**           | Your AWS region (e.g., `us-west-1`)                         |
| **Auth Method**      | IAM                                                         |

## 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

<AccordionGroup>
  <Accordion title="sts:TagSession AccessDenied">
    Add `sts:TagSession` to both the trust policy AND as a permission policy on the role.
  </Accordion>

  <Accordion title="sts:AssumeRole AccessDenied (self-assume)">
    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.
  </Accordion>

  <Accordion title="redshift-data:ExecuteStatement AccessDenied">
    Add the Redshift Data API permissions policy (see [step 5](#5-attach-permission-policies)).
  </Accordion>

  <Accordion title="Connection timeout">
    Check the Redshift security group allows port 5439 from the EKS cluster security group.
  </Accordion>

  <Accordion title="Missing path parameter for 'cluster_name'">
    The connector path parameters (`ClusterNamePath`, `DatabaseNamePath`, `CatalogNamePath`) must be configured in the xpander UI with the correct values.
  </Accordion>
</AccordionGroup>
