--- title: "OIDC S3 Federation Guide for VAST with Azure Entra ID" slug: "oidc-s3-federation-guide-for-vast-with-azure-entra-id" description: "Configure OIDC-based S3 federation with Azure Entra ID using VAST for secure, temporary credentials and streamlined access management." updated: 2026-10-28T20:02:03Z published: 2026-10-28T20:02:03Z canonical: "docs.vastdata.com/oidc-s3-federation-guide-for-vast-with-azure-entra-id" --- > ## Documentation Index > Fetch the complete documentation index at: https://docs.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 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](https://cdn.us.document360.io/89b91113-5dc6-49db-86ec-57b16dc8ce56/Images/Documentation/sts-flow.svg?sv=2026-02-06&spr=https&st=2026-11-08T09%3A18%3A51Z&se=2026-11-08T10%3A18%3A51Z&sr=c&sp=r&sig=ab7rb4te9Sgo1d%2BFouULbxWrmVRD74xsczkaTjPmcYY%3D) ### 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](https://cdn.us.document360.io/89b91113-5dc6-49db-86ec-57b16dc8ce56/Images/Documentation/sts-token-expire.svg?sv=2026-02-06&spr=https&st=2026-11-08T09%3A18%3A51Z&se=2026-11-08T10%3A18%3A51Z&sr=c&sp=r&sig=ab7rb4te9Sgo1d%2BFouULbxWrmVRD74xsczkaTjPmcYY%3D) **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):** ```bash # 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 - [https://www.jwt.io/](https://www.jwt.io/) - [https://pyjwt.readthedocs.io/en/stable/](https://pyjwt.readthedocs.io/en/stable/) - **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 ```bash az login ``` **Validate**: You should see your subscription details and be returned to the command prompt. ### 1.2 Get your Azure Tenant ID ```bash 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 ```bash 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 ```bash 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 ```bash az ad sp create --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: ```bash # Azure Configuration AZURE_TENANT_ID="" AZURE_APP_ID="" # VAST Configuration VAST_TENANT_NAME="" VAST_TENANT_ID= VAST_VIP="" VAST_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 ```bash az ad app update --id --is-fallback-public-client true ``` **Why**: Required for device code flow (no client secret needed). **Validate**: No output on success. Verify with: ```bash az ad app show --id --query "isFallbackPublicClient" ``` Expected: `true` ### 1.7 Grant OpenID Permissions ```bash # az ad app permission add \  --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](https://learn.microsoft.com/en-us/graph/permissions-reference) **Validate**: No output on success. ### 1.8 Admin Consent (if you have admin rights) ```bash az ad app permission admin-consent --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 ```plaintext https://login.microsoftonline.com//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 ```bash vcli: admin> oidc create \  --name "entra-id-v1" \  --discovery-url "https://login.microsoftonline.com//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 ```bash vcli: admin> oidc list ``` ![Screenshot 2025-11-25 at 3.19.10 PM.png](https://cdn.us.document360.io/89b91113-5dc6-49db-86ec-57b16dc8ce56/Images/Documentation/Screenshot%202025-11-25%20at%203.19.10%E2%80%AFPM.png?sv=2026-02-06&spr=https&st=2026-11-08T09%3A18%3A51Z&se=2026-11-08T10%3A18%3A51Z&sr=c&sp=r&sig=ab7rb4te9Sgo1d%2BFouULbxWrmVRD74xsczkaTjPmcYY%3D) **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`): ```bash vcli: admin> oidc refresh_keys --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](https://cdn.us.document360.io/89b91113-5dc6-49db-86ec-57b16dc8ce56/Images/Documentation/Screenshot%202025-11-25%20at%203.25.04%E2%80%AFPM.png?sv=2026-02-06&spr=https&st=2026-11-08T09%3A18%3A51Z&se=2026-11-08T10%3A18%3A51Z&sr=c&sp=r&sig=ab7rb4te9Sgo1d%2BFouULbxWrmVRD74xsczkaTjPmcYY%3D) 1. Navigate to **VAST Web UI** → **User Management** → **Tenants** 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 ```bash 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 ```bash vcli: admin> identitypolicy create \  --name "s3-full-access" \  --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 to`allow`or `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 ```bash vcli: admin> identitypolicy list --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 ```bash vcli: admin> iamrole create \  --name "oidc-s3-role" \  --tenant-id \  --description "Role for OIDC federated S3 access" \  --identity-policies-ids \  --trust-policy '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Federated":"oidc-provider/login.microsoftonline.com//v2.0"},"Action":"sts:AssumeRoleWithWebIdentity","Condition":{"StringEquals":{"login.microsoftonline.com//v2.0:aud":""}}}]}' ``` **Validate**: Command returns successfully with role details. ### 5.3 Verify the Role Configuration ```bash vcli: admin> iamrole list ``` **Validate**: Your role appears. Note the `ID` column value. To see full details, including the attached identity policy: ```bash vcli: admin> iamrole show --id --identity-policies ``` **Validate**: The output shows your identity policy attached to the role. ```bash 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: ```bash 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) ```bash vcli: admin> view create \  --path /mybucket \  --bucket mybucket \  --protocols S3 \  --policy-id \  --tenant-id \  --bucket-owner \  --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 ```bash vcli: admin> view list --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 ```bash curl -s -X POST \  -H "Content-Type: application/x-www-form-urlencoded" \  -d "client_id=" \  -d "scope=openid profile email" \  "https://login.microsoftonline.com//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: ```json {  "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: ```bash curl -s -X POST \  -H "Content-Type: application/x-www-form-urlencoded" \  -d "client_id=" \  -d "grant_type=urn:ietf:params:oauth:grant-type:device_code" \  -d "device_code=" \  "https://login.microsoftonline.com//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: ```bash # 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: ```bash 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 ```bash aws sts assume-role-with-web-identity \  --duration-seconds 900 \  --role-arn "arn:vast:::role/oidc-s3-role" \  --role-session-name "test-session" \  --web-identity-token "$(cat /tmp/jwt_token.txt)" \  --endpoint-url "https://" \  --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: ```json {  "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: ```bash export AWS_ACCESS_KEY_ID="" export AWS_SECRET_ACCESS_KEY="" export AWS_SESSION_TOKEN="" ``` Or, if you captured the response in a variable: ```bash STS_RESPONSE=$(aws sts assume-role-with-web-identity \  --duration-seconds 900 \  --role-arn "arn:vast:::role/oidc-s3-role" \  --role-session-name "test-session" \  --web-identity-token "$(cat /tmp/jwt_token.txt)" \  --endpoint-url "https://" \  --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 ```bash 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 ```bash aws s3api list-buckets \  --endpoint-url "https://" \  --no-verify-ssl ``` **Validate**: Response shows your bucket in the `Buckets` array. ### 9.2 Upload an Object (PUT) ```bash echo "Hello from OIDC federation!" | aws s3 cp - s3://mybucket/test.txt \  --endpoint-url "https://" \  --no-verify-ssl ``` **Validate**: No error output. You can verify with the next step. ### 9.3 List Bucket Contents ```bash aws s3 ls s3://mybucket/ \  --endpoint-url "https://" \  --no-verify-ssl ``` **Validate**: Output shows `test.txt` with timestamp and size. ### 9.4 Download an Object (GET) ```bash aws s3 cp s3://mybucket/test.txt - \  --endpoint-url "https://" \  --no-verify-ssl ``` **Validate**: Output is `Hello from OIDC federation!` ### 9.5 Test Nested Objects (Optional) ```bash echo "Nested content" | aws s3 cp - s3://mybucket/folder/nested.txt \  --endpoint-url "https://" \  --no-verify-ssl aws s3 ls s3://mybucket/ --recursive \  --endpoint-url "https://" \  --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) ```plaintext 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) ```go 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. ```bash # Wait for credentials to expire (15+ minutes) sleep 960 # This should fail with ExpiredToken or InvalidAccessKeyId aws s3 ls s3://mybucket/ \  --endpoint-url "https://" \  --no-verify-ssl ``` Expected error: ```plaintext 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: ```bash #!/bin/bash CLIENT_ID="" TENANT_ID="" VAST_ENDPOINT="https://" ROLE_ARN="arn:vast:::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: ```bash eval $(./sts_auth.sh) aws s3 ls s3://mybucket/ --endpoint-url "https://" --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` |