OPAでテストコードを書く
OPAでPolicyをRegoで書いていくという話は何度かしてきました。
では、そのPolicyを正しく書けたかどうかはどう判断するのでしょう? これまで扱ってきた記事ではInputを用意してOutputをチェックしてました。 それって大変ですし、Policyが鬼のようにたくさんあった場合、デグレチェックも大変です。
そこで登場するのがOPAのテストコードの仕組みです!公式ドキュメントでも詳しい説明があります。
本記事では、以前とりあげたIAM Policyの例を使ってテストコードを書いてみます!
上の記事でテストしたパターンは以下の4つです。
- iam:ChangePasswordが 許可 されるパターン
- s3:ListAllMyBucketsが 許可 されるパターン
- s3:ListBucketが 許可 されるパターン
- s3:CreateBucketが 拒否 されるパターン
対象となるRegoは以下のとおり。
package aws allow { input.action == "iam:ChangePassword" } allow { input.action == "s3:ListAllMyBuckets" } allow { actions_match resources_match } actions_match { actions := ["s3:List.*","s3:Get.*"] action := actions[_] regex.globs_match(input.action, action) } resources_match { resources := ["arn:aws:s3:::confidential-data","arn:aws:s3:::confidential-data/.*"] resource := resources[_] regex.globs_match(input.resource, resource) }
テストの書き方
Regoのテストは(当たり前かもしれませんが)Regoで書きます!
ルールに test_
というprefixをつけることでOPAが暗黙的にテストだと認識します。
opa test
を実行した場合もOPAは test_
で始まるルールを集めてテストを実行するようになってます。
では、さっそくテストを順に書いていってみましょう!
テスト用のルールはどこに書いてもいいんですけど、 iam_test.rego
ファイルを作ってそこに書きましょう。
パッケージは同じにしておきたいので、先頭に package aws
を忘れずに書きます。
iam:ChangePasswordが許可されるパターン
まずは以下のルールをテストしてみましょう。
allow { input.action == "iam:ChangePassword" }
これはすごくシンプルで、 input.actionがiam:ChangePasswordだったら許可する
です。
そのままですね!テストも実はかなりそのまま書くことができます。
test_change_password_action_allowed { allow with input as { "action": "iam:ChangePassword" } }
test_
以降はわかりやすい、好きな名前をつけると良いです。
で、テストしたいルール(この場合 allow
)を書いて、
input
の中身を指定したいので with
と as
を使ってテストデータを注入します。
as
以降にはJSONをそのまま書くことができます!
OPAのバイナリを持ってる場合は以下のコマンドでテストが実行できます。
注意:iam.regoとiam_test.regoがあること
opa test .
VSCodeで拡張を入れている方は OPA: Test Workspace
というコマンドが用意されているので、
それを実行することで結果を見ることも可能です。
s3:ListAllMyBucketsが許可されるパターン
次にテストしたいパターンも上のパターン同様です(actionが違うだけ)。
allow { input.action = "s3:ListAllMyBuckets" }
先程と同様な考えでテストを書くと、次のようになります。
test_s3_ListAllMyBuckets_allowed { allow with input as { "action": "s3:ListAllMyBuckets" } }
s3:ListBucketが許可されるパターン
次にテストしたいのは以下のパターンです。
allow { actions_match resources_match } actions_match { actions = ["s3:List.*","s3:Get.*"] action = actions[_] regex.globs_match(input.action, action) } resources_match { resources = ["arn:aws:s3:::confidential-data","arn:aws:s3:::confidential-data/.*"] resource = resources[_] regex.globs_match(input.resource, resource) }
言葉で表すと arn:aws:s3:::confidential-data配下のresourceであればs3:List系あるいはs3:Get系のactionを許可
になります。
それでは input
を調整してテストしてみましょう!
test_s3_ListBucket_to_confidential_data_allowed { allow with input as { "action": "s3:ListBucket", "resource": "arn:aws:s3:::confidential-data/12345678" } }
これもわかりやすいですね!
s3:ListBucket
が arn:aws:s3:::confidential-data/12345678
に対して許可されることをテストしてます。
opa test .
してテストが通ることを確認しましょう。
s3:CreateBucketが拒否されるパターン
最後に拒否されるパターンも見てみましょう!
test_s3_CreateBucket_to_confidential_data_not_allowed { not allow with input as { "action": "s3:CreateBucket", "resource": "arn:aws:s3:::confidential-data/12345678" } }
action
を s3:CreateBucket
にしてみました。
ここのポイントは、 拒否されること
をテストするということです!
テストをよく見ると not allow
になっていることに気づきましたでしょうか?
そうです、 not
を使うことで拒否されることのテストができます。
テストを実行してみましょう!
$ opa test . data.aws.test_change_password_action_allowed: PASS (7.772µs) data.aws.test_s3_ListAllMyBuckets_allowed: PASS (1.735µs) data.aws.test_s3_ListBucket_to_confidential_data_allowed: PASS (1.239µs) data.aws.test_s3_CreateBucket_to_confidential_data_not_allowed: PASS (978ns) -------------------------------------------------------------------------------- PASS: 4/4
全部PASSしているはずです!
そして、VSCodeでやっている人は OPA: Toggle Workspace Coverage
を実行するとカバレッジも見れますよ!
おわりに
ということがわかりましたー。CIと組み合わせてPolicyのロバストネスを上げれそうですね!
2019-04-12追記:下の記事でCI連携についても書きました!