19
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Keycloak by OpenStandiaAdvent Calendar 2023

Day 14

KeycloakとReBAC実装であるOpenFGAの連携を試してみよう

Last updated at Posted at 2023-12-18

はじめに

本記事では、Keycloakと近年注目を集めている新しいアクセス制御方式であるReBAC (Relationship-based Access Control) の実装の1つである、OpenFGAを連携させてアプリケーションのアクセス制御を行う例を紹介します。

本記事で紹介する方法は、まだPoCレベルのものです。

ReBAC (Relationship-Based Access Control) とは

ReBACとは、リソースへのアクセス権限がサブジェクトとリソースの間の関係性によって定義されるアクセス制御方式です。Wikipediaの説明によると、ReBACという用語自体は2006年と随分前に登場しているようですが、ここ数年でたくさん聞くようになりました。そのきっかけといえば、2019年にGoogleより公開された論文「Zanzibar: Google’s Consistent, Global Authorization System」でしょう。Googleのサービス群を支える巨大な認可システムであるZanzibarでは、このReBACによるアクセス制御方式を実現したものでした。そしてこの論文からインスパイアを受けて、今回紹介するOpenFGAを含めたいくつかのOSSやプロダクト、サービスが誕生しています。

OpenFGAとは

OSSのReBAC実装のひとつで、Auth0が提供するAuthorization as a ServiceであるAuth0 Fine Grained Authorization (FGA) のコア部分がOpenFGAとして公開されて誕生しました。その後、CNCFのSandboxプロジェクトとして承認され、クラウドネイティブなReBAC実装として開発が進められています。

OIDC / OAuth 2との関係

OpenFGAなどのReBAC実装が提供する範囲は、OIDCやOAuth 2のスコープ外の領域です。OIDCはOPからRPに認証連携するところまでが担当範囲であり、アプリケーション側でのアクセス制御をどのように実現するかはノータッチです。一方、OAuth 2によるAPI認可では、アクセストークンをOAuthクライアントに渡し、リソースサーバのAPI呼び出しでアクセス制御に利用するため、関連があるように思えるかもしれません。しかしながらOAuth 2では、あくまでOAuthクライアントに対してAPIアクセス権を委譲するためのものであり、最終的にエンドユーザがアプリケーションを利用する際のきめ細かいアクセス制御に利用するのは推奨されません。このあたりの議論は以下の記事によくまとまっていますので、参照されるとよいでしょう。

一方で、2023年10月にOpenID FoundationにてAuthZENというWorking Groupが設立されましたので、これについて少し触れておきます。昨今、ReBAC実装も含めてですが認可をアプリケーションから切り離して外部化するプロダクトやサービスが数多く登場しています。OpenFGAと同じくCNCFのOpen Policy Agent (OPA) も、ReBAC方式ではありませんがそのひとつです。この分野の標準化は、20年前以上にXMLベースのプロトコルとしてXACMLが存在しましたが、SAMLやOIDC、OAuthと比べると普及は限定的でした。このWorking Groupでは、ユースケースや認可方式の文書化、PEP-PDP間プロトコルの標準化に取り組んでいくとのことです。将来、OpenFGAを含めた様々なプロダクト間で相互接続性が高まるかもしれません。今後の動向に注目しています。

KeycloakとOpenFGAの連携

KeycloakとOpenFGAを連携させるPoCプロジェクトとして、Martin Besozzi氏によるKeycloak integration with OpenFGA (based on Zanzibar) for Fine-Grained Authorization at Scale (ReBAC) が公開されていますので、今回はこれを試してみます。下図は、このサンプルのアーキテクチャ図です (出所:https://github.com/embesozzi/keycloak-openfga-workshop )。KeycloakとOpenFGA、そしてサンプルアプリケーションが含まれています。

image.png

サンプルアプリケーションは、Eコマースサイトの商品を管理するアプリケーションを模したものです。アプリにログインすると商品一覧を参照できます。商品は公開中かどうかを示すステータスを持っており、未公開の商品については公開するための更新オペレーションを実行できます。以下、商品一覧を参照する際の処理シーケンスになります (図中の番号と対応しています)。

  1. ユーザは「App」にアクセスし、OIDCでKeycloakでログインして認証連携します。
  2. ユーザが「App」の画面で「View product」というアクションを行うと、「App」はそのバックエンドである「API Products」に対して、OIDCで取得したアクセストークンを付与してREST APIを呼び出します。
  3. 「API Products」はアクセストークンの検証に加えて、アクセストークンに紐づいているユーザ (つまりログインしたユーザ) が「View product」の権限を持っているかどうかを、OpenFGAに問い合わせて確認します。

このサンプルではKeycloakでユーザとレルムロールを管理し、そしてユーザとレルムロール、レルムロールとレルムロールのアサイン関係をKeycloak上で結ぶタイミングで、その情報をOpenFGAに登録するアーキテクチャになっています。レルムロールでは商品の参照権限を表すview-product、更新権限を表すedit-productと、それらのレルムロールを含むビジネスロールとしてadmin-cataloganalyst-catalogの合計4つのレルムロールを定義しています。KeycloakではComposit Roleという、ロールの中にロールを含める機能がありますので、admin-catalogview-productedit-productの両方を含み、analyst-catalogview-productのみを含むように設定されています。これにより、analyst-catalogをアサインしたユーザに関してはview-product権限しかなく更新処理は実施できない、ということを表現しようとしています。

KeycloakからOpenFGAへの上記関係データの登録は、「Event Publisher」という形で連携するアーキテクチャになっています。KeycloakのアドオンとしてEventListener SPIを実装したKeycloak OpenFGA Event Publisher が組み込まれており、このアドオンはKeycloakのレルムロールのアサイン処理のイベントをフックして、その内容をOpenFGAにプッシュするようになっています。

現状このアドオンは、障害ケースにうまく対応できないのが気になるところです。KeycloakからOpenFGAへのプッシュが何らかの理由で失敗した場合に、それをリトライしたり、別途同期するような仕組みが現状はありません。

さっそく動かしてみる

GitとDocker (Docker Compose) が利用できる環境であれば、すぐに試すことができます。

# git clone
git clone https://github.com/embesozzi/keycloak-openfga-workshop
cd keycloak-openfga-workshop

# 起動
docker-compose -f docker-compose.yml -f docker-compose-apps.yml -f docker-compose-openfga.yml -f docker-compose-import.yml up

また、ブラウザでアクセスする端末のhostsファイルに以下を追加しておきます。

127.0.0.1  keycloak openfga store store-api

動作テスト: 参照権限があるユーザでアクセス

  1. 「App」のURL http://store:9090/ にアクセスします。右上の「LOG IN」をクリックします。
    image.png

  2. paulaでログインします。パスワードはdemo1234!です。
    image.png

  3. このユーザは商品を参照する権限を持っていますので、商品一覧を参照することができます。
    image.png

  4. 適当な商品のPUBLISHというリンクをクリックします。すると、以下のように権限のない旨のエラーが表示されます。

image.png

動作テスト: 更新権限があるユーザでアクセス

一度ログアウトし、今度はrichardでログインします (パスワードは同じくdemo1234!)。先程と同じようにPUBLISHリンクをクリックすると、今度は成功メッセージが表示されます。

image.png

※注意:サンプルアプリで実際に商品ステータスを更新する処理は実装されていないため、リンクをクリックしてもPUBLISHEDにはなりません。

動作テスト: 参照権限もないユーザでアクセス

同様に、今度はpeterでログインします (パスワードは同じくdemo1234!)。すると、このユーザは参照権限すら持たないため、商品を何も参照することができません。

image.png

OpenFGAを覗いてみる

http://localhost:3000/playground をブラウザで開くと、OpenFGAが提供するプレイグラウンド機能が利用できます。ここでは、今回のサンプルアプリのReBACモデルの確認と評価を、画面から確認することができます。

image.png

このサンプルでは、以下のReBACモデル定義がされています。ReBACではこのように、アプリケーションのモデル定義を行い、その定義したリレーションに基づいてアクセス制御の判断 (認可判断) を自動的に行います。なお、この定義は専用のDSLを利用することが多いですが、残念ながら標準的なものは存在しないため、現状ではReBACの実装ごとにそれぞれの文法を覚える必要があります。

model
  schema 1.1

type group
  relations
    define assignee: [user]

type role
  relations
    define assignee: [user] or assignee from parent or assignee from parent_group
    define parent: [role]
    define parent_group: [group]

type user

サンプルでは、userrolegroupという3つのオブジェクトタイプを定義していますが、アプリケーション内で現状使われているのはuserroleのみです。ポイントは、roleのリレーション定義から抜粋した以下の部分です。

    define assignee: [user] or assignee from parent
    define parent: [role]
  • 1行目:assigneeというリレーションで、userタイプの要素と関係性を持っています。つまり、ロールはユーザにassigneeというリレーションでアサインされることを示しています。また、OR条件でassignee from parentと書いてあります。OpenFGAでは、間接的なリレーションをX from Yという表記で表すことができます。これにより、parentリレーションで結ばれた親ロールからassigneeリレーションで関係性のあるオブジェクトに対しても間接的にリレーションがあることを示しています。
  • 2行目:parentというリレーションで、roleタイプと関係性を持っています。つまり、ロールは親ロールを持てる構成になっていることを示しています。

モデルの定義に加えて、画面左下にはOpenFGAに登録されたタプル (定義したモデルに対する実際の関係データの組み合わせ) の一覧を参照することができます。これらのタプルは、前述で紹介したKeycloak OpenFGA Event Publisherにより、KeycloakからOpenFGAにプッシュされたデータ群です。サンプルでは最初から以下のように5つのタプルが登録されています。

image.png

これらの関係性を図示すると、以下のようになります。

image.png

このサンプルのモデル定義は、OpenFGAや他のReBAC実装のサンプルモデルと比べると、違和感を感じるかもしれません。というのも、このサンプルプロジェクトはKeycloakのユーザとレルムロール (とグループ) の関係性をタブルとしてOpenFGAに登録する都合上、その関係性をそのままReBACのモデルとして実装しているためです。
よりReBACらしいサンプルモデルとしては、OpenFGAのModeling Guidesを一読するとよいでしょう。Google DriveやGitHub、Slackという馴染みのあるサービスを例にモデル定義例も紹介されています。

このように、view-productedit-productという権限を表す末端ロールはユーザに直接関連付けられてはいません。このような関係性であっても、ユーザが間接的にview-productedit-productロールを持っているかどうかをリレーションのグラフを辿って自動的に評価してくれます。この点がReBACのメリットのひとつです。

アプリケーションのアクセス制御の実装

最後に、アプリケーション側で実施しているOpenFGAを利用したアクセス制御箇所についても見ておきましょう。バックエンドである「API Products」は、Node.jsでExpressを利用して実装されています。OpenFGAではNode.js用のSDK Clientを提供しているため、これを利用したMiddlewareを実装して組み込み、API呼び出し時にアクセス制御を実装しています。具体的には、OpenFGAのRelationship Queries (Check API) を利用して権限があるか (リレーションを持っているか) をOpenFGAに問い合わせ、結果として true or false を受け取っています。

const checkTuple = async function (user, relation, object) {
    console.log(`[Store API] Check tuple (user: '${user}', rel: '${relation}', obj: '${object}')`);
    try {
        let client = await getClient();
        let { allowed } = await client.check({
            tuple_key: {
                user: user,
                relation: relation,
                object: object
            }
        });
        console.log(`[Store API] Check tuple for user: ${user} isAllowed: ${allowed}`); 
        return allowed;
    } catch ( e ) {
        console.log(e);
        return false;
    }
}

作成されたMiddlewareをRouterにて以下のように組み込み、使用しています。商品の公開操作は、ログインユーザにedit-productのロールがリレーションを辿って存在するかチェックしています。

router.route('/:id/publish')
    .post(
        [ 
            jwt.validateToken,
            jwt.decodeToken,  
            fga.checkUserHasRole("edit-product")
        ],
        productsController.publish);

まとめ

KeycloakとReBACのOSS実装であるOpenFGAとの連携について、PoCプロジェクトを一例に紹介しました。このサンプルではKeycloakのロールモデルをそのままReBACモデル化しているため、あまりReBACらしさを感じないサンプルにはなってしまっているのが少々残念なところです。ReBACに興味を持たれた方は、是非公式ドキュメントを参照してみてください。ReBACはアプリケーションによってははまると強力なツールになる可能性を秘めているのではないか、と期待していますので、引き続きウォッチしていきたいと思います。

19
5
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?