IAM PolicyをOPAで書く
OPAシリーズです!今回は公式ドキュメントの「Comparison to Other Systems」について見てみます。 世の中に存在しているPolicyのパターンをOPAで実装した場合、どうなるかというのを教えてくれている項目です。
この中でも「Amazon Web Services IAM」をOPAで実装した場合について本記事では見ていきます。
IAM PolicyをOPAで書くとどうなるか
AWSのIAMではユーザーやロール、グループに加えてリソースに対してPolicyをアタッチできます。Policyは「 誰が 、 何に 、 何を して 良いか 、あるいは ダメか 」を定義することができます。
次のIAMのPolicyを想定してみます。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "FirstStatement", "Effect": "Allow", "Action": ["iam:ChangePassword"], "Resource": "*" }, { "Sid": "SecondStatement", "Effect": "Allow", "Action": "s3:ListAllMyBuckets", "Resource": "*" }, { "Sid": "ThirdStatement", "Effect": "Allow", "Action": [ "s3:List*", "s3:Get*" ], "Resource": [ "arn:aws:s3:::confidential-data", "arn:aws:s3:::confidential-data/*" ] } ] }
IAMに馴染みの無い人(僕もその一人)からするとわかりにくいと思うので、要点だけあげると
iam:ChangePasswordというアクションだったら許可s3:ListAllMyBucketsというアクションだったら許可arn:aws:s3:::confidential-dataというリソース、あるいはその配下のリソースに対して、s3:Listから始まるアクション、s3:Getから始まるアクションを許可
という内容になります。これをOPAのRegoで書くと次のようになります!
注意:一部、公式のコメントを日本語に差し替えています
package aws
# FirstStatement
allow {
input.action == "iam:ChangePassword"
}
# SecondStatement
allow {
input.action == "s3:ListAllMyBuckets"
}
# ThirdStatement
# ここではヘルパールールを定義することで見やすくしています
# actions_matchとresources_matchがtrueの場合にallowがtrueになります
allow {
actions_match
resources_match
}
# actions_matchはinput.actionがactionsに含まれていればtrueになる
actions_match {
actions := ["s3:List.*","s3:Get.*"]
# actionはactionsを順番にイテレートする、という意味があります
action := actions[_]
# check if input.action matches an action
regex.globs_match(input.action, action)
}
# resources_matchはinput.resourceがresourcesに含まれていればtrueになる
resources_match {
resources := ["arn:aws:s3:::confidential-data","arn:aws:s3:::confidential-data/.*"]
# resourceはresourcesを順番にイテレートする、という意味があります
resource := resources[_]
# check if input.resource matches a resource
regex.globs_match(input.resource, resource)
}
Regoに対する慣れは必要ですが、上から読んでいってそれなりに意味を読み取れるのではないかと思います。 ただし、トリッキーな部分が少なくとも2個あります。
action := actions[_]のようにイテレーションを宣言してる部分regex.globs_match(input.action, action)でビルトインな関数を使っている部分
actions_match のルールをもう少し噛み砕いてみます。
まず、このルールですけど action := actions[_] と代入してる場所を無くしてみましょう。
個人的には action が単数形なのがわかりにくくしている気がします。
関係のある箇所だけ見ると下のようになります。
actions_match {
actions := ["s3:List.*","s3:Get.*"]
regex.globs_match(input.action, actions[_])
}
だいぶすっきりしましたね!このルールを言葉で言うと
インプットで渡ってきた action が actions 内の正規表現( s3:List.*, s3:Get.* ) とマッチすれば許可する
という意味になります。 regex.globs_match は公式ドキュメントにも記載がありますが、最初から使えるビルトインな関数です。
true if the intersection of regex-style globs glob1 and glob2 matches a non-empty set of non-empty strings.
ドキュメントにも書いてあるように、 第1引数と第2引数を総当たりして、正規表現がマッチするものがあれば true になるという関数です。
試してみたところ、引数は string でも stringの配列 でも正常に動作する様です。
Playgroundで検証
OPAにはPlaygroundがあるので、下のPlaygroundを使って検証してみましょう!
次のようなInputを渡してみましょう。
{ "action": "s3:ListBucket", "resource": "arn:aws:s3:::confidential-data/12345678" }
arn:aws:s3:::confidential-data 配下のリソースに対して s3:List から始まるアクションを行っているので許可されるはずです。
Outputをみてみましょう。
{ "result": { "actions_match": true, "allow": true, "resources_match": true } }
true の評価結果が全部返ってくるので actions_match も resources_match も返ってきますが、本命の allow も true になっているのがわかりますね!
では s3:ListBucket を下のように s3:CreateBucket にしたらどうなるでしょう?
{ "action": "s3:CreateBucket", "resource": "arn:aws:s3:::confidential-data/12345678" }
結果は下の通り。
{ "result": { "resources_match": true } }
リソースのルールは許可されているけど、アクションが拒否されたので allow は返ってきませんでした!
想定した動きになってそうですね。
では、ちょっと戻りますけど、最初に登場する2つの allow ルールについても検証してみましょう。
パスワード変更を想定して次のようなInputを渡します。
{ "action": "iam:ChangePassword", "resource": "arn:aws:iam::123456789012:user/JohnDoe" }
結果は下の通り。
{ "result": { "allow": true } }
iam:ChangePassword であればなんでも許可なので allow が true になってます。
最後に s3:ListAllMyBuckets についても見てみましょう。
{ "action": "s3:ListAllMyBuckets", "resource": "arn:aws:s3:::*" }
結果は下の通り。
{ "result": { "actions_match": true, "allow": true } }
allow は true で返ってきますね!また、 s3:List から始まるアクションなので actions_match も true で返ってきているのがわかります。
おわりに
OPAではこのようにAWSのIAMに似たPolicy管理もしていくことができそうですね。ただし、シンプルなパターンを見ているだけなので、複雑なパターンでも検証してみたいところです。 今後のネタとしてその点についてはストックしておきます!
おまけ
regex.globs_match(glob1, glob2) は第1引数( glob1 )と第2引数( glob2 )を総当たりして true があれば true になるみたいなので、Inputを下のようにすると
{ "action": ".*", "resource": ".*" }
正規表現としては なんでもOK という意味になるので結果は下の通り。
{ "result": { "actions_match": true, "allow": true, "resources_match": true } }
この動きはどうなんだろう、と思いながらも使う側の問題なので制御自体はできそうです。
あるいは、ヘルパー関数は自分でも作れるので、例えば 第1引数の文字列が、第2引数の正規表現のいずれかにマッチする かチェックする関数を作れば良さそうです。
この点についても今後深掘りしていけたらと思います!