Documentation Index

Fetch the complete documentation index at: https://kb.vastdata.com/llms.txt

Use this file to discover all available pages before exploring further.

OIDC S3 Federation Guide for VAST with Azure Entra ID

Prev Next

This guide provides a streamlined walkthrough for configuring OIDC-based S3 federation with Azure Entra ID on VAST (version 5.4+).


STS Primer: Temporary Credentials for S3 Access

What is STS?

Security Token Service (STS) is an AWS-compatible API that issues temporary, limited-privilege credentials. Instead of distributing long-lived access keys, applications exchange an identity token (JWT) for short-lived credentials that automatically expire.

VAST implements the AssumeRoleWithWebIdentity STS action, which accepts a JWT from an OIDC-compliant identity provider (e.g., Azure Entra ID, Keycloak, Okta) and returns temporary S3 credentials.

Why Customers Request STS

Driver

Problem with Long-Lived Keys

How STS Helps

Security/Compliance

Static keys can be leaked, shared, or forgotten

Credentials expire automatically (15 min – 12 hours)

Audit Requirements

Hard to trace which user performed an action

Each session tied to authenticated identity via JWT claims

Key Rotation Burden

Manual rotation is error-prone and disruptive

No rotation needed - credentials are ephemeral

Least Privilege

Broad keys often over-permissioned

Scoped to specific IAM role and identity policy

Typical triggers: SOC 2 audits, infosec reviews, cloud security posture assessments, or migration from on-prem to hybrid environments.

Core Concepts

OIDC Provider: The external identity system (Azure Entra ID, Keycloak, etc.) that authenticates users and issues JWT tokens. VAST fetches the provider's public keys to verify token signatures.

Trust Policy: A JSON document attached to an IAM role that specifies who can assume the role. It matches JWT claims (iss, aud, optionally sub) against expected values.

Identity Policy: A JSON document that specifies what the assumed role can do (e.g., s3:GetObject, s3:PutObject on specific buckets).

IAM Role: Links a trust policy (authentication) to identity policies (authorization). Users don't get S3 keys directly - they assume a role.

Authentication Flow

Authentication flow

Credential Lifecycle

There are two independent expiration timers that applications must track:

Credential

Issuer

Typical Lifetime

Indicated By

JWT (id_token)

OIDC Provider (Azure/Keycloak)

~1 hour

exp claim in JWT payload

STS temp credentials

VAST

15 min – 12 hours

Expiration field in STS response

Application responsibility:

  • Before calling STS: check if JWT is expired (compare exp claim to current time)

  • Before calling S3: check if STS credentials are expired (compare Expiration to current time)

Credential Renewal Flow:

OIDC/STS Credential Renewal flow

Step-by-step:

  1. User authenticates with OIDC provider → receives JWT (valid ~1 hour)

  2. Application calls AssumeRoleWithWebIdentity with JWT → receives temp creds (valid 15 min)

  3. The application uses temp creds for S3 operations

  4. When temp creds expire, the application calls STS again with the same JWT → receives fresh temp creds

  5. Repeat step 4 as long as JWT is valid

  6. When JWT expires, STS call returns ExpiredToken error

  7. The application must re-authenticate with OIDC provider to get a new JWT

Error messages from VAST:

Error Code

HTTP Status

Meaning

ExpiredToken

400

JWT exp claim has passed, or STS credentials have expired

InvalidToken

400

JWT signature invalid, malformed, or claims don't match trust policy

How to check JWT expiration (client-side):

# Decode JWT and check exp claim
cat /tmp/jwt_token.txt | cut -d'.' -f2 | base64 -d 2>/dev/null | jq '.exp'

# Compare to current time
date +%s

# If current_time >= exp, JWT is expired → re-authenticate

Key Points for the field

  • Credentials are tenant-scoped: STS credentials only work against VIPs assigned to that tenant.

  • JWT lifetime ≠ credential lifetime: JWTs typically last ~1 hour; STS credentials can be 15 minutes to 12 hours (configurable per role).

  • Verify the JWT using tools like

  • No key management overhead: Unlike static S3 keys, credentials self-expire.


Prerequisites

  • VAST cluster running version 5.4+

  • Azure Entra ID tenant with permissions to create App Registrations

  • Azure CLI (az) installed

  • vcli access to the VAST cluster

  • AWS CLI installed on the client machine

  • jq installed for JSON parsing


Configuration Variables


Step 1: Create Azure Entra ID App Registration

Why: Azure Entra ID acts as the identity provider (IdP). The app registration defines your application and allows users to authenticate and receive JWT tokens that VAST can verify.

1.1 Login to Azure

az login

Validate: You should see your subscription details and be returned to the command prompt.

1.2 Get your Azure Tenant ID

az account show --query tenantId -o tsv

Validate: Output is a GUID like xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. Save this as AZURE_TENANT_ID.

1.3 Create the App Registration

az ad app create \
  --display-name "vast-oidc-federation" \
  --sign-in-audience "AzureADMyOrg" \
  --enable-id-token-issuance true \
  --enable-access-token-issuance true

Validate: Output shows JSON with "displayName": "vast-oidc-federation".

1.4 Get the Application (Client) ID

az ad app list --display-name "vast-oidc-federation" --query "[0].appId" -o tsv

Validate: Output is a GUID. Save this as AZURE_APP_ID.

1.5 Create a Service Principal

az ad sp create --id <AZURE_APP_ID>

Why: Required for users to authenticate against the app.

Validate: Output shows JSON with "appId" matching your AZURE_APP_ID.


Set these variables at the start - they'll be used throughout:

# Azure Configuration
AZURE_TENANT_ID="<your-azure-tenant-id>"
AZURE_APP_ID="<your-app-client-id>"

# VAST Configuration
VAST_TENANT_NAME="<your-vast-tenant-name>"
VAST_TENANT_ID=<your-vast-tenant-id>
VAST_VIP="<your-vast-vip-address>"
VAST_POLICY_ID=<your-view-policy-id>

# Names
OIDC_PROVIDER_NAME="entra-id-v1"
IAM_ROLE_NAME="oidc-s3-role"
IDENTITY_POLICY_NAME="s3-full-access"
BUCKET_NAME="mybucket"

1.6 Enable Public Client Flows

az ad app update --id <AZURE_APP_ID> --is-fallback-public-client true

Why: Required for device code flow (no client secret needed).

Validate: No output on success. Verify with:

az ad app show --id <AZURE_APP_ID> --query "isFallbackPublicClient"

Expected: true

1.7 Grant OpenID Permissions

#
az ad app permission add \
  --id <AZURE_APP_ID> \
  --api 00000003-0000-0000-c000-000000000000 \
  --api-permissions e1fe6dd8-ba31-4d61-89e7-88639da4683d=Scope

GUID

Meaning

00000003-0000-0000-c000-000000000000

Microsoft Graph API identifier

e1fe6dd8-ba31-4d61-89e7-88639da4683d

The openid permission scope

Why: Grants the openid scope so the app can request ID tokens.

About these GUIDs (these are fixed Microsoft values, the same for all Azure tenants):

Reference: Microsoft Graph permission IDs

Validate: No output on success.

az ad app permission admin-consent --id <AZURE_APP_ID>

Note: If you're not a tenant admin, skip this - users will be prompted to consent on first login.

Validate: No output on success.

1.9 Record the Discovery URL

https://login.microsoftonline.com/<AZURE_TENANT_ID>/v2.0/.well-known/openid-configuration

Validate: Open this URL in a browser - you should see a JSON document with keys like issuer, authorization_endpoint, jwks_uri.


Step 2: Create OIDC Provider on VAST

Why: VAST needs to know about the identity provider to validate JWT tokens. The discovery URL tells VAST where to fetch the provider's public keys (JWKS) for signature verification.

2.1 Create the OIDC Provider

vcli: admin> oidc create \
  --name "entra-id-v1" \
  --discovery-url "https://login.microsoftonline.com/<AZURE_TENANT_ID>/v2.0/.well-known/openid-configuration" \
  --user-jwt-attribute "email"

Why --user-jwt-attribute "email": This tells VAST which JWT claim to use for identifying the user. The email claim from Azure tokens maps users to VAST identities.

Validate: Command returns successfully with provider details.

2.2 Verify Provider State

vcli: admin> oidc list

Screenshot 2025-11-25 at 3.19.10 PM.png

Validate: Look for your provider in the output. Check:

  • Name = entra-id-v1 // the name you put in earlier for the OIDC

  • State = OK

If State is not OK (e.g., PROVIDER_ACCESS_FAILURE):

vcli: admin> oidc refresh_keys --id <OIDC_ID>

Common causes of PROVIDER_ACCESS_FAILURE:

  • VAST cluster cannot reach login.microsoftonline.com (firewall/DNS)

  • Discovery URL is incorrect (typo in tenant ID)

2.3 Note the OIDC Provider ID

From the oidc list output, note the ID column value. You'll need this for Step 3.


Step 3: Associate OIDC Provider with Tenant

Why: The OIDC provider must be linked to a VAST tenant so that users authenticating via this provider can access resources in that tenant. Without this association, STS requests will fail.

3.1 Open Tenant Settings in Web UI

Screenshot 2025-11-25 at 3.25.04 PM.png

  1. Navigate to VAST Web UIUser ManagementTenants

  2. Right-click your tenant → Edit

3.2 Associate the OIDC Provider

  1. Under Providers And Users Access, find the OIDC dropdown

  2. Select your provider (entra-id-v1)

  3. Click Update

3.3 Verify the Association

vcli: admin> tenant list
vcli: admin> tenant show --id 10
+-----------------------------------------------------+-----------------------------------------------------------------------+
| ID                                                  | 10                                                                    |
| Name                                                | bgolliher-sts                                                         |
| Enable privileged domain user restore access        | True                                                                  |
... snip ...   
| Active Directory provider ID                        | 1                                                                     |
| Open LDAP provider ID                               | None                                                                  |
| NIS provider ID                                     | None                                                                  |
| Local provider ID                                   | 1                                                                     |
| Kerberos provider ID                                | None                                                                  |
| OIDC provider ID                                    | 3                                                                     |
| Encryption Group                                    |                                                                       |
... snip ...
| Data-engine-enabled                                 | False                                                                 |
| VIP Pools IP ranges                                 | ['172.200.201.70 - 172.200.201.72', '172.200.201.7 - 172.200.201.10'] |
| Access IP Ranges                                    | []                                                                    |
| QoS                                                 | None                                                                  |
| Tenant Admins Group Name                            |                                                                       |
| App Users Group Name                                |                                                                       |
| Allowed NFSv4 File Delegations                      | READ_WRITE                                                            |
| Grant Unrequested NFSv4 File Delegations by Default | True                                                                  |
| SMB Encryption State                                | OFF                                                                   |
+-----------------------------------------------------+-----------------------------------------------------------------------+
vcli: admin>

Validate: Look for your tenant row and check:

  • The OIDC provider ID column shows the ID from Step 2.3

  • If this column is empty or shows a different ID, repeat Step 3.2


Step 4: Create Identity Policy

Why: The identity policy defines what S3 actions the IAM role can perform. This is the authorization layer - it answers "what can this role do?"

4.1 Create the Identity Policy

vcli: admin> identitypolicy create \
  --name "s3-full-access" \
  --tenant-id <VAST_TENANT_ID> \
  --policy '{"Version":"2012-10-17","Statement":[{"Sid":"FullS3Access","Effect":"Allow","Action":"s3:*","Resource":"*"}]}'

About the policy JSON:

Field

Value

Meaning

Version

2012-10-17

AWS policy language version (required, always this value)

Sid

FullS3Access

Statement ID - a label for this rule (mainly for readability)

Effect

Allow

Whether toallowor deny the actions

Action

s3:*

Which S3 operations are permitted (* = all / full control)

Resource

*

Which buckets/objects this applies to (* = all)

Validate: Command returns successfully with policy details.

4.2 Verify and Note the Policy ID

vcli: admin> identitypolicy list --tenant-id <VAST_TENANT_ID>

Validate: Your policy appears in the list. Note the ID column value - you'll need this for Step 6.


Step 5: Create IAM Role with Trust Policy

Why: The IAM role links authentication (who can assume it) to authorization (what it can do). It connects the OIDC provider to the identity policy.

5.1 Understand the Trust Policy

The trust policy specifies who can assume this role. It must match the JWT claims from your OIDC provider:

Trust Policy Field

Must Match JWT Claim

Example Value

Principal.Federated

iss (issuer)

oidc-provider/login.microsoftonline.com/<AZURE_TENANT_ID>/v2.0

Condition.StringEquals...:aud

aud (audience)

<AZURE_APP_ID>

Important: The Principal.Federated value is the iss claim with <https://> removed and oidc-provider/ prepended.

5.2 Create the IAM Role

vcli: admin> iamrole create \
  --name "oidc-s3-role" \
  --tenant-id <VAST_TENANT_ID> \
  --description "Role for OIDC federated S3 access" \
  --identity-policies-ids <IDENTITY_POLICY_ID> \
  --trust-policy '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Federated":"oidc-provider/login.microsoftonline.com/<AZURE_TENANT_ID>/v2.0"},"Action":"sts:AssumeRoleWithWebIdentity","Condition":{"StringEquals":{"login.microsoftonline.com/<AZURE_TENANT_ID>/v2.0:aud":"<AZURE_APP_ID>"}}}]}'

Validate: Command returns successfully with role details.

5.3 Verify the Role Configuration

vcli: admin> iamrole list

Validate: Your role appears. Note the ID column value.

To see full details, including the attached identity policy:

vcli: admin> iamrole show --id <ROLE_ID> --identity-policies

Validate: The output shows your identity policy attached to the role.

vcli: admin> iamrole show --id 7 --identity-policies
+----+------------------+
| Id | Name             |
+----+------------------+
| 21 | S3ReadOnlyPolicy |
+----+------------------+
vcli: admin> iamrole show --id 8 --identity-policies
+----+--------------------+
| Id | Name               |
+----+--------------------+
| 23 | s3-sts-full-access |
+----+--------------------+
vcli: admin>

Step 6: Create S3 Bucket (View)

Why: VAST requires an S3 view (bucket) to exist before you can PUT/GET objects. In VAST, an S3 bucket is backed by a "view" - a logical mapping to a filesystem path with protocol and access settings.

6.1 Identify the View Policy ID

You need a view policy to create a view. List existing policies:

vcli: admin> viewpolicy list

Validate: Note the ID of an appropriate policy (e.g., a default policy). Save this as VIEW_POLICY_ID.

6.2 Create the View (Bucket)

vcli: admin> view create \
  --path /mybucket \
  --bucket mybucket \
  --protocols S3 \
  --policy-id <VIEW_POLICY_ID> \
  --tenant-id <VAST_TENANT_ID> \
  --bucket-owner <OWNER_EMAIL> \
  --create-dir

About the parameters:

Parameter

Meaning

--path

Filesystem path where bucket data is stored

--bucket

S3 bucket name (what clients use)

--protocols S3

Enable S3 protocol access

--policy-id

View policy governing access rules

--tenant-id

Which tenant owns this bucket

--bucket-owner

Email of the bucket owner

--create-dir

Create the directory if it doesn't exist

Validate: Command returns successfully with view details.

6.3 Verify the View

vcli: admin> view list --tenant-id <VAST_TENANT_ID>

Validate: Check:

  • Bucket column shows mybucket

  • Protocols column includes S3

  • Tenant ID matches your tenant


Step 7: Authenticate and Get JWT Token

Why: To assume the IAM role, you need a valid JWT (JSON Web Token) from Azure. We use the device code flow, which allows authentication without embedding secrets in scripts - users authenticate interactively via a browser.

7.1 Request a Device Code

curl -s -X POST \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "client_id=<AZURE_APP_ID>" \
  -d "scope=openid profile email" \
  "https://login.microsoftonline.com/<AZURE_TENANT_ID>/oauth2/v2.0/devicecode"

Validate: Response is JSON containing:

  • user_code - A short code like ABCD1234 to enter in the browser

  • device_code - A longer code used in the next step (save this)

  • verification_uri - URL to visit (usually <https://microsoft.com/devicelogin)>

  • message - Human-readable instructions

Example response:

{
  "user_code": "ABCD1234",
  "device_code": "DAQABAAEAAAAm-06...",
  "verification_uri": "https://microsoft.com/devicelogin",
  "message": "To sign in, use a web browser to open..."
}

7.2 Authenticate in Browser

  1. Open <https://microsoft.com/devicelogin> in a browser

  2. Enter the user_code from Step 8.1

  3. Sign in with your Azure credentials

  4. Approve the permissions request

Validate: Browser shows "You have signed in to the vast-oidc-federation application on your device."  Yours will differ based on what you call your application.

7.3 Exchange Device Code for Token

After browser authentication completes, request the token:

curl -s -X POST \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "client_id=<AZURE_APP_ID>" \
  -d "grant_type=urn:ietf:params:oauth:grant-type:device_code" \
  -d "device_code=<DEVICE_CODE_FROM_STEP_8.1>" \
  "https://login.microsoftonline.com/<AZURE_TENANT_ID>/oauth2/v2.0/token"

Validate: Response is JSON containing id_token, access_token, and other fields.

7.4 Extract and Save the ID Token

IMPORTANT: Use the id_token. The id_token contains the correct iss and aud claims that VAST validates.

From the JSON response, extract the id_token value and save it:

# If you saved the response to a variable:
echo "$TOKEN_RESPONSE" | jq -r .id_token > /tmp/jwt_token.txt

7.5 Verify the Token Claims

Decode and inspect the JWT payload:

cat /tmp/jwt_token.txt | cut -d'.' -f2 | base64 -d 2>/dev/null | jq .

Validate: Check these claims match your configuration:

Claim

Expected Value

iss

<https://login.microsoftonline.com/<AZURE_TENANT_ID>>/v2.0

aud

<AZURE_APP_ID>

exp

Unix timestamp in the future (token not expired)

email

Your email address

If iss or aud don't match what's in your trust policy (Step 6), the STS call will fail.


Step 8: Assume Role and Get Temporary Credentials

Why: AssumeRoleWithWebIdentity is the STS API call that exchanges your JWT for temporary S3 credentials. This is the core of the federation flow.

8.1 Call AssumeRoleWithWebIdentity

aws sts assume-role-with-web-identity \
  --duration-seconds 900 \
  --role-arn "arn:vast::<VAST_TENANT_NAME>:role/oidc-s3-role" \
  --role-session-name "test-session" \
  --web-identity-token "$(cat /tmp/jwt_token.txt)" \
  --endpoint-url "https://<VAST_VIP>" \
  --no-verify-ssl

About the parameters:

Parameter

Meaning

--duration-seconds

How long are credentials valid (900 = 15 minutes)

--role-arn

The IAM role to assume (format: arn:vast::<TENANT_NAME>:role/<ROLE_NAME>)

--role-session-name

A label for this session (for audit logs)

--web-identity-token

The JWT from Step 8

--endpoint-url

VAST VIP address (must be assigned to the tenant)

--no-verify-ssl

Skip TLS certificate verification (for self-signed certs)

Validate: Response is JSON containing Credentials with:

  • AccessKeyId - Temporary access key

  • SecretAccessKey - Temporary secret key

  • SessionToken - Session token (required for all S3 calls)

  • Expiration - When these credentials expire

Example successful response:

{
  "Credentials": {
    "AccessKeyId": "TEMP...",
    "SecretAccessKey": "...",
    "SessionToken": "...",
    "Expiration": "2025-11-15T12:30:00Z"
  },
  "AssumedRoleUser": {
    "AssumedRoleId": "...",
    "Arn": "arn:vast::your-tenant:assumed-role/oidc-s3-role/test-session"
  }
}

8.2 Export Credentials for AWS CLI

To use these credentials with subsequent AWS CLI commands, export them as environment variables:

export AWS_ACCESS_KEY_ID="<AccessKeyId from response>"
export AWS_SECRET_ACCESS_KEY="<SecretAccessKey from response>"
export AWS_SESSION_TOKEN="<SessionToken from response>"

Or, if you captured the response in a variable:

STS_RESPONSE=$(aws sts assume-role-with-web-identity \
  --duration-seconds 900 \
  --role-arn "arn:vast::<VAST_TENANT_NAME>:role/oidc-s3-role" \
  --role-session-name "test-session" \
  --web-identity-token "$(cat /tmp/jwt_token.txt)" \
  --endpoint-url "https://<VAST_VIP>" \
  --no-verify-ssl 2>/dev/null)

export AWS_ACCESS_KEY_ID=$(echo "$STS_RESPONSE" | jq -r .Credentials.AccessKeyId)
export AWS_SECRET_ACCESS_KEY=$(echo "$STS_RESPONSE" | jq -r .Credentials.SecretAccessKey)
export AWS_SESSION_TOKEN=$(echo "$STS_RESPONSE" | jq -r .Credentials.SessionToken)

8.3 Verify Credentials Are Set

echo "Access Key: $AWS_ACCESS_KEY_ID"
echo "Expiration: $(echo "$STS_RESPONSE" | jq -r .Credentials.Expiration)"

Validate:

  • AWS_ACCESS_KEY_ID is not empty

  • Expiration time is in the future

Common Errors at This Step

Error

Cause

Fix

InvalidIdentityToken

JWT iss or aud doesn't match the trust policy

Check Step 7.5 claims match Step 5.1

ExpiredTokenException

JWT has expired

Get a new token (repeat Step 7)

AccessDenied

Role doesn't exist or wrong tenant

Verify role name and tenant name

Connection refused

Wrong VIP or VIP not assigned to tenant

Check Step 4


Step 9: Test S3 Operations

Why: Verify the complete federation flow works end-to-end by performing actual S3 operations with your temporary credentials.

Prerequisite: Ensure environment variables from Step 8.2 are exported in your current shell.

9.1 List Buckets

aws s3api list-buckets \
  --endpoint-url "https://<VAST_VIP>" \
  --no-verify-ssl

Validate: Response shows your bucket in the Buckets array.

9.2 Upload an Object (PUT)

echo "Hello from OIDC federation!" | aws s3 cp - s3://mybucket/test.txt \
  --endpoint-url "https://<VAST_VIP>" \
  --no-verify-ssl

Validate: No error output. You can verify with the next step.

9.3 List Bucket Contents

aws s3 ls s3://mybucket/ \
  --endpoint-url "https://<VAST_VIP>" \
  --no-verify-ssl

Validate: Output shows test.txt with timestamp and size.

9.4 Download an Object (GET)

aws s3 cp s3://mybucket/test.txt - \
  --endpoint-url "https://<VAST_VIP>" \
  --no-verify-ssl

Validate: Output is Hello from OIDC federation!

9.5 Test Nested Objects (Optional)

echo "Nested content" | aws s3 cp - s3://mybucket/folder/nested.txt \
  --endpoint-url "https://<VAST_VIP>" \
  --no-verify-ssl

aws s3 ls s3://mybucket/ --recursive \
  --endpoint-url "https://<VAST_VIP>" \
  --no-verify-ssl

Validate: Output shows both test.txt and folder/nested.txt.

Common Errors at This Step

Error

Cause

Fix

InvalidAccessKeyId

Credentials not set or expired

Re-export from Step 8.2, or get new STS creds

AccessDenied

Identity policy doesn't allow this action

Check Step 5 policy allows the S3 action

NoSuchBucket

Bucket doesn't exist or wrong name

Verify bucket name from Step 6

Success!

If all operations complete without errors, your OIDC STS federation is working correctly. The temporary credentials will expire after the duration specified in Step 9 (default: 15 minutes).

To continue working after expiration:

  1. If JWT is still valid: Repeat Step 8 to get new STS credentials

  2. If JWT expired: Repeat Steps 7-8 to get a new JWT and STS credentials


Minimal SDK Examples (STS + PUT)

These snippets assume /tmp/jwt_token.txt already contains a valid id_token from Step 8 and use the VAST endpoint for both STS and S3.

Python (boto3)

import boto3

# Example values – replace if different
VAST_ENDPOINT = "https://10.0.0.10"
ROLE_ARN = "arn:vast::acme-tenant:role/oidc-s3-role"
BUCKET = "demo-bucket"
OBJECT_KEY = "demo-boto3.txt"

with open("/tmp/jwt_token.txt", "r", encoding="utf-8") as f:
    web_identity_token = f.read().strip()

sts = boto3.client("sts", endpoint_url=VAST_ENDPOINT, verify=False)
creds = sts.assume_role_with_web_identity(
    RoleArn=ROLE_ARN,
    RoleSessionName="boto3-demo",
    WebIdentityToken=web_identity_token,
    DurationSeconds=900,
)["Credentials"]

s3 = boto3.client(
    "s3",
    endpoint_url=VAST_ENDPOINT,
    aws_access_key_id=creds["AccessKeyId"],
    aws_secret_access_key=creds["SecretAccessKey"],
    aws_session_token=creds["SessionToken"],
    verify=False,
)

s3.put_object(Bucket=BUCKET, Key=OBJECT_KEY, Body=b"hello from boto3")
print(f"Uploaded {OBJECT_KEY} to {BUCKET}")

Go (AWS SDK v2)

package main

import (
        "context"
        "crypto/tls"
        "fmt"
        "net/http"
        "os"
        "strings"

        "github.com/aws/aws-sdk-go-v2/aws"
        "github.com/aws/aws-sdk-go-v2/config"
        "github.com/aws/aws-sdk-go-v2/credentials"
        "github.com/aws/aws-sdk-go-v2/service/s3"
        "github.com/aws/aws-sdk-go-v2/service/sts"
)

const (
        // Example values – replace if different
        vastEndpoint = "https://10.0.0.10"
        roleArn      = "arn:vast::acme-tenant:role/oidc-s3-role"
        bucketName   = "demo-bucket"
        objectKey    = "demo-go.txt"
)

func main() {
        token, err := os.ReadFile("/tmp/jwt_token.txt")
        if err != nil {
                panic(err)
        }

        // Use the VAST endpoint for all services; disable TLS verification if needed.
        resolver := aws.EndpointResolverWithOptionsFunc(
                func(service, region string, options ...interface{}) (aws.Endpoint, error) {
                        return aws.Endpoint{URL: vastEndpoint, HostnameImmutable: true}, nil
                })

        cfg, err := config.LoadDefaultConfig(
                context.Background(),
                config.WithEndpointResolverWithOptions(resolver),
                config.WithHTTPClient(&http.Client{
                        Transport: &http.Transport{
                                TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
                        },
                }),
        )
        if err != nil {
                panic(err)
        }

        stsClient := sts.NewFromConfig(cfg)
        out, err := stsClient.AssumeRoleWithWebIdentity(context.Background(), &sts.AssumeRoleWithWebIdentityInput{
                RoleArn:          aws.String(roleArn),
                RoleSessionName:  aws.String("go-demo"),
                WebIdentityToken: aws.String(string(token)),
                DurationSeconds:  aws.Int32(900),
        })
        if err != nil {
                panic(err)
        }

        staticCreds := aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider(
                aws.ToString(out.Credentials.AccessKeyId),
                aws.ToString(out.Credentials.SecretAccessKey),
                aws.ToString(out.Credentials.SessionToken),
        ))

        s3Client := s3.NewFromConfig(cfg, func(o *s3.Options) {
                o.Credentials = staticCreds
                o.UsePathStyle = true
        })

        _, err = s3Client.PutObject(context.Background(), &s3.PutObjectInput{
                Bucket: aws.String(bucketName),
                Key:    aws.String(objectKey),
                Body:   strings.NewReader("hello from go"),
        })
        if err != nil {
                panic(err)
        }

        fmt.Printf("Uploaded %s to %s\n", objectKey, bucketName)
}

Verify Credential Expiration

Why: Confirm that temporary credentials properly expire after the specified duration.

# Wait for credentials to expire (15+ minutes)
sleep 960

# This should fail with ExpiredToken or InvalidAccessKeyId
aws s3 ls s3://mybucket/ \
  --endpoint-url "https://<VAST_VIP>" \
  --no-verify-ssl

Expected error:

An error occurred (ExpiredToken) when calling the ListObjectsV2 operation: The provided token has expired.

Helper Script

Save this as sts_auth.sh for repeated testing:

#!/bin/bash

CLIENT_ID="<AZURE_APP_ID>"
TENANT_ID="<AZURE_TENANT_ID>"
VAST_ENDPOINT="https://<VAST_VIP>"
ROLE_ARN="arn:vast::<VAST_TENANT_NAME>:role/oidc-s3-role"
TOKEN_FILE="/tmp/jwt_token.txt"

# Check if token is expired
GET_NEW_TOKEN=false
if [ ! -f "$TOKEN_FILE" ]; then
  GET_NEW_TOKEN=true
else
  EXP=$(cat "$TOKEN_FILE" | cut -d'.' -f2 | base64 -d 2>/dev/null | jq -r .exp 2>/dev/null)
  NOW=$(date +%s)
  if [ -z "$EXP" ] || [ "$NOW" -ge "$EXP" ]; then
    GET_NEW_TOKEN=true
  fi
fi

if [ "$GET_NEW_TOKEN" = true ]; then
  echo "=== Getting new token ===" >&2
  DEVICE_RESPONSE=$(curl -s -X POST \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "client_id=${CLIENT_ID}" \
    -d "scope=openid profile email" \
    "https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/devicecode")

  USER_CODE=$(echo "$DEVICE_RESPONSE" | jq -r .user_code)
  DEVICE_CODE=$(echo "$DEVICE_RESPONSE" | jq -r .device_code)

  echo "Go to: https://microsoft.com/devicelogin" >&2
  echo "Enter code: $USER_CODE" >&2
  read -p "Press Enter after completing browser login..." >&2

  curl -s -X POST \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "client_id=${CLIENT_ID}" \
    -d "grant_type=urn:ietf:params:oauth:grant-type:device_code" \
    -d "device_code=${DEVICE_CODE}" \
    "https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token" | jq -r .id_token > "$TOKEN_FILE"
else
  echo "=== Using cached token ===" >&2
fi

STS_RESPONSE=$(aws sts assume-role-with-web-identity \
  --duration-seconds 900 \
  --role-arn "$ROLE_ARN" \
  --role-session-name "test-session" \
  --web-identity-token "$(cat $TOKEN_FILE)" \
  --endpoint-url "$VAST_ENDPOINT" \
  --no-verify-ssl 2>/dev/null)

echo "export AWS_ACCESS_KEY_ID=$(echo "$STS_RESPONSE" | jq -r .Credentials.AccessKeyId)"
echo "export AWS_SECRET_ACCESS_KEY=$(echo "$STS_RESPONSE" | jq -r .Credentials.SecretAccessKey)"
echo "export AWS_SESSION_TOKEN=$(echo "$STS_RESPONSE" | jq -r .Credentials.SessionToken)"

echo "# Credentials expire: $(echo "$STS_RESPONSE" | jq -r .Credentials.Expiration)" >&2

Usage:

eval $(./sts_auth.sh)
aws s3 ls s3://mybucket/ --endpoint-url "https://<VAST_VIP>" --no-verify-ssl

Troubleshooting

"Token verification failed" on AssumeRoleWithWebIdentity

  1. Wrong token type: Use id_token, not access_token

  2. Issuer mismatch: Check iss in JWT matches trust policy principal

  3. Audience mismatch: Check aud in JWT matches trust policy condition

  4. Expired token: JWT has limited lifetime (~1 hour)

"InvalidAccessKeyId" on S3 operations

  1. Wrong VIP: Must use a VIP assigned to the tenant

  2. Expired credentials: STS credentials expire after duration-seconds

  3. Credentials not exported: Run export commands after assume-role

"InvalidSecurity" on CreateBucket

  1. Policy restriction: Identity policy may not allow s3:CreateBucket

  2. Bucket exists: Bucket names are globally unique per cluster

OIDC provider shows "PROVIDER_ACCESS_FAILURE"

  1. Verify discovery URL is reachable from VAST cluster

  2. Refresh keys: oidc refresh_keys --id <ID>


vcli Command Reference

Task

Command

Create OIDC provider

oidc create --name NAME --discovery-url URL --user-jwt-attribute email

List OIDC providers

oidc list

Refresh OIDC keys

oidc refresh_keys --id ID

Create identity policy

identitypolicy create --name NAME --tenant-id ID --policy '$JSON'

Create IAM role

iamrole create --name NAME --tenant-id ID --identity-policies-ids IDS --trust-policy '$JSON'

List IAM roles

iamrole list

Show IAM role

iamrole show --id ID --identity-policies

Create S3 view

view create --path PATH --bucket NAME --protocols S3 --policy-id ID --tenant-id ID --bucket-owner EMAIL --create-dir

Revoke role keys

iamrole revoke_access_keys --id ID