Carpe Diem

備忘録

OPA (Open Policy Agent) を使ってみる

背景

Policy as a Code(ポリシーをコードベースで管理する)の汎用的なエンジンとしてOPA - Open Policy Agentオーパ)があります。

用途としては

  • APIの権限管理(Authorization)を汎用化&共通化したい
  • ネットワークの疎通に関するホワイトリストブラックリスト)を汎用化したい
  • Terraformやらk8sのコードに独自のLinterを用意したい
    • TerraformでAWSのtagつけを必須にしたい、等

といった際に利用できます。

またエコシステムも充実しており、すでに多数のツールと組み合わせることが可能です。

f:id:quoll00:20210910071747p:plain

ref: Open Policy Agent | Ecosystem

今回はそのOPAの基本的な使い方を説明します。

環境

  • opa v0.32.0

アーキテクチャ

OPAのアーキテクチャは以下です。

f:id:quoll00:20210910054744p:plain

ref: Open Policy Agent | Documentation

OPAでは主に3つの要素があります。

要素 データ形式 説明
Input(Query) JSON 評価してほしいデータ
Policy Rego 評価ロジック
Data JSON 評価する上でベースとなるデータ。
例えば権限の一覧データなど。
Policy内にベタ書きすることも可能

Playground

The Rego Playground

で検証環境が用意されています。

f:id:quoll00:20210910065243p:plain

まず試したいときはここで確認すると良いです。

ローカル環境での検証

事前準備

インストール

$ brew install opa

でインストールできます。

Policy

以下のようにAPIのアクセス権限を持つかどうかのポリシーを用意します。

package example.authz

default allow = false

allow {
    some id
    input.method == "GET"
    input.path = ["salary", id]
    input.subject.user = id
}

allow {
    is_admin
}

is_admin {
    input.subject.groups[_] = "admin"
}

Input

3種類用意しました。

a. 権限を持っている場合(admin)

{
    "input": {
        "method": "GET",
        "path": ["salary", "bob"],
        "subject": {
            "user": "alice",
            "groups": ["admin"]
        }
    }
}

b. 権限を持っている場合(自分のデータへのアクセス)

{
    "input": {
        "method": "GET",
        "path": ["salary", "bob"],
        "subject": {
            "user": "bob"
        }
    }
}

c. 権限を持っていない場合(別ユーザがアクセス)

{
    "input": {
        "method": "GET",
        "path": ["salary", "bob"],
        "subject": {
            "user": "charlie"
        }
    }
}

実行

では実行していきます。

サーバ起動

$ opa run -s example.rego
{"addrs":[":8181"],"diagnostic-addrs":[],"level":"info","msg":"Initializing server.","time":"2021-09-10T07:06:20+09:00"}

もしdata.jsonがある場合は

$ opa run -s example.rego data.json

とすればDataとして読み込んでくれます。

APIリクエス

APIに対してはエンドポイント/v1/data/<package path>というpathでアクセスします。
./に変換します

$ curl localhost:8181/v1/data/example/authz \
   -H 'Content-Type: application/json' \
   -d @input.json

{
  "result": {
    "allow": true
  }
}

pathの後ろに<rule name>をつけると取得したいルールを絞ることもできます。
以下はallowルールのみ取得したい書き方です。

$ curl localhost:8181/v1/data/example/authz/allow \
   -H 'Content-Type: application/json' \
   -d @input.json

{
  "result": true
}

結果

以下のように期待通りの結果が返りました。

ケース 結果
a (admin) true
b (自分のデータへのアクセス) true
c (別ユーザがアクセス) false

その他

初めて触る時に詰まったこと

自分が初めて触った時に躓いた点を羅列していきます。

PlaygroundのInputとAPIで渡すInputのJSON構造の違い

Playgroundのデータをコピペすればそのまま使えると思っていたら、実は違っていてハマりました。

Playground

{
    "user": "alice",
    "action": "read",
    "object": "id123",
    "type": "dog"
}

API

ルートにinputが必要です。

{
    "input": {
        "user": "alice",
        "action": "read",
        "object": "id123",
        "type": "dog"
    }
}

ルールのAND条件とOR条件

AND条件

allow {
  x == y  #1
  i == j  #2
}

#1、#2はAND条件で、どちらかが条件を満たさなければその時点でfalseになります。

OR条件

allow {  #1
  x == y
}

allow {  #2
  i == j
}

#1、#2は OR条件で、どちらか片方がtrueであればtrueが返ります。

Unification

通常プログラミングだと等式は==が多いのですが、OPAのサンプルルールには

allow {
    some id
    input.method = "GET"
    input.path = ["salary", id]
    input.subject.user = id
}

という=の記述のみのものがあります。

これはUnificationというオペレータで、式を真にする変数の値を割り当てるという、比較と変数割り当てを組み合わせたものです。

なので例えばこんなルールを作ると

package play


d = [x,y] {
    [x, "hoge"] = ["fuga", y]
}

ref: https://play.openpolicyagent.org/p/dcjlPRNV75

この式を真にする変数の値を抽出できます。

{
    "d": [
        "fuga",
        "hoge"
    ]
}

まとめ

OPAの基本的な使い方について説明しました。

参考