Модель безопасности
Модель безопасности
Заголовок раздела «Модель безопасности»Wippy реализует контроль доступа на основе атрибутов. Каждый запрос несёт актёра (кто) и область (какие политики применяются). Политики оценивают доступ на основе действия, ресурса и метаданных актёра и ресурса.
flowchart LR A[Актёр + Область] --> PE[Оценка политики] --> AD[Разрешить/Запретить] A -.->|Идентификатор<br/>Метаданные| PE PE -.->|Условия<br/>актёр, ресурс, действие| ADТипы записей
Заголовок раздела «Типы записей»| Тип | Описание |
|---|---|
security.policy | Декларативная политика с условиями |
security.policy.expr | Политика на основе выражений |
security.token_store | Хранение и валидация токенов |
Актёр представляет того, кто выполняет действие.
local security = require("security")
-- Создаём актёра с метаданнымиlocal actor = security.new_actor("user:123", { role = "admin", team = "backend", department = "engineering", clearance = 3})
-- Доступ к свойствам актёраlocal id = actor:id() -- "user:123"local meta = actor:meta() -- {role="admin", ...}Актёр в контексте
Заголовок раздела «Актёр в контексте»-- Получаем текущего актёра из контекстаlocal actor = security.actor()if not actor then return nil, errors.new("UNAUTHORIZED", "No actor in context")endПолитики
Заголовок раздела «Политики»Политики определяют правила доступа с действиями, ресурсами, условиями и эффектами.
Декларативная политика
Заголовок раздела «Декларативная политика»version: "1.0"namespace: app.security
entries: # Полный доступ для админов - name: admin_policy kind: security.policy policy: actions: "*" resources: "*" effect: allow conditions: - field: actor.meta.role operator: eq value: admin groups: - admin
# Доступ только на чтение - name: readonly_policy kind: security.policy policy: actions: - "*.read" - "*.get" - "*.list" resources: "*" effect: allow groups: - default
# Доступ владельца ресурса - 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
# Запрет конфиденциальных без допуска - 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Структура политики
Заголовок раздела «Структура политики»policy: actions: "*" | "action" | ["action1", "action2"] resources: "*" | "resource" | ["res1", "res2"] effect: allow | deny conditions: # Необязательно - field: "field.path" operator: "eq" value: "static_value" # ИЛИ value_from: "other.field.path"Политика на основе выражений
Заголовок раздела «Политика на основе выражений»Для сложной логики используйте политики с выражениями:
- 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Условия
Заголовок раздела «Условия»Условия позволяют динамически оценивать политики на основе актёра, действия, ресурса и метаданных.
Пути к полям
Заголовок раздела «Пути к полям»| Путь | Описание |
|---|---|
actor.id | Уникальный идентификатор актёра |
actor.meta.* | Метаданные актёра (поддерживает вложенность) |
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 | Совпадение по regex | resource matches "^doc:.*" |
nmatches | Несовпадение по regex | actor.id nmatches "^system:.*" |
Примеры условий
Заголовок раздела «Примеры условий»# Проверка роли актёраconditions: - field: actor.meta.role operator: eq value: admin
# Сравнение полейconditions: - field: meta.owner operator: eq value_from: actor.id
# Числовое сравнениеconditions: - field: actor.meta.clearance operator: gte value: 3
# Членство в массивеconditions: - field: actor.meta.role operator: in value: - admin - moderator
# Сопоставление по шаблонуconditions: - field: resource operator: matches value: "^api:/v[0-9]+/admin/.*"
# Несколько условий (AND)conditions: - field: actor.meta.department operator: eq value: engineering - field: meta.environment operator: eq value: productionОбласти
Заголовок раздела «Области»Области объединяют несколько политик в контекст безопасности.
local security = require("security")
-- Получаем политикиlocal admin_policy = security.policy("app.security:admin_policy")local readonly_policy = security.policy("app.security:readonly_policy")
-- Создаём область с политикамиlocal scope = security.new_scope()scope = scope:with(admin_policy)scope = scope:with(readonly_policy)
-- Области иммутабельны — :with() возвращает новую областьИменованные области (группы политик)
Заголовок раздела «Именованные области (группы политик)»Загрузка всех политик из группы:
-- Загружаем область со всеми политиками группыlocal scope, err = security.named_scope("app.security:admin")Политики назначаются группам через поле groups:
- name: admin_policy kind: security.policy policy: # ... groups: - admin # Эта политика в группе "admin" - default # Может быть в нескольких группахОперации с областями
Заголовок раздела «Операции с областями»-- Добавить политикуlocal new_scope = scope:with(policy)
-- Удалить политикуlocal new_scope = scope:without("app.security:temp_policy")
-- Проверить наличие политикиlocal has = scope:contains("app.security:admin_policy")
-- Получить все политикиlocal policies = scope:policies()Вычисление политик
Заголовок раздела «Вычисление политик»Порядок вычисления
Заголовок раздела «Порядок вычисления»1. Проверяем каждую политику в области2. Если ЛЮБАЯ политика возвращает Deny → Результат Deny3. Если хотя бы один Allow и нет Deny → Результат Allow4. Нет применимых политик → Результат UndefinedРезультаты вычисления
Заголовок раздела «Результаты вычисления»| Результат | Значение |
|---|---|
allow | Доступ разрешён |
deny | Доступ явно запрещён |
undefined | Ни одна политика не сработала |
-- Прямое вычислениеlocal 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 -- Ни одна политика не сработала — зависит от строгого режимаendБыстрая проверка прав
Заголовок раздела «Быстрая проверка прав»-- Проверка относительно текущего актёра и области из контекстаlocal allowed = security.can("read", "document:123", { owner = "user:456"})
if not allowed then return nil, errors.new("FORBIDDEN", "Access denied")endХранилища токенов
Заголовок раздела «Хранилища токенов»Хранилища токенов обеспечивают безопасное создание, валидацию и отзыв токенов.
Конфигурация
Заголовок раздела «Конфигурация»version: "1.0"namespace: app.auth
entries: # Регистрация переменной окружения - name: os_env kind: env.storage.os
- name: AUTH_SECRET_KEY kind: env.variable variable: AUTH_SECRET_KEY storage: app.auth:os_env
# Хранилище для данных токенов - name: token_data kind: store.memory lifecycle: auto_start: true
# Хранилище токенов - name: tokens kind: security.token_store store: app.auth:token_data token_length: 32 default_expiration: "24h" token_key_env: "AUTH_SECRET_KEY"Параметры хранилища токенов
Заголовок раздела «Параметры хранилища токенов»| Параметр | По умолчанию | Описание |
|---|---|---|
store | обязательно | Ссылка на key-value хранилище |
token_length | 32 | Размер токена в байтах (256 бит) |
default_expiration | 24h | TTL токена по умолчанию |
token_key | нет | Ключ подписи HMAC-SHA256 (прямое значение) |
token_key_env | нет | Имя переменной окружения для ключа подписи |
В продакшене используйте token_key_env, чтобы не хранить секреты в записях. См. Система окружения для регистрации переменных окружения.
Создание токенов
Заголовок раздела «Создание токенов»local security = require("security")
-- Получаем хранилище токеновlocal store, err = security.token_store("app.auth:tokens")if err then return nil, errend
-- Создаём актёра и областьlocal actor = security.new_actor("user:123", { role = "user", email = "user@example.com"})
local scope, _ = security.named_scope("app.security:default")
-- Создаём токенlocal token, err = store:create(actor, scope, { expiration = "7d", -- Переопределяем срок действия meta = { device = "mobile", ip = "192.168.1.1" }})
if err then return nil, errend
-- Формат токена: base64_token.hmac_signature (если задан token_key)-- Пример: "dGVzdHRva2VuMTIz.a1b2c3d4e5f6"Валидация токенов
Заголовок раздела «Валидация токенов»-- Валидация токенаlocal actor, scope, err = store:validate(token)if err then return nil, errors.new("UNAUTHORIZED", "Invalid token")end
-- Актёр и область восстанавливаются из сохранённых данныхprint(actor:id()) -- "user:123"Отзыв токенов
Заголовок раздела «Отзыв токенов»-- Отзыв отдельного токенаlocal ok, err = store:revoke(token)
-- Закрыть хранилище по завершенииstore:close()Передача контекста
Заголовок раздела «Передача контекста»Контекст безопасности передаётся через вызовы функций.
Установка контекста
Заголовок раздела «Установка контекста»local funcs = require("funcs")
-- Вызов функции с контекстом безопасностиlocal result, err = funcs.new() :with_actor(actor) :with_scope(scope) :call("app.api:protected_endpoint", data)Наследование контекста
Заголовок раздела «Наследование контекста»| Компонент | Наследуется |
|---|---|
| Actor | Да — передаётся дочерним вызовам |
| Scope | Да — передаётся дочерним вызовам |
| Strict mode | Нет — уровень приложения |
Функции наследуют контекст безопасности вызывающего. Порождённые процессы начинают с чистого листа.
Безопасность на уровне сервиса
Заголовок раздела «Безопасность на уровне сервиса»Настройка безопасности по умолчанию для сервисов:
- 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Строгий режим
Заголовок раздела «Строгий режим»Включите строгий режим для запрета доступа при отсутствии контекста безопасности:
security: strict_mode: true| Режим | Отсутствует контекст | Поведение |
|---|---|---|
| Обычный | Нет actor/scope | Разрешить (permissive) |
| Строгий | Нет actor/scope | Запретить (secure default) |
Поток аутентификации
Заголовок раздела «Поток аутентификации»Валидация токена в HTTP-обработчике:
local http = require("http")local security = require("security")
local function protected_handler() local req = http.request() local res = http.response()
-- Извлекаем и валидируем токен 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
-- Проверяем права 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 }Создание токена при логине:
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"})Лучшие практики
Заголовок раздела «Лучшие практики»- Минимальные привилегии — выдавайте минимально необходимые права
- Запрет по умолчанию — используйте явные allow-политики, включайте строгий режим
- Группы политик — организуйте политики по ролям/функциям
- Подпись токенов — всегда задавайте
token_key_envв продакшене - Короткий срок действия — используйте меньший TTL для критичных операций
- Условия по контексту — предпочитайте динамические условия статическим политикам
- Аудит критичных действий — логируйте операции, связанные с безопасностью
Справочник модуля Security
Заголовок раздела «Справочник модуля Security»| Функция | Описание |
|---|---|
security.actor() | Получить текущего актёра из контекста |
security.scope() | Получить текущую область из контекста |
security.can(action, resource, meta?) | Проверить права |
security.new_actor(id, meta?) | Создать нового актёра |
security.new_scope(policies?) | Создать пустую или заполненную область |
security.policy(id) | Получить политику по ID |
security.named_scope(group_id) | Получить область со всеми политиками группы |
security.token_store(id) | Получить хранилище токенов |