Modelo de Seguridad
Modelo de Seguridad
Sección titulada «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 basándose en la acción, el recurso y los metadatos tanto del actor como del recurso.
flowchart LR A[Actor + Scope] --> PE[Policy Evaluation] --> AD[Allow/Deny] A -.->|Identity<br/>Metadata| PE PE -.->|Conditions<br/>actor, resource, action| ADTipos de Entrada
Sección titulada «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
Sección titulada «Actores»Un actor representa quién está realizando una acción.
local security = require("security")
-- Crear actor con metadatoslocal actor = security.new_actor("user:123", { role = "admin", team = "backend", department = "engineering", clearance = 3})
-- Acceder a propiedades del actorlocal id = actor:id() -- "user:123"local meta = actor:meta() -- {role="admin", ...}Actor en el Contexto
Sección titulada «Actor en el Contexto»-- Obtener el actor actual desde el contextolocal actor = security.actor()if not actor then return nil, errors.new("UNAUTHORIZED", "No actor in context")endPolíticas
Sección titulada «Políticas»Las políticas definen reglas de acceso con acciones, recursos, condiciones y efectos.
Política Declarativa
Sección titulada «Política Declarativa»version: "1.0"namespace: app.security
entries: # Acceso total de administrador - name: admin_policy kind: security.policy policy: actions: "*" resources: "*" effect: allow conditions: - field: actor.meta.role operator: eq value: admin groups: - admin
# Acceso solo de lectura - name: readonly_policy kind: security.policy policy: actions: - "*.read" - "*.get" - "*.list" resources: "*" effect: allow groups: - default
# Acceso del propietario del 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 nivel de 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: - securityEstructura de Política
Sección titulada «Estructura de Política»policy: actions: "*" | "action" | ["action1", "action2"] resources: "*" | "resource" | ["res1", "res2"] effect: allow | deny conditions: # Opcional - field: "field.path" operator: "eq" value: "static_value" # O value_from: "other.field.path"Política Basada en Expresiones
Sección titulada «Política Basada en Expresiones»Para lógica compleja, use políticas de expresión:
- 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: - editorsCondiciones
Sección titulada «Condiciones»Las condiciones permiten la evaluación dinámica de políticas basada en actor, acción, recurso y metadatos.
Rutas de Campo
Sección titulada «Rutas de Campo»| Ruta | Descripción |
|---|---|
actor.id | Identificador único del actor |
actor.meta.* | Metadatos del actor (admite anidamiento) |
action | La acción que se está realizando |
resource | El identificador del recurso |
meta.* | Metadatos del recurso |
Operadores
Sección titulada «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 arreglo | action in ["read", "write"] |
nin | Valor no en arreglo | meta.status nin ["deleted", "archived"] |
exists | El campo existe | meta.owner exists true |
nexists | El campo no existe | meta.deleted nexists true |
contains | String contiene | resource contains "sensitive" |
ncontains | String no contiene | resource ncontains "public" |
matches | Coincide con regex | resource matches "^doc:.*" |
nmatches | No coincide con regex | actor.id nmatches "^system:.*" |
Ejemplos de Condiciones
Sección titulada «Ejemplos de Condiciones»# Coincidir con rol del actorconditions: - field: actor.meta.role operator: eq value: admin
# Comparar camposconditions: - field: meta.owner operator: eq value_from: actor.id
# Comparación numéricaconditions: - field: actor.meta.clearance operator: gte value: 3
# Pertenencia a arregloconditions: - field: actor.meta.role operator: in value: - admin - moderator
# Coincidencia de patrónconditions: - field: resource operator: matches value: "^api:/v[0-9]+/admin/.*"
# Múltiples condiciones (AND)conditions: - field: actor.meta.department operator: eq value: engineering - field: meta.environment operator: eq value: productionLos scopes combinan múltiples políticas en un contexto de seguridad.
local security = require("security")
-- Obtener políticaslocal admin_policy = security.policy("app.security:admin_policy")local readonly_policy = security.policy("app.security:readonly_policy")
-- Crear scope con políticaslocal scope = security.new_scope()scope = scope:with(admin_policy)scope = scope:with(readonly_policy)
-- Los scopes son inmutables - :with() devuelve un nuevo scopeScopes Nombrados (Grupos de Políticas)
Sección titulada «Scopes Nombrados (Grupos de Políticas)»Cargar todas las políticas de un grupo:
-- Cargar scope con todas las políticas del grupolocal scope, err = security.named_scope("app.security:admin")Las políticas se asignan a grupos mediante el campo groups:
- name: admin_policy kind: security.policy policy: # ... groups: - admin # Esta política está en el grupo "admin" - default # Puede estar en varios gruposOperaciones de Scope
Sección titulada «Operaciones de Scope»-- Agregar políticalocal new_scope = scope:with(policy)
-- Eliminar políticalocal new_scope = scope:without("app.security:temp_policy")
-- Verificar si una política está en el scopelocal has = scope:contains("app.security:admin_policy")
-- Obtener todas las políticaslocal policies = scope:policies()Evaluación de Políticas
Sección titulada «Evaluación de Políticas»Flujo de Evaluación
Sección titulada «Flujo de Evaluación»1. Verifica cada política en el scope2. Si ALGUNA política devuelve Deny → El resultado es Deny3. Si hay al menos un Allow y ningún Deny → El resultado es Allow4. Sin políticas aplicables → El resultado es UndefinedResultados de Evaluación
Sección titulada «Resultados de Evaluación»| Resultado | Significado |
|---|---|
allow | Acceso concedido |
deny | Acceso denegado explícitamente |
undefined | Ninguna política coincidió |
-- Evaluar directamentelocal result = scope:evaluate(actor, "read", "document:123", { owner = "user:456", classification = "internal"})
if result == "deny" then return nil, errors.new("FORBIDDEN", "Access denied")elseif result == "undefined" then -- Ninguna política coincidió - depende del modo estrictoendVerificación Rápida de Permisos
Sección titulada «Verificación Rápida de Permisos»-- Verificar contra el actor y scope del contexto actuallocal allowed = security.can("read", "document:123", { owner = "user:456"})
if not allowed then return nil, errors.new("FORBIDDEN", "Access denied")endAlmacenes de Tokens
Sección titulada «Almacenes de Tokens»Los almacenes de tokens proporcionan creación, validación y revocación seguras de tokens.
Configuración
Sección titulada «Configuración»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
Sección titulada «Opciones del Almacén de Tokens»| Opción | Predeterminado | Descripción |
|---|---|---|
store | requerido | Referencia al almacén clave-valor de respaldo |
token_length | 32 | Tamaño del token en bytes (256 bits) |
default_expiration | 24h | TTL predeterminado del token |
token_key | ninguno | Clave de firma HMAC-SHA256 (valor directo) |
token_key_env | ninguno | Nombre de la variable de entorno para la clave de firma |
Use token_key_env en producción para evitar incrustar secretos en las entradas. Consulte Sistema de Entorno para registrar variables de entorno.
Creación de Tokens
Sección titulada «Creación de Tokens»local security = require("security")
-- Obtener almacén de tokenslocal store, err = security.token_store("app.auth:tokens")if err then return nil, errend
-- Crear actor y scopelocal actor = security.new_actor("user:123", { role = "user", email = "user@example.com"})
local scope, _ = security.named_scope("app.security:default")
-- Crear tokenlocal token, err = store:create(actor, scope, { expiration = "7d", -- Sobrescribir expiración predeterminada meta = { device = "mobile", ip = "192.168.1.1" }})
if err then return nil, errend
-- Formato del token: base64_token.hmac_signature (si token_key está definido)-- Ejemplo: "dGVzdHRva2VuMTIz.a1b2c3d4e5f6"Validación de Tokens
Sección titulada «Validación de Tokens»-- Validar tokenlocal actor, scope, err = store:validate(token)if err then return nil, errors.new("UNAUTHORIZED", "Invalid token")end
-- Actor y scope se reconstruyen desde los datos almacenadosprint(actor:id()) -- "user:123"Revocación de Tokens
Sección titulada «Revocación de Tokens»-- Revocar un tokenlocal ok, err = store:revoke(token)
-- Cerrar el almacén cuando terminestore:close()Flujo de Contexto
Sección titulada «Flujo de Contexto»El contexto de seguridad se propaga a través de las llamadas a funciones.
Establecer el Contexto
Sección titulada «Establecer el Contexto»local funcs = require("funcs")
-- Llamar función con contexto de seguridadlocal result, err = funcs.new() :with_actor(actor) :with_scope(scope) :call("app.api:protected_endpoint", data)Herencia del Contexto
Sección titulada «Herencia del Contexto»| Componente | Hereda |
|---|---|
| Actor | Sí - se pasa a llamadas hijas |
| Scope | Sí - se pasa a llamadas hijas |
| Modo estricto | No - es a nivel de aplicación |
Las funciones heredan el contexto de seguridad del llamador. Los procesos generados comienzan sin contexto.
Seguridad a Nivel de Servicio
Sección titulada «Seguridad a Nivel de Servicio»Configure la seguridad predeterminada 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: - workersModo Estricto
Sección titulada «Modo Estricto»Active el modo estricto para denegar el acceso cuando falte el contexto de seguridad:
security: strict_mode: true| Modo | Contexto Ausente | Comportamiento |
|---|---|---|
| Normal | Sin actor/scope | Permite (permisivo) |
| Estricto | Sin actor/scope | Deniega (seguro por defecto) |
Flujo de Autenticación
Sección titulada «Flujo de Autenticación»Validación de token en un handler HTTP:
local http = require("http")local security = require("security")
local function protected_handler() local req = http.request() local res = http.response()
-- Extraer y validar token local auth = req:header("Authorization") if not auth then return res:set_status(401):write_json({error = "Missing authorization"}) end
local token = auth:gsub("^Bearer%s+", "") local store, _ = security.token_store("app.auth:tokens") local actor, scope, err = store:validate(token) if err then return res:set_status(401):write_json({error = "Invalid token"}) end
-- Verificar permiso if not security.can("api.users.read", "users") then return res:set_status(403):write_json({error = "Forbidden"}) end
res:write_json({user = actor:id()})end
return { handler = protected_handler }Creación de token durante el login:
local actor = security.new_actor("user:" .. user.id, {role = user.role})local scope, _ = security.named_scope("app.security:" .. user.role)
local store, _ = security.token_store("app.auth:tokens")local token, err = store:create(actor, scope, {expiration = "24h"})Mejores Prácticas
Sección titulada «Mejores Prácticas»- Privilegio mínimo - Otorgue los permisos mínimos requeridos
- Denegar por defecto - Use políticas de permiso explícitas, active el modo estricto
- Use grupos de políticas - Organice las políticas por rol/función
- Firme los tokens - Siempre defina
token_key_enven producción - Expiración corta - Use tiempos de vida cortos para operaciones sensibles
- Condicione sobre el contexto - Prefiera condiciones dinámicas frente a políticas estáticas
- Audite acciones sensibles - Registre operaciones relevantes para la seguridad
Referencia del Módulo security
Sección titulada «Referencia del Módulo security»| Función | Descripción |
|---|---|
security.actor() | Obtiene el actor actual desde el contexto |
security.scope() | Obtiene el scope actual desde el contexto |
security.can(action, resource, meta?) | Verifica permiso |
security.new_actor(id, meta?) | Crea un nuevo actor |
security.new_scope(policies?) | Crea un scope vacío o con políticas iniciales |
security.policy(id) | Obtiene política por ID |
security.named_scope(group_id) | Obtiene scope con todas las políticas del grupo |
security.token_store(id) | Obtiene un almacén de tokens |