Saltar a contenido

ADR-013: RBAC simple: 3 roles

  • Status: Accepted
  • Date: 2026-05-14
  • Tags: security, authorization, identity

Context and Problem Statement

Camunda tiene authorization model complejo: 20 resource types × 40 permission types × 3 scope types (ID/PROPERTY/ANY). Esto es flexible pero complejo de configurar y mantener. ¿Replicamos esta granularidad o simplificamos?

Decision Drivers

  • 95% de use cases requieren solo 2-3 roles
  • Complexity de auth model bug-prone (escalation vulnerabilities)
  • Fine-grained permissions raro implementados correctamente en producción
  • Simple model = onboarding easier
  • Si negocio requiere fine-grained, build later

Considered Options

  1. 3 roles fixed: admin, operator, worker
  2. Camunda model full (20 × 40 × 3)
  3. Sin authorization (todos pueden todo en su tenant)
  4. Custom role model (usuario define roles + permissions)
  5. OAuth2 scopes (delegate al IdP)

Decision Outcome

Chosen option: 3 roles fixed (admin, operator, worker) por tenant. Phase 1.

Positive Consequences

  • Onboarding trivial (3 conceptos, no 20×40)
  • Bug surface mínima (impossible escalate vía granular missing permission)
  • Code simpler (3 checks vs miles de combinations)
  • Documentation clara
  • Tenant-scoped (no cross-tenant authorization)

Negative Consequences

  • Sin fine-grained permissions
  • Compliance audits stricter pueden requerir más granularity
  • Migrating to fine-grained later requires schema change
  • Algunos use cases no representables (e.g., "puede ver process X pero no Y")

Roles definidos

Role Permissions
admin Todo en su tenant: deploy, manage forms/processes, manage users, batch ops, resolve incidents, cancel instances
operator Monitor (read): list processes/instances/incidents. Operate: resolve incidents, cancel instances, batch ops
worker Activate jobs, complete/fail/throwError jobs, publish messages, read process definitions

Schema

CREATE TABLE user_tenants (
    user_id TEXT NOT NULL,           -- sub claim from OIDC
    tenant_id TEXT REFERENCES tenants(tenant_id),
    role TEXT NOT NULL CHECK (role IN ('admin', 'operator', 'worker')),
    granted_at TIMESTAMPTZ DEFAULT NOW(),
    granted_by TEXT,
    PRIMARY KEY (user_id, tenant_id)
);

CREATE TABLE api_keys (
    api_key_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id TEXT REFERENCES tenants(tenant_id),
    name TEXT NOT NULL,
    key_hash TEXT NOT NULL UNIQUE,    -- bcrypt/argon2
    role TEXT NOT NULL,
    expires_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

Authorization checks

Middleware approach:

function requireRole(roles: string[]) {
  return async (req, res, next) => {
    const userRole = await getUserRole(req.user.sub, req.tenantId);
    if (!roles.includes(userRole)) {
      return res.status(403).json({ error: 'Insufficient permissions' });
    }
    next();
  };
}

// Apply to routes
app.post('/v2/deployments', requireRole(['admin']), handleDeploy);
app.post('/v2/process-instances', requireRole(['admin', 'operator', 'worker']), handleCreate);
app.delete('/v2/process-instances/:key', requireRole(['admin', 'operator']), handleCancel);
app.post('/v2/jobs/:key/complete', requireRole(['worker', 'admin']), handleComplete);

Tenant isolation enforced

TODAS las queries deben incluir tenant_id:

async function listInstances(req) {
  return db.query(`
    SELECT * FROM process_instances
    WHERE tenant_id = $1 AND state = $2
  `, [req.tenantId, req.query.state]);
}

Postgres RLS como defense in depth:

ALTER TABLE process_instances ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON process_instances
  FOR ALL USING (tenant_id = current_setting('app.current_tenant'));

Cuándo upgradear

Build fine-grained authorization SI:

  1. Cliente regulatory pide audit logs with specific permission grants
  2. Use case "process X visible, process Y oculto" para mismo role
  3. Compliance HIPAA/SOC2/etc. requires granularity

Migration: agregar permissions table, keep roles como aggregations.