Skip to main content

AUTHORIZATION

Authorization in swamp serve is grant-based. Each grant binds a subject to an effect (allow or deny) on a set of actions against a resource selector. The server evaluates all matching grants at access-check time.

Default deny

If no grant matches a request, the request is denied. There is no implicit allow.

Subjects

A grant's --subject identifies who the grant applies to.

Format Description
user:<id> A specific authenticated user
group:<name> Members of a local group managed via swamp access group
idp-group:<collective-slug> Users whose IdP token includes the group

Local groups are resolved at policy reload time. IdP groups come from the OAuth provider's userinfo response, using the field specified by --groups-field (default: collectives).

IdP group refresh

The server periodically refreshes IdP group memberships by re-fetching each authenticated user's groups from the OAuth provider. The refresh interval is controlled by --group-refresh-interval (default: 4h). On each refresh cycle, the server calls the provider's userinfo endpoint for every active session and updates the policy snapshot with the current group memberships.

Transient refresh errors (network timeouts, provider unavailability) are fail-open: the server retains the last known group memberships and retries on the next cycle. A 401 Unauthorized response from the provider is treated as a hard revocation — the user's session is invalidated and all idp-group: grants for that user stop matching immediately.

See swamp serve — IdP group refresh and deprovisioning for the design rationale behind fail-open vs hard revocation.

Effects

Each grant has exactly one effect:

Effect Meaning
allow Permits the specified actions
deny Blocks the specified actions

Deny wins

When both allow and deny grants match a request, deny wins. A deny grant always overrides an allow grant, regardless of specificity.

Actions

Action Description
run Execute a model method or workflow
read Read data outputs, model definitions, vaults
write Write data, create models
admin Manage access: grants, tokens, groups

Actions are specified as comma-separated values in --allow or --deny.

Admin superuser

A grant of admin on access:* implies all actions on all resources. This is the superuser grant — the holder can perform any operation. The --admins flag materializes this grant for the listed principals on every server start.

Resource selectors

The --on flag specifies which resources the grant applies to.

Selector Matches
workflow:@acme/* All workflows in the @acme namespace
workflow:@acme/deploy A specific workflow
model:hello A specific model
model:* All models
data:* All data outputs
data:@acme/secrets-* Data outputs matching the prefix
access:* The access control system itself

The * wildcard matches any suffix.

CEL conditions

The --when flag adds a CEL expression that must evaluate to true for the grant to apply. Conditions are evaluated against resource fields at access-check time.

--when 'tags.env == "staging"'

See the CEL Expressions reference for the full syntax, operators, and available functions.

Resource fields can be passed to swamp access check using the --field flag for testing condition evaluation.

Condition cost limits

Grant conditions are subject to complexity limits. Conditions that exceed a limit are rejected at grant creation time, except the aggregate limit which is enforced at access-check time.

Limit Threshold Enforcement
AST depth ≤ 24 Write-time
Comprehension nesting ≤ 2 Write-time
Per-condition cost budget ≤ 500 Write-time
matches() pattern See below Write-time
Aggregate per-decision ≤ 100 Access-check

AST depth counts the maximum nesting depth of the parsed expression tree.

Comprehension nesting limits the levels of chained exists, all, filter, and map macros.

Per-condition cost budget is an estimated cost derived from AST node count and comprehension weight.

matches() restrictions: the pattern argument must be a string literal, not a variable. Patterns containing catastrophic backtracking constructs are rejected.

Aggregate per-decision limit: if more than 100 --when conditions are evaluated in a single access check, the request is denied.

Existing grants created before these limits were introduced are unaffected. Error messages identify which limit was exceeded and the actual value.

# Rejected: comprehension nesting exceeds limit (3 > 2)
--when 'tags.all(k, tags[k].all(v, v.all(c, c == "x")))'

Grant source types

Grants can originate from different sources:

Source Description
method Created via swamp access grant create
config Materialized from --admins at startup
file:<filename> Loaded from a YAML file in the grants/ directory
extension:* Contributed by an extension

The reconciler (triggered by swamp access reload) only creates, updates, and revokes file:* grants. Grants with source method, config, or extension:* are never modified by the reconciler.

Policy snapshot

Grants and group memberships are compiled into a policy snapshot. The snapshot is rebuilt:

  • On swamp access reload
  • On every grant or group change when --grant-reload auto is active
  • On server start (including admin grant materialization from --admins)

Changes to grants and groups do not take effect until the snapshot is rebuilt.