Modelo de Segurança
Modelo de Segurança
Seção intitulada “Modelo de Segurança”O Wippy implementa controle de acesso baseado em atributos. Toda requisição carrega um ator (quem) e um escopo (quais políticas se aplicam). Políticas avaliam acesso baseado na ação, recurso e metadados tanto do ator quanto do recurso.
flowchart LR A[Ator + Escopo] --> PE[Avaliação de Política] --> AD[Permitir/Negar] A -.->|Identidade<br/>Metadados| PE PE -.->|Condições<br/>ator, recurso, ação| ADTipos de Entradas
Seção intitulada “Tipos de Entradas”| Tipo | Descrição |
|---|---|
security.policy | Política declarativa com condições |
security.policy.expr | Política baseada em expressão |
security.token_store | Armazenamento e validação de tokens |
Um ator representa quem está executando uma ação.
local security = require("security")
-- Cria ator com metadadoslocal actor = security.new_actor("user:123", { role = "admin", team = "backend", department = "engineering", clearance = 3})
-- Acessa propriedades do atorlocal id = actor:id() -- "user:123"local meta = actor:meta() -- {role="admin", ...}Ator no Contexto
Seção intitulada “Ator no Contexto”-- Obtém ator atual do contextolocal actor = security.actor()if not actor then return nil, errors.new("UNAUTHORIZED", "Sem ator no contexto")endPolíticas
Seção intitulada “Políticas”Políticas definem regras de acesso com ações, recursos, condições e efeitos.
Política Declarativa
Seção intitulada “Política Declarativa”version: "1.0"namespace: app.security
entries: # Acesso total de admin - name: admin_policy kind: security.policy policy: actions: "*" resources: "*" effect: allow conditions: - field: actor.meta.role operator: eq value: admin groups: - admin
# Acesso somente leitura - name: readonly_policy kind: security.policy policy: actions: - "*.read" - "*.get" - "*.list" resources: "*" effect: allow groups: - default
# Acesso do proprietário do 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
# Nega confidencial sem autorização - 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: - securityEstrutura da Política
Seção intitulada “Estrutura da Política”policy: actions: "*" | "action" | ["action1", "action2"] resources: "*" | "resource" | ["res1", "res2"] effect: allow | deny conditions: # Opcional - field: "field.path" operator: "eq" value: "static_value" # OU value_from: "other.field.path"Política Baseada em Expressão
Seção intitulada “Política Baseada em Expressão”Para lógica complexa, use políticas de expressão:
- 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: - editorsCondições
Seção intitulada “Condições”Condições permitem avaliação dinâmica de políticas baseada em ator, ação, recurso e metadados.
Caminhos de Campo
Seção intitulada “Caminhos de Campo”| Caminho | Descrição |
|---|---|
actor.id | Identificador único do ator |
actor.meta.* | Metadados do ator (suporta aninhamento) |
action | A ação sendo executada |
resource | O identificador do recurso |
meta.* | Metadados do recurso |
Operadores
Seção intitulada “Operadores”| Operador | Descrição | Exemplo |
|---|---|---|
eq | Igual | actor.meta.role eq "admin" |
ne | Diferente | meta.status ne "deleted" |
lt | Menor que | meta.priority lt 5 |
gt | Maior que | actor.meta.clearance gt 2 |
lte | Menor ou igual | meta.size lte 1000 |
gte | Maior ou igual | actor.meta.level gte 3 |
in | Valor em array | action in ["read", "write"] |
nin | Valor não em array | meta.status nin ["deleted", "archived"] |
exists | Campo existe | meta.owner exists true |
nexists | Campo não existe | meta.deleted nexists true |
contains | String contém | resource contains "sensitive" |
ncontains | String não contém | resource ncontains "public" |
matches | Correspondência regex | resource matches "^doc:.*" |
nmatches | Não corresponde regex | actor.id nmatches "^system:.*" |
Exemplos de Condições
Seção intitulada “Exemplos de Condições”# Corresponde role do atorconditions: - field: actor.meta.role operator: eq value: admin
# Compara camposconditions: - field: meta.owner operator: eq value_from: actor.id
# Comparação numéricaconditions: - field: actor.meta.clearance operator: gte value: 3
# Pertencimento a arrayconditions: - field: actor.meta.role operator: in value: - admin - moderator
# Correspondência de padrãoconditions: - field: resource operator: matches value: "^api:/v[0-9]+/admin/.*"
# Múltiplas condições (AND)conditions: - field: actor.meta.department operator: eq value: engineering - field: meta.environment operator: eq value: productionEscopos
Seção intitulada “Escopos”Escopos combinam múltiplas políticas em um contexto de segurança.
local security = require("security")
-- Obtém políticaslocal admin_policy = security.policy("app.security:admin_policy")local readonly_policy = security.policy("app.security:readonly_policy")
-- Cria escopo com políticaslocal scope = security.new_scope()scope = scope:with(admin_policy)scope = scope:with(readonly_policy)
-- Escopos são imutáveis - :with() retorna novo escopoEscopos Nomeados (Grupos de Políticas)
Seção intitulada “Escopos Nomeados (Grupos de Políticas)”Carrega todas as políticas de um grupo:
-- Carrega escopo com todas as políticas do grupolocal scope, err = security.named_scope("app.security:admin")Políticas são atribuídas a grupos via campo groups:
- name: admin_policy kind: security.policy policy: # ... groups: - admin # Esta política está no grupo "admin" - default # Pode estar em múltiplos gruposOperações de Escopo
Seção intitulada “Operações de Escopo”-- Adiciona políticalocal new_scope = scope:with(policy)
-- Remove políticalocal new_scope = scope:without("app.security:temp_policy")
-- Verifica se política está no escopolocal has = scope:contains("app.security:admin_policy")
-- Obtém todas as políticaslocal policies = scope:policies()Avaliação de Políticas
Seção intitulada “Avaliação de Políticas”Fluxo de Avaliação
Seção intitulada “Fluxo de Avaliação”1. Verifica cada política no escopo2. Se QUALQUER política retorna Deny -> Resultado é Deny3. Se pelo menos um Allow e nenhum Deny -> Resultado é Allow4. Nenhuma política aplicável -> Resultado é UndefinedResultados de Avaliação
Seção intitulada “Resultados de Avaliação”| Resultado | Significado |
|---|---|
allow | Acesso concedido |
deny | Acesso explicitamente negado |
undefined | Nenhuma política correspondeu |
-- Avalia diretamentelocal result = scope:evaluate(actor, "read", "document:123", { owner = "user:456", classification = "internal"})
if result == "deny" then return nil, errors.new("FORBIDDEN", "Acesso negado")elseif result == "undefined" then -- Nenhuma política correspondeu - depende do modo estritoendVerificação Rápida de Permissão
Seção intitulada “Verificação Rápida de Permissão”-- Verifica contra ator e escopo do contexto atuallocal allowed = security.can("read", "document:123", { owner = "user:456"})
if not allowed then return nil, errors.new("FORBIDDEN", "Acesso negado")endToken Stores
Seção intitulada “Token Stores”Token stores fornecem criação, validação e revogação seguras de tokens.
Configuração
Seção intitulada “Configuração”version: "1.0"namespace: app.auth
entries: # Registra variável de ambiente - name: os_env kind: env.storage.os
- name: AUTH_SECRET_KEY kind: env.variable variable: AUTH_SECRET_KEY storage: app.auth:os_env
# Store de apoio para tokens - name: token_data kind: store.memory lifecycle: auto_start: true
# Token store - name: tokens kind: security.token_store store: app.auth:token_data token_length: 32 default_expiration: "24h" token_key_env: "AUTH_SECRET_KEY"Opções do Token Store
Seção intitulada “Opções do Token Store”| Opção | Padrão | Descrição |
|---|---|---|
store | obrigatório | Referência do store chave-valor de apoio |
token_length | 32 | Tamanho do token em bytes (256 bits) |
default_expiration | 24h | TTL padrão do token |
token_key | nenhum | Chave de assinatura HMAC-SHA256 (valor direto) |
token_key_env | nenhum | Nome da variável de ambiente para chave de assinatura |
Use token_key_env em produção para evitar embutir segredos em entradas. Veja Sistema de Ambiente para registrar variáveis de ambiente.
Criando Tokens
Seção intitulada “Criando Tokens”local security = require("security")
-- Obtém token storelocal store, err = security.token_store("app.auth:tokens")if err then return nil, errend
-- Cria ator e escopolocal actor = security.new_actor("user:123", { role = "user", email = "user@example.com"})
local scope, _ = security.named_scope("app.security:default")
-- Cria tokenlocal token, err = store:create(actor, scope, { expiration = "7d", -- Sobrescreve expiração padrão meta = { device = "mobile", ip = "192.168.1.1" }})
if err then return nil, errend
-- Formato do token: base64_token.hmac_signature (se token_key definido)-- Exemplo: "dGVzdHRva2VuMTIz.a1b2c3d4e5f6"Validando Tokens
Seção intitulada “Validando Tokens”-- Valida tokenlocal actor, scope, err = store:validate(token)if err then return nil, errors.new("UNAUTHORIZED", "Token inválido")end
-- Ator e escopo são reconstruídos dos dados armazenadosprint(actor:id()) -- "user:123"Revogando Tokens
Seção intitulada “Revogando Tokens”-- Revoga token únicolocal ok, err = store:revoke(token)
-- Fecha store quando terminarstore:close()Fluxo de Contexto
Seção intitulada “Fluxo de Contexto”O contexto de segurança se propaga através de chamadas de função.
Definindo Contexto
Seção intitulada “Definindo Contexto”local funcs = require("funcs")
-- Chama função com contexto de segurançalocal result, err = funcs.new() :with_actor(actor) :with_scope(scope) :call("app.api:protected_endpoint", data)Herança de Contexto
Seção intitulada “Herança de Contexto”| Componente | Herda |
|---|---|
| Ator | Sim - passa para chamadas filhas |
| Escopo | Sim - passa para chamadas filhas |
| Modo estrito | Não - aplicação-wide |
Funções herdam contexto de segurança do chamador. Processos criados iniciam do zero.
Segurança em Nível de Serviço
Seção intitulada “Segurança em Nível de Serviço”Configure segurança padrão para serviços:
- 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 Estrito
Seção intitulada “Modo Estrito”Habilite modo estrito para negar acesso quando contexto de segurança está ausente:
security: strict_mode: true| Modo | Contexto Ausente | Comportamento |
|---|---|---|
| Normal | Sem ator/escopo | Permite (permissivo) |
| Estrito | Sem ator/escopo | Nega (padrão seguro) |
Fluxo de Autenticação
Seção intitulada “Fluxo de Autenticação”Validação de token em um handler HTTP:
local http = require("http")local security = require("security")
local function protected_handler() local req = http.request() local res = http.response()
-- Extrai e valida token local auth = req:header("Authorization") if not auth then return res:set_status(401):write_json({error = "Autorização ausente"}) 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 = "Token inválido"}) end
-- Verifica permissão if not security.can("api.users.read", "users") then return res:set_status(403):write_json({error = "Proibido"}) end
res:write_json({user = actor:id()})end
return { handler = protected_handler }Criação de token durante 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"})Boas Práticas
Seção intitulada “Boas Práticas”- Menor privilégio - Conceda permissões mínimas necessárias
- Negue por padrão - Use políticas de allow explícitas, habilite modo estrito
- Use grupos de políticas - Organize políticas por role/função
- Assine tokens - Sempre defina
token_key_envem produção - Expiração curta - Use tempos de vida de token mais curtos para operações sensíveis
- Condicione no contexto - Use condições dinâmicas sobre políticas estáticas
- Audite ações sensíveis - Registre operações relevantes para segurança
Referência do Módulo Security
Seção intitulada “Referência do Módulo Security”| Função | Descrição |
|---|---|
security.actor() | Obtém ator atual do contexto |
security.scope() | Obtém escopo atual do contexto |
security.can(action, resource, meta?) | Verifica permissão |
security.new_actor(id, meta?) | Cria novo ator |
security.new_scope(policies?) | Cria escopo vazio ou semeado |
security.policy(id) | Obtém política por ID |
security.named_scope(group_id) | Obtém escopo com todas as políticas do grupo |
security.token_store(id) | Obtém token store |