「誰」が「何」をできるかをOPAのレスポンスで返す

OPA(Open Policy Agent)で「○○をやっていいですか?」に対して「Allow/Deny」の返答がくるパターンについていくつか記事にしました。

今回は、 「誰」が「何」をしていいか をOPAからのレスポンスで知る方法について検証したので共有します。

どういった場面でこれを知りたくなるかと言うと、

例えばSPA(Single Page Application)で、 ログイン中のユーザー が「何」をして良いか知りたいときが考えられます。

この情報があることによって、

  • 新規作成ボタンを表示する
  • 編集ボタンを表示する
  • 削除ボタンを表示する
  • 特定のメニューを表示する

などの判断をSPA側で行うことができるようになります。

今回の記事で作成するPolicyもOPAのSlackでやりとりがされていた内容をちょっとカスタマイズさせてもらったものになります。

ドキュメントには登場しないようなユースケースがコアメンバーによってPlaygroundでシェアされていたりするので、OPAに興味がある方はぜひSlackに参加することをおすすめします!

slack.openpolicyagent.org

権限の定義

今回の例では以下のような状況があると仮定します。

  • article というリソースがある
  • alicearticleread できるし write もできる権限がある
  • bobarticleread する権限しかもっていない

これをOPAのRegoで表現すると次のようになります。

package foo.bar

permission["alice"] = [
    { "resources": ["article"], "actions": ["read", "write"] }
]
permission["bob"] = [
    { "resources": ["article"], "actions": ["read"]}
]

実際はこの permission をハードコードすることは無く、データベースなりどこかに保持されているところからOPAに読みこませることになりますが、Playground上で表現するために直でデータを書いておきます。

指定したユーザーの権限を取得

では、データが用意されたのでここから aliceの権限を取得 したいとなったらどうすれば良いか。 欲しいのは以下の情報です。

[
    { "resources": ["article"], "actions": ["read", "write"] }
]

OPAでは「Allow/Deny」な答えを返すことが多いのですが、オブジェクトを返すこともできます。 これがドキュメントにも書かれている「Generating Objects」をする方法です。

www.openpolicyagent.org

OPAへのリクエストの input{ "user": "alice" } とした場合に、 alice の権限が返ってくるようにするには次のようなルールを定義します。

user_permission = perm {
    perm := permission[input.user]
}

ここでポイントとなる点は user_permission = perm の、 = perm の部分です。 普通のルールの場合は key だけ指定して value 側を指定することはありません。

value 側を指定(ここで言う perm )し、その値をルールの中身で用意することでレスポンスとして返すことができます。

実際に試してみましょう!下図はPlayground実行している様子です。

PlaygroundにはちょっとしたTipsがあって、図のように、選択した部分のルールのみを実行してOutputを確認することができます。

f:id:kenev:20190506222034p:plain

下記リンクからPlaygroundが実行できるので、ぜひ試してみてください!

play.openpolicyagent.org

Inputを

{
  "user": "alice"
}

としたことで、 user_permission のみを実行した結果が

[
  {
    "actions": [
      "read",
      "write"
    ],
    "resources": [
      "article"
    ]
  }
]

となり、 alice の権限がレスポンスとしてオブジェクトで返ってきているのがわかります。

また、 bob であれば以下のように、 article に対して read しか許可されていないのがわかります。

[
  {
    "actions": [
      "read"
    ],
    "resources": [
      "article"
    ]
  }
]

このように、「Allow/Deny」のレスポンス以外にも、オブジェクトでレスポンスを返すことができるということもわかりました!

そして、例えばこのレスポンスを使ってUI側では

  • write があるから新規作成、編集、削除ボタンを表示する

という処理が書けて、ログインしているユーザーの権限に応じて画面の見た目を変えることができます。

やっていいかどうかの判断

「誰」が「何」をしていいか を確認する方法についてはこれでOKですが、実際に「やっていいかどうか」の判断も必要になります。

具体的には以下の質問への答えが必要になります。

  • alicearticlewrite できますか?
  • bobarticlewrite できますか?

この質問をするときのInputはきっとこんなJSONになると思います。

{
  "user": "alice",
  "action": "write",
  "resource": "article"
}

そしてこの質問に対して、「Allow/Deny」の答えを出すためのRuleはRegoで以下のように表現することができます。

default allow = false
allow {
    permission[input.user][i].resources[_] == input.resource
    permission[input.user][i].actions[_] == input.action
}
  • permission["alice"] の中のデータを順番にチェック
  • i番目の情報の resourcesarticle が含まれるかチェック
  • i番目の情報の actionswrite が含まれるかチェック

結果として、 alice{ "resources": ["article"], "actions": ["read", "write"] } という permission を持っているので、すべて true となるので、結果も true です!

逆に bob の情報を下のように渡してみるとどうなるでしょう?

{
  "user": "bob",
  "action": "write",
  "resource": "article"
}

bob には { "resources": ["article"], "actions": ["read"]} という permission しか定義されていないので、 write できるかという質問に対しては false になります。

よって、このリクエストの結果は false になります。

まとめ

  • OPAは「Allow/Deny」以外にもオブジェクトをレスポンスとして返すことができる
  • 「誰」が「何」をできるか、を返すことでUIでその情報を使うことができる(ボタンの表示・非表示など)