Saltar a contenido

Modelo de Autorización en Camunda 8

Resumen: Camunda 8 implementa un modelo de autorización granular con ~20 tipos de recursos, ~40 tipos de permisos (clasificados como read o write), y 6 tipos de owners (USER, CLIENT, ROLE, GROUP, MAPPING_RULE, TENANT). El flujo de autorización pasa por SecurityContext → ResourceAccessController → ResourceAccessChecks, donde los IDs autorizados se inyectan como filtros directamente en las queries. La configuración es declarativa via YAML.


Estructura del modelo

Resource types (~20)

Camunda 8 define aproximadamente 20 tipos de recursos protegibles. Cada recurso representa una entidad del sistema sobre la cual se pueden otorgar o denegar permisos:

Categoría Resource types
Workflow PROCESS_DEFINITION, DECISION_DEFINITION, FORM
Runtime PROCESS_INSTANCE, DECISION_INSTANCE
Operational JOB, INCIDENT, BATCH_OPERATION
Data VARIABLE, FLOWNODE_INSTANCE
Infrastructure DEPLOYMENT, MESSAGE
Identity USER, GROUP, ROLE, CLIENT, MAPPING_RULE, TENANT, AUTHORIZATION
System SYSTEM (configuración global)

Permission types (~40)

Cada resource type tiene un conjunto de permisos aplicables. Los permisos están clasificados como read (lectura/consulta) o write (modificación/acción):

Permisos read (ejemplos): - READ — ver la definición o instancia del recurso. - READ_PROCESS_INSTANCE — ver instancias de un proceso específico. - READ_USER_TASK — ver user tasks asignadas.

Permisos write (ejemplos): - CREATE — crear nuevas instancias o recursos. - CREATE_PROCESS_INSTANCE — iniciar una nueva instancia de proceso. - UPDATE — modificar un recurso existente. - DELETE — eliminar un recurso. - UPDATE_PROCESS_INSTANCE — modificar variables, resolver incidents. - DELETE_PROCESS_INSTANCE — cancelar una instancia.

La clasificación read/write es relevante porque permite que los checks de autorización se optimicen: las operaciones de solo lectura pueden resolverse más rápidamente y con menor overhead.


Owner types

Los permisos se asignan a owners — entidades que poseen autorizaciones:

USER

  • Representa un usuario humano autenticado.
  • Identificado por username (para basic auth) o subject claim (para OIDC).
  • Puede tener permisos directos o heredados via roles/grupos.

CLIENT

  • Representa una aplicación o servicio (machine-to-machine).
  • Identificado por clientId del token OAuth2.
  • Típicamente usado por job workers, conectores, y servicios internos.

ROLE

  • Agrupación lógica de permisos.
  • Un usuario o cliente puede tener múltiples roles.
  • Roles predefinidos: admin, operator, viewer, taskuser.

GROUP

  • Agrupación de usuarios.
  • Los grupos heredan los permisos asignados al grupo.
  • Útil para organizar permisos por equipo o departamento.

MAPPING_RULE

  • Regla que mapea claims de un token OIDC a roles, grupos, o tenants.
  • Permite configuración declarativa: "si el claim department = engineering, asignar rol developer".
  • Ver concepts/authentication-flow para detalles del mapeo.

TENANT

  • Entidad de aislamiento en arquitecturas multi-tenant.
  • Los permisos de un tenant acotan el scope de acceso a recursos de ese tenant.
  • Ver concepts/multi-tenancy para detalles.

Authorization scopes

Los permisos se aplican con un scope que determina a qué instancias específicas del recurso aplica:

ID scope (specific)

El permiso aplica a un recurso identificado por un ID específico:

- resourceType: PROCESS_DEFINITION
  permission: CREATE_PROCESS_INSTANCE
  scope:
    type: ID
    value: "order-process"  # solo este proceso

PROPERTY scope (attribute)

El permiso aplica a recursos que coincidan con un atributo específico:

- resourceType: PROCESS_DEFINITION
  permission: READ
  scope:
    type: PROPERTY
    attribute: "tenantId"
    value: "tenant-a"  # solo procesos del tenant A

ANY scope (wildcard *)

El permiso aplica a todas las instancias del resource type:

- resourceType: PROCESS_DEFINITION
  permission: READ
  scope:
    type: ANY  # equivalente a "*"

Resolución de scopes

Cuando se verifica un permiso, el sistema evalúa los scopes en orden de especificidad:

  1. ID match: si hay un permiso con scope ID que coincida exactamente → aplica.
  2. PROPERTY match: si hay un permiso con scope PROPERTY que coincida con el atributo → aplica.
  3. ANY match: si hay un permiso con scope ANY → aplica.
  4. No match: acceso denegado.

Flujo de autorización (check flow)

Pipeline de verificación

flowchart TD
    A[Request HTTP/gRPC] --> B[SecurityContext<br/>extrae identidad del request]
    B --> C[ResourceAccessController<br/>orquesta el check]
    C --> D[ResourceAccessChecks<br/>evalúa permisos concretos]
    D -->|AUTHORIZED| E[proceder]
    D -->|UNAUTHORIZED| F[403 Forbidden]

SecurityContext

El SecurityContext es un objeto que encapsula la identidad del request actual:

  • Fuente: extraído del token de autenticación (JWT para OIDC, header para basic auth).
  • Contenido: username/clientId, roles, grupos, tenants, claims adicionales.
  • Thread-local: se almacena en un ThreadLocal para ser accesible en cualquier punto del stack sin pasarlo explícitamente.
  • Inmutable: una vez creado para un request, no se modifica.

ResourceAccessController

Es el orquestador central de los checks de autorización:

  1. Recibe la solicitud de check: "¿puede user-X hacer READ sobre PROCESS_DEFINITION:order-process?"
  2. Resuelve los permisos efectivos del usuario:
  3. Permisos directos del usuario.
  4. Permisos heredados de sus roles.
  5. Permisos heredados de sus grupos.
  6. Permisos derivados de mapping rules.
  7. Evalúa los scopes contra el recurso solicitado.
  8. Retorna AUTHORIZED o UNAUTHORIZED.

ResourceAccessChecks

Implementaciones específicas por contexto:

  • REST API checks: verifican acceso a endpoints REST.
  • gRPC checks: verifican acceso a servicios gRPC del broker.
  • Search checks: verifican acceso a queries de Elasticsearch/OpenSearch.
  • Job checks: verifican que un worker puede activar/completar jobs de un proceso.

IDs autorizados como filtros en queries

Una optimización clave del sistema de autorización es cómo se integra con las consultas de datos:

Problema

En un sistema con miles de process definitions, verificar permisos uno por uno para cada resultado de una query sería prohibitivamente lento:

// Enfoque ingenuo (lento)
results = query("SELECT * FROM process_definitions")
filtered = results.filter(pd -> authorizationCheck(user, READ, pd))

Solución: inyección de filtros

En lugar de filtrar post-query, el sistema inyecta los IDs autorizados directamente como filtros en la query:

// Enfoque optimizado
authorizedIds = getAuthorizedResourceIds(user, PROCESS_DEFINITION, READ)
results = query("SELECT * FROM process_definitions WHERE id IN (:authorizedIds)")

Este enfoque:

  1. Resuelve los IDs autorizados una vez al inicio de la query.
  2. Inyecta los IDs como un filtro (cláusula IN en SQL, terms filter en Elasticsearch).
  3. La base de datos aplica el filtro como parte de su ejecución normal de la query.
  4. Solo retorna resultados autorizados — no hay post-filtrado.

Casos especiales

  • ANY scope: si el usuario tiene permiso con scope ANY, no se agrega filtro (acceso total).
  • Sin permisos: si el usuario no tiene permisos, la query retorna vacío sin ejecutarse.
  • Muchos IDs: si la lista de IDs autorizados es muy larga, se usan técnicas de batching o subqueries.

Configuración declarativa en YAML

Las autorizaciones se configuran declarativamente, típicamente en archivos YAML que se cargan al inicio:

authorizations:
  - ownerType: ROLE
    ownerKey: "operator"
    resourceType: PROCESS_INSTANCE
    permissions:
      - type: READ
        scope: ANY
      - type: UPDATE_PROCESS_INSTANCE
        scope: ANY
      - type: DELETE_PROCESS_INSTANCE
        scope: ANY

  - ownerType: ROLE
    ownerKey: "viewer"
    resourceType: PROCESS_DEFINITION
    permissions:
      - type: READ
        scope: ANY

  - ownerType: USER
    ownerKey: "admin-user"
    resourceType: AUTHORIZATION
    permissions:
      - type: READ
        scope: ANY
      - type: UPDATE
        scope: ANY

Ventajas del enfoque declarativo

  1. Versionable: la configuración de permisos puede estar en control de versiones (Git).
  2. Auditable: cambios de permisos se rastrean como cualquier cambio de configuración.
  3. Reproducible: un entorno se puede recrear con la misma configuración.
  4. Testeable: la configuración se puede validar en CI/CD.

Consideraciones para un MVP

  • Esencial: algún modelo de autorización es necesario para producción, pero puede empezar simple.
  • MVP mínimo: un modelo con solo USER y ROLE, sin scopes (equivalente a ANY en todo), es suficiente para empezar. Agregar scopes ID y PROPERTY cuando haya necesidad de control granular.
  • Recomendado: la inyección de IDs autorizados como filtros en queries es una optimización importante que previene problemas de rendimiento. Implementar desde el inicio.
  • Simplificable: los ~20 resource types pueden reducirse a los esenciales: PROCESS_DEFINITION, PROCESS_INSTANCE, JOB, DECISION_DEFINITION.
  • Simplificable: MAPPING_RULE puede diferirse hasta que se implemente OIDC; con basic auth, asignar roles directamente a usuarios es suficiente.
  • Simplificable: la configuración YAML puede simplificarse a un formato más simple o incluso a una tabla en la base de datos.