Modelo de Seguridad
Wippy implementa control de acceso basado en atributos. Cada solicitud lleva un actor (quién) y un scope (qué políticas aplican). Las políticas evalúan el acceso basado en la acción, recurso, y metadatos tanto del actor como del recurso.
flowchart LR
A[Actor + Scope] --> PE[Evaluación de Política] --> AD[Permitir/Denegar]
A -.->|Identidad
Metadatos| PE
PE -.->|Condiciones
actor, recurso, acción| AD
Tipos de Entrada
| Tipo | Descripción |
|---|---|
security.policy |
Política declarativa con condiciones |
security.policy.expr |
Política basada en expresiones |
security.token_store |
Almacenamiento y validación de tokens |
Actores
Un actor representa quién está realizando una acción.
local security = require("security")
-- Crear actor con metadatos
local actor = security.new_actor("user:123", {
role = "admin",
team = "backend",
department = "engineering",
clearance = 3
})
-- Acceder propiedades del actor
local id = actor:id() -- "user:123"
local meta = actor:meta() -- {role="admin", ...}
Actor en Contexto
-- Obtener actor actual del contexto
local actor = security.actor()
if not actor then
return nil, errors.new("UNAUTHORIZED", "Sin actor en contexto")
end
Políticas
Las políticas definen reglas de acceso con acciones, recursos, condiciones y efectos.
Política Declarativa
# src/security/_index.yaml
version: "1.0"
namespace: app.security
entries:
# Acceso completo admin
- name: admin_policy
kind: security.policy
policy:
actions: "*"
resources: "*"
effect: allow
conditions:
- field: actor.meta.role
operator: eq
value: admin
groups:
- admin
# Acceso de solo lectura
- name: readonly_policy
kind: security.policy
policy:
actions:
- "*.read"
- "*.get"
- "*.list"
resources: "*"
effect: allow
groups:
- default
# Acceso de propietario de recurso
- name: owner_policy
kind: security.policy
policy:
actions:
- read
- write
- delete
resources: "document:*"
effect: allow
conditions:
- field: meta.owner
operator: eq
value_from: actor.id
groups:
- default
# Denegar confidencial sin autorización
- name: deny_confidential
kind: security.policy
policy:
actions: "*"
resources: "document:*"
effect: deny
conditions:
- field: meta.classification
operator: eq
value: confidential
- field: actor.meta.clearance
operator: lt
value: 3
groups:
- security
Estructura de Política
policy:
actions: "*" | "accion" | ["accion1", "accion2"]
resources: "*" | "recurso" | ["res1", "res2"]
effect: allow | deny
conditions: # Opcional
- field: "ruta.campo"
operator: "eq"
value: "valor_estatico"
# O
value_from: "otra.ruta.campo"
Política Basada en Expresiones
Para lógica compleja, use políticas de expresiones:
- name: flexible_access
kind: security.policy.expr
policy:
actions:
- read
- write
resources: "file:*"
effect: allow
expression: |
(actor.meta.role == "editor" && action == "write") ||
(action == "read" && meta.public == true) ||
actor.id == meta.owner
groups:
- editors
Condiciones
Las condiciones permiten evaluación dinámica de políticas basada en actor, acción, recurso y metadatos.
Rutas de Campo
| Ruta | Descripción |
|---|---|
actor.id |
Identificador único del actor |
actor.meta.* |
Metadatos del actor (soporta anidamiento) |
action |
La acción que se está realizando |
resource |
El identificador del recurso |
meta.* |
Metadatos del recurso |
Operadores
| Operador | Descripción | Ejemplo |
|---|---|---|
eq |
Igual | actor.meta.role eq "admin" |
ne |
No igual | meta.status ne "deleted" |
lt |
Menor que | meta.priority lt 5 |
gt |
Mayor que | actor.meta.clearance gt 2 |
lte |
Menor o igual | meta.size lte 1000 |
gte |
Mayor o igual | actor.meta.level gte 3 |
in |
Valor en array | action in ["read", "write"] |
nin |
Valor no en array | meta.status nin ["deleted", "archived"] |
exists |
Campo existe | meta.owner exists true |
nexists |
Campo no existe | meta.deleted nexists true |
contains |
String contiene | resource contains "sensitive" |
ncontains |
String no contiene | resource ncontains "public" |
matches |
Coincidencia regex | resource matches "^doc:.*" |
nmatches |
No coincide regex | actor.id nmatches "^system:.*" |
Scopes
Los scopes combinan múltiples políticas en un contexto de seguridad.
local security = require("security")
-- Obtener políticas
local admin_policy = security.policy("app.security:admin_policy")
local readonly_policy = security.policy("app.security:readonly_policy")
-- Crear scope con políticas
local scope = security.new_scope()
scope = scope:with(admin_policy)
scope = scope:with(readonly_policy)
-- Los scopes son inmutables - :with() retorna nuevo scope
Scopes Nombrados (Grupos de Políticas)
Cargar todas las políticas de un grupo:
-- Cargar scope con todas las políticas del grupo
local scope, err = security.named_scope("app.security:admin")
Evaluación de Políticas
Flujo de Evaluación
1. Verificar cada política en scope
2. Si ALGUNA política retorna Deny -> Resultado es Deny
3. Si al menos un Allow y ningún Deny -> Resultado es Allow
4. Sin políticas aplicables -> Resultado es Undefined
Resultados de Evaluación
| Resultado | Significado |
|---|---|
allow |
Acceso concedido |
deny |
Acceso explícitamente denegado |
undefined |
Ninguna política coincidió |
Almacenes de Tokens
Los almacenes de tokens proporcionan creación, validación y revocación segura de tokens.
Configuración
# src/auth/_index.yaml
version: "1.0"
namespace: app.auth
entries:
# Registrar variable de entorno
- name: os_env
kind: env.storage.os
- name: AUTH_SECRET_KEY
kind: env.variable
variable: AUTH_SECRET_KEY
storage: app.auth:os_env
# Almacén respaldo para tokens
- name: token_data
kind: store.memory
lifecycle:
auto_start: true
# Almacén de tokens
- name: tokens
kind: security.token_store
store: app.auth:token_data
token_length: 32
default_expiration: "24h"
token_key_env: "AUTH_SECRET_KEY"
Opciones del Almacén de Tokens
| Opción | Por Defecto | Descripción |
|---|---|---|
store |
requerido | Referencia al almacén clave-valor respaldo |
token_length |
32 | Tamaño del token en bytes (256 bits) |
default_expiration |
24h | TTL por defecto del token |
token_key |
ninguno | Clave de firma HMAC-SHA256 (valor directo) |
token_key_env |
ninguno | Nombre de variable de entorno para clave de firma |
Use token_key_env en producción para evitar embeber secretos en entradas.
Verificación Rápida de Permisos
-- Verificar contra actor y scope del contexto actual
local allowed = security.can("read", "document:123", {
owner = "user:456"
})
if not allowed then
return nil, errors.new("FORBIDDEN", "Acceso denegado")
end
Flujo de Contexto
El contexto de seguridad se propaga a través de llamadas de funciones.
Estableciendo Contexto
local funcs = require("funcs")
-- Llamar función con contexto de seguridad
local result, err = funcs.new()
:with_actor(actor)
:with_scope(scope)
:call("app.api:protected_endpoint", data)
Seguridad a Nivel de Servicio
Configure seguridad por defecto para servicios:
- name: worker_service
kind: process.lua
source: file://worker.lua
lifecycle:
auto_start: true
security:
actor:
id: "service:worker"
meta:
role: worker
service: true
policies:
- app.security:worker_policy
groups:
- workers
Modo Estricto
Habilite modo estricto para denegar acceso cuando el contexto de seguridad está ausente:
# wippy.yaml
security:
strict_mode: true
| Modo | Contexto Ausente | Comportamiento |
|---|---|---|
| Normal | Sin actor/scope | Allow (permisivo) |
| Estricto | Sin actor/scope | Deny (seguro por defecto) |
Ver También
- Módulo Security - API de seguridad en Lua