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引数の正規表現のいずれかにマッチする
かチェックする関数を作れば良さそうです。
この点についても今後深掘りしていけたらと思います!