セキュリティモデル
セキュリティモデル
Section titled “セキュリティモデル”Wippyは属性ベースのアクセス制御を実装します。すべてのリクエストはアクター(誰が)とスコープ(どのポリシーが適用されるか)を持ちます。ポリシーはアクション、リソース、およびアクターとリソース両方からのメタデータに基づいてアクセスを評価します。
flowchart LR A[Actor + Scope] --> PE[ポリシー評価] --> AD[許可/拒否] A -.->|アイデンティティ<br/>メタデータ| PE PE -.->|条件<br/>actor, resource, action| ADエントリ種別
Section titled “エントリ種別”| 種別 | 説明 |
|---|---|
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", ...}コンテキスト内のアクター
Section titled “コンテキスト内のアクター”-- コンテキストから現在のアクターを取得local actor = security.actor()if not actor then return nil, errors.new("UNAUTHORIZED", "No actor in context")endポリシーはアクション、リソース、条件、効果を持つアクセスルールを定義します。
宣言的ポリシー
Section titled “宣言的ポリシー”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ポリシー構造
Section titled “ポリシー構造”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"式ベースのポリシー
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: - editors条件はアクター、アクション、リソース、メタデータに基づいた動的ポリシー評価を可能にします。
フィールドパス
Section titled “フィールドパス”| パス | 説明 |
|---|---|
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 | 正規表現マッチ | resource matches "^doc:.*" |
nmatches | 正規表現マッチしない | 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()は新しいスコープを返す名前付きスコープ(ポリシーグループ)
Section titled “名前付きスコープ(ポリシーグループ)”グループからすべてのポリシーをロード:
-- グループ内のすべてのポリシーを持つスコープをロードlocal scope, err = security.named_scope("app.security:admin")ポリシーはgroupsフィールドでグループに割り当てられます:
- name: admin_policy kind: security.policy policy: # ... groups: - admin # このポリシーは"admin"グループ内 - default # 複数グループに所属可能スコープ操作
Section titled “スコープ操作”-- ポリシーを追加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()ポリシー評価
Section titled “ポリシー評価”1. スコープ内の各ポリシーをチェック2. いずれかのポリシーがDenyを返す → 結果はDeny3. 少なくとも1つの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 -- ポリシーがマッチしなかった - strictモードに依存endクイック権限チェック
Section titled “クイック権限チェック”-- 現在のコンテキストのアクターとスコープに対してチェックlocal allowed = security.can("read", "document:123", { owner = "user:456"})
if not allowed then return nil, errors.new("FORBIDDEN", "Access denied")endトークンストア
Section titled “トークンストア”トークンストアはセキュアなトークン作成、検証、失効を提供します。
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"トークンストアオプション
Section titled “トークンストアオプション”| オプション | デフォルト | 説明 |
|---|---|---|
store | 必須 | バッキングキーバリューストア参照 |
token_length | 32 | トークンサイズ(バイト、256ビット) |
default_expiration | 24h | デフォルトトークンTTL |
token_key | なし | HMAC-SHA256署名キー(直接値) |
token_key_env | なし | 署名キー用環境変数名 |
本番環境ではエントリにシークレットを埋め込まないようtoken_key_envを使用してください。環境変数の登録については環境変数システムを参照してください。
トークンの作成
Section titled “トークンの作成”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"トークンの検証
Section titled “トークンの検証”-- トークンを検証local actor, scope, err = store:validate(token)if err then return nil, errors.new("UNAUTHORIZED", "Invalid token")end
-- アクターとスコープは保存されたデータから再構築されるprint(actor:id()) -- "user:123"トークンの失効
Section titled “トークンの失効”-- 単一トークンを失効local ok, err = store:revoke(token)
-- 完了したらストアを閉じるstore:close()コンテキストフロー
Section titled “コンテキストフロー”セキュリティコンテキストは関数呼び出しを通じて伝播します。
コンテキストの設定
Section titled “コンテキストの設定”local funcs = require("funcs")
-- セキュリティコンテキスト付きで関数を呼び出しlocal result, err = funcs.new() :with_actor(actor) :with_scope(scope) :call("app.api:protected_endpoint", data)コンテキスト継承
Section titled “コンテキスト継承”| コンポーネント | 継承 |
|---|---|
| アクター | はい - 子呼び出しに渡される |
| スコープ | はい - 子呼び出しに渡される |
| Strictモード | いいえ - アプリケーション全体 |
関数は呼び出し元のセキュリティコンテキストを継承します。生成されたプロセスは新規に開始します。
サービスレベルセキュリティ
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| モード | 欠落コンテキスト | 動作 |
|---|---|---|
| 通常 | アクター/スコープなし | 許可(寛容) |
| Strict | アクター/スコープなし | 拒否(セキュアデフォルト) |
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"})ベストプラクティス
Section titled “ベストプラクティス”- 最小権限 - 必要最小限の権限を付与
- デフォルトで拒否 - 明示的な許可ポリシーを使用し、strictモードを有効化
- ポリシーグループを使用 - ロール/機能ごとにポリシーを整理
- トークンに署名 - 本番環境では常に
token_key_envを設定 - 短い有効期限 - 機密操作には短いトークン寿命を使用
- コンテキストで条件付け - 静的ポリシーより動的条件を使用
- 機密アクションを監査 - セキュリティ関連の操作をログ
セキュリティモジュールリファレンス
Section titled “セキュリティモジュールリファレンス”| 関数 | 説明 |
|---|---|
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) | トークンストアを取得 |