背景
Policy as a Code(ポリシーをコードベースで管理する)の汎用的なエンジンとしてOPA - Open Policy Agent(オーパ)があります。
用途としては
- APIの権限管理(Authorization)を汎用化&共通化したい
- ネットワークの疎通に関するホワイトリスト(ブラックリスト)を汎用化したい
- Terraformやらk8sのコードに独自のLinterを用意したい
- TerraformでAWSのtagつけを必須にしたい、等
といった際に利用できます。
またエコシステムも充実しており、すでに多数のツールと組み合わせることが可能です。
ref: Open Policy Agent | Ecosystem
今回はそのOPAの基本的な使い方を説明します。
環境
- opa v0.32.0
アーキテクチャ
OPAのアーキテクチャは以下です。
ref: Open Policy Agent | Documentation
OPAでは主に3つの要素があります。
要素 | データ形式 | 説明 |
---|---|---|
Input(Query) | JSON | 評価してほしいデータ |
Policy | Rego | 評価ロジック |
Data | JSON | 評価する上でベースとなるデータ。 例えば権限の一覧データなど。 Policy内にベタ書きすることも可能 |
Playground
で検証環境が用意されています。
まず試したいときはここで確認すると良いです。
ローカル環境での検証
事前準備
インストール
$ brew install opa
でインストールできます。
Policy
以下のようにAPIのアクセス権限を持つかどうかのポリシーを用意します。
package example.authz default allow = false allow { some id input.method == "GET" input.path = ["salary", id] input.subject.user = id } allow { is_admin } is_admin { input.subject.groups[_] = "admin" }
Input
3種類用意しました。
a. 権限を持っている場合(admin)
{ "input": { "method": "GET", "path": ["salary", "bob"], "subject": { "user": "alice", "groups": ["admin"] } } }
b. 権限を持っている場合(自分のデータへのアクセス)
{ "input": { "method": "GET", "path": ["salary", "bob"], "subject": { "user": "bob" } } }
c. 権限を持っていない場合(別ユーザがアクセス)
{ "input": { "method": "GET", "path": ["salary", "bob"], "subject": { "user": "charlie" } } }
実行
では実行していきます。
サーバ起動
$ opa run -s example.rego {"addrs":[":8181"],"diagnostic-addrs":[],"level":"info","msg":"Initializing server.","time":"2021-09-10T07:06:20+09:00"}
もしdata.json
がある場合は
$ opa run -s example.rego data.json
とすればData
として読み込んでくれます。
APIリクエスト
APIに対してはエンドポイント/v1/data/<package path>
というpathでアクセスします。
※.
は/
に変換します
$ curl localhost:8181/v1/data/example/authz \ -H 'Content-Type: application/json' \ -d @input.json { "result": { "allow": true } }
pathの後ろに<rule name>
をつけると取得したいルールを絞ることもできます。
以下はallow
ルールのみ取得したい書き方です。
$ curl localhost:8181/v1/data/example/authz/allow \ -H 'Content-Type: application/json' \ -d @input.json { "result": true }
結果
以下のように期待通りの結果が返りました。
ケース | 結果 |
---|---|
a (admin) | true |
b (自分のデータへのアクセス) | true |
c (別ユーザがアクセス) | false |
その他
初めて触る時に詰まったこと
自分が初めて触った時に躓いた点を羅列していきます。
PlaygroundのInputとAPIで渡すInputのJSON構造の違い
Playgroundのデータをコピペすればそのまま使えると思っていたら、実は違っていてハマりました。
Playground
{ "user": "alice", "action": "read", "object": "id123", "type": "dog" }
API
ルートにinput
が必要です。
{ "input": { "user": "alice", "action": "read", "object": "id123", "type": "dog" } }
ルールのAND条件とOR条件
AND条件
allow { x == y #1 i == j #2 }
#1、#2はAND条件で、どちらかが条件を満たさなければその時点でfalseになります。
OR条件
allow { #1 x == y } allow { #2 i == j }
#1、#2は OR条件で、どちらか片方がtrueであればtrueが返ります。
Unification
通常プログラミングだと等式は==
が多いのですが、OPAのサンプルルールには
allow { some id input.method = "GET" input.path = ["salary", id] input.subject.user = id }
という=
の記述のみのものがあります。
これはUnificationというオペレータで、式を真にする変数の値を割り当てるという、比較と変数割り当てを組み合わせたものです。
なので例えばこんなルールを作ると
package play d = [x,y] { [x, "hoge"] = ["fuga", y] }
ref: https://play.openpolicyagent.org/p/dcjlPRNV75
この式を真にする変数の値を抽出できます。
{ "d": [ "fuga", "hoge" ] }
まとめ
OPAの基本的な使い方について説明しました。