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

# AWS federation

> Use Basaltic VM identity in AWS and AWS workload identity in Basaltic, with temporary credentials in both directions.

Both directions use a separate identity JWT followed by a credential exchange.
Neither requires copying a long-lived access key into the destination workload.

| Direction | Mint the identity JWT | Exchange it |
| - | - | - |
| Basaltic → AWS | Basaltic VM metadata endpoint or token API | AWS STS `AssumeRoleWithWebIdentity` |
| AWS → Basaltic | AWS STS `GetWebIdentityToken` | Basaltic `AssumeRoleWithWebIdentity` |

## Basaltic to AWS

You can also use an authenticated Basaltic role or service-account session. Call the [token API](https://docs.basaltic.sh/iam/oidc.md#generate-a-token-through-the-api) with `audience: "sts.amazonaws.com"`, then exchange its JWT with AWS STS. In the AWS trust policy below, use the exact role or service-account subject from that proof. The VM flow below uses the metadata endpoint and guest helper.

You need an administrator in the target AWS account, a Basaltic VM with an
attached role, and guest-agent `1.15.11-1` or later for automatic file refresh.
The VM must reach AWS STS over HTTPS. AWS must reach Basaltic's public discovery
and JWKS endpoints.

### Permit the VM to request an AWS proof

Grant the attached Basaltic role `iam:GetWebIdentityToken` on its own CRN,
restricted to `basalt:IdentityTokenAudience = sts.amazonaws.com`. Use the
[VM identity policy example](https://docs.basaltic.sh/iam/vm-identity.md), then obtain a token inside the VM.
Its subject has this immutable form:

```text theme={null}
org:ORGANIZATION_UUID:account:ACCOUNT_UUID:instance:INSTANCE_UUID
```

Use your actual organization, account, and instance UUIDs in the AWS trust
policy. Do not use a VM name or a shared role name as a substitute.

### Register Basaltic in AWS

In the target AWS account, register the issuer `https://iam.basaltic.sh` with
client ID (audience) `sts.amazonaws.com`. An administrator can run:

> [tab] CLI
>
> ```bash theme={null}
> aws iam create-open-id-connect-provider \
>   --url https://iam.basaltic.sh \
>   --client-id-list sts.amazonaws.com
> ```

If that issuer is already registered, reuse its provider ARN and confirm its
client ID list includes `sts.amazonaws.com`. Use the issuer URL, not a JWKS URL.
AWS obtains the verification keys from Basaltic's discovery document.
See [AWS OIDC provider setup](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html).

Create an AWS role with this trust policy, replacing `123456789012` with the
target AWS account ID and the subject with your VM's exact subject:

```json aws-trust.json theme={null}
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {
      "Federated": "arn:aws:iam::123456789012:oidc-provider/iam.basaltic.sh"
    },
    "Action": "sts:AssumeRoleWithWebIdentity",
    "Condition": {
      "StringEquals": {
        "iam.basaltic.sh:aud": "sts.amazonaws.com",
        "iam.basaltic.sh:sub": "org:ORGANIZATION_UUID:account:ACCOUNT_UUID:instance:INSTANCE_UUID"
      }
    }
  }]
}
```

> [tab] CLI
>
> ```bash theme={null}
> aws iam create-role --role-name BasalticWorker \
>   --assume-role-policy-document file://aws-trust.json
> ```

Attach an AWS permission policy for the specific resources the application
needs. The trust policy permits the exchange; it does not grant S3 or other
service permissions. Keep the subject restriction even if the provider is shared
by several roles or Basaltic accounts.

### Configure the workload

Start the [token-file helper](https://docs.basaltic.sh/iam/vm-identity.md#maintain-a-token-file) as the
application user. With the systemd example from that page, configure the
application's environment:

```bash theme={null}
export AWS_ROLE_ARN=arn:aws:iam::123456789012:role/BasalticWorker
export AWS_WEB_IDENTITY_TOKEN_FILE=/run/basaltic-aws-identity/token
export AWS_ROLE_SESSION_NAME=basaltic-worker
export AWS_REGION=us-east-1
export AWS_EC2_METADATA_DISABLED=true
```

`AWS_EC2_METADATA_DISABLED=true` prevents an AWS client from falling back to the
Basaltic metadata credential endpoint, whose credentials are for Basaltic.
Do not configure static AWS access keys or another credential profile in this
application: those can take precedence over the web-identity provider.

The AWS CLI and compatible SDKs exchange the file's current JWT and refresh
AWS credentials as needed. The guest helper independently refreshes the file.
Check support in your SDK's [assume-role credential provider](https://docs.aws.amazon.com/sdkref/latest/guide/feature-assume-role-credentials.html).

```bash theme={null}
aws sts get-caller-identity
```

The returned ARN should be an assumed-role session of `BasalticWorker` in your
AWS account. This command proves which identity is in use; test one permitted
application operation too.

To inspect the exchange directly without an existing AWS credential:

```bash theme={null}
umask 077
aws sts assume-role-with-web-identity --no-sign-request \
  --region us-east-1 \
  --role-arn arn:aws:iam::123456789012:role/BasalticWorker \
  --role-session-name basaltic-check \
  --web-identity-token file:///run/basaltic-aws-identity/token \
  --duration-seconds 900 > aws-session.json
```

Treat `aws-session.json` as a credential and delete it after use. The returned
access key, secret key, and session token are AWS credentials. Basaltic does not
hold or refresh them on the application's behalf.

AWS sessions default to one hour and can last from 900 seconds to the role's
maximum. They may outlive the five-minute Basaltic proof. Removing a VM's Basaltic
role prevents new metadata proofs; use AWS's session revocation controls to stop AWS
sessions already issued. See [AWS STS session behavior](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html).

## AWS to Basaltic

AWS outbound federation turns an existing AWS identity into a separate signed
JWT. An EC2 workload uses its instance-profile credentials to authenticate that
request. Other AWS credentials, including an assumed role or IAM Identity Center
session, can authenticate it too when permitted.

### Enable AWS outbound federation

An AWS administrator enables this once for the source account:

> [tab] CLI
>
> ```bash theme={null}
> aws iam enable-outbound-web-identity-federation
> aws iam get-outbound-web-identity-federation-info
> ```

Record the account-specific issuer returned in `IssuerIdentifier`. It resembles
`https://ACCOUNT-SPECIFIC-ID.tokens.sts.global.api.aws`.
Do not substitute the ordinary STS API endpoint.
See [AWS outbound federation setup](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_outbound_getting_started.html).

Grant the source AWS role permission to mint proofs for your Basaltic audience.
This AWS policy permits only the chosen audience and at most five minutes:

```json theme={null}
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": "sts:GetWebIdentityToken",
    "Resource": "*",
    "Condition": {
      "ForAllValues:StringEquals": {
        "sts:IdentityTokenAudience": "urn:example:basaltic:production"
      },
      "NumericLessThanEquals": {"sts:DurationSeconds": 300}
    }
  }]
}
```

### Register the AWS issuer in Basaltic

Register that exact issuer in the Basaltic account containing the target role.

> [tab] Console
>
> Open **IAM** → **OIDC providers** → **Create provider**. Enter `aws`, the
> account-specific issuer, and `urn:example:basaltic:production` as the allowed
> audience.

> [tab] API
>
> ```http theme={null}
> POST https://iam.basaltic.sh/v1/oidc-providers
> Authorization: Bearer YOUR_BASALTIC_ACCESS_TOKEN
> X-Account-Id: production
> Content-Type: application/json
>
> {
>   "name": "aws",
>   "issuer": "https://ACCOUNT-SPECIFIC-ID.tokens.sts.global.api.aws",
>   "audiences": ["urn:example:basaltic:production"]
> }
> ```

> [tab] CLI
>
> ```bash theme={null}
> basaltic iam oidc-provider create --account-id production \
>   --name aws --issuer "$AWS_ISSUER" \
>   --audiences urn:example:basaltic:production
> ```

Create a Basaltic role named `aws-worker`, attach its required Basaltic permission
policies, and use this trust policy with your actual source AWS IAM role ARN:

```json theme={null}
{
  "principals": ["crn:iam::production:oidc-provider/aws"],
  "conditions": [
    {
      "operator": "equals",
      "key": "basalt:webidentity:Subject",
      "values": ["arn:aws:iam::123456789012:role/BasalticClient"]
    },
    {
      "operator": "equals",
      "set_operator": "for_any_value",
      "key": "basalt:webidentity:Audience",
      "values": ["urn:example:basaltic:production"]
    }
  ]
}
```

Use the IAM role ARN in the verified JWT's `sub`, including its path. It differs
from the STS `arn:aws:sts::...:assumed-role/.../session` ARN returned by
`GetCallerIdentity`. See [AWS token claims](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_outbound_token_claims.html).

> [!WARNING]
>
> This trust identifies the AWS IAM role. EC2 instances sharing that role share
> its subject. Basaltic's generic OIDC verifier does not map AWS custom EC2
> claims into trust conditions. Use a dedicated AWS role when workloads need
> separate Basaltic access; do not claim this policy selects one EC2 instance.

### Mint and exchange from AWS

Use a regional STS endpoint and an AWS CLI version that includes
`get-web-identity-token`. Run this under the source AWS role:

```bash theme={null}
umask 077
aws sts get-web-identity-token --region us-east-1 \
  --audience urn:example:basaltic:production \
  --signing-algorithm RS256 --duration-seconds 300 \
  --query WebIdentityToken --output text > aws-identity.jwt

jq -n --rawfile token aws-identity.jwt \
  '{role:"crn:iam::production:role/aws-worker",
    web_identity_token:($token | rtrimstr("\n")),
    session_name:"aws-workload", duration_seconds:900}' \
  > exchange.json

curl --fail --silent --show-error \
  -H 'Content-Type: application/json' --data-binary @exchange.json \
  https://iam.basaltic.sh/v1/assume-role-with-web-identity \
  > basaltic-session.json

rm -f aws-identity.jwt exchange.json
```

Basaltic accepts both AWS `RS256` and `ES384` identity tokens. AWS proof lifetimes
range from 60 to 3600 seconds, defaulting to 300. The regional endpoint is
required; the global STS endpoint does not serve this mint operation.
See [GetWebIdentityToken](https://docs.aws.amazon.com/STS/latest/APIReference/API_GetWebIdentityToken.html).

Use the returned Basaltic bearer token for APIs or its temporary access-key
credentials for Basaltic S3. Mint and exchange again before the Basaltic session
expires. Keep the response private and remove it when the session is no longer
needed.

Deleting the Basaltic provider or changing its audiences blocks new exchanges.
Revoke existing Basaltic sessions separately. Disabling AWS outbound federation
is an account-wide change, so use it only when no other integration needs it.
AWS-issued outbound JWTs cannot be exchanged back into AWS through
`AssumeRoleWithWebIdentity`.

## Diagnose the correct side

| Symptom | Check |
| - | - |
| Basaltic metadata returns `401` | Obtain a fresh IMDSv2 token on the same VM |
| Basaltic metadata returns `403` | Attached role, `iam:GetWebIdentityToken`, audience condition, and permission boundary |
| AWS reports `InvalidIdentityToken` | Use the VM identity JWT, then check issuer discovery, JWKS, audience, and expiration |
| AWS exchange is denied | Provider ARN and exact `aud`/`sub` conditions in the AWS role trust policy |
| AWS CLI reports `FeatureDisabled` | Enable outbound federation in the source AWS account |
| AWS `GetWebIdentityToken` is denied | Source AWS role permission and its audience/duration conditions |
| Basaltic exchange returns `401` | Account-specific AWS issuer, provider audience, and JWT validity |
| Basaltic exchange returns `403` | IAM role ARN in `sub` and `for_any_value` on the audience condition |
| An application uses an unexpected AWS identity | Remove overriding static credentials/profiles and confirm its web-identity token-file settings |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.