メインコンテンツまでスキップ

ランタイム 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意味
onDecisionDecision 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 id
  • tegataHash:その Consumer Projection を生成した認可仕様の hash
  • TypeScript 上だけで使う、そのポリシー用の生成入力型との結び付き

この結び付きにより、別ポリシー用の入力を check() へ渡す誤りを TypeScript の型チェックで検出できます。

check(policy, input)

Consumer Projection から利用する check() は、生成された policy reference を受け取ります。

const allowed: boolean = authz.check(policies.invoiceAccess, input);

check() は同期処理で、結果は次のどちらかです。

  • true:allow
  • false: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 は次です。

  • Ninka
  • AuthzInput
  • PolicyRefLike
  • EmbeddedBundle
  • InputValidationError
  • DecisionLogEntry
  • DecisionLogOptions
  • NinkaLoadOptions

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 directorytooling、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 の次の場所へ対応します。

手形の keyruntime input
actionaction.name
resource_typeresource.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.nameresource.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"
resultboolean verdict
input前述した projection + masking 後のログ表現
bundlespolicy id → { revision }revision は manifest の tegata_hash
labelsruntime / operational label。Ninka 拡張の policy_id も含む
ninkaNinka 固有の { schema: 1, policies: [{ id, verdict, missing? }] }

将来、互換性を壊さない形でフィールドが追加される可能性があります。利用側は既知フィールドだけを閉世界で検証して未知フィールドを理由に entry 全体を捨てるのではなく、必要なフィールドを読み、追加フィールドを許容してください。

関連項目