Security Model
Security
Section titled “Security”Ironflow implements Always-on Authentication. Every request to the API, WebSocket, or Dashboard must present valid credentials.
Authentication Layers
Section titled “Authentication Layers”Ironflow uses a unified API key model with RBAC-based authorization:
| Layer | Protects | Credential |
|---|---|---|
| Tenant API Key | /api/*, /ws, ConnectRPC (tenant operations). | ifkey_... (Bearer Token). |
| Platform API Key | /api/* (platform/cross-tenant operations). | ifplatform_... (Bearer Token). |
| Dashboard Auth | Web UI (/). | Email + password → JWT cookie session. |
Both ifkey_ and ifplatform_ keys are stored in the same api_keys table and use the same authentication path. The prefix is a cosmetic distinction — authorization is determined by assigned roles. See API Keys for details.
Dashboard login exchanges email + password (bcrypt-verified) for a JWT cookie at /api/v1/auth/login; subsequent dashboard requests authenticate via that cookie. The JWT signing secret is persisted to .ironflow_jwt_secret next to the database so sessions survive restarts (override via IRONFLOW_JWT_SECRET).
Public Endpoints
Section titled “Public Endpoints”The following requests bypass authentication (internal/server/auth.go::isPublicPath):
OPTIONSon any path (CORS preflight)./health(liveness probe)./ready(readiness probe — checks DB + NATS)./metrics(Prometheus metrics, when enabled)./api/v1/capabilities(feature discovery)./api/v1/auth/login(dashboard login)./api/v1/auth/validate(JWT validation — stale session detection)./api/v1/platform/auth/login(platform login).- Dashboard SPA shell — any path that is not prefixed by
/api/,/ws, or/ironflow.v1.is served by the embedded React app. The HTML/JS bundle is public; the app itself is gated bydashboardAuthMiddlewareand the JWT cookie before any data API is reachable.
Zero-Config Bootstrap
Section titled “Zero-Config Bootstrap”On the first boot only, Ironflow auto-generates a secure environment:
- Default Org:
org_defaultis created. - Default Project:
proj_default_defaultis created within the default org. - Built-in Roles:
admin,developer, andviewerare initialized. - Credentials: A random admin password and a unique API Key are generated.
Organizations & Multi-Tenancy
Section titled “Organizations & Multi-Tenancy”Ironflow supports multi-tenant isolation via Organizations. Each organization is a fully isolated boundary with its own API keys, roles, projects, environments, and policies.
Resources are namespaced using IRN (Ironflow Resource Names) with the following 7-segment structure:
irn:ironflow:{org_id}:{project_id}:{resource_type}:{env}:{resource_id}IRN patterns support wildcards (*) for flexible policy matching. For example, irn:ironflow:*:*:function:prod:* matches all functions in any organization’s and project’s production environment.
Tenant provisioning creates:
- An organization record
- A default project
- Built-in roles (admin, developer, viewer)
- A default environment (within the default project)
Authorization
Section titled “Authorization”Ironflow ships a single closed-source build (ADR 0015, 2026-05-07). There is no Enterprise/Core split — the CEL policy engine, custom roles, and the full audit recorder are all wired unconditionally in cmd/ironflow/serve.go.
Key capabilities:
- Built-in roles —
admin,developer,viewerare seeded at bootstrap and immutable in normal operation. - Custom roles — tenant admins can define additional roles scoped to their organization on top of the three built-ins.
- CEL policy engine with deny-wins semantics — a tenant-authored CEL policy can only subtract permissions an L1 role already granted. See Authorization Policies (CEL) for the two-layer model, the canonical request/subject shape, and the audit hash chain.
- Epoch-based cache invalidation — every role or policy mutation increments the organization’s
policy_epoch. The bump is debounced ~1 s (DefaultDebounceininternal/auth/policyepoch/manager.go) so an admin saving repeatedly collapses to one cluster-wide broadcast over theSYS_policy_epochNATS KV bucket, with PG as the durable backup. - Decision caching — a per-node 16,384-entry LRU of CEL decisions (
DefaultDecisionCacheSizeininternal/auth/rbac/cel/evaluator.go), plus a 4,096-entry compiled-program cache. Caches self-purge on the next lookup after an epoch bump.
See Custom Roles & CEL Policies for the full how-to guide.
Audit Trail
Section titled “Audit Trail”Every Ironflow deployment records a full audit trail of security-relevant activity. The AuthAuditEmitter interface is satisfied in production by AuthAuditRecorder, wired unconditionally at server start. NoopEmitter exists as the pre-SetEmitter default and as a test seam; it is not the production runtime path.
Tracked events:
- Authorization decisions — every Layer 2 DENY (and audit-write failures on the DENY path) writes a row to
policy_decisionswith a SHA-256 hash chain link — see policies.md for the chain construction. - CRUD on API keys, roles, and policies — captures who changed what and when.
- Platform events — platform user lifecycle (created/updated/deleted), impersonation events, and platform role changes.
- Tenant API key lifecycle — creation and revocation.
- Environment lifecycle — environment-scoped create/update/delete events.
Query surfaces:
- Tenant audit log:
GetAuthAuditTrailConnectRPC endpoint. - Platform audit log:
GET /api/v1/platform/audit.