IAM PolicyをOPAで書く

OPAシリーズです!今回は公式ドキュメントの「Comparison to Other Systems」について見てみます。 世の中に存在しているPolicyのパターンをOPAで実装した場合、どうなるかというのを教えてくれている項目です。

www.openpolicyagent.org

この中でも「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[_])
}

だいぶすっきりしましたね!このルールを言葉で言うと

インプットで渡ってきた actionactions 内の正規表現s3:List.*, s3:Get.* ) とマッチすれば許可する

という意味になります。 regex.globs_match は公式ドキュメントにも記載がありますが、最初から使えるビルトインな関数です。

www.openpolicyagent.org

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を使って検証してみましょう!

play.openpolicyagent.org

次のような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_matchresources_match も返ってきますが、本命の allowtrue になっているのがわかりますね!

では 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 であればなんでも許可なので allowtrue になってます。

最後に s3:ListAllMyBuckets についても見てみましょう。

{
  "action": "s3:ListAllMyBuckets",
  "resource": "arn:aws:s3:::*"
}

結果は下の通り。

{
  "result": {
    "actions_match": true,
    "allow": true
  }
}

allowtrue で返ってきますね!また、 s3:List から始まるアクションなので actions_matchtrue で返ってきているのがわかります。

おわりに

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

参考

kenfdev.hateblo.jp

kenfdev.hateblo.jp