Skip to content
🐋 Cetacean

Authorization

Cetacean supports grant-based RBAC authorization that controls which resources each user can view and modify. Authorization is independent of authentication — any auth provider can be combined with an ACL policy.

With no policy configured, all authenticated users have full access. With a policy, access is default-deny: only explicitly granted resources are visible. Auth mode none bypasses authorization entirely.

Grant Model

A grant is a tuple of (resources, audience, permissions). All matching grants are unioned, there are no Deny rules.

grants:
  - resources: ["stack:webapp-*", "stack:api-*"]
    audience: ["group:engineering"]
    permissions: ["read", "write"]

  - resources: ["*"]
    audience: ["group:ops"]
    permissions: ["read", "write"]

  - resources: ["stack:public-*"]
    audience: ["user:*@example.com"]
    permissions: ["read"]

Resources use type:pattern with glob wildcards (*, ?). Supported types: service, stack, node, task, config, secret, network, volume, plugin, swarm. Bare * matches all types. A stack:X grant covers the stack and all its member resources. Tasks inherit from their parent service. Node grants use hostnames, not Docker IDs.

Audience uses user:pattern (matches subject and email) or group:pattern (matches group memberships). Bare * matches everyone.

Permissions are read (view in lists, detail pages, SSE, search) and write (mutate; implies read).

Policy Configuration

Policies can be provided as a file or inline. Inline takes precedence. Format is auto-detected (JSON, YAML, or TOML).

SettingEnv varConfig file keyDescription
Inline policyCETACEAN_ACL_POLICYacl.policyPolicy string (requires restart to change)
Policy fileCETACEAN_ACL_POLICY_FILEacl.policy_filePath to policy file (hot-reloaded on change)

File policies are watched for changes and swapped atomically. Invalid updates are logged and rejected, keeping the previous policy in effect.

Provider Grant Sources

Auth providers can also supply per-user grants directly, unioned with file policy. Provider grants are scoped to the authenticated user (no audience field).

ProviderSourceConfig
TailscaleCapMap peer capabilityCETACEAN_AUTH_TAILSCALE_ACL_CAPABILITY
OIDCCustom token claimCETACEAN_AUTH_OIDC_ACL_CLAIM
HeadersProxy-injected header (JSON)CETACEAN_AUTH_HEADERS_ACL
CertFile policy only
NoneN/A (no authorization)

Examples

Read-only observers: everyone browses, only Ops writes:

grants:
  - resources: ["*"]
    audience: ["*"]
    permissions: ["read"]
  - resources: ["*"]
    audience: ["group:ops"]
    permissions: ["write"]

Team-scoped stacks: each team manages their own stacks, shared infra is read-only:

grants:
  - resources: ["stack:frontend-*"]
    audience: ["group:frontend"]
    permissions: ["read", "write"]
  - resources: ["stack:api-*"]
    audience: ["group:backend"]
    permissions: ["read", "write"]
  - resources: ["stack:monitoring", "stack:ingress"]
    audience: ["*"]
    permissions: ["read"]

On-call with limited blast radius: write services and tasks, read-only infra. Combine with operations_level=1:

grants:
  - resources: ["service:*", "task:*"]
    audience: ["group:oncall"]
    permissions: ["read", "write"]
  - resources:
      ["node:*", "swarm:*", "config:*", "secret:*", "network:*", "volume:*"]
    audience: ["group:oncall"]
    permissions: ["read"]

Multi-tenant isolation: tenants see only their own stacks, no cross-visibility:

grants:
  - resources: ["stack:acme-*"]
    audience: ["group:tenant-acme"]
    permissions: ["read", "write"]
  - resources: ["stack:globex-*"]
    audience: ["group:tenant-globex"]
    permissions: ["read", "write"]

After applying a policy, verify effective permissions with curl -s localhost:9000/auth/whoami | jq .permissions.

Interaction with Operations Level

ACL and operations level are independent checks; both must pass for a write operation to succeed. Operations level is a global ceiling (which categories of writes are enabled), while ACL controls which resources each user can modify. A common pattern is operations_level=1 (safe ops only) combined with ACL grants for per-team scoping.

ScenarioResult
Operations level allows, ACL grants writeAllowed
Operations level allows, ACL denies writeDenied (403 ACL002)
Operations level blocks, ACL grants writeDenied (403 OPS001)