Carpe Diem

備忘録

OpenFGAでアクセス制御を柔軟に

概要

OpenFGAという認可システムでは、ReBACという認可モデルが採用されています。
ベースとなっているのはZanzibarというGoogle Driveなどので使われているグローバル認証システムで、Google Driveを使ったことがある人はご存じの通り非常に細かい粒度でのアクセス制御が可能です。

ReBACは従来のRBACやABACが持つ課題を解決し、かつ直感的に表現できる認可モデルです。

今回はこのReBACのイメージがつかめるような説明をします。

ReBACとは

従来の認可ロジックは

ユーザーUがオブジェクトOに対してアクションAを実行できるか?

と直接的に表現するのに対し、ReBACでは

ユーザーUがオブジェクトOに対して関係Rを持っているか?

という関係という抽象概念を挟むことで、認可モデルをACLにでもRBACにでもABACにでもできる柔軟性を実現しています。

ReBACを使ってそれぞれのモデルや、よくあるユースケースを表現してみます。

事前知識

Sandbox

OpenFGAはSandbox環境を提供しています。

https://play.fga.dev/sandbox

ここで

  • モデルの登録・可視化
  • タプルの登録
  • 関係のチェック(=権限のチェック)

などができます。

認可モデルはStore単位で登録できます。IDは右上の三点リーダーから取得できます。

また登録した認可モデルはイミュータブル(バージョン管理されている)なので、バージョン(= Authorization Model ID)を指定していれば誰かが上書きしても期待する挙動が返ります。

タプル

OpenFGAを使う中でタプルという言葉が出てきます。

これはユーザやグループなどの主体(subject)と、保護対象となるリソース(object)、そしてそれらの間に存在する関係(relation)を表すための最小単位のデータ構造のことを言います。

document:doc123#reader@user:alice

というタプルは

項目 内容
リソース(object) document:doc123
関係(relation) reader
主体(subject) user:alice

を示しています。
これは「ユーザ(alice)は、このドキュメント(doc123)においてreader(読み取り)関係を持つ」という意味になります。

CLI

CLIも提供されているのでターミナルからも簡単に動作確認ができます。

$ brew install openfga/tap/fga

でインストールします。

sandboxのAPIを使う場合は

$ export FGA_API_URL=https://api.playground-us1.fga.dev

を環境変数に設定しておきます。その他の環境変数は以下を参照してください。

Use the FGA CLI | OpenFGA

ReBACでACLを表現する

ACLとは

ACLはユーザと権限が直接的に紐付いた認可モデルです。

ReBACでモデリング

ACLをOpenFGAでモデリングしてみます。

model
  schema 1.1

type user

type document
  relations
    define viewer: [user]
    define editor: [user]

とし、実際のユーザに権限を付けるときは次のようなタプルを登録します。

{"user":"user:bob","relation":"editor","object":"document:meeting_notes.doc"}

CLIで登録する際は次のように実行します。

$ export FGA_STORE_ID=xxx
$ fga tuple write --store-id=${FGA_STORE_ID} \
  user:bob editor document:meeting_notes.doc

{
  "successful": [
    {
      "object":"document:meeting_notes.doc",
      "relation":"editor",
      "user":"user:bob"
    }
  ]
}

権限が付与されているかもチェックできます。

$ fga query check --store-id=${FGA_STORE_ID} \
  user:bob editor document:meeting_notes.doc

{
  "allowed":true,
  "resolution":""
}

欠点

ACLはシンプルである一方、共通化(複数人に同じような権限を持たせる)には不向きです。

ReBACでRBACを表現する

RBACとは

RBACはユーザと権限の間にロールという枠組みを作り、権限はそのロールに紐付けます。

これによって同じロールを持つユーザは同じ権限を持つといった共通化が可能になります。

ReBACでモデリング

RBACをOpenFGAでモデリングしてみます。

model
  schema 1.1

type user

type trip
  relations
    define owner: [user]   # role
    define viewer: [user]   # role
    define booking_adder: owner   # permission
    define booking_viewer: viewer or owner   # permission

このように権限であるbooking_adderbooking_viewerに、ロールであるownerviewerを紐付けます。

そしてユーザに対しては、ロールに対して紐付けるタプルを設定します。

{"user":"user:bob","relation":"viewer","object":"trip:Europe"},  
{"user":"user:alice","relation":"owner","object":"trip:Europe"}
$ export FGA_STORE_ID=xxx
$ fga tuple write --store-id=${FGA_STORE_ID} \
  user:bob viewer trip:Europe

{
  "successful": [
    {
      "object":"trip:Europe",
      "relation":"viewer",
      "user":"user:bob"
    }
  ]
}

チェック時はロールでなく権限でチェックできる事を確認します。

$ fga query check --store-id=${FGA_STORE_ID} \
  user:bob booking_viewer trip:Europe

{
  "allowed":true,
  "resolution":""
}

欠点

ACLより複数人での運用が向いている一方で、次のような条件が入ってきたりするとその都度ロールを用意しないといけないため、組み合わせによるロール数増大が起きます。

  • エンジニアはパブリッククラウドのEditor権限を持てる
    • ただし新卒入社して半年間はViewer権限

ReBACでABACを表現する

ABACとは

RBACで問題となった、ユーザ属性に基づいて条件が組み合わさるようなモデルを表現できるのがABACです。

例えば先ほどのRBACのモデルに対して 「xx部門に所属していれば閲覧権限を与える」 のように、ユーザの属性に関する関係性を持たせます。

ReBACでモデリング

model
  schema 1.1

type user

type department
  relations
    # ユーザーが所属する部門
    define member: [user]

type trip
  relations
    define owner: [user]
    # tripの閲覧可能ユーザーは、直接指定されたユーザーまたは、tripの部門に所属するユーザーとする
    define viewer: [user, department#member]
    define booking_adder: owner
    define booking_viewer: viewer or owner

タプルは

  • ユーザと属性の紐付け
  • 属性とロールの紐付け

の2つを登録します。

$ export FGA_STORE_ID=xxx
$ fga tuple write --store-id=${FGA_STORE_ID} \
  user:alice member department:hr

{
  "successful": [
    {
      "object":"department:hr",
      "relation":"member",
      "user":"user:alice"
    }
  ]
}

$ fga tuple write --store-id=${FGA_STORE_ID} \
  department:hr#member viewer trip:Europe

{
  "successful": [
    {
      "object":"trip:Europe",
      "relation":"viewer",
      "user":"department:hr#member"
    }
  ]
}

こうすることでaliceはロールや直接的な権限は持っていませんが、department:hrに属していることでbooking_viewer権限を手に入れる事ができます。

$ fga query check --store-id=${FGA_STORE_ID} \
  user:alice booking_viewer trip:Europe

{
  "allowed":true,
  "resolution":""
}

ReBACで継承関係(親子関係)を表現する

例えば同じバックエンドメンバーだとしても、経験年数であったりから次のようにロールを分けたりすることがあります。

  • backend_admin
  • backend_editor
  • backend_viewer

しかしこの場合新しい権限が増えた際に、一部のロールへの追加が漏れるような事が起きます。

そうならないように権限の継承関係が作れたら良いのですが、通常のRBACでは難しいです。

ReBACでモデリング

しかしReBACならそれも可能です。

model
  schema 1.1

type user

type api
  relations
    define backend_admin: [user]
    define backend_editor: [user] or backend_admin
    define backend_viewer: [user] or backend_editor

そして次のようにタプルを登録します。

{"user":"user:alice","relation":"backend_editor","object":"api:user"}
{"user":"user:bob","relation":"backend_viewer","object":"api:user"}
$ export FGA_STORE_ID=xxx
$ fga tuple write --store-id=${FGA_STORE_ID} \
  user:alice backend_editor api:user

{
  "successful": [
    {
      "object":"api:user",
      "relation":"backend_editor",
      "user":"user:alice"
    }
  ]
}

$ fga tuple write --store-id=${FGA_STORE_ID} \
  user:bob backend_viewer api:user

{
  "successful": [
    {
      "object":"api:user",
      "relation":"backend_viewer",
      "user":"user:bob"
    }
  ]
}

これでapi:userというリソースに対して、bobは閲覧権限のみ、aliceは閲覧権限と編集権限の両方を持つことが可能です。

$ fga query check --store-id=${FGA_STORE_ID} \
  user:alice backend_editor api:user

{
  "allowed":true,
  "resolution":""
}

$ fga query check --store-id=${FGA_STORE_ID} \
  user:alice backend_viewer api:user

{
  "allowed":true,
  "resolution":""
}

まとめ

ReBACを使うことで従来の権限モデルをどれも直感的に作ることができます。

参考