Use short-lived OIDC tokens to securely authenticate to cloud resources
OpenID Connect (OIDC) lets your Baseten deployments authenticate to cloud
resources like S3 buckets and container registries using short-lived tokens
instead of long-lived credentials.Without OIDC, accessing cloud resources requires long-lived credentials: static
API keys or service account keys stored as secrets in Baseten. These keys don’t
expire on their own, so if they’re leaked or forgotten, they remain valid until
someone manually rotates them. You’re responsible for tracking which keys exist,
where they’re used, and when to rotate them.OIDC takes a different approach. Instead of static keys, Baseten issues
short-lived tokens scoped to a specific deployment. There are no secrets to
store, rotate, or clean up.Baseten OIDC currently supports:
AWS: Amazon ECR (container images) and Amazon S3 (model weights)
Google Cloud: Artifact Registry, GCR (container images), and Google Cloud Storage (model weights)
Baseten acts as an OIDC identity provider with the following configuration:
Issuer: https://oidc.baseten.co
Audience: oidc.baseten.co
When you deploy your model, Baseten generates short-lived OIDC tokens that
identify your specific workload. Your cloud provider validates these tokens
against the trust relationship you configure, then grants access to the
specified resources.
Common patterns for scoping which workloads can access your resources:
AWS: Use these in the IAM role trust policy under Condition.StringLike for oidc.baseten.co:sub. Wildcards (*) are supported.
GCP: Use these in the Workload Identity Provider attribute-condition. With the mapping google.subject=assertion.sub (see Create a Workload Identity Provider), reference the sub claim as google.subject. GCP does not support wildcards; use startsWith() (and contains() where needed).
Use truss whoami --show-oidc to view your organization and team IDs, issuer, audience, and subject claim format needed for configuring cloud provider trust policies.
Run this script to create the OIDC provider, IAM role, and permission policies. Set the variables at the top, then execute the entire script.Prerequisites
AWS credentials configured for the target account (aws configure, environment variables, or an IAM role) with permission to create OIDC identity providers, IAM roles, and inline role policies (for example iam:CreateOpenIDConnectProvider, iam:CreateRole, iam:PutRolePolicy).
Review each variable before running. Replace the empty values with your actual AWS account ID, S3 bucket name, organization ID, and team ID.
#!/usr/bin/env bashset -euo pipefailrequire_non_empty() { local _name="$1" local _desc="${2:-$1}" local _val eval "_val=\${${_name}-}" if [ -z "$_val" ]; then echo "error: ${_desc} is empty; set ${_name} in the configuration section." >&2 exit 1 fi}# ──────────────────────────────────────────────# Configuration — replace these with your values# ──────────────────────────────────────────────AWS_ACCOUNT_ID="" # Your AWS account IDS3_BUCKET="" # S3 bucket for model weightsROLE_NAME="BasetenOIDCRole" # IAM role nameBASETEN_ORG_ID="" # From `truss whoami --show-oidc`BASETEN_TEAM_ID="" # From `truss whoami --show-oidc`require_non_empty AWS_ACCOUNT_ID "AWS account ID"require_non_empty S3_BUCKET "S3 bucket name (model weights)"require_non_empty BASETEN_ORG_ID "Baseten organization ID"require_non_empty BASETEN_TEAM_ID "Baseten team ID"require_non_empty ROLE_NAME "IAM role name"OIDC_ISSUER="oidc.baseten.co"OIDC_ISSUER_URL="https://${OIDC_ISSUER}"# ──────────────────────────────────# 1. Create the OIDC identity provider# ──────────────────────────────────echo "Creating OIDC identity provider..."if ! output=$(aws iam create-open-id-connect-provider \ --url "${OIDC_ISSUER_URL}" 2>&1); then if [[ "$output" == *"EntityAlreadyExists"* ]]; then echo "OIDC provider already exists, continuing..." else echo "$output" >&2 exit 1 fielse echo "OIDC provider created."fi# ──────────────────────────────────# 2. Create the IAM trust policy# ──────────────────────────────────TRUST_POLICY=$(cat <<EOF{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::${AWS_ACCOUNT_ID}:oidc-provider/${OIDC_ISSUER}" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "${OIDC_ISSUER}:aud": "${OIDC_ISSUER}" }, "StringLike": { "${OIDC_ISSUER}:sub": "v=1:org=${BASETEN_ORG_ID}:team=${BASETEN_TEAM_ID}:*" } } } ]}EOF)# ──────────────────────────────────# 3. Create the IAM role# ──────────────────────────────────echo "Creating IAM role ${ROLE_NAME}..."aws iam create-role \ --role-name "${ROLE_NAME}" \ --assume-role-policy-document "${TRUST_POLICY}" \ --description "Baseten OIDC role for model workloads"echo "IAM role created."# ──────────────────────────────────# 4. Attach ECR read policy# ──────────────────────────────────ECR_POLICY=$(cat <<EOF{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ecr:GetAuthorizationToken", "ecr:BatchCheckLayerAvailability", "ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage" ], "Resource": "*" } ]}EOF)echo "Attaching ECR read policy..."aws iam put-role-policy \ --role-name "${ROLE_NAME}" \ --policy-name "BasetenECRReadAccess" \ --policy-document "${ECR_POLICY}"# ──────────────────────────────────# 5. Attach S3 read policy# ──────────────────────────────────S3_POLICY=$(cat <<EOF{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::${S3_BUCKET}", "arn:aws:s3:::${S3_BUCKET}/*" ] } ]}EOF)echo "Attaching S3 read policy..."aws iam put-role-policy \ --role-name "${ROLE_NAME}" \ --policy-name "BasetenS3ReadAccess" \ --policy-document "${S3_POLICY}"# ──────────────────────────────────# Done# ──────────────────────────────────ROLE_ARN="arn:aws:iam::${AWS_ACCOUNT_ID}:role/${ROLE_NAME}"echo ""echo "Setup complete. IAM role ARN:"echo " ${ROLE_ARN}"echo ""echo "Use this role ARN in your Truss configuration."
This creates a single role with both ECR and S3 permissions. If you only need ECR or S3 access (not both), comment out or remove the policy section you don’t need (step 4 or step 5).
If you prefer to walk through each step manually, or need to customize individual resources, follow the instructions below.
If your AWS account requires sts.amazonaws.com as a trusted audience, add it to the OIDC provider first, then add oidc.baseten.co as an additional audience.
Configure the trust policy: Edit the role’s trust policy to include subject claim conditions. After creating the role, go to the role → Trust relationships → Edit and use a policy like this:
Replace <aws-account-id> with your AWS account ID, and adjust the sub claim pattern to match your security requirements.
Provision all resources with a single script
Run this script to create the service account, Workload Identity Pool, provider, and role bindings. Set the variables at the top, then execute the entire script.Prerequisites
gcloud authenticated (gcloud auth login and application-default credentials if needed) with permission to create service accounts, Workload Identity Pools and providers, and modify project IAM (for example Service Account Admin, Workload Identity Pool Admin, and Project IAM Admin or a custom role with equivalent actions on the target project).
Review each variable before running. Replace the empty values with your actual GCP project ID, project number, organization ID, and team ID.
#!/usr/bin/env bashset -euo pipefailrequire_non_empty() { local _name="$1" local _desc="${2:-$1}" local _val eval "_val=\${${_name}-}" if [ -z "$_val" ]; then echo "error: ${_desc} is empty; set ${_name} in the configuration section." >&2 exit 1 fi}# ──────────────────────────────────────────────# Configuration — replace these with your values# ──────────────────────────────────────────────PROJECT_ID="" # Your GCP project IDPROJECT_NUMBER="" # Your GCP project numberSERVICE_ACCOUNT_NAME="baseten-oidc" # Service account namePOOL_NAME="baseten-pool" # Workload Identity Pool namePROVIDER_NAME="baseten-provider" # Workload Identity Provider nameBASETEN_ORG_ID="" # From `truss whoami --show-oidc`BASETEN_TEAM_ID="" # From `truss whoami --show-oidc`require_non_empty PROJECT_ID "GCP project ID"require_non_empty PROJECT_NUMBER "GCP project number"require_non_empty BASETEN_ORG_ID "Baseten organization ID"require_non_empty BASETEN_TEAM_ID "Baseten team ID"require_non_empty SERVICE_ACCOUNT_NAME "Service account name"require_non_empty POOL_NAME "Workload Identity Pool name"require_non_empty PROVIDER_NAME "Workload Identity Provider name"OIDC_ISSUER_URL="https://oidc.baseten.co"OIDC_AUDIENCE="oidc.baseten.co"SA_EMAIL="${SERVICE_ACCOUNT_NAME}@${PROJECT_ID}.iam.gserviceaccount.com"# ──────────────────────────────────# 1. Create the service account# ──────────────────────────────────echo "Creating service account ${SERVICE_ACCOUNT_NAME}..."gcloud iam service-accounts create "${SERVICE_ACCOUNT_NAME}" \ --project="${PROJECT_ID}" \ --display-name="Baseten OIDC Service Account"# ──────────────────────────────────# 2. Grant Artifact Registry reader# ──────────────────────────────────echo "Granting Artifact Registry reader role..."gcloud projects add-iam-policy-binding "${PROJECT_ID}" \ --member="serviceAccount:${SA_EMAIL}" \ --role="roles/artifactregistry.reader"# ──────────────────────────────────# 3. Grant Cloud Storage object viewer# ──────────────────────────────────echo "Granting Cloud Storage object viewer role..."gcloud projects add-iam-policy-binding "${PROJECT_ID}" \ --member="serviceAccount:${SA_EMAIL}" \ --role="roles/storage.objectViewer"# ──────────────────────────────────# 4. Create the Workload Identity Pool# ──────────────────────────────────echo "Creating Workload Identity Pool..."gcloud iam workload-identity-pools create "${POOL_NAME}" \ --project="${PROJECT_ID}" \ --location="global" \ --display-name="Baseten Workload Identity Pool"# ──────────────────────────────────# 5. Create the Workload Identity Provider# ──────────────────────────────────ATTRIBUTE_CONDITION="google.subject.startsWith('v=1:org=${BASETEN_ORG_ID}:team=${BASETEN_TEAM_ID}:')"echo "Creating Workload Identity Provider..."gcloud iam workload-identity-pools providers create-oidc "${PROVIDER_NAME}" \ --project="${PROJECT_ID}" \ --location="global" \ --workload-identity-pool="${POOL_NAME}" \ --issuer-uri="${OIDC_ISSUER_URL}" \ --allowed-audiences="${OIDC_AUDIENCE}" \ --attribute-mapping="google.subject=assertion.sub" \ --attribute-condition="${ATTRIBUTE_CONDITION}"# ──────────────────────────────────# 6. Allow Workload Identity to impersonate the service account# ──────────────────────────────────echo "Binding workload identity to service account..."gcloud iam service-accounts add-iam-policy-binding "${SA_EMAIL}" \ --project="${PROJECT_ID}" \ --role="roles/iam.workloadIdentityUser" \ --member="principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${POOL_NAME}/*"# ──────────────────────────────────# Done# ──────────────────────────────────echo ""echo "Setup complete. Service account:"echo " ${SA_EMAIL}"echo ""echo "Workload Identity Provider:"echo " projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${POOL_NAME}/providers/${PROVIDER_NAME}"echo ""echo "Use these values in your Truss configuration."
This grants both Artifact Registry and GCS permissions to the service account. If you only need Artifact Registry or GCS access (not both), comment out or remove the role binding you don’t need (step 2 or step 3).
To find your GCP project number, run: gcloud projects describe PROJECT_ID --format="value(projectNumber)"
If you prefer to walk through each step manually, or need to customize individual resources, follow the instructions below.
The attribute mappinggoogle.subject=assertion.sub maps the OIDC sub claim into the google.subject attribute. After this mapping, you can use google.subject everywhere (including in attribute-condition) to reference the subject claim.
GCP doesn’t support wildcard subject claims. Use startsWith() in attribute-condition to match workloads by prefix. Replace the organization and team IDs with your own values.
Use the most specific subject claim pattern that fits your use case. Create separate roles or Workload Identity providers for different environments, workload types, or models rather than one role with broad permissions. Always test your OIDC configuration in a non-production environment first.
Don’t grant access to v=1:org=*:team=*:*. This allows any Baseten workload to access your resources.
Enable detailed logging to see exactly why authentication or authorization is failing:AWS CloudTrail: Look for AssumeRoleWithWebIdentity events to see token validation attempts.GCP Cloud Audit Logs: Check iam.googleapis.com logs for workload identity authentication events.
GCP doesn’t support wildcard subject claims or subject-based scoping in IAM role conditions. Use the Workload Identity Provider attribute-condition instead.
Cloudflare R2, Azure containers, and Hugging Face aren’t yet supported.