KAKEHASHI Tech Blog

カケハシのEngineer Teamによるブログです。

マイクロサービスを考慮した認可の設計

この記事は、カケハシ Advent Calendar 2022 の 24 日目 の記事になります。

こんにちは、木村です。(@kimutyam)
医薬品発注管理最適化領域の新規事業のテックリード兼エリアPO、プラットフォームドメイン全体のアーキテクト、データ基盤チームのアーキテクトサポートの3つを兼務しているエンジニアです。直近はプラットフォームドメインの領域のテナント管理の立ち上げに集中をしています。

プラットフォームドメインとは
カケハシはMusubiを始めとした5つのサービスを提供しています。これらは独立して価値を届けられる単位ではありますが、サービスを横断してしなやかなユーザー体験を提供するためには、サービス間の協調と横断的な問題領域の汎用化が重要になってきています。プラットフォームドメインではこれらの関心事に向き合っています。プラットフォームの代表例としては認証があります。認証は共通のマイクロサービスが既にありますが、その他にプラットフォームドメインが扱うドメイン領域は、ユーザー管理(認証はその一部)やテナント管理、請求管理、処方箋データ管理など多岐に渡ります。また、これらは現在仕込んでいる新規事業・サービスを高速に立ち上げるための社内基盤としても扱われます。

テナント管理とは
テナントの組織の階層構造やユーザーの所属、それらに付属可能な(一部の)ロールを管理するドメインです。組織構造やロールはテナント毎に異なります。

問題意識
さて、ここで問題になるのは「認可のステート管理とロジックを中央で1つの共通基盤上で提供するべきか?」ということです。テナント管理が扱うドメインモデルは認可においてもほとんどのケースで利用されることになります。当初はテナント管理ドメイン上に認可サービスを設ける設計案が考えられていました。果たして無策に認可モデルをサービスドメインから切り離して中央集権化してもいいのでしょうか。

Centric_Auth

認可モデルの概念整理

まず、認可についてもう少し詳しくなるためにOSOが提供しているWhat is Authorization?のドキュメントで紹介されているEnforcement and decision architectureを取り上げます。Enforcement and decision architectureは大別してAuthorization Interface、Decision、Enforcementの3つの概念で説明されています。

Enforcement and decision architecture

Enforcement_and_decision_architecture
出典: What is Authorization?

Authorization Interface

  • Input
    • Actor (Principal)
    • Resource
    • Action
  • Output
    • 判定結果 (ex. Allow / Deny)

Principalは「誰(何)が」、Resourceは「何に対して」、Actionは「どんな操作を」を表します。 なお、Enforcement and decision architectureでは取り上げられていませんが、AWSのIAMはCondition「どんな状況下で」を含んでいます。これも認可データとして扱えます。
判定結果は必ずしもAllowとDenyの二値である必要はありません。他のイベントやチェックにさらに依存したり、警告を発するなどの付加的な効果を持つこともあります。上記で紹介しているAuthorization Interfaceのインプット/アウトプットを要件に合わせてカスタマイズしてもいいかもしれませんが、ガバナンスを効かせる上では統一されていることが重要です。

Decision

DecisionはAuthorization Interfaceの実装です。具体的な認可の判定を行います。認可の判定には、認可データ(Authorization Data)と認可ロジック(Authorization Logic)が必要です。認可ロジックは、PrincipalがResourceに対してActionすることを許可されているかを決定するためのルールであり、認可データはそのルールを適用するための事前条件です。例えば、「ある部署のユーザーに、ある取引先の請求書の閲覧を許可する」という認可ロジックがある場合、ユーザーの所属と、請求書の取引先の2つのエンティティが認可データになります。

Enforcement

リクエストを受け取り、Authorization Interfaceに沿ったデータに変換して、Decision層に問い合わせます。Decision層での決定(Allow or Deny)に従ってそのシステムの振る舞い要求を実現します。例えばDenyの場合にAPIで403応答をする振る舞いなどです。

マイクロサービス化した場合の認可データと認可ロジックの配置はどうすればいいか?

あるユーザーが請求書を取得する /billing/:id のエンドポイントにアクセスする場合を例に考えてみます。ユーザーの所属と、請求書の取引先の認可データがサービス内で解決できれば何も問題がありません。

monolith_uml

さて、サービス開発が進み以下のようにマイクロサービス化した場合は、どこに認可データと認可ロジックを配置すればいいでしょうか?

microservice_uml

認可のシステム境界

What is Authorization?では3つのアプローチが紹介されています。

分散型

Decentralized_authorization_type 出典: What is Authorization?

認可ロジックを別のサービスとして切り出すのではなく、素朴にサービス内部で認可ロジックを実装するパターンです。認可ロジックに必要な認可データはサービス内部に存在する必要があります。同じ認可ロジックやデータ取得ロジックを再発明してしまうかもしれません。

中央集権型

Centralized_type
出典: What is Authorization?

認可ロジックを中央集権化するアプローチで、後述するGoogleのZanzibarシステムが採用している設計方針です。サービス全体の認可ロジックがSingle Source of Trueとなりガバナンスが効き、最適化が施しやすい構成になります。ただし、他のサービスのデータ構造への結合度が高く、データ取得やマイグレーションが大変です。サービス全てのドメイン知識が求められるため、サービス開発チーム以上に継続的に投資する認可基盤チームが必要になります。明確に必要な要求がない限りデメリットが勝ることの方が多いのではないでしょうか。

ハイブリッド型

Hybrid_type
出典: What is Authorization?

分散型と似た構成ですが、Decisionから別のサービスのEnforcementを呼び出しているところが違いです。このアプローチは既存のアーキテクチャの形の延長線上に認可ロジックを組み込めることが魅力的です。あえて名前をつけるならば分散協調型でしょうか。

アプローチの型まとめ

Type Pros Cons
分散型 - 単純であり、認可においてサービスを横断した協調が不要である - サービス間で一貫した認可モデルを定義することが難しい
- 認可ロジック変更時の拡張に弱い
中央集権型 - SSoT
- パフォーマンス・チューニング (最適化) がしやすい
- 結合度が高い
- 運用負荷
- SPoF
- 開発工数大
ハイブリッド型 - 既存のシステム境界の中の変更が不要
- ドメインロジックが凝集するサービス内に認可ロジックを閉じることができる
- 全体のガバナンスを保つのが難しい
- 協調するサービスが増えるとレイテンシー問題に発展する場合がある
Googleはなぜ中央集権アプローチを採用したか?

掻い摘むと、GoogleはSearchやDocs、Sheets、Gmailなどの高トラフィックなサービスがあり、それらのサービスはGoogleアカウントで共有されています。権限決定をするにあたってサービス間の常時通信が現実的ではなかったそうです。
Googleのアプローチについては詳しく知りたい方はZanzibar: Google’s Consistent, Global Authorization Systemに掲載されている論文をおすすめします。日本語でZanzibarを紹介している秒間100万リクエストをさばく - Googleの共通認可基盤 Zanzibarを見つけたのでこちらも紹介しておきます。

現時点でのカケハシでの認可のアプローチは?

現在は分散型になっていますが、サービスを横断してしなやかなユーザー体験を提供するために分散型からの改善をする必要が出てきています。結論から言うと、大枠ではハイブリッド型の方向に舵を切っています。現時点で中央集権型を採用する蓋然性やメリットがないからです。

中央集権型にしない理由

サービス間の協調やトラフィックはGoogleほどではなく、常時通信し合うサービス間の協調は局所的です。カケハシはサービスを立ち上げる速度が早く、それを支えるだけの認可基盤チームを発足するのは現実的ではありません。SPoFを回避する開発工数や、各サービスのドメイン知識の変化や機能に適応するための体制作りに勝るメリットがありませんでした。体制面の考え方はデータ基盤のアーキテクチャを考える上でも似た結論に至りました。(FYI: カケハシがDatabricksを導入した背景と技術選定のポイント)

ユーザー管理・テナント管理の立ち位置

Principalと、それに付随する一部のResource(所属、ロール、ユーザーInfo)などのマスタデータ管理をし、サービス間で協調する上で汎用的に使い回せるマスタデータ管理をすることに主眼を置きます。これらを使う各サービスはそのマスタデータを一部の認可データとして扱うようになります。つまり、ユーザー管理・テナント管理は全てのサービスに対する上流となります。
なお、ロールに関してはAdminロールなどの一部の汎用ロールは提供しますが、サービス固有の具体的なロールやポリシーは管理しません。例えば薬剤師向けのサービスと薬局オーナー向けのサービスでは目的も管理手法も異なるからです。

ハイブリッド型のガバナンスをどのように保つか?

ハイブリッド型のデメリットであるガバナンスについて触れておきます。

XBAC(X Based Access Control)の方針を揃える

認可ロジックの主体が各サービス側にあり、XBAC(X Based Access Control)のXの実装は各サービスで行います。XBACにおけるパターンは以下です。

  • RBAC (Roles)
  • ReBAC (Relationships: ex. データの所有権 / 親子関係 / グループ )
  • ABAC (Attributes)

これらはRoles ∈ Relationships ∈ Attributesの関係になっています。RolesからRelationshipsは導出可能で、RoleまたはRelationshipsからAttributesは導出可能です。その逆はできない場合があります。

Relationship-Based Access Control (ReBAC)

出典: Relationship-Based Access Control (ReBAC)

各サービス側に実装を委ねた時に認可の実装が設計方針に基づいていることのガバナンスを効かせることが重要だと考えます。全体的な方針がRBACなのに、とあるサービスだけがABACであるような状況はサービスの継続的な協調とプラットフォーム化を見据えた時には悪手のように思います。また、他のサービスの認可データを利用して、独自のRelationshipsやAttributesを定義するとプラットフォームとしての抽象化の妨げに発展する場合があるでしょう。

ユーザーカスタマイズの方針を揃える

ユーザーカスタマイズはユーザーの自由度を上げる反面、サービス開発のアジリティを下げる結果になるかもしれません。機能を変更する時やXBACのポリシーを変更する時に割当のマイグレーションが必要な場面もあるかもしれません。また、サービスの機能一覧をユーザーに公開することと同義であり、サービス開発チームはメンテナンスし続けなければなりません。

Authorization as a Serviceの利用の統一

OSOは、内部のドメインモデルを変更せずに認可ポリシーをサービス内に組み込めるサービスでありPoC次第では全体的に推進する可能性がありますが、サービス個別で独自にAuthorization as a Serviceを利用することは許容しない方向で考えています。

Enforcement層の設計・ソースコードレビューをする

これは運用に乗せてはいませんが、認可の上流サービスであるユーザー管理・テナント管理のドメインチームが各サービスのEnforcementの設計・ソースコードレビューをすることは有用だと考えています。

最後に

この記事はまさに今推進している内容でして、これから多くの設計観点を詰め、開発を行う必要があります。事業・サービス開発だけでなく、それらを支えるプラットフォームドメイン領域でもエキサイティングな開発体験ができると思います。少しでも興味を持っていただけましたら、ご応募をお待ちしています。