Open Policy Agentへ入門
CNCFのプロジェクトにOpen Policy Agentっというものがあります。 Open Policy Agentはポリシーエンジンとして動作し、指定したポリシーに準じているかどうかをチェックしてくれます。 そんなOpen Policy Agentについて軽く触ってみたのでどういった機能があるのか、気になった部分を中心に書いていきたいと思います。
目次
- Open Policy Agent
- Regoの特徴
- Regoの記述
- Regoのテスト
- 終わりに
1. Open Policy Agentについて
上記で簡単に説明しましたが、Open Policy Agentとはポリシーエンジンとして動作し、ポリシーのチェックを行います。
利用方法としてはコマンドラインで利用したり、デーモンで動かすパターンとライブラリとして利用するパターンがあります。
KubernetesやEnvoy、Kafkaと連携したり、Terraformのプラン結果をチェックできたりと、いろんな用途で扱うことができそうです。
基本的にはJSONデータであればポリシーチェックを行える感じですかね。
Terraformのポリシーチェックに関しても plan
した結果を JSON形式に出力してチェックをしてますし、Kubernetesに関してもマニュフェストファイルをJSON形式に変換してチェックをしているようです。
次は、Open Policy Agentでポリシーを記述する「Rego」についてみて行きたいと思います。
2. Regoの特徴
Open Policy Agentではポリシーを定義するため、「Rego」っという言語で記述していきます。 宣言的にポリシーを記述でき、強力な表現方法を持っていきます。 特に配列に対する操作が特殊な部分があり理解するのに少々手間取ってしまいました。。。 例えば下記のような記述があったとします。
example.rego
package example default allow = false allow { has_environment["prod"] } has_environment [name] { sites = [ { "name": "prod" }, { "name": "dev" }, ] name := sites[_].name }
まずは詳細は理解せず雰囲気で見てもらいたいのですが、 allow
から has_environment
を呼び、引数として prod
っという文字列を渡しています。
これをコマンドラインから実行すると下記のような結果が返ってきます。
下記のコマンドでは上記のRegoのファイルを引数として渡し、 allow
を実行しています。
$ ./opa eval --data example.rego 'data.example.allow' { "result": [ { "expressions": [ { "value": true, "text": "data.example.allow", "location": { "row": 1, "col": 1 } } ] } ] }
"value": true,
が返ってきていますね。これはRegoの評価した結果が true
になった証拠です。
ためしに、 has_environment
の引数を stg
にしてみます。すると結果が false
になってしまいます。
これは、 sites
の要素で name
属性の値を網羅的に引数の値(name)と比較して同じものがあるかどうかをチェックしてるっという感じです。
また、Regoでは先ほどの has_environment
や allow
はルールをまとめるルールとして定義しています。
has_environment
では引数の値が sites
の要素で name
属性の値と比較し評価しています。allow
では has_environment
の結果を評価しています。
そして、allow
ではすべての評価対象が true
のときのみ true
を返します。
Regoではこうしてルールを定義し、評価していくことでポリシーに準じているかどうかをチェックしています。 また、こうして宣言的に記述したほうがポリシーの評価を簡単に行えます。
どちらかというとAlloyとか形式手法の言語に近いんですかね。
3. Regoの記述
基本的な記述方法
ここからREPLを使ってRegoの記述について触れていこうかと思います。 一般的な記述として以下のように値を扱えます。
x := 42 rect := {"width": 2, "height": 4}
ここでは x
を42にとし、 rect
を {"width": 2, "height": 4}
として扱えます。
> x == 42 true > x == 43 false
等価の評価は ==
で行うことができます。
また、下記のように記述することでルールを作成することができます。
> t { x := 42; y := 41; x > y } > t true
仮にルールを評価したときに false
になった時は下記のようになります。
> t2 { x := 42; y := 41; x < y } > t2 undefined
Regoではルールの中の評価する順序について特殊な扱いをすることができ、下記のように記述することが可能となります。
> s { x > y; y = 41; x = 42 }
ここでは :=
ではなく =
を扱っています。こうすることで記述する順番を気にすることなくルールを定義することも可能です。
次に配列を見ていきたいと思います。 上記でも記載しましたが、配列は下記のように記述することが可能です。
> sites = [{"name": "prod"}, {"name": "smoke1"}, {"name": "dev"}] > r { sites[_].name == "prod" } > r true
sites[_].name
で sites
の要素を網羅的に name
属性の値をチェックし、prod
がないかチェックしています。
[_]
が配列の中を網羅的に参照するので下記のようなに記述した場合、sites
の要素の name
属性のみが返ってきます。
> q[name] { name := sites[_].name } > q [ "prod", "smoke1", "dev" ]
ここで、 q
のルールを包括している p
のルールを作成して実行してみたいと思います。
> p { q["prod"] } > p true
true
が返ってきましたね。ここで sites
要素の name
属性に存在しない値を入れてみたいと思います。
> w { q["smoke2"] } > w undefined
true
ではなくなりましたね。ちゃんと評価できているのが確認できます。
他にも配列ではイテレート処理に関して下記のように記述することも可能です。
> some i; sites[i] +---+-------------------+ | i | sites[i] | +---+-------------------+ | 0 | {"name":"prod"} | | 1 | {"name":"smoke1"} | | 2 | {"name":"dev"} | +---+-------------------+
見てわかるように、ほかの言語(PHPとか)と同じように変数っぽく値を扱えたり、スカラー値や配列なども表現することができます。
違いとしては何度も言っていますが、宣言的な記述で手続きではないっというところですかね。
4. Regoのテスト
もう一つ、Open Policy Agentで気になった部分として先ほどのRegoに対してテストを書くことができます。 まずはサンプルを見てみたいと思います。
example.rego
package authz allow { input.path == ["users"] input.method == "POST" } allow { some profile_id input.path = ["users", profile_id] input.method == "GET" profile_id == input.user_id }
そして、先ほどのRegoをテストするコードを記述していきます。
example_test.rego
package authz test_post_allowed { allow with input as {"path": ["users"], "method": "POST"} } test_get_anonymous_denied { not allow with input as {"path": ["users"], "method": "GET"} } test_get_user_allowed { allow with input as {"path": ["users", "bob"], "method": "GET", "user_id": "bob"} } test_get_another_user_denied { not allow with input as {"path": ["users", "bob"], "method": "GET", "user_id": "alice"} }
ではこれを実行してみます。
$ ./opa test . -v data.authz.test_post_allowed: PASS (785.051µs) data.authz.test_get_anonymous_denied: PASS (443.895µs) data.authz.test_get_user_allowed: PASS (526.708µs) data.authz.test_get_another_user_denied: PASS (362.388µs) -------------------------------------------------------------------------------- PASS: 4/4
おぉーテストできましたね(サンプルをコピペしただけですが)。
これは、ルールのプレフィックスが test_
で始まっているものを実行していっています。
テストは実行した結果が true
になる場合のみ PASS
となり、それ以外は FAIL
、もしくは ERROR
となります(そもそもランタイムエラーとか)。
サンプルとして下記のテストを実行してみます。
pass_fail_error_test.rego
package example # This test will pass. test_ok { true } # This test will fail. test_failure { 1 == 2 } # This test will error. test_error { 1 / 0 }
$ ./opa test pass_fail_error_test.rego data.example.test_failure: FAIL (414.31µs) data.example.test_error: ERROR (400.987µs) pass_fail_error_test.rego:15: eval_builtin_error: div: divide by zero -------------------------------------------------------------------------------- PASS: 1/3 FAIL: 1/3 ERROR: 1/3
test_failure
で結果が FAIL
となり、 test_error
ではランタイムエラーとなり ERROR
となりましたね。
テストを書けることでポリシーの品質の担保を行えるので、非常にありがたいですね。
4. 終わりに
Open Policy Agentについて軽く触ってみて、Regoの記述の仕方など興味深いものがありました。 ポリシーを評価するにあたって配列の扱いについてかなり表現の仕方が柔軟でほかの言語と書き方がだいぶ違うので戸惑うかと思いますが、慣れてくればすごく扱いやすいものになるんじゃないかと思います。
テストについてもポリシー自体の品質を担保してくれるので、とてもありがたい機能ですね。
次はOpen Policy Agentをアプリケーションの中で使ってみるか、Terraformの出力結果とかで使ってみたいと思います。