Security 模型
Security 模型
Section titled “Security 模型”Wippy 实现基于属性的访问控制。每个请求携带一个 actor(谁)和一个 scope(应用哪些策略)。策略根据 action、resource 以及来自 actor 和 resource 的元数据评估访问权限。
flowchart LR A[Actor + Scope] --> PE[策略评估] --> AD[允许/拒绝] A -.->|身份<br/>元数据| PE PE -.->|条件<br/>actor, resource, action| ADEntry 类型
Section titled “Entry 类型”| Kind | 描述 |
|---|---|
security.policy | 带条件的声明式策略 |
security.policy.expr | 基于表达式的策略 |
security.token_store | Token 存储和验证 |
Actors
Section titled “Actors”Actor 表示执行操作的主体。
local security = require("security")
-- Create actor with metadatalocal actor = security.new_actor("user:123", { role = "admin", team = "backend", department = "engineering", clearance = 3})
-- Access actor propertieslocal id = actor:id() -- "user:123"local meta = actor:meta() -- {role="admin", ...}上下文中的 Actor
Section titled “上下文中的 Actor”-- Get current actor from contextlocal actor = security.actor()if not actor then return nil, errors.new("UNAUTHORIZED", "No actor in context")endPolicies
Section titled “Policies”策略定义访问规则,包含 actions、resources、conditions 和 effects。
version: "1.0"namespace: app.security
entries: # Admin full access - name: admin_policy kind: security.policy policy: actions: "*" resources: "*" effect: allow conditions: - field: actor.meta.role operator: eq value: admin groups: - admin
# Read-only access - name: readonly_policy kind: security.policy policy: actions: - "*.read" - "*.get" - "*.list" resources: "*" effect: allow groups: - default
# Resource owner access - 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
# Deny confidential without clearance - 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: - securitypolicy: actions: "*" | "action" | ["action1", "action2"] resources: "*" | "resource" | ["res1", "res2"] effect: allow | deny conditions: # Optional - field: "field.path" operator: "eq" value: "static_value" # OR value_from: "other.field.path"基于表达式的策略
Section titled “基于表达式的策略”对于复杂逻辑,使用表达式策略:
- 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: - editorsConditions
Section titled “Conditions”条件允许基于 actor、action、resource 和元数据进行动态策略评估。
| 路径 | 描述 |
|---|---|
actor.id | Actor 的唯一标识符 |
actor.meta.* | Actor 元数据(支持嵌套) |
action | 正在执行的操作 |
resource | 资源标识符 |
meta.* | 资源元数据 |
| 运算符 | 描述 | 示例 |
|---|---|---|
eq | 等于 | actor.meta.role eq "admin" |
ne | 不等于 | meta.status ne "deleted" |
lt | 小于 | meta.priority lt 5 |
gt | 大于 | actor.meta.clearance gt 2 |
lte | 小于等于 | meta.size lte 1000 |
gte | 大于等于 | actor.meta.level gte 3 |
in | 值在数组中 | action in ["read", "write"] |
nin | 值不在数组中 | meta.status nin ["deleted", "archived"] |
exists | 字段存在 | meta.owner exists true |
nexists | 字段不存在 | meta.deleted nexists true |
contains | 字符串包含 | resource contains "sensitive" |
ncontains | 字符串不包含 | resource ncontains "public" |
matches | 正则匹配 | resource matches "^doc:.*" |
nmatches | 正则不匹配 | actor.id nmatches "^system:.*" |
# Match actor roleconditions: - field: actor.meta.role operator: eq value: admin
# Compare fieldsconditions: - field: meta.owner operator: eq value_from: actor.id
# Numeric comparisonconditions: - field: actor.meta.clearance operator: gte value: 3
# Array membershipconditions: - field: actor.meta.role operator: in value: - admin - moderator
# Pattern matchingconditions: - field: resource operator: matches value: "^api:/v[0-9]+/admin/.*"
# Multiple conditions (AND)conditions: - field: actor.meta.department operator: eq value: engineering - field: meta.environment operator: eq value: productionScopes
Section titled “Scopes”Scope 将多个策略组合成一个安全上下文。
local security = require("security")
-- Get policieslocal admin_policy = security.policy("app.security:admin_policy")local readonly_policy = security.policy("app.security:readonly_policy")
-- Create scope with policieslocal scope = security.new_scope()scope = scope:with(admin_policy)scope = scope:with(readonly_policy)
-- Scopes are immutable - :with() returns new scope命名 Scope(策略组)
Section titled “命名 Scope(策略组)”从组加载所有策略:
-- Load scope with all policies in grouplocal scope, err = security.named_scope("app.security:admin")策略通过 groups 字段分配到组:
- name: admin_policy kind: security.policy policy: # ... groups: - admin # This policy is in "admin" group - default # Can be in multiple groupsScope 操作
Section titled “Scope 操作”-- Add policylocal new_scope = scope:with(policy)
-- Remove policylocal new_scope = scope:without("app.security:temp_policy")
-- Check if policy is in scopelocal has = scope:contains("app.security:admin_policy")
-- Get all policieslocal policies = scope:policies()1. Check each policy in scope2. If ANY policy returns Deny → Result is Deny3. If at least one Allow and no Deny → Result is Allow4. No applicable policies → Result is Undefined| 结果 | 含义 |
|---|---|
allow | 访问已授权 |
deny | 访问被明确拒绝 |
undefined | 没有策略匹配 |
-- Evaluate directlylocal 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 -- No policy matched - depends on strict modeend快速权限检查
Section titled “快速权限检查”-- Check against current context's actor and scopelocal allowed = security.can("read", "document:123", { owner = "user:456"})
if not allowed then return nil, errors.new("FORBIDDEN", "Access denied")endToken Stores
Section titled “Token Stores”Token store 提供安全的 token 创建、验证和撤销。
version: "1.0"namespace: app.auth
entries: # Register environment variable - name: os_env kind: env.storage.os
- name: AUTH_SECRET_KEY kind: env.variable variable: AUTH_SECRET_KEY storage: app.auth:os_env
# Backing store for 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"Token Store 选项
Section titled “Token Store 选项”| 选项 | 默认值 | 描述 |
|---|---|---|
store | required | 后端键值存储引用 |
token_length | 32 | Token 大小(字节,256 位) |
default_expiration | 24h | 默认 token TTL |
token_key | none | HMAC-SHA256 签名密钥(直接值) |
token_key_env | none | 签名密钥的环境变量名 |
在生产环境中使用 token_key_env 以避免在 entry 中嵌入密钥。参见 Environment 系统 了解环境变量注册。
创建 Token
Section titled “创建 Token”local security = require("security")
-- Get token storelocal store, err = security.token_store("app.auth:tokens")if err then return nil, errend
-- Create actor and scopelocal actor = security.new_actor("user:123", { role = "user", email = "user@example.com"})
local scope, _ = security.named_scope("app.security:default")
-- Create tokenlocal token, err = store:create(actor, scope, { expiration = "7d", -- Override default expiration meta = { device = "mobile", ip = "192.168.1.1" }})
if err then return nil, errend
-- Token format: base64_token.hmac_signature (if token_key set)-- Example: "dGVzdHRva2VuMTIz.a1b2c3d4e5f6"验证 Token
Section titled “验证 Token”-- Validate tokenlocal actor, scope, err = store:validate(token)if err then return nil, errors.new("UNAUTHORIZED", "Invalid token")end
-- Actor and scope are reconstructed from stored dataprint(actor:id()) -- "user:123"撤销 Token
Section titled “撤销 Token”-- Revoke single tokenlocal ok, err = store:revoke(token)
-- Close store when donestore:close()安全上下文通过函数调用传播。
local funcs = require("funcs")
-- Call function with security contextlocal result, err = funcs.new() :with_actor(actor) :with_scope(scope) :call("app.api:protected_endpoint", data)| 组件 | 继承 |
|---|---|
| Actor | 是 - 传递给子调用 |
| Scope | 是 - 传递给子调用 |
| Strict mode | 否 - 应用全局 |
函数继承调用者的安全上下文。新生成的进程从头开始。
服务级别安全
Section titled “服务级别安全”为服务配置默认安全设置:
- 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: - workersStrict 模式
Section titled “Strict 模式”启用 strict 模式,在缺少安全上下文时拒绝访问:
security: strict_mode: true| 模式 | 缺少上下文 | 行为 |
|---|---|---|
| Normal | 无 actor/scope | 允许(宽松) |
| Strict | 无 actor/scope | 拒绝(安全默认) |
HTTP 处理器中的 Token 验证:
local http = require("http")local security = require("security")
local function protected_handler() local req = http.request() local res = http.response()
-- Extract and validate 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
-- Check permission 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 }登录时创建 Token:
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"})- 最小权限 - 授予最小必需权限
- 默认拒绝 - 使用显式允许策略,启用 strict 模式
- 使用策略组 - 按角色/功能组织策略
- 签名 Token - 生产环境始终设置
token_key_env - 短期过期 - 对敏感操作使用较短的 token 生命周期
- 基于上下文的条件 - 使用动态条件而非静态策略
- 审计敏感操作 - 记录安全相关操作
Security 模块参考
Section titled “Security 模块参考”| 函数 | 描述 |
|---|---|
security.actor() | 从上下文获取当前 actor |
security.scope() | 从上下文获取当前 scope |
security.can(action, resource, meta?) | 检查权限 |
security.new_actor(id, meta?) | 创建新 actor |
security.new_scope(policies?) | 创建空或预设的 scope |
security.policy(id) | 通过 ID 获取策略 |
security.named_scope(group_id) | 获取包含所有组策略的 scope |
security.token_store(id) | 获取 token store |