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

# IAM

> Identities, the resources they can reach, and exactly how a request is allowed or denied.

IAM decides who may do what. Every other Basaltic API asks it the same question
before acting: *may this principal perform this action on this resource?*

It is a **global** service — `iam.basaltic.sh`, no region segment. One identity
works in every region.

<CardGroup cols={2}>
  <Card title="Writing policies" icon="file-text" href="/iam/policies">
    The document format, every condition operator, and worked examples.
  </Card>

  <Card title="Roles and credentials" icon="key-round" href="/iam/roles">
    Access keys, trust policies, assumed roles, and workload federation.
  </Card>

  <Card title="Permission boundaries" icon="shield" href="/iam/permission-boundaries">
    Capping what a policy can ever grant, and safe delegation.
  </Card>

  <Card title="How a request is decided" icon="git-branch" href="#how-a-request-is-authorized">
    The five steps, in the order they actually run.
  </Card>
</CardGroup>

## The identity model

<Columns cols={2}>
  <Card title="Organization" icon="building">
    The top of the tree. Users, groups, service accounts, roles and policies
    all belong to one organization and are never visible outside it.
  </Card>

  <Card title="Account" icon="folder">
    A resource container inside the organization. Instances, volumes and zones
    belong to an account; identities do not.
  </Card>
</Columns>

Four kinds of principal can make a request:

| Principal           | What it is                                   | Holds credentials     |
| ------------------- | -------------------------------------------- | --------------------- |
| **User**            | A person.                                    | Console session       |
| **Service account** | A non-human identity for programs and CI.    | Access keys           |
| **Role**            | A set of permissions something else assumes. | None of its own       |
| **Assumed role**    | A live session created by assuming a role.   | Temporary credentials |

**Groups** collect users *and* service accounts, so a policy attached to a
group reaches both. Policies attach to users, service accounts, groups and
roles.

<Info>
  A user is a person and does not hold an access key. Anything programmatic —
  a script, a CI job, a deployed workload — is a **service account** or an
  **assumed role**. See [roles and credentials](/iam/roles).
</Info>

## Resource names

Policies name resources by **CRN**:

```
crn:<service>:<region>:<account>:<resource_type>/<resource_id>
```

```
crn:storage:sa-saopaulo-1:my-account:volume/01HXYZ...   regional, account-scoped
crn:dns::my-account:zone/example.com                    global service, empty region
crn:iam:::user/01HXYZ...                                org-scoped, both slots empty
crn:certificate::my-account:certificate/prod-frontend   name-based
```

Two slots are empty on purpose. **Region** is empty for a global service — IAM,
DNS, certificates and billing. **Account** is empty for resources that belong to
the organization rather than to an account, which is every IAM identity.

<Note>
  **A CRN never carries an organization id.** The organization comes from the
  authenticated request, which makes cross-organization access impossible by
  construction rather than by a check somebody has to remember to write.
</Note>

`*` is the only wildcard, and the colon/slash layout is compared literally — a
pattern written in any other shape matches nothing.

## How a request is authorized

Every service calls the same authorizer. It runs these five steps in order:

<Steps>
  <Step title="Bind the principal to the organization">
    Before anything is evaluated, the principal is confirmed to belong to the
    organization the request names. This is what stops a wildcard policy from
    authorizing against a CRN in someone else's organization.
  </Step>

  <Step title="Organization owner short-circuit">
    An organization owner has implicit full access, subject only to an
    **explicit deny**. Permission boundaries do not apply to an owner. A
    session policy on temporary credentials still does — credentials advertised
    as scoped-down must not inherit the bypass.
  </Step>

  <Step title="Collect and evaluate the effective policies">
    Everything attached to the principal, directly and through its groups. An
    **assumed role authorizes as the role**, so the role's policies are what
    get evaluated, not the caller's.

    The result is one of three: explicit deny, explicit allow, or nothing
    matched.
  </Step>

  <Step title="Intersect with the session policy">
    If the caller is using temporary credentials that carry a session policy,
    an allow survives only if the session policy also allows it. This is an
    intersection, never an expansion — a session policy can never grant
    something the identity does not already have.
  </Step>

  <Step title="Apply the permission boundary">
    If the principal has a [boundary](/iam/permission-boundaries), the action
    must be allowed by the boundary as well. Same intersection rule.
  </Step>
</Steps>

If the decision is still allow, the request proceeds. Otherwise it is denied and
the denial is logged for audit.

### The rule that decides everything

```mermaid theme={null}
flowchart TD
    A[Request] --> B{Explicit deny anywhere?}
    B -->|yes| D[Denied]
    B -->|no| C{Any statement allows?}
    C -->|no| D
    C -->|yes| E{Session policy allows?}
    E -->|no| D
    E -->|yes| F{Within permission boundary?}
    F -->|no| D
    F -->|yes| G[Allowed]
```

<Warning>
  **An explicit deny always wins.** It beats any allow, from any policy,
  anywhere in the evaluation — including an organization owner's implicit
  access. Nothing overrides it, and there is no ordering or priority you can
  use to work around it.
</Warning>

Access is otherwise **denied by default**: if no statement matches, the answer
is no. A principal with no policies attached can do nothing.

## Failing closed

Two behaviours are worth knowing because they are deliberate rather than
incidental:

<AccordionGroup>
  <Accordion title="An unreadable boundary denies" icon="shield-alert">
    If a permission boundary is set but the boundary or its policy cannot be
    loaded, the request is denied. A boundary that exists but cannot be read
    might be capping this very action, so the safe answer is no.
  </Accordion>

  <Accordion title="A broken condition operator denies on Deny" icon="triangle-alert">
    An unrecognised condition operator never matches. On an **allow** statement
    that means the statement is skipped. On a **deny** statement it is treated
    as a hard deny when the action and resource match — otherwise a typo in a
    guardrail would silently disable it.
  </Accordion>
</AccordionGroup>

## Next

<CardGroup cols={2}>
  <Card title="Writing policies" icon="file-text" href="/iam/policies">
    Statements, actions, resources, conditions, and a set of examples to start
    from.
  </Card>

  <Card title="Roles and credentials" icon="key-round" href="/iam/roles">
    How a program gets credentials in the first place.
  </Card>
</CardGroup>
