Skip to main content
A policy is a JSON document of statements. Each statement says whether an effect applies to a set of actions on a set of resources, optionally gated by conditions.
version is always 2024-01-01. Anything else is rejected.

Statements

string, optional
A label for your own use. It has no effect on evaluation.
allow | deny
required
Lowercase. An explicit deny beats every allow, everywhere.
array
required
Exactly one of the pair. Setting both, or neither, is rejected when the document is saved.
array
required
Exactly one of the pair, same rule.
array, optional
All of them must hold for the statement to apply.

Actions

Actions are service:Action, and * is the only wildcard.

Resources

Resources are CRNs, with * as the only wildcard. The colon and slash layout is compared literally, so the shape has to be right:
Some resources are named rather than UUID-keyed, which makes a naming convention directly policy-able: crn:certificate::my-account:certificate/prod-*.

Naming by exclusion

not_actions and not_resources cover everything except what they list.
not_actions with effect: allow grants every action the patterns do not name — including actions that do not exist yet, added by services shipped after the policy was written. Pairing exclusion with deny carves a hole out of a broad allow and has no such surprise. Prefer that.

Conditions

A condition compares a context key against values using an operator. Every condition on a statement must hold for it to apply.

Operators

What happens when the key is missing

This is the part that decides whether a guardrail works, so it is worth being precise about.
A condition whose context key is absent from the request fails — except for the negated operators, which hold.not_equals, not_in, not_ip_address and not_exists are satisfied by a request that does not carry the key at all. Every other operator asserts something positive about a value that is not there, so it fails closed.
The reason is that a deny needs to fire on the request it is guarding against. “Deny unless the request comes from these addresses” has to catch a request with no address — treating the missing key as no match would make the guardrail fail open exactly when it matters.

Multi-valued keys

Some context keys are sets rather than single values — basalt:TagKeys is the set of tag keys a request carries. To compare against one, add a set_operator:
  • for_all_values holds when every member of the request set satisfies the operator. An absent or empty set holds vacuously — a request carrying no tags is not fenced by a tag-key restriction.
  • for_any_value holds when at least one member does. An absent or empty set does not hold.

Context keys

The two tag prefixes answer different questions. ResourceTag fences access to things already labelled a certain way; RequestTag fences what a caller is allowed to label something as.

Worked examples

Reaches only resources already tagged env=staging:
This grants nothing on an untagged resource: equals on a missing key fails. That is usually what you want — an unlabelled resource is not quietly in scope.
A caller may create instances only while tagging them env=staging:
Written as a deny with the negated operator, so it also fires on a request that carries no source address. The inverse — allow when ip_address matches — leaves the fence off whenever the key is absent.
Attach it anywhere in the principal’s set. An explicit deny is not overridden by any allow, including an organization owner’s implicit access.
GetCertificateMaterial is a separate action from reading a certificate, precisely so this can be granted narrowly. See certificates.

Managed and inline policies

Managed policy

A standalone object with its own CRN, attached to any number of users, groups, service accounts and roles. Edit once, everywhere it is attached changes. This is the default choice.

Inline policy

Written directly onto one principal, named rather than identified, and deleted with it. For a one-off grant that should never be reused or accidentally attached elsewhere.
Inline policies live under the principal:
The same four routes exist for service-accounts, roles and groups. Some managed policies are system policies, marked is_system. They are maintained by the platform, shared across organizations, and cannot be edited — attach them or don’t.

Validation

A document is rejected on save, not silently ignored, when:
  • version is missing or is not 2024-01-01
  • statements is empty
  • effect is not allow or deny
  • a statement sets both actions and not_actions, or neither
  • a statement sets both resources and not_resources, or neither
  • a condition has no key, or an unrecognised operator or set_operator
An unrecognised operator in a stored document — one saved before an operator was renamed, say — never matches. On an allow statement it is skipped; on a deny it is treated as a hard deny when the action and resource match, so a broken guardrail fails closed rather than open.

Next

Permission boundaries

Capping what these policies can ever grant.

Roles and credentials

Session policies scope credentials down the same way.