ランタイム API
このページでは TypeScript で Ninka を利用するときの API を説明します。アプリケーションが通常 import する Consumer Projection と、その下で動く ninka-authz/runtime の両方を扱います。
認可判定は WASM を使ってプロセス内で実行されます。リクエストごとにネットワーク越しの認可サーバへ問い合わせたり、opa binary を起動したり、Ninka のホスト型サービスへ通信したりはしません。
.NET については .NET ランタイム API を参照してください。
アプリケーションから使う API: Consumer Projection
通常のアプリケーションコードは、raw runtime を直接組み立てるのではなく、生成された Consumer Projection を import します。
import { createNinka, policies } from "@/src/generated/ninka";
const authz = await createNinka({
onDecision(event) {
logger.info(event);
},
});
const allowed = authz.check(policies.invoiceAccess, input);
Consumer Projection には実行 bundle と manifest / build metadata が埋め込まれています。実行時には ninka-authz/runtime を import し、埋め込まれた情報を runtime が検証・instantiate できる形へ戻します。ninka/out/ のファイルを実行時に読み込む必要はありません。
createNinka(options?)
async function createNinka(options?: {
onDecision?: (event: DecisionLogEntry) => void;
}): Promise<ProjectedNinka>
createNinka() は埋め込まれた bundle を検証し、利用可能な Ninka instance を返します。instance の生成時だけ await し、その後の check() は同期処理です。
生成された Consumer Projection がアプリケーション向けに公開する option は現在一つです。
| option | 意味 |
|---|---|
onDecision | Decision Log を有効にし、一回の判定ごとにマスク済みの DecisionLogEntry を受け取る。callback が例外を投げても認可判定は変わらない |
これは raw runtime の NinkaLoadOptions より意図的に小さい API です。Consumer Projection はポリシーの取得元を既に固定しており、onDecision を runtime の Decision Log sink へ接続します。
policies
policies は、各policyのpolicy idから導出した名前(ハイフンを取り除き、次の文字をcamelCase化したもの)をキーにした readonly の生成 table です。
policies.invoiceAccess
policies.a1
この導出は単射ではないため、導出結果が一致してしまう組は関所がコンパイル時に拒否します。コンパイルを通った projection でキーが衝突することはありません。policy id 自体は変換されず、reference上にそのままデータとして残ります(policies.invoiceAccess.id === "invoice-access")。
各 reference には次が結び付いています。
id:正確な policy idtegataHash:その Consumer Projection を生成した認可仕様の hash- TypeScript 上だけで使う、そのポリシー用の生成入力型との結び付き
この結び付きにより、別ポリシー用の入力を check() へ渡す誤りを TypeScript の型チェックで検出できます。
check(policy, input)
Consumer Projection から利用する check() は、生成された policy reference を受け取ります。
const allowed: boolean = authz.check(policies.invoiceAccess, input);
check() は同期処理で、結果は次のどちらかです。
true:allowfalse:deny
ポリシーを評価する前に、runtime は API / integrity 上の前提を確認します。たとえば、policy reference が有効か、policy id がロード済みか、tegataHash が manifest と一致するか、トップレベルの input がオブジェクトか、入力の深さが上限以内か、などです。これらの違反は認可上の deny ではなく caller / integrity error なので例外になります。
一方、正しいトップレベル input object の中で属性が欠けている場合や、属性値の runtime type がポリシーの期待と合わない場合は、認可入力として扱います。最終判定を決めるのは WASM に焼き込まれた三値意味論であり、runtime が事前検証で別の判定へ置き換えることはありません。
アプリケーション側に残る責任
Consumer Projection は、次を決めません。
- Ninka instance をどこで保持するか
- どの処理で認可判定を呼ぶか
- どの具体的な resource を認可対象にするか
- session やドメインデータをどう input へ対応づけるか
falseを受け取った後にアプリケーションをどう振る舞わせるか
これらはアプリケーション側の責任です。Ninka はアプリケーションの composition root を生成・所有しません。
Raw runtime: ninka-authz/runtime
ディレクトリから成果物を読む場合、tooling、テスト、Consumer Projection の下で実際に何が動いているかを理解したい場合は raw runtime API を使います。
アプリケーション向けに文書化している主な export は次です。
NinkaAuthzInputPolicyRefLikeEmbeddedBundleInputValidationErrorDecisionLogEntryDecisionLogOptionsNinkaLoadOptions
package には conformance test 等で使う internal な export もありますが、通常のアプリケーション API ではありません。通常の統合では Consumer Projection を優先してください。
runtime package の production dependency は @open-policy-agent/opa-wasm です。
class Ninka
constructor は private です。検証を伴う次の acquisition path から instance を作ります。
| API | 読み込むもの | 主な用途 |
|---|---|---|
Ninka.load(dir, options?) | artifact directory | tooling、script、artifact をファイルとして配備するアプリケーション |
Ninka.fromEmbedded(bundles, options?) | コード内に埋め込まれた metadata + base64 module | 生成された Consumer Projection |
二つの経路は、最終的に同じ bundle verification core を通ります。
Ninka.load(dir?, options?)
static async load(
dir = "ninka/out",
options: NinkaLoadOptions = {},
): Promise<Ninka>
dir/bundles/ 以下の build record を探し、対応する WASM と policy manifest を読み、bundle 全体を検証してから instance を返します。
共有の verification core は、bundle ごとに主に次を確認します。
- WASM の実バイト列が
build.json.wasm_sha256と一致すること - build file 名と
bundle_idが対応していること - binding の
policy_idと manifest の identity が一致すること - build record と各 manifest の
artifact_format_versionが存在し、runtime が対応していること - manifest と binding の
rego_sha256が一致すること - manifest に空でない
tegata_hashがあること - manifest と binding が同じ entrypoint を宣言していること
- 宣言された entrypoint が実際の WASM entrypoint table に存在すること
- module の builtins table を検査でき、host builtin を必要としていないこと
- ロードした policy id が重複していないこと
必須の整合性を一つでも確認できなければ、問題が起きた policy だけを除外するのではなく bundle 全体を拒否します。
load() は元の手形を読みません。そのため、artifact を生成した後に手形が変更されたかどうかは判断できません。ソースと artifact の両方を見られる ninka-authz verify や LIVE Authorization Reference が、その種の stale 状態を検出します。
Ninka.fromEmbedded(bundles, options?)
static async fromEmbedded(
bundles: EmbeddedBundle[],
options: NinkaLoadOptions = {},
): Promise<Ninka>
module graph 内に既に埋め込まれた bundle をロードする低レベル API です。EmbeddedBundle には次が含まれます。
- build record のファイル名
- parse 済み build record
- base64 形式の WASM
- policy id をキーにした parse 済み manifest
runtime が WASM を decode し、ディレクトリ読み込みと同じ artifact shape へ戻したうえで、同じ verification core を通します。
アプリケーションからは、通常 Consumer Projection の createNinka() 経由でこの処理を利用します。
Raw Ninka.check()
公開 runtime API では PolicyRefLike を受け取ります。
check(policy: PolicyRefLike, input: AuthzInput): boolean
runtime には checkByPolicyId という別のメソッドもあります。validation と CLI のための内部配管です。これは check() のオーバーロードではありません。check() に policy id の文字列を渡すと、TypeScript でも JavaScript でも例外になります。checkByPolicyId は @internal で、公開する型定義からは外れています。
生成された reference を渡す場合、policy.tegataHash と、読み込んだ manifest の tegata_hash を比較します。hash が無い、または一致しなければ、別の生成契約で評価を続けず例外にします。
トップレベル input は null でも配列でもない object である必要があります。次はいずれも caller / integrity error として例外になります。未知の policy、壊れた reference、Consumer Projection と bundle のハッシュ不一致、そして共有の上限である 64 container level を超える input です。
トップレベルの形が正しい input の内部で、属性が欠けている、または runtime type が合わない場合は、コンパイル済みの認可意味論へ渡します。通常はその三値意味論に従って deny 側へ倒れ、API shape error へ読み替えることはありません。
policyIds
get policyIds(): string[]
現在 runtime にロードされている policy id を文字列配列で返します。これは runtime introspection 用です。アプリケーションで型付きのポリシー選択を行う場合は、通常 policies.<name> を使います。
AuthzInput
export interface AuthzInput {
subject: { properties: Record<string, unknown> };
action: { name: string };
resource: { type: string; properties?: Record<string, unknown> };
context?: Record<string, unknown>;
}
手形の key は runtime input の次の場所へ対応します。
| 手形の key | runtime input |
|---|---|
action | action.name |
resource_type | resource.type |
subject.* | subject.properties.* |
resource.* | resource.properties.* |
environment.* | context.* |
custom.* | context.custom.* |
Consumer Projection は policy ごとに、より狭い入力型を生成し、対応する policies reference へ結び付けます。
input のネスト上限は 64 container level です。これより深い入力は認可上の deny ではなく caller contract error として例外になります。
NinkaLoadOptions
export interface NinkaLoadOptions {
onInputError?: (error: InputValidationError) => void;
decisionLog?: DecisionLogOptions;
}
これは raw runtime の option です。生成された Consumer Projection は、前述したより小さい onDecision だけをアプリケーション向けに公開します。
onInputError
export interface InputValidationError {
policyId: string;
missing: string[];
}
decision が deny のとき、runtime は manifest の input requirement と由来ルールを見ます。必要な input key が欠けていると観測できれば、この callback へ通知します。
観測専用のチャネルです。 callback の有無や内容が WASM の verdict を変更したり置き換えたりすることはありません。callback が例外を投げても decision は維持されます。
callback を指定しない場合、現在の TypeScript runtime は stderr へ簡潔な診断を出します。ただし「必ず一行であること」は runtime contract ではありません。
decisionLog
export interface DecisionLogOptions {
enable?: boolean;
sink?: (entry: DecisionLogEntry) => void;
}
Decision Log は既定では無効です。enable: true にすると、各 check() につき一件の DecisionLogEntry を、指定した sink へ渡します。sink が無ければ既定で JSON Lines として stdout に出力します。
sink が例外を投げても認可判定には影響しません。runtime は警告を出して処理を続けます。
ログに入る input は、caller が渡したすべてのフィールドを単純にマスクしたコピーではありません。まず AuthzInput として宣言された形だけへ projection し、その後、残った値を masking します。
action.nameとresource.typeは常に生の値で記録する- subject / resource / custom / environment の属性は
decision_log.unmaskedで許可されたものだけ生の値を残す - それ以外の保持対象は
"***"にする AuthzInputの宣言形に無いトップレベル key は、元の input に存在していても log representation から省略する
WASM が評価するのは caller が渡した元の input です。Decision Log 用の projection / masking が verdict を変えることはありません。
開示設定は 語彙 を参照してください。
DecisionLogEntry
Ninka は、意味が同等であると artifact contract で定義した範囲に限って OPA Decision Log の field model を使います。フィールド名が似ているだけで、OPA と完全互換であることを意味しません。
| フィールド | 意味 |
|---|---|
decision_id | 判定ごとの一意な id |
timestamp | 判定時刻 |
path | "ninka/result" |
result | boolean verdict |
input | 前述した projection + masking 後のログ表現 |
bundles | policy id → { revision }。revision は manifest の tegata_hash |
labels | runtime / operational label。Ninka 拡張の policy_id も含む |
ninka | Ninka 固有の { schema: 1, policies: [{ id, verdict, missing? }] } |
将来、互換性を壊さない形でフィールドが追加される可能性があります。利用側は既知フィールドだけを閉世界で検証して未知フィールドを理由に entry 全体を捨てるのではなく、必要なフィールドを読み、追加フィールドを許容してください。