Volver a los textos

Quién puede dar poder a quién

Usar una capacidad y concederla son autoridades distintas. Modela las dos.

En casi todo panel de administración existe una propiedad que significa, a la vez, usar el producto, dar de alta a personas, conceder privilegios y atravesar fronteras. Normalmente se llama admin. Es cómoda hasta el día en que alguien pregunta: ¿un administrador del cliente puede crear otro administrador igual que él?

La separación de funciones parte de la idea de que ciertas autoridades no deberían ir juntas solo por comodidad. En el catálogo, eso se traduce en permissions distintos:

users.write                -- dar de alta a personas
authz.roles.assign_tenant  -- asignar roles del tenant
authz.grants.direct        -- conceder excepciones temporales
platform.tenant.bypass     -- atravesar tenants

Por qué falla “solo concede lo que posee”

La regla más común es: un usuario solo puede conceder los permissions que él mismo tiene. Parece segura y tiene dos defectos.

El primero es la autorreplicación. Si el administrador posee todos los permissions de admin, el subset check también le permite conceder admin a cualquiera. La política “el admin del tenant da de alta a personas y asigna roles operativos, pero no crea otro admin” simplemente no se puede expresar.

El segundo es el inverso: la regla obliga a quien solo necesita conceder una capacidad a poseerla. Un gestor que reparte acceso a microdatos pasa a leer microdatos, aunque no tenga ninguna razón para ello.

La matriz de autoridad

La salida es modelar la autoridad de concesión directamente:

role_grantable(grantor_role, target_role)

tenant_admin   -> viewer
tenant_admin   -> analyst
platform_admin -> tenant_admin
platform_admin -> viewer
platform_admin -> analyst

Fíjate en lo que no está ahí: tenant_admin -> tenant_admin. La regla de negocio deja de ser un efecto colateral de una comparación de conjuntos y pasa a ser una línea inspeccionable. Para los grants directos vale el mismo razonamiento, con otra matriz:

permission_grantable(grantor_role, permission_code)

tenant_admin -> analysis.run
tenant_admin -> data.query
tenant_admin -> content.share

Y por encima de las dos matrices hay un hard deny: ninguna autoridad de tenant concede permissions de plataforma, diga lo que diga la matriz. Si alguien la configura mal, la frontera sigue en pie.

El predicado, con los paréntesis en su sitio

can_assign(actor, target, role) :=
  (
      tenant_branch(actor, target)
   OR platform_branch(actor)
  )
  AND role_is_grantable(actor, role)
  AND target_role_content_is_allowed(actor, role)

Parece pedantería, pero aquí la precedencia es seguridad. Sin los paréntesis, A OR B AND C AND D se convierte en A OR (B AND C AND D), y cualquier actor que satisfaga la rama de tenant se salta las comprobaciones de la matriz. Es un bypass completo escondido en un operador. La implementación debe reflejar la fórmula, o usar early returns equivalentes que no dejen lugar a dudas.

El último término, target_role_content_is_allowed, importa cuando el tenant puede crear roles propios: la validación mira el contenido del role, no el nombre. Un role custom llamado “Lector” que contiene authz.roles.assign_tenant no es un lector.

Dar de alta no es conceder

POST /users              -- crea la identidad
POST /users/{id}/roles   -- concede autoridad

Dos endpoints, dos servicios, dos rastros. Juntar los dos en un formulario de “nuevo usuario” con un desplegable de role convierte cada alta en una concesión, y la auditoría pasa a tener que separar lo que la arquitectura juntó.

Bloquear la autoasignación, actor == target, es una salvaguarda razonable. Pero es solo eso: el control real es la matriz. Si la matriz permite la escalada, impedir que alguien la haga sobre sí mismo solo le obliga a pedírsela a un compañero.

Grants directos con plazo

Las excepciones temporales existen, y es mejor modelarlas que fingir que no. Pero un grant directo no puede convertirse en una segunda implementación descontrolada de RBAC. Las reglas que sigo:

  • expires_at obligatorio. No existe el grant eterno.
  • Justificación obligatoria, guardada junto a él.
  • Concesión auditada en la misma transacción.
  • El vencimiento entra en la frescura de la sesión.
  • Recurrencia monitorizada: el mismo grant renovado cada mes es un role que nadie quiso crear.
  • Permissions de plataforma indelegables por cualquier mecanismo de tenant.

Procedencia

Para cada permission efectivo, el sistema debe poder decir de dónde vino: role de sistema, role custom o grant directo, quién lo concedió, cuándo y hasta cuándo. Eso convierte “¿por qué esta persona puede hacer esto?” en una consulta, y no en una investigación manual de logs.

Este texto forma parte de una serie que empieza en Autorización en cuatro preguntas.