> ## 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.

# VM identity tokens

> Obtain audience-bound OIDC proofs from a Basaltic instance and refresh them for external services.

A Basaltic VM can obtain an identity JWT from its metadata service without
storing a long-lived credential. Use it with [AWS STS](https://docs.basaltic.sh/iam/aws-federation.md) or
another service that trusts Basaltic's OIDC issuer.

The VM needs an [attached IAM role](https://docs.basaltic.sh/compute/instances.md#change-an-existing-instances-role). Its policies
must allow `iam:GetWebIdentityToken` on that role's CRN. Restrict the audience to
the destination service:

```json theme={null}
{
  "version": "2024-01-01",
  "statements": [{
    "effect": "allow",
    "actions": ["iam:GetWebIdentityToken"],
    "resources": ["crn:iam::production:role/aws-worker"],
    "conditions": [{
      "operator": "equals",
      "key": "basalt:IdentityTokenAudience",
      "values": ["sts.amazonaws.com"]
    }]
  }]
}
```

Apply this as an [inline or attached role policy](https://docs.basaltic.sh/iam/policies.md). Role boundaries
also apply. `basalt:IdentityTokenDurationSeconds` contains the requested lifetime
in seconds, including the default `300` when no duration is supplied.

## Request a token inside the VM

First obtain an IMDSv2 session token. Then request an identity JWT with one
external audience:

```bash theme={null}
umask 077
IMDS_TOKEN=$(curl --fail --silent --show-error --noproxy '*' \
  -X PUT -H 'X-aws-ec2-metadata-token-ttl-seconds: 300' \
  http://169.254.169.254/latest/api/token)

curl --fail --silent --show-error --noproxy '*' \
  -H "X-aws-ec2-metadata-token: $IMDS_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"audience":"sts.amazonaws.com","duration_seconds":300}' \
  http://169.254.169.254/latest/basaltic/iam/identity-token \
  > identity-response.json

jq -r .token identity-response.json > identity.jwt
rm -f identity-response.json
unset IMDS_TOKEN
```

The JSON response has `token` and `expires_at`. Tokens default to 300 seconds;
you can request 60–300 seconds. Repeated requests can return a still-valid cached
token, so use the returned expiration. Every request checks current role
permissions, including cache hits.

This endpoint always requires IMDSv2, even if ordinary metadata permits IMDSv1.
It accepts only `audience` and optional `duration_seconds`. The platform supplies
the VM identity; a request cannot choose another VM, account, organization, or
subject. The audience `basaltic` is reserved for API access and is rejected here.

The three tokens involved are different:

| Token | Used for |
| - | - |
| IMDSv2 session token | Access to this VM's metadata service |
| VM identity JWT | Proof presented to an external credential exchange |
| Basaltic API access token | Calls to Basaltic APIs through the attached role |

## Identity claims

The issuer is `https://iam.basaltic.sh`. JWTs use ES256 and include `iss`, `sub`,
`aud`, `iat`, `exp`, and `jti`.

The subject is:

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

Use the exact subject in the destination's trust policy. These UUIDs are
immutable. Renaming a resource does not change its identity, and recreating a VM
with the same name produces a different subject. Two VMs sharing a Basaltic role
still have different subjects.

The destination must verify the signature against the issuer's discovered
[JWKS](https://docs.basaltic.sh/iam/oidc.md#discovery-and-keys) before trusting these claims. Decoding a JWT
without verifying it is useful for inspecting its contents, but proves nothing.

## Maintain a token file

Guest-agent version `1.15.11-1` or later includes an `identity-token` command.
Upgrade `basaltic-guest-agent` from your VM's configured Basaltic package
repository before using it.

Run the helper as the same user as your application, with a private output
directory. It refreshes the proof before expiration and atomically replaces a
mode-0600 file:

```bash theme={null}
install -d -m 0700 "$HOME/.local/run/basaltic"
basaltic-guest-agent identity-token \
  --audience sts.amazonaws.com \
  --output "$HOME/.local/run/basaltic/aws.jwt"
```

The command stays running. Use `--once` for a single short task; that file is not
refreshed. Continuous mode removes the file when stopped and removes an expired
proof if refresh has failed. A second helper cannot own the same output file.

For a service running as the existing `app` user, use a separate systemd unit:

```ini /etc/systemd/system/basaltic-aws-identity.service theme={null}
[Unit]
Description=Basaltic VM identity for AWS
Wants=network-online.target
After=network-online.target

[Service]
User=app
Group=app
RuntimeDirectory=basaltic-aws-identity
RuntimeDirectoryMode=0700
UMask=0077
ExecStart=/usr/bin/basaltic-guest-agent identity-token --audience sts.amazonaws.com --output /run/basaltic-aws-identity/token
Restart=on-failure
RestartSec=5
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target
```

```bash theme={null}
sudo systemctl daemon-reload
sudo systemctl enable --now basaltic-aws-identity.service
```

Configure the application to wait for the token file before its first AWS
request. The helper refreshes identity proofs; the AWS SDK exchanges them and
refreshes AWS credentials. See the [AWS configuration](https://docs.basaltic.sh/iam/aws-federation.md#configure-the-workload).

## Revocation and limits

Removing the role, changing its permissions, or revoking the underlying Basaltic
role session blocks further issuance through that authorization. Existing
identity JWTs remain usable until their expiration, at most five minutes.
They are bearer proofs, not single-use tokens.

Credentials already issued by an external service have that service's own
lifetime and revocation rules. A five-minute proof can produce a longer AWS
session. To stop existing AWS access, use AWS session revocation controls too.

All processes that can reach the VM metadata endpoint can request proofs under
the attached role. A token file's permissions limit local readers of that file;
they do not isolate processes that can independently access metadata. Use
separate VMs for workloads that require separate cloud identities.

Detaching a role stops new metadata proofs. Existing Basaltic sessions can still call the [token API](https://docs.basaltic.sh/iam/oidc.md#generate-a-token-through-the-api) until revoked or expired; revoke them too when removing access.


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