OPA から移行する
Ninka でも、build の途中では Rego と OPA を使います。変わるのは、認可の意図をどこに書くかです。手書きの Rego を認可仕様の中心に置く代わりに、Ninka が扱える範囲の認可要件を 手形 (Tegata) として記述します。Ninka がその手形を Rego へコンパイルし、固定された OPA toolchain が実行用の WASM を build します。
したがって、移行は単なる構文変換ではありません。旧システムの認可の意味を保ちながら、人間がレビューする対象を手形へ移す作業です。
1. 既存の判定を手形へ対応づける
代表的な対応は次のとおりです。
| 既存ポリシーの概念 | 手形 |
|---|---|
| 保護するリソースの種類 | resource.type |
| 呼び出し元の role | subject.roles |
| 実行しようとしている操作 | actions |
| 属性とリテラル値の比較 | conditions |
| subject 属性と resource 属性の比較 | relationships |
たとえば次の Rego は、
allow if {
input.action == "approve"
input.subject.roles[_] == "manager"
input.resource.amount <= 500000
}
次のようなルールで表現できます。
{
"id": "allow-manager-approve-invoice-within-limit",
"effect": "allow",
"subject": { "roles": ["manager"] },
"actions": ["approve"],
"resource": { "type": "invoice" },
"conditions": [
{ "key": "resource.amount", "op": "lte", "value": 500000 }
]
}
subject と resource の属性を比較する所有関係は relationship で表します。
"relationships": [
{
"label": "ownership",
"subject_attribute": "user_id",
"op": "eq",
"resource_attribute": "submitted_by"
}
]
対応しない構文を無理に移植する前に、手形スキーマ で現在の表現範囲を確認してください。
role は IdP が発行した値をそのまま使う
role は外部の IdP や JWT claim が所有する値なので、Ninka 都合の名前へ変換せず、発行元から届く文字列をそのまま手形へ書きます。admin:operate や Domain Admins も、role の文字列制約を満たしていればそのまま使えます。
"subject": { "roles": ["admin:operate", "Domain Admins"] }
Ninka は admin:operate を admin_operate に変えたり、大文字小文字を揃えたりしません。評価時には別の文字列として完全一致で比較します。
閉世界の 語彙 を使っているプロジェクトでは、どちらの表記を追加する場合も vocabulary.json の変更として明示されます。ただし Ninka が、コロンとアンダースコアの違いを見て「同じ role の表記揺れ」と自動判定するわけではありません。
移行と同時に identity model まで変える場合、たとえば role:verb 形式の token を role と action に分解する場合は、別の認可設計変更として明示的にレビューしてください。単なる移植の副作用として行わないようにします。
2. そのまま転記できないポリシーを先に見つける
コレクションや絞り込み結果を返すポリシー
Ninka が 1 回の check() で判定するのは、入力として渡した 一つの具体的な resource です。コレクション全体に対する判定を定義したり、ポリシーから SQL の絞り込み条件を生成したりはしません。
たとえば次のような legacy Rego は、
allow if { input.Data[_].CompanyKey == input.CompanyKey }
records := [r | r := input.Data[_]; r.CompanyKey == input.CompanyKey]
アプリケーション側で、どの単位を認可するかを明示して設計し直す必要があります。典型的には候補レコードごとに判定します。
const visible = [];
for (const record of candidates) {
if (authz.check(policies.documentRead, inputFor(record))) {
visible.push(record);
}
}
レコードごとの判定を、意図せず別のエンドポイント認可へまとめないでください。たとえば records.some(r => check(...)) は「一件でも許可されたレコードがあればリクエスト全体を許可する」という別の認可規則をアプリケーション側で作っています。これは Tegata が定義した意味論ではありません。
大量のデータを扱う場合、データベースで候補を絞ること自体は問題ありません。ただし、クエリで候補を選ぶことと、認可することは別です。Ninka は WHERE 句とポリシーが同値かを検証せず、SQL 条件を生成もしません。最終的に認可済みと扱う具体的な resource は check() を通します。
レコード単位の判定コストは候補件数に応じて増えます。別のマシンや別のポリシーで測った固定のマイクロ秒値を前提にせず、自分のワークロードで計測してください。
else などで優先順位を持つ legacy ポリシー
Rego の else 連鎖には、単純に独立した allow ルールへ分けると失われる優先順位が含まれる場合があります。
移植する前に、「どの条件が成立したとき、より広い権限を無効にするのか」を文章で明確にしてください。その優先順位が必要なら、Tegata の条件として明示します。Tegata には、Rego の記述順を暗黙の優先順位として引き継ぐ仕組みはありません。
legacy の要件を解釈して表現を選んだ場合は、該当する audit.ambiguities の code を使って、その判断をレビューできる形で残します。
Tegata の表現範囲に無いロジック
Ninka に収めるためだけに、元の認可を別物へ変えないでください。現在の Tegata で表せない処理がある場合は、次のどちらかを選びます。
- 呼び出し側で計算した結果を、明示的な
custom.*入力として手形へ渡す - 置き換え方を決めるまで、その認可処理だけを Ninka の外に残す
custom.* の値を使って Ninka が判定することはできますが、その値を外部コードがどう計算したかまで Ninka が検証するわけではありません。
3. 認可の権威を切り替える前に parity を確認する
Ninka が生成する validation は、現在の手形が定義している認可を検査するためのものです。移行元のポリシーとの一致を確認するものではありません。
移行中は、代表的なケースを旧認可と Ninka の両方へ通し、判定を独立に比較してください。特に次を含めます。
- allow と deny の両方
- 境界値
- 旧実装で意味を持っていた欠損値や空文字列
- 複数ルールにまたがる role の組み合わせ
- legacy の優先順位や絞り込みに依存するケース
プロジェクト固有の小さな比較スクリプトでも十分です。
import { createNinka, policies } from "./src/generated/ninka";
import { execFileSync } from "node:child_process";
const authz = await createNinka();
let mismatches = 0;
for (const c of cases) {
const legacy = JSON.parse(execFileSync("opa", [
"eval",
"-d", "opa/policies/document_read.rego",
"--format", "raw",
"--stdin-input",
"data.app.document_read.allow",
], { input: JSON.stringify(c.legacyInput), encoding: "utf8" }));
const ninka = authz.check(policies.documentRead, c.ninkaInput);
if (legacy !== ninka) {
mismatches++;
console.error("MISMATCH:", c.name, { legacy, ninka });
}
}
process.exit(mismatches ? 1 : 0);
このスクリプトを Ninka 本体の機能にしないのは意図的です。legacy 側の入力形式と、それを subject・action・resource にどう対応づけるかは、移行するプロジェクト固有の知識だからです。
すべて green でも、実際に比較したケースについて一致したことしか分かりません。完全な同値性の証明ではありません。また、期待したポリシーとケースが本当に実行されたことも確認してください。一件も比較しなかった処理でも「不一致 0 件」にはなります。
legacy 側がレコード集合を返すポリシーなら、集合全体の boolean と Ninka の一判定を比べるのではなく、同じ具体的なレコードについて「旧実装で返却対象に含まれるか」と check() の結果を比較します。
4. 意味が変わりやすい入力を明示的に試す
空文字列
Ninka の三値意味論では、present() に対して "" は欠落として扱われます。一方、Rego の等値比較では x == "" が true になる場合があります。
そのため、空文字列を sentinel として使っていた旧ポリシーは、Ninka へ移すと fail-closed 側、つまり deny 側へ挙動が変わる可能性があります。parity テストには空文字列と入力欠損のケースを含めてください。
「値が無い」と「空文字列が明示的に入っている」を区別する必要があるなら、その違いを入力モデル上で明示してください。二つのシステムで意味が異なる空文字列比較に依存しないようにします。
role と語彙
role は完全一致・case-sensitive で比較します。外部の IdP が所有する role 値を Ninka が自動で正規化することはありません。
一方、action、resource type、属性名など Ninka 側で設計する語は手形の命名規則に従い、vocabulary.json を使う場合はそこで明示的に宣言します。
5. コンパイルして、手形としてレビューする
npx ninka-authz compile
npx ninka-authz docs
通常の作成フローは ポリシーを書く、要件と手形をどうレビューするかは 認可をレビューする を参照してください。
6. enforcement を切り替える前に shadow 実行する
Ninka を初めて組み込む変更と、旧認可から Ninka へ権威を切り替える変更を同時にしないことを推奨します。
shadow 期間では、legacy の判定を本番の権威として維持したまま、同じケースを Ninka にも評価させて比較結果を記録します。この段階では、Ninka 側の障害が本番トラフィックの可用性を下げないようにします。
一方、enforcement を Ninka へ切り替えた後は失敗時の姿勢が逆になります。認可を Ninka で強制するサービスが、bundle を読めない、または評価できないという理由で allow へ退避してはいけません。
| 状況 | Shadow | Enforcement |
|---|---|---|
| Ninka bundle を読み込めない | shadow の失敗として記録し、legacy の判定を変えない | 起動を失敗させるなど、認可無しでサービスしない |
| Ninka の評価でエラー | 比較不能として記録し、legacy の判定を変えない | エラーを allow に読み替えない |
| legacy と Ninka が不一致 | 記録して原因を調べる | legacy を外す前に解消する |
CI の ninka-authz verify | 強く推奨 | deploy する生成状態に対する release gate として必須扱いにする |
legacy が allow したリクエストだけを shadow 比較すると、最も危険な「legacy は deny、Ninka は allow」を観測できません。deny 側のサンプルも比較対象に含めます。
また、shadow 用に「Ninka の失敗をすべて飲み込む」ラッパーを、そのまま enforcement 用へ昇格させないでください。enforcement では実際の runtime contract に沿った経路を作り、読み込みや評価の失敗が見えるようにします。
不一致が意図的な要件解釈の違いによるものなら、定義済みの適切な audit.ambiguities code でその判断を記録します。単なる移植ミスなら、手形や入力の対応づけを修正してください。
Toolchain について
現在の Ninka が既定で使う build toolchain は OPA 0.65.0 に固定されています。通常の build で、PATH 上の任意の opa を探して使うことはありません。自前の binary を使う必要がある環境では、NINKA_OPA_PATH を明示的な override として指定できます。
ただし、その override は Ninka が配布・検証する pinned binary ではありません。NINKA_OPA_PATH で指定した binary を Ninka が checksum 検証することもありません。その binary の選定と管理は指定した側の責任です。
Tegata は意図的に表現範囲を限定しています。未対応の構文を移行するときは、手形スキーマ を確認し、アプリケーションコードへ隠れた認可ロジックを増やすのではなく、明示された委譲境界を使ってください。