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

# Roles and credentials

> Access keys, roles and trust policies, temporary credentials, and giving an instance an identity.

A **user** is a person and signs in through the console. Everything
programmatic uses one of two things instead: a **service account** with an
access key, or a **role** that something assumes to get temporary credentials.

<CardGroup cols={2}>
  <Card title="Service accounts" icon="bot" href="#service-accounts">
    Long-lived access keys for scripts and CI.
  </Card>

  <Card title="Roles" icon="user-check" href="#roles">
    Permissions something else borrows, gated by a trust policy.
  </Card>

  <Card title="Temporary credentials" icon="clock" href="#assuming-a-role">
    Assume a role, optionally scoped down further.
  </Card>

  <Card title="Instance identity" icon="server" href="#giving-an-instance-an-identity">
    A VM that gets credentials with no secret to deploy.
  </Card>
</CardGroup>

## Service accounts

A service account is a non-human identity that holds access keys. Create one,
give it permissions, then create a credential on it:

<Tabs>
  <Tab title="Console">
    Go to **IAM → Service Accounts** and choose **Create Service Account**.
    Under **Service Account Details**, give it a **Name** — lowercase letters,
    numbers and hyphens, starting with a letter — and optionally a
    **Description**.

    On the service account itself, the **Credentials** card has **Create
    Credential**: a **Name**, and an **Expires At** you can leave empty for a
    credential that does not expire.

    The secret then appears in a **Credential Created** dialog, with **Copy
    access key ID** and **Copy secret access key**. That dialog is the only
    place it is ever shown.
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    POST /v1/service-accounts
    { "name": "deploy-bot" }

    POST /v1/service-accounts/{service_account_id}/credentials
    { "name": "ci-key" }
    ```
  </Tab>
</Tabs>

The response carries `access_key_id` and `secret_access_key`.

<Warning>
  The secret is returned **once**, at creation, and is not stored in a form the
  API can show you again. If you lose it, delete the credential and create
  another.
</Warning>

A service account starts with **no permissions**. Attach a policy directly, or
put it in a group that carries one — groups hold service accounts as well as
users.

<Tabs>
  <Tab title="Console">
    On the service account, **Policies → Attach Policy** picks managed policies
    to attach, and **Groups → Add to Group** puts it in a group.
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    POST /v1/service-accounts/{service_account_id}/policies
    POST /v1/service-accounts/{service_account_id}/groups
    ```
  </Tab>
</Tabs>

## Roles

A role is a set of permissions with **no credentials of its own**. Something
else assumes it and gets temporary credentials that authorize *as the role*.

A role has two halves:

<Columns cols={2}>
  <Card title="Permission policies" icon="file-text">
    What the role can do. Attached exactly like a user's or service account's
    policies.
  </Card>

  <Card title="Trust policy" icon="handshake">
    Who is allowed to assume it. Without this, nobody can.
  </Card>
</Columns>

<Note>
  In the console both halves are one screen apart: **IAM → Roles → Create
  Role** has **Role Details** and a **Trust Policy** JSON editor, and the saved
  role's **Attached Policies** card takes permission policies through **Attach
  Policy**.
</Note>

### Trust policies

The trust policy lists **CRN patterns matched against the caller's own CRN**:

```json theme={null}
{
  "principals": [
    "crn:iam:::service-account/7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "crn:compute:*:my-account:instance/*"
  ],
  "conditions": [
    { "operator": "ip_address", "key": "basalt:SourceIp", "values": ["10.0.0.0/8"] }
  ]
}
```

`principals` gates **who**; `conditions` gates **under what circumstances**.
Both must hold.

| Caller           | Presents                                       |
| ---------------- | ---------------------------------------------- |
| Service account  | `crn:iam:::service-account/<id>`               |
| User             | `crn:iam:::user/<id>`                          |
| Compute instance | `crn:compute:<region>:<account>:instance/<id>` |

<Note>
  `*` is the only wildcard and the colon/slash layout is compared literally. A
  pattern written in any other shape matches nothing — it does not fail loudly,
  it simply never matches, so check the shape when a trust policy seems to be
  ignored.
</Note>

## Assuming a role

<Note>
  This is **API only**, along with everything below it in this section. Nothing
  in the console mints temporary credentials or lists STS sessions — a person
  signing in gets their own identity, never a role's.
</Note>

```bash theme={null}
POST https://iam.basaltic.sh/v1/assume-role
{
  "role_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "duration_seconds": 3600
}
```

The response carries `access_key_id`, `secret_access_key`, a `session_token`
and an `expiration`. Sign requests as usual, and send the session token as
`X-Amz-Security-Token` — it must also appear in `SignedHeaders`. See
[authentication](/authentication).

<ResponseField name="duration_seconds" type="900–43200, default 3600">
  15 minutes to 12 hours.
</ResponseField>

### Scoping a session down

`policy` on the assume-role call attaches a **session policy** to the
credentials being minted:

```json theme={null}
{
  "role_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "policy": {
    "version": "2024-01-01",
    "statements": [{
      "effect": "allow",
      "actions": ["storage:GetObject"],
      "resources": ["crn:storage:sa-saopaulo-1:my-account:bucket/reports/*"]
    }]
  }
}
```

<Warning>
  **A session policy grants nothing.** Every request made with the resulting
  credentials must be allowed by the role's own policies **and** by the session
  policy. It is an intersection, so it can only narrow.

  Session policy statements take the same shape as a managed policy's but carry
  **no conditions** — a session policy fences on actions and resources only.
</Warning>

An invalid session policy fails the call with `INVALID_INPUT` rather than being
ignored.

### Watching sessions

```bash theme={null}
GET    /v1/sts-sessions
GET    /v1/sts-sessions/{session_id}
DELETE /v1/sts-sessions/{session_id}
```

Deleting a session revokes it. Revocation is re-checked when the session
authorizes, so a revoked or expired session stops working immediately rather
than at the next token refresh.

## Giving an instance an identity

This is the reason roles are worth the setup: an instance can hold credentials
without any secret being deployed to it.

<Steps>
  <Step title="Write a trust policy that accepts instances">
    ```json theme={null}
    { "principals": ["crn:compute:*:my-account:instance/*"] }
    ```

    Narrow it to a specific instance id if you can.
  </Step>

  <Step title="Attach permission policies to the role">
    Whatever the workload actually needs — and no more, since anything running
    on the instance can reach these credentials.
  </Step>

  <Step title="Launch the instance with the role">
    <Tabs>
      <Tab title="Console">
        On **Compute → Instances → Create instance**, the **IAM role** card
        has a **Role** select. It defaults to **No role**.
      </Tab>

      <Tab title="API">
        ```bash theme={null}
        POST /v1/instances
        { "name": "web-1", "iam_role_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", ... }
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="Read credentials from inside the VM">
    The instance metadata service at `169.254.169.254` mints and serves
    temporary credentials for the attached role. They rotate before expiry, so
    a process that re-reads them keeps working indefinitely.
  </Step>
</Steps>

<Info>
  Nothing secret is ever written to the instance. The metadata service
  identifies the caller by which instance it is, mints a session against the
  attached role, and hands back credentials that expire on their own.
</Info>

<Warning>
  The role's trust policy has to permit `crn:compute:*:*:instance/*`, or the
  specific instance CRN. An instance launched with `iam_role_id` set but a
  trust policy that does not accept it gets no credentials.
</Warning>

## Which identity to use

<AccordionGroup>
  <Accordion title="CI pipeline" icon="git-branch">
    A **service account** with an access key, stored in the CI secret store.
    Scope it to the account and the actions the pipeline needs. If the pipeline
    does several unrelated things, prefer several service accounts over one
    broad key.
  </Accordion>

  <Accordion title="Software running on an instance" icon="server">
    An **instance role**. No secret is deployed, credentials rotate on their
    own, and revoking access is a change to the role rather than a redeploy.
  </Accordion>

  <Accordion title="A person doing operational work" icon="user">
    Their **user**, through the console. If they need programmatic access, give
    them a service account whose permissions match what they are allowed to do
    — not a copy of their own.
  </Accordion>

  <Accordion title="Granting temporary elevated access" icon="clock">
    A **role** with a trust policy naming who may assume it, plus a short
    `duration_seconds`. The assumption is recorded as an STS session, so the
    elevation is visible and revocable rather than implicit.
  </Accordion>
</AccordionGroup>

## Next

<CardGroup cols={2}>
  <Card title="Writing policies" icon="file-text" href="/iam/policies">
    What to put in a role's permission policies.
  </Card>

  <Card title="Permission boundaries" icon="shield" href="/iam/permission-boundaries">
    A role's boundary caps its sessions too.
  </Card>
</CardGroup>
