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

# Identity tokens

> Let a Sailbox use AWS, Google Cloud, or another service without storing a key

Sail can sign OpenID Connect identity tokens for a Sailbox. A token proves
which Sailbox holds it and which organization the Sailbox belongs to. AWS,
Google Cloud, and many other services accept these tokens in place of a
stored key: you tell them to trust Sail's issuer,
`https://oidc.sailresearch.com`, and they exchange a token for short-lived
credentials of their own.

You ask for tokens in the Sailbox's [egress policy](https://docs.sailresearch.com/sailboxes-egress-policy.md).
Each entry under `oidc_identity_tokens` gives the audience, the service the
token is for, and the file in the Sailbox to keep the token in:

```json theme={null}
{
  "oidc_identity_tokens": [
    { "audience": "sts.amazonaws.com", "path": "/run/sailbox/oidc/aws" }
  ]
}
```

A token is valid for one hour, and Sail rewrites the file before it
expires, so code that reads the file when it needs a token always gets a
current one. Any user in the Sailbox can read the file. Only a saved policy
may set `oidc_identity_tokens`.

## AWS

Create an identity provider for Sail in your AWS account, once:

```bash theme={null}
aws iam create-open-id-connect-provider \
  --url https://oidc.sailresearch.com \
  --client-id-list sts.amazonaws.com
```

Create a role whose trust policy accepts your organization's Sailboxes.
Replace `<account_id>` and `<org_id>`.
[`GET /v1/whoami`](https://docs.sailresearch.com/sailboxes-http-api#authentication) returns your
organization ID.

```json trust-policy.json theme={null}
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::<account_id>:oidc-provider/oidc.sailresearch.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": { "oidc.sailresearch.com:aud": "sts.amazonaws.com" },
        "StringLike": { "oidc.sailresearch.com:sub": "org:<org_id>:sailbox:*" }
      }
    }
  ]
}
```

```bash theme={null}
aws iam create-role --role-name sailbox --assume-role-policy-document file://trust-policy.json
```

Attach the permissions the Sailbox needs to the role. Then save a policy
that asks for a token:

```json aws.json theme={null}
{
  "oidc_identity_tokens": [
    { "audience": "sts.amazonaws.com", "path": "/run/sailbox/oidc/aws" }
  ]
}
```

Create a Sailbox with the policy and the two variables AWS SDKs and the AWS
CLI read to find the role and the token. Then, with the AWS CLI installed in
the Sailbox, check which role it has:

```bash theme={null}
sail egress-policy create aws --file aws.json
sail box create --app my-app --name aws-job --egress-policy <policy-id> \
  --env AWS_ROLE_ARN=arn:aws:iam::<account_id>:role/sailbox \
  --env AWS_WEB_IDENTITY_TOKEN_FILE=/run/sailbox/oidc/aws
sail box exec <sailbox-id> -- aws sts get-caller-identity
```

The last command prints your role. The AWS SDKs get new credentials from
the token file on their own when the old ones expire. In Sail's SDKs, pass
the variables as `env` to [`Sailbox.create`](https://docs.sailresearch.com/sailbox-sdk#create-env).

## Google Cloud

Create a workload identity pool and a provider for Sail that accepts only
your organization's tokens:

```bash theme={null}
gcloud iam workload-identity-pools create sail --location=global
gcloud iam workload-identity-pools providers create-oidc sail \
  --location=global \
  --workload-identity-pool=sail \
  --issuer-uri=https://oidc.sailresearch.com \
  --attribute-mapping=google.subject=assertion.sub \
  --attribute-condition="assertion.org_id == '<org_id>'"
```

Grant roles to the pool's identities, for example on a bucket:

```bash theme={null}
gcloud storage buckets add-iam-policy-binding gs://<bucket> \
  --role=roles/storage.objectViewer \
  --member="principalSet://iam.googleapis.com/projects/<project_number>/locations/global/workloadIdentityPools/sail/*"
```

Save a policy that asks for a token issued to the provider:

```json google-policy.json theme={null}
{
  "oidc_identity_tokens": [
    {
      "audience": "//iam.googleapis.com/projects/<project_number>/locations/global/workloadIdentityPools/sail/providers/sail",
      "path": "/run/sailbox/oidc/google"
    }
  ]
}
```

Write a credential configuration that tells Google's client libraries to
read the token file. It holds no secret. Create a Sailbox with the policy
and a variable that points the client libraries at the configuration, then
copy the configuration in:

```bash theme={null}
gcloud iam workload-identity-pools create-cred-config \
  projects/<project_number>/locations/global/workloadIdentityPools/sail/providers/sail \
  --credential-source-file=/run/sailbox/oidc/google \
  --output-file=google.json
sail egress-policy create google --file google-policy.json
sail box create --app my-app --name gcp-job --egress-policy <policy-id> \
  --env GOOGLE_APPLICATION_CREDENTIALS=/etc/google.json
sail box cp --user 0:0 google.json <sailbox-id>:/etc/google.json
```

To act as a service account instead, grant the pool's identities
`roles/iam.workloadIdentityUser` on the service account and add
`--service-account=<name>@<project>.iam.gserviceaccount.com` to the
`create-cred-config` command.

## Other services

Any service that accepts OpenID Connect tokens can trust Sail's issuer the
same way. Use the audience the service expects, and have your code read the
token from its file each time it calls the service.

## Token claims

Every token Sail signs identifies the Sailbox and your organization:

| Claim | Value |
| - | - |
| `iss` | `https://oidc.sailresearch.com` |
| `sub` | `org:<org_id>:sailbox:<sailbox_id>` |
| `aud` | The `audience` of the policy entry |
| `org_id` | Your organization ID |
| `sailbox_id` | The Sailbox ID |
| `exp` | One hour after Sail signed the token |

## When a policy changes

Setting a policy on a running Sailbox writes its token files before the call
returns.

When you remove an entry, or clear the policy, Sail stops rewriting that
entry's file. The token already in it stays valid until it expires, within
an hour.

A Sailbox that starts from another one's checkpoint gets new tokens, which
name the new Sailbox, before it can reach the network. Credentials that a
process got with the other Sailbox's token before the checkpoint stay in its
memory until they expire.

## Who your cloud trusts

Anyone who can set an egress policy on one of your Sailboxes can ask for a
token with any audience. A trust rule that matches your whole organization
therefore trusts every Sailbox your organization runs. To trust fewer, match
a specific `sub`, such as `org:<org_id>:sailbox:<sailbox_id>`.

Code in the Sailbox can read the token and the credentials the cloud gives
for it, and can use them anywhere until they expire. Give the role or
service account only what the work in the Sailbox needs.

## Allowlists

The Sailbox exchanges its token with the cloud itself. Under an
[allowlist](https://docs.sailresearch.com/reference/egress-policy.md#entries), add the hosts that exchange
uses as well as the ones your code calls. For AWS, add `*.amazonaws.com`,
or list the STS host for your region, such as `sts.us-west-2.amazonaws.com`,
and the hosts of the services you use: AWS SDKs exchange the token with the
STS endpoint of the region they are configured for. For Google Cloud, add
`sts.googleapis.com` and the Google APIs you use.
