docs-mintlify/admin/deployment/oidc/aws.mdx
This guide walks through configuring AWS to trust Cube's OIDC issuer and shows the trust policies for the most common targets — Athena, an S3 export bucket, Cube Store CSPS, and Bedrock for bring-your-own LLM.
If you haven't enabled OIDC for your tenant yet, start with the OIDC overview.
<Info>Available on the Enterprise plan.
</Info>AWS token config exists under
Admin → OIDC.<tenant-name> (and the full
issuer URL as https://<tenant-name>.cubecloud.dev). Substitute your
actual slug everywhere it appears.The trust policies, env vars, and CLI commands in this guide use angle-bracket
placeholders — <tenant-name>, <aws-account-id>, <deployment-id>,
etc. Replace each placeholder with your real value before
copying. AWS will accept these strings literally and the federation call will
fail with a confusing error.
This is a one-time setup per AWS account. Once registered, every IAM role in this account can be configured to trust deployments in your Cube tenant.
aws iam create-open-id-connect-provider \
--url https://<tenant-name>.cubecloud.dev \
--client-id-list sts.amazonaws.com
AWS no longer requires a TLS thumbprint for HTTPS OIDC providers. The
--thumbprint-list parameter is accepted for compatibility but ignored —
AWS validates the issuer's certificate chain against its own trust store.
The command returns the provider ARN, which looks like:
arn:aws:iam::<aws-account-id>:oidc-provider/<tenant-name>.cubecloud.dev
You'll reference this ARN as the Federated principal in every trust policy
below. From AWS's point of view, this provider is your Cube tenant.
Add AWS_ROLE_ARN to your deployment's environment variables under
Settings → Environment variables. This is the IAM role Cube assumes by
default for every AWS SDK call inside the deployment — drivers, export
bucket I/O, custom code in cube.py / cube.js. You can either grant this
role direct access to your data, or use it as the entry point for further
AssumeRole hops.
AWS_ROLE_ARN=arn:aws:iam::<aws-account-id>:role/cube-deployment-<tenant-name>
sts:AssumeRoleWithWebIdentity does not accept sts:ExternalId. Trust
policies for OIDC-federated roles can only condition on the standard OIDC
claims (aud, sub, iss). If you copy a trust policy from a non-federated
role assumption (cross-account access keys, for example) and it includes
sts:ExternalId, remove it — STS will reject the federation call. Use the
sub claim to pin the role to a specific deployment or component instead.
Every IAM role you want Cube to assume needs a trust policy with this shape:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<aws-account-id>:oidc-provider/<tenant-name>.cubecloud.dev"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"<tenant-name>.cubecloud.dev:aud": "sts.amazonaws.com"
},
"StringLike": {
"<tenant-name>.cubecloud.dev:sub": "cube:deployment:<deployment-id>:component:cube_api"
}
}
}
]
}
The aud condition pins the audience to AWS STS — exactly what Cube's aws
token config emits. The sub condition is what scopes the trust to a
specific deployment (and optionally a specific Cube component). Patterns:
| Trust scope | sub pattern |
|---|---|
| One specific deployment, any component | cube:deployment:<deployment-id>:component:* |
| One deployment, only the Cube API and refresh worker | cube:deployment:<deployment-id>:component:cube_api |
| One deployment, only Cube Store | cube:deployment:<deployment-id>:component:cube_store |
| Every deployment in the tenant, only Cube Store (e.g. tenant-wide CSPS) | cube:deployment:*:component:cube_store |
| Every deployment, every component | cube:deployment:*:component:* |
Cube's default sub claim is cube:deployment:<deployment_id>. To match
the :component:<component> patterns in the table above (or to add
:region:<region>), open your AWS token config in Admin → OIDC and
paste one of these templates into the Subject Claim Format field:
cube:deployment:{deployment_id}:component:{component} — for the patterns
in the table above.cube:deployment:{deployment_id}:component:{component}:region:{region} —
to additionally pin a Cube Cloud region, useful
when you have dedicated regions per environment.See the subject editor section for the full syntax.
<Warning>Update your AWS trust policy first, then change the Subject Claim Format
on the token config — otherwise existing tokens won't match the trust policy
and AssumeRoleWithWebIdentity will start failing.
Configure an IAM role with permissions to query Athena and read query results from your S3 results bucket.
<Steps> <Step title="Create the IAM role"> Trust policy — substitute your AWS account ID, tenant slug, and deployment ID for the angle-bracket placeholders:```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<aws-account-id>:oidc-provider/<tenant-name>.cubecloud.dev"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"<tenant-name>.cubecloud.dev:aud": "sts.amazonaws.com"
},
"StringLike": {
"<tenant-name>.cubecloud.dev:sub": "cube:deployment:<deployment-id>:component:cube_api"
}
}
}
]
}
```
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"athena:StartQueryExecution",
"athena:GetQueryExecution",
"athena:GetQueryResults",
"athena:StopQueryExecution",
"athena:ListQueryExecutions",
"athena:GetWorkGroup",
"athena:ListWorkGroups"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"glue:GetDatabase",
"glue:GetDatabases",
"glue:GetTable",
"glue:GetTables",
"glue:GetPartitions"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"s3:GetBucketLocation",
"s3:GetObject",
"s3:ListBucket",
"s3:PutObject",
"s3:DeleteObject",
"s3:AbortMultipartUpload",
"s3:ListMultipartUploadParts"
],
"Resource": [
"arn:aws:s3:::my-athena-results",
"arn:aws:s3:::my-athena-results/*",
"arn:aws:s3:::my-athena-data",
"arn:aws:s3:::my-athena-data/*"
]
}
]
}
```
```dotenv
CUBEJS_DB_TYPE=athena
AWS_ROLE_ARN=arn:aws:iam::<aws-account-id>:role/cube-deployment-<tenant-name>
CUBEJS_AWS_REGION=us-east-1
CUBEJS_AWS_S3_OUTPUT_LOCATION=s3://my-athena-results/queries/
```
If Athena lives in a different role / account from the deployment's
default identity, set [`CUBEJS_AWS_ATHENA_ASSUME_ROLE_ARN`][ref-athena-assume-role]
in addition to `AWS_ROLE_ARN`. The Athena driver uses the deployment
identity to perform a second `AssumeRole` hop into the Athena role.
If your data source uses an export bucket for
pre-aggregation unloads (Snowflake, Redshift, Athena, BigQuery, …), Cube
needs s3:PutObject / s3:GetObject / s3:ListBucket on the bucket. The
deployment's default identity is the simplest place to put this.
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket",
"s3:GetBucketLocation",
"s3:AbortMultipartUpload",
"s3:ListMultipartUploadParts"
],
"Resource": [
"arn:aws:s3:::my-export-bucket",
"arn:aws:s3:::my-export-bucket/*"
]
}
]
}
```
```dotenv
CUBEJS_DB_EXPORT_BUCKET_TYPE=s3
CUBEJS_DB_EXPORT_BUCKET=my-export-bucket
CUBEJS_DB_EXPORT_BUCKET_AWS_REGION=us-east-1
```
See the [export bucket reference][ref-export-bucket] for the full set
of variables.
Cube Store CSPS lets you store pre-aggregations in your own S3 bucket.
Cube Store gets a separate OIDC token whose sub claim ends in
component:cube_store, so the trust policy can be locked down to that
component — even if the same role were ever shared with the rest of the
deployment, only Cube Store would be able to assume it.
Because every Cube Store worker emits a sub of the form
cube:deployment:<deployment-id>:component:cube_store, the trust policy's
StringLike condition controls how broadly the role is shared:
cube:deployment:*:component:cube_store — one role + one bucket for the
whole tenant. Every deployment in the tenant writes pre-aggregations to
the same bucket, isolated only by Cube Store's own per-deployment path
prefix. Easiest to operate and the most common setup.cube:deployment:<deployment-id>:component:cube_store — per-deployment
isolation. Pin the role to one deployment so its pre-aggregations live
in a dedicated bucket that no other deployment can read or write.The example below shows the tenant-wide pattern; swap * for a specific
deployment ID if you want isolation.
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<aws-account-id>:oidc-provider/<tenant-name>.cubecloud.dev"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"<tenant-name>.cubecloud.dev:aud": "sts.amazonaws.com"
},
"StringLike": {
"<tenant-name>.cubecloud.dev:sub": "cube:deployment:*:component:cube_store"
}
}
}
]
}
```
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket",
"s3:GetBucketLocation",
"s3:AbortMultipartUpload",
"s3:ListMultipartUploadParts"
],
"Resource": [
"arn:aws:s3:::my-csps-bucket",
"arn:aws:s3:::my-csps-bucket/*"
]
}
]
}
```
- Toggle **Enable CSPS** on.
- **Storage Provider**: Amazon S3.
- **S3 Bucket**: `my-csps-bucket`.
- **S3 Region**: e.g. `us-east-1`.
- **IAM Role ARN**: `arn:aws:iam::<aws-account-id>:role/cube-cubestore-<tenant-name>`.
Click **Test Connection** to verify Cube Store can assume the role and
access the bucket, then **Apply**. Cube Store starts writing
pre-aggregations to your bucket on the next refresh. With the
tenant-wide trust policy above, every deployment in the tenant points
at the same role — no need to provision a new IAM role per deployment.
<Frame>
</Frame>
Bring-your-own LLM lets the AI engineer service call Bedrock through your
own AWS account. The AI engineer's sub claim ends in
component:ai_engineer, so the trust policy uses a StringLike match on
cube:deployment:*:component:ai_engineer to grant access tenant-wide
(every deployment's AI engineer assumes the same role and writes against
the same Bedrock account).
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<aws-account-id>:oidc-provider/<tenant-name>.cubecloud.dev"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"<tenant-name>.cubecloud.dev:aud": "sts.amazonaws.com"
},
"StringLike": {
"<tenant-name>.cubecloud.dev:sub": "cube:deployment:*:component:ai_engineer"
}
}
}
]
}
```
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream",
"bedrock:Converse",
"bedrock:ConverseStream"
],
"Resource": [
"arn:aws:bedrock:*::foundation-model/anthropic.*",
"arn:aws:bedrock:*:<aws-account-id>:inference-profile/*"
]
}
]
}
```
Cross-region inference profiles need access to the foundation model in
every region the profile routes through, hence the `*` region in the
foundation-model ARN.
- **Name** — a human-readable label for this BYOM entry.
- **Model Type** — `LLM`.
- **Provider** — `AWS Bedrock`.
- **Model** — the Claude (or other) model you want the AI engineer to
call.
- **Region** — the Bedrock region (e.g. `us-east-1`).
- **Inference Profile ID** — optional; leave blank to call the
foundation model directly, or set to a cross-region inference
profile ID.
- **Assume Role ARN** — the role you created above.
- **Use OIDC workload identity** — toggle on. With this on, the AI
engineer authenticates via the deployment's OIDC token instead of
static AWS credentials.
<Frame>
</Frame>
The fastest way to confirm the trust policy is wired up correctly is the
Test connection button on the relevant settings page (data source
wizard, CSPS settings, BYO LLM provider). Behind the scenes, this issues a
real Cube OIDC token, runs AssumeRoleWithWebIdentity against AWS STS, and
returns a precise error if the trust policy rejects it.
If the test fails:
| Symptom | Likely cause |
|---|---|
Not authorized to perform sts:AssumeRoleWithWebIdentity | The trust policy's sub condition doesn't match the deployment / component. Compare the sub in the error with what your StringLike allows. |
Incorrect token audience | aud condition is wrong, or you copied a trust policy from a different tenant. The condition key must be <tenant-name>.cubecloud.dev:aud. |
OpenIDConnectProvider not found | The OIDC provider hasn't been registered in this AWS account yet, or its URL doesn't match your tenant's domain. Re-run aws iam create-open-id-connect-provider with the correct URL. |
An error occurred ... InvalidIdentityToken | Trying to use sts:ExternalId. Remove it — federated assume-role doesn't accept it. |
AssumeRoleWithWebIdentity events show up in CloudTrail with the deployment
subject in the userIdentity.webIdFederationData.federatedProvider and
...attributes fields — useful for auditing which deployments are
authenticating against which roles.