Skip to content

Roles & Permissions

Ironflow ships with built-in roles for both tenant and platform operations. All roles are stored in a unified roles table and enforced via RBAC on every authenticated request.

RoleDescription
adminFull access — all read, write, and delete operations
developerRead and write access — can create and modify resources
viewerRead-only access — can list and read resources
RoleDescription
platform_adminFull access to all platform operations (wildcard permission)
platform_operatorTenant management, impersonation, read access to users/keys/roles
platform_viewerRead-only access to all platform resources

See Platform Roles & Actions for the complete platform permissions matrix.

The table below lists every permission action and which roles can perform it.

ActionAdminDeveloperViewer
functions:register
functions:invoke
functions:list
functions:read
runs:read
runs:cancel
events:emit
events:subscribe
streams:read
entities:read
entities:append
projections:read
projections:manage
secrets:read
secrets:manage
users:read
users:manage
apikeys:read
apikeys:manage
orgs:read
orgs:manage
agent:tools:register
agent:tools:invoke
agent:tools:unregister
agent:tools:read

Each incoming request is mapped to a permission action based on the HTTP method and path:

  • ConnectRPC — the RPC method name determines the action (e.g. IronflowService/Invokefunctions:invoke)
  • REST API — the HTTP method, first path segment, and any verb sub-path determine the action. Most routes follow method + resource (e.g. GET /api/v1/runsruns:read); verb sub-paths override (e.g. POST /api/v1/runs/{id}/cancelruns:cancel, POST /api/v1/functions/{id}/invokefunctions:invoke, POST /api/v1/projections/{name}/rebuildprojections:manage)
  • WebSocket — the /ws upgrade requires events:subscribe

If a request maps to an action that the caller’s roles don’t include, the server returns 403 Forbidden. If no valid credentials are provided at all, the server returns 401 Unauthorized.

Every resource in Ironflow has a unique identifier following the IRN format:

irn:ironflow:{org_id}:{project_id}:{type}:{environment}:{resource}

Valid resource types: function, run, event, stream, projection, secret, org, role, policy, user, project.

IRN patterns support wildcards (*) for any segment, enabling flexible permission matching.

Beyond the built-in roles, you can create custom roles scoped to an organization. Custom roles have no built-in permissions — they derive all access from attached policies.

Terminal window
# Create a custom role
ironflow role create billing-team --org org_default
# List all roles (built-in + custom)
ironflow role list --org org_default
# Get a single role
ironflow role get <role_id>
# Assign a policy to a role
ironflow role assign-policy <role_id> <policy_id>
# Remove a policy from a role
ironflow role remove-policy <role_id> <policy_id>
# Delete a custom role (built-in roles cannot be deleted)
ironflow role delete <role_id>

Policies define fine-grained access rules using actions, resource patterns, and optional CEL conditions.

FieldDescriptionExample
nameHuman-readable policy name (unique per org)allow-prod-reads
effectallow or denyallow
actionsComma-separated action patterns (wildcards supported)functions:list,runs:read
resourcesComma-separated IRN patterns (wildcards supported)irn:ironflow:*:*:function:prod:*
conditionOptional CEL expression (must return boolean)request.environment == "production"

Authorization is two-layered:

  1. System RBAC (Layer 1) is the authoritative ALLOW gate. If the caller’s roles do not grant the action, access is denied immediately — CEL policies are not consulted.
  2. CEL policies (Layer 2) are subtractive only. Only deny-effect policies are evaluated; allow-effect policies are stored for clarity but do not grant additional access beyond Layer 1.
  3. Matching deny policies have their CEL conditions evaluated. Deny always wins — if any matching deny policy’s condition passes, access is denied.
  4. If RBAC allowed and no deny policy matches (or no policies exist), access is allowed.
Terminal window
# Create an allow policy
ironflow policy create \
--name allow-prod-reads \
--effect allow \
--actions "functions:list,runs:read,events:subscribe" \
--resources "irn:ironflow:*:*:*:prod:*"
# Create a deny policy with CEL condition
ironflow policy create \
--name deny-weekend-deploys \
--effect deny \
--actions "functions:register" \
--resources "irn:ironflow:*:*:function:prod:*" \
--condition 'request.timestamp.getDayOfWeek() == 0 || request.timestamp.getDayOfWeek() == 6'
# List policies
ironflow policy list --org org_default
# Get a single policy
ironflow policy get <policy_id>
# Update a policy (creates a new version)
ironflow policy update <policy_id> --actions "..." --resources "..."
# Test a policy against a sample request
ironflow policy test <policy_id> --request request.json
# List a policy's version history
ironflow policy versions <policy_id>
# Roll back to a previous version
ironflow policy rollback <policy_id> <version>
# Install a policy template bundle
ironflow policy template install <template_name>
# Delete a policy
ironflow policy delete <policy_id>

For full details on CEL expressions and the REST API, see Custom Roles & CEL Policies.