Open6

権限設定の認可処理の設計・実装パターン

shimakaze_softshimakaze_soft

Chapter 2: 認可とは何ですか?

認可は、アプリケーション内で誰が何を行うことができるかを制御する仕組みです。
これにより、ユーザーが自分のデータにアクセスでき、他人のデータを見られないようにします。認可にはどのアプリケーションアーキテクチャにも適合する共通のアーキテクチャパターンがあり、それらを知っておくことで認可コードを書くのがはるかに簡単になります。このガイドでは、これらのパターンを教えます。

認可はすべてのアプリケーションの重要な要素ですが、ユーザーにはほとんど見えません。また、通常、作業中のコア機能やビジネスロジックとは関係ありません。

1. 認証 vs. 認可

多くの人が「認証」と「認可」の両方を指して「認証」という用語を使用します。これらはアプリケーションセキュリティにおいて関連し、多少重なる仕組みですが、同じではありません。

認証はユーザーが誰であるかを確認する仕組みです。これはアプリケーションの玄関です。例えば、ユーザー名とパスワードが組み合わさって一つのアイデンティティ(ユーザー名)と検証方法(パスワードを知っているかどうか)を形成します。他の認証方法には、OAuthやOpenID Connect(OIDC)があり、「Googleでログイン」や「Facebookでログイン」といった機能を追加するのに使用されます。SAMLもあり、企業が複数のアプリに対して単一のログインを提供するために使用する標準です。

認可はユーザーが何を行うことができるかを制御する仕組みです。認証が玄関であれば、認可は中に入った後に開けることができるドアを制御します。

認可は認証の上に構築されることが多く、ユーザーが誰であるかという情報が、彼らが何を行うことができるかを決定する入力として使用されるときに最も密接に重なります。例えば、ユーザーが認証された後、彼女のユーザー名を使用して彼女の権限を調べたり、他の属性に基づいて彼女の権限を推測したりすることができます。

このコースは認可に焦点を当てており、関連する場合のみ認証に触れます。認証システムが既に存在するか、他のガイダンスに従ってセットアップできるものと仮定しています。

2. アプリケーションでの認可の実装例

理論だけでなく、実際に認可がどのように機能するかを示す例を見せたいと思います。そのために、各概念を実証できる例のアプリケーションを構築しました。

私たちのアプリケーションはGitClubで、ソースコードのホスティング、コラボレーション、バージョン管理を行うウェブサイトです。実際のGitホスティングサービスであるGitHubやGitLabと非常に似ています。GitClubは、リソースへのアクセスを保護するために認可がそもそも動機付けられる純粋な例を提供します。GitClubのリソースの一つはリポジトリであり、リポジトリはアクセスが制限されています。ユーザーはリポジトリを読み取ったり変更を加えたりできる場合とできない場合があります。つまり、認可が必要です!

ウェブサイトアーキテクチャ

私たちのサービスの合理的なアーキテクチャから始めます。これは、GitLabのプロダクションアーキテクチャに関する素晴らしいドキュメントを提供しているGitLabの人々が提供するものに基づいています。これが複雑に見えるかもしれませんが、認可がこのアーキテクチャにどのようにフィットするかを説明するにつれて、徐々に複雑さの層を取り除いていきます。

GitClub Architecture

上記の図には、私たちのアーキテクチャの主要な部分が含まれています。gitclub.devは三種類のトラフィックを処理する。

  • ウェブサイトを訪問する。これは、ブラウザにHTMLを返します。
  • APIリクエスト。これはユーザーやサードパーティの統合からのデータリクエストを処理し、JSONまたは他の構造化データ形式を返します。
  • Git接続。これはSSHまたはHTTP接続になります。すべてのリクエストはプロキシに接続し、そこから適切な場所にリダイレクトされます。プロキシは多くの部分で構成されることがあり、ロードバランサや認証プロキシなどが含まれます。

ウェブサイトとAPIはデータベース(DB)にアクセスし、ユーザーアカウントデータなどの情報を保持します。APIとGit接続はファイルシステム(FS)にアクセスし、Gitリポジトリを保存します。

認証については、ウェブアプリケーションがシンプルなユーザー名とパスワードメカニズムを使用してユーザーを認証します。最初の認証後、ウェブアプリケーションはユーザーにトークンを返し、その後のリクエストではパスワードを再送信する必要がなくなります。ウェブアプリケーションはAPIアプリケーションとやり取りするためのAPIトークンも作成できます。これらのトークンは「ベアラートークン」として知られ、トークンの保持者にアクセス権を与える軽量なトークンです。

Gitサービスはウェブアプリケーションと同じユーザー名とパスワードペアを使用して認証を行います。

データモデル

このシステムには多くのGitリポジトリがあります。

組織はリポジトリへのグループアクセスを許可します。組織(会社やオープンソースグループのようなもの)は多くのリポジトリを持つことができます。各組織には多くのユーザーが含まれます。

個人はユーザーで表されます。ユーザーは多くの組織に所属している場合があります。

認可の目標

システム全体で、次の認可ルールを強制したいと思います:ユーザーは、彼らが所属する組織が所有するリポジトリにのみアクセスできます。

後でさらにルールを追加します!

shimakaze_softshimakaze_soft

3. 認可ロジックの配置場所

アーキテクチャを概説したので、リクエストがアプリケーションコンポーネントを通過する過程を追いながら、認可がどこで行われるかを考えてみましょう。

GitClubのユーザー「alice@acme.org」がウェブインターフェースを介してアプリケーションにアクセスしています。特定のリポジトリページ(例: https://gitclub.dev/acme/anvil )にアクセスしようとした場合、何が起こりますか?

彼女は以前にパスワードで認証されているため、リクエストには認証情報としてトークンが含まれています。この場合、ユーザーはこのリポジトリにアクセスすることが許可されています。なぜなら、acmeは組織であり、ユーザーがその組織のメンバーだからです。

インフラストラクチャを通じてリクエストを追跡する際に、認可の三つの主要な側面に何度も戻ってきます:

  • 誰がリクエストを行っているか?
  • 彼らは何をしようとしているか?
  • 彼らは何に対してそれをしているか?

初回接続

まず、ユーザーのブラウザが外部向けプロキシと接続します。

誰がリクエストを行っているか?

リクエストからトークンを抽出しない限り、リクエストを行っているユーザーの身元はまだわかりません。おそらくIPアドレスは知っています。

彼らは何をしようとしているか?

TLS接続を確立しようとしています。

彼らは何に対してそれをしているか?

ホスト:gitclub.dev、ポート443。

まだアプリケーションレベルの認可は行っていませんが、ここでネットワークレベルの認可を行うことは可能です。
例えば、IPアドレスの許可リストを持っているか、相互TLSを要求するかもしれません。

プロキシで

プロキシは必要に応じてトラフィックをウェブ、API、およびGitサーバーに転送するように構成されています。この三つのすべてが最終的にはある程度のユーザー認証を行う必要があります。前述の通り、認証はユーザー名/パスワードの組み合わせか、ベアラートークンのいずれかです。

多くの下流サービスが認証を行う必要がある場合、認証プロキシを追加する価値があります。例えば、ウェブアプリケーションとAPIアプリケーションの両方がほとんどのルートにアクセスするためにトークンを必要とします。プロキシでトークン検証を行う場合、次のような認可を行うことができます:

誰がリクエストを行っているか?

プロキシはリクエストに含まれるトークンを検証し、ユーザーに関する情報も含まれている場合があります。例えば、JSONエンコードされたデータを使用するかもしれません。これはJSON Web Token(JWT)のようなものです。

デコードされたトークンが次のように見えるとしましょう:

https://cdn.prod.website-files.com/5f1483105c9a72fd0a3b662a/6051850fd07de863b8b6b99d_carbon%20(15)%201.png

そうすると、ユーザーが「alice@acme.org」であることがわかります。

彼らは何をしようとしているか?

HTTPリクエストを検査することで、ユーザーがGETリクエストを行っていることがわかります。

彼らは何に対してそれをしているか?

再度、HTTPリクエストを検査することで、URLが/acme/anvilであることがわかります。

この情報に基づいて認可を行うことができるか?

それは適用したい認可の種類によります。リクエストに存在する情報だけを考慮すると、ルートレベルで適用する認可のみが行えます。ユーザー「alice@acme.org」がGETリクエストを/acme/anvilに行うことが許可されているかどうか?

ユーザーに関する情報はメールアドレスだけです。すべてのユーザーは/<owner>/<repository>の形式のパスに対してGETリクエストを行うことができますので、これは許可されます。

しかし、完全な要件を強制するためには、ユーザーがリポジトリの組織に所属している必要があるので、十分な情報がありません。この情報をどのように取得しますか?トークンにますます多くの情報を追加することを考えるか、データベースにアクセスするようにプロキシを構成することができます。このアプローチは、プロキシにかなりの複雑さとデータベースアクセスロジックの重複をもたらします。

ただし、プロキシは、APIキーの要求や認証の強制、Webアプリケーションファイアウォール(WAF)のような悪意のあるペイロードのスキャンなど、認可に関連する問題を適用するのに適した候補です。

ウェブサイトルーターで

GitClub architecture, highlighting the website and database.

プロキシをカバーしました!次はウェブサイトとデータベースに進みましょう。

最後に、認証されたリクエストがウェブアプリケーションのルーターに届き、ルーターはリクエストの処理方法を決定します。
この時点で、認証ミドルウェアがリクエストに提供されたアイデンティティをデータモデルに変換し、データベースからフェッチすることが一般的です。

誰がリクエストを行っているか?

認証ミドルウェアがリクエストに提供されたアイデンティティをユーザーオブジェクトに変換し、ユーザーに関するすべての情報にアクセスできると仮定します。

彼らは何をしようとしているか?

引き続きHTTPリクエストオブジェクトを操作しており、HTTPメソッドはGETです。

彼らは何に対してそれをしているか?

ここではまだリクエストオブジェクトとパス「/acme/anvil」しかありません。

アプリケーションデータにアクセスできるようになったので、関連情報を調べることができます。例えば、既存のロジックを使用して、/acme/anvilに対応する組織を調べ、そのユーザーがその組織に所属しているかを確認できます。

しかし、これは次のステップであるコントローラ層で行う必要があることとほぼ同じです。コントローラはリクエストを受け取り、データをルックアップし、必要なデータ操作を行い、ビジネスロジックを適用します。

したがって、ミドルウェアで認可を行おうとすると、ロジックが重複し、データベースへの呼び出しが冗長になる可能性があります。

このレイヤーで認可を行うのが理にかなっているシナリオはいくつかあります:

  • 認可を適用するための多層防御アプローチを適用し、簡単なルートレベルのプロパティを確認するため。例えば、ウェブサイトの管理者専用エリア/admin/...があり、管理者権限がユーザーオブジェクトに保存されている場合、このチェックを迅速に適用できます。
  • リクエスト処理のどこかで認可が行われたことを確認するため。これにより、すべての場所で認可が適用されることを保証しつつ、粗いルートレベルの問題に限定されない。
  • リクエストルーティングとデータアクセスが密接に結びついているアプリケーションで。例えば、データベースの前に薄いRESTまたはGraphQLインターフェースを持つAPI駆動のアプリケーション。この場合、ルーター+コントローラ層は事実上一つの層に圧縮され、認可は両方に同時に適用できます。

ウェブアプリケーション/コントローラで

ウェブルーターはGET /acme/anvilをコントローラメソッドにマッピングしました。このメソッドがview_repository(owner: "acme", name: "anvil")だとしましょう。

このメソッドでは、/acme/anvilウェブページを表示するために必要なデータを収集し、そのデータをビューに渡す責任があります。GitClubではシンプルなコードを好むので、ページのサーバーサイドレンダリングを行います。つまり、UI用のすべてのデータを取得し、ユーザーのためにテンプレートをレンダリングします。

詳細に立ち入らずに言えば、リポジトリビューにはソースコード、問題、プルリクエスト、貢献者などのデータ、およびリポジトリ自体に関する基本情報が含まれる可能性があります。

最終的に、ユーザーに表示されるすべてのデータは認可されるべきです。リポジトリを最初に表示することができるか?ページ上のすべての追加データについてはどうか?

誰がリクエストを行っているか?

前のステップから、ユーザーオブジェクトが認証ミドルウェアの一部としてフェッチされたと仮定できます。

彼らは何をしようとしているか?

リポジトリページを表示し、リポジトリ情報にアクセスしようとしています。コントローラメソッド内では、ユーザーが何をしようとしているかの完全なコンテキストがあります。

彼らは何に対してそれをしているか?

リポジトリacme/anvilであり、このメソッドの一部としてデータベースから取得します。

ついに、すべての要素が揃ったようです。必要なすべてのデータが揃っており、ユーザーが正確に何を達成しようとしているかを知っています。anvilリポジトリはacme組織によって所有されており、ユーザーはその組織のメンバーなので、読み取ることを許可するべきです。

追加の考慮事項

しかし、まだ終わりではありません!

ビューのレンダリングの一部としてアクセスされるすべての補助データはどうでしょうか?リポジトリのメンバー、問題、コメントなど。これらすべてがユーザーに見えるのでしょうか?

必要に応じてアクセス制御の決定を続けることができます。必要な情報にアクセスでき、ユーザーのためにデータを取得しようとしていることを知っています。

さらに進んで、ユーザーがリポジトリの設定を構成するアクセス権を持っていない場合、つまりリポジトリの管理者ではない場合、この情報をビューのレンダラに返して、ページからそのオプションを隠すことができます。この詳細については、今後のガイドで取り上げます。

ユーザーがリポジトリを閲覧する権限がなかった場合、役立つ情報を表示するビューをレンダリングするか、「Forbidden」レスポンスを返すのに最適な場所でもあります。

データベース/データベースコネクタで

認可を適用する最後の場所は、データベースからデータをフェッチするときです。SQLクエリ自体に認可を適用する(ミドルウェアやクエリプロキシを使用する)か、データベース内で認可を適用することができます。

誰がリクエストを行っているか?

クエリコンテキストにユーザーに関する情報を含めることができます。

彼らは何をしようとしているか?

SQL SELECTステートメントを実行しようとしています。

彼らは何に対してそれをしているか?

リポジトリ名が「acme/anvil」であるリポジトリテーブル。

現在、データアクセス層にいるので、データベース接続/リクエストまたはデータベースクエリに必要なアプリケーションコンテキストを含める必要があります。

強制したいロジックを思い出してください:

ユーザーは、リポジトリと同じ組織に所属している場合にのみリポジトリを読み取ることができます。

データ層は、このルールを強制するのに最適な場所です。これはSQLクエリとして簡単に表現できます。この場合、リポジトリテーブルでSELECTステートメントを実行するときに、organization_membersテーブルでジョインし、ユーザーと同じ組織のリポジトリのみを選択することができます。

元のクエリが次のようだったとしましょう:

認可を適用すると、代わりに次のようになります:

このアプローチの利点は、単なるはい/いいえの認可クエリに答えるだけでなく、より広範な質問をすることができることです。例えば、ホームページでユーザーが表示できるすべてのリポジトリをリストするには、同じフィルタを適用します。

このアプローチの課題は、アプリケーションで他の場所で使用するメカニズムと同じメカニズムを使用して認可フィルタを生成することです。

まとめ

まとめると、認可を適用する場所の候補はいくつかあります:

  • ネットワークレイヤー。ここには非常に限られたデータがあり、許可/拒否リストのような単純なネットワークアクセス制御手段のみが許されます。ここでの認可には重点を置くべきではありません。
  • プロキシやルーターで。これはルートレベルの認可に最適です。より詳細なアクセス制御には、認可の決定を行うためにサービスやデータベースへの追加の呼び出しが必要です。
  • アプリケーション/コントローラで。ここでは、必要なすべての情報が利用可能なので、認可要件を簡単に適用できます。ここに認可ロジックを配置するのが良いでしょう。
  • データベースで。アプリケーションがデータベースフィルタを生成する場合、ここで認可を適用できます。これにより、アクセスに関する広範な質問が可能になります。
    認可は保護したいリソースにできるだけ近い場所で適用するのが最も簡単です。これにより、ユーザーが何をしようとしているか、正確に判断するための文脈やデータが豊富にあります。
shimakaze_softshimakaze_soft

ロールベースアクセス制御 (RBAC)

ロールベース認可、またはロールベースアクセス制御とは、「ユーザー」や「管理者」といったロールに権限をグループ化し、それらのロールをユーザーに割り当てることを意味します。

これは、認可コードを構造化するための一般的かつ効果的な方法です。この構造により、開発者とユーザーの両方が、誰がどのリソースにアクセスできるかを理解しやすくなります。

1. ロールベース認可はシステムのどの部分か?

前章では、GitClubという典型的なWebアプリケーションを紹介し、そのアプリに認可を追加する手順を説明しました。認可は、決定強制の2つの部分に分けられることを示しました。

決定とは、「このユーザーがこのリソースに対してこのアクションを実行できるか?」という質問です。多くの場合、それは「はい」または「いいえ」です。

強制とは、一度決定が下された後に取る行動です。決定が「拒否」であった場合、ユーザーをリダイレクトするのか、それとも「権限がありません」ページを表示するのか?ユーザーが要求されたアクションを実行できる場合、それがどのように見えるのか?

この章では、決定に焦点を当てます。具体的には、「アプリケーション内で誰が何をできるのか?」という質問をします。また、「誰が何をできるのか?」を尋ねる際に、どのデータ構造を調べるのかについても話します。

これが完全にオープンエンドの問題にならないようにするために、実装をガイドする認可モデルを使用します。モデルとは、認可コードを構造化し、実装をガイドする方法です。

この章では、ロールベースアクセス制御 (RBAC) を使用する方法について説明します。異なる種類の企業間取引(B2B)アプリケーションに焦点を当てたさまざまなバリエーションをカバーします。それぞれのモデルについて次のことを説明します:

  • 認可モデルとは何か。適切な認可モデルを選択することは、望ましいユーザー体験に依存します。モデルが高いレベルで何を表しているのかを説明し、実際の例を示し、それを使用するタイミングを説明し、そのユースケースに適した理由を説明します。
  • モデルの実装方法。前章で述べたように、認可決定はデータとロジックの2つの情報から成り立ちます。認可ロジックは、誰が何をできるのかを決定する抽象的なルールのセットです。例:組織のメンバーはその組織に属するリポジトリにアクセスできます。これらのルールは認可データに基づいて表現されます。

また、リレーショナルデータベース(例:PostgreSQL)にこれを保存するためのスキーマ図を含めて、認可データをどのように構造化するかについて説明します。

多くの場合、前章で紹介したGitClubの例のアプリケーションを使用します。リマインダーとして:GitClubは、GitLabやGitHubのようなソースコードホスティング、コラボレーション、およびバージョン管理のためのWebサイトです。GitClubは、認可の動機となるものを純粋に例示しています。それはリソースへのアクセスを保護することです。GitClubの例では、リソースはリポジトリです。ユーザーはリポジトリを読み取ったり、変更を加えたりすることができるかもしれません。

強制

各例では、前述の推奨に従ってアプリケーション内で認可が強制されることを前提としています。たとえば、リポジトリを返すメソッドは、ユーザーがそのリポジトリを読み取ることができるかどうかを確認し、できない場合は「権限がありません」と返します。

アプリコードで認可を強制する例

これは、前章で紹介したis_allowedインターフェースを使用しています。

このインターフェースは、アクター、アクション、およびリソースを受け取り、True/Falseを返します。

なぜ認可モデルを使用するのか?

認可モデルは、アプリケーション内で誰が何をできるのかというオープンエンドの質問に構造を適用するのに役立ちます。これは実装の観点から素晴らしいです。テンプレートを適用するのに役立ちます。また、ユーザーエクスペリエンスを大幅に向上させます。

認可モデルは実装をガイドするだけでなく、アプリケーション内で何ができるのかをユーザーに教えるためのメンタルモデルでもあります。明確な認可モデルを持つことで、ユーザーに「ここではこれらの役割を果たすことが期待されています」と伝えることができます。

これがうまくいかなかった場合の例として、多くの権限の巨大なマトリックスがあり、アプリケーションがどのように機能するかについて詳しく知っている必要がある場面を見たことがあるでしょう。

複雑な権限選択画面

また、どこかの管理者に迷惑をかけるように求められるダイアログもあります。

認可警告ダイアログボックス

では、これらの状況を回避するにはどうすればよいのでしょうか?

適切な認可モデルを使用することで、現在のニーズに適したモデルを使用し、将来の新たなニーズに対応するための柔軟性を確保します。

時間が経つにつれて新たなニーズに対応することが困難な部分であり、これがチームが苦労する点です。この章では、モデルが互いに補完し合い、柔軟性とシンプルさのバランスを取ることでこの問題に対処します。

2. ロールベースアクセス制御 (RBAC) モデル

ロールは認可の一般的なアプローチです。ロールベースアクセス制御とも呼ばれ、ロールは実装者とユーザーの両方にとって認可ロジックを簡素化する効果的な方法です。

ロールは、権限をグループ化してユーザーに割り当てるための方法です。

ユーザーがロールに割り当てられると、そのユーザーはロールが持つすべての権限を取得します。

権限は、ユーザーがリソースに対して実行できるアクションを指定します。たとえば、組織内のユーザーがリポジトリを読み取る権限を持っていると言えるかもしれません。

ロールの権限は任意に選ばれるものではありません。一般的に、ロールはユーザーが誰であるか、アプリケーションで何をしたいか、さらにはその組織内での役割やタイトルに一致する必要があります。

たとえば、GitClubの主要なユーザーは開発者です。これがシステム内の1つの「ロール」となります。他にもロールがあります。たとえば、組織の設定を担当するIT管理者や請求を担当する財務ユーザーなどです。これらのユーザーは、アプリケーションを使用するために必要な一連の権限を持っています。これらのそれぞれがGitClubの認可モデル内の1つのロールとなります。

「請求」ロールに割り当てられた人は、自然に組織の請求を設定できることを期待します。新しいメンバーシップの支払い、支払い情報の更新などです。

組織のロール

GitClubでは、多くの他のアプリケーションと同様に、マルチテナントアプリケーションとしてアプリケーションを構築しています。これは、すべてのユーザーと組織のリクエストを処理するための単一のバージョンのアプリケーションがあることを意味します。自然に、ユーザーが属していない組織のリソースを表示できないようにする必要があります。では、どうやってこれを達成するのでしょうか?

認可モデルとは何か

ロールの簡単な開始点は、各ユーザーを単一の組織に関連付け、それにロールを割り当てることです。その後、すべてのアクセスはユーザーが組織内で持つロールによって制御されます。

しかし、どのロールを作成すべきでしょうか?

ユーザーエクスペリエンスをできるだけシンプルに保つために、少数のロールから始めるべきです。ロールが多すぎると、すぐに混乱し、管理が難しくなります。

GitClubの場合、最初に2つのロール、管理者とメンバーから始めます。これは多くのB2Bアプリケーションの一般的な開始点です。メンバーロールはアプリケーションのすべてのコア機能(例:リポジトリの読み書き)にアクセスできます。管理者は、ユーザーを組織に招待することができるなど、メンバーができるすべてのことを行うことができます。実際のシステムでは、管理者に支払いの設定、設定の構成、組織の削除などの権限を与えるかもしれません。

モデルの実装方法

まず、ユーザーを特定の組織に関連付け、ロールを割り当てる方法が必要です。データモデルでこれを達成する簡単な方法は、組織からユーザーへの1対多の関係を持ち、ユーザーの別のロール列を持つことです。この列には、ロール名自体が文字列として格納されます。

ユーザーと組織のテーブルを示すデータベース図

このデータモデルでは、ユーザーがリソースに対してアクションを実行できるかどうかを確認するためのロジックは次のチェックから成り立ちます:

  • ユーザーがどの組織に所属し、どのロールを持っているか
  • ロールが持つ権限
  • 権限がアクションとリソースに適用されるかどうか

ロールと組織はユーザーデータから取得します。

ロールが持つ権限は、ロール名から文字列のリストへの単純な辞書に格納します。まず、リポジトリの読み取りと書き込み、および組織へのユーザーの追加に関する簡単な権限に焦点を当てます。たとえば:

ロール→権限マッピングの図

権限には「アクション:リソース」という形式を使用しています。これは、ユーザーがその権限を持っている場合、そのタイプのリソースに対して「アクション」を実行できることを意味します。ユーザーは特定の組織のロールを持っていますが、権限は組織とリポジトリの両方に適用されます。

権限が対象リソースに適用されるかどうかは、リソースがロールと同じ組織に属しているかどうかで判断します。これをすべて組み合わせると、次のようになります:

認可決定フロー

いつこのモデルを使用するのか

これは、アプリケーション内のアクセスを決定する唯一の要因が組織レベルのロールである場合にうまく機能する、シンプルで管理しやすいモデルを提供します。

このモデルにはいくつかの制限があります:各ユーザーは1つの組織にしか関連付けられず、組織内のすべてのリソースを同じ方法で扱います。異なるリポジトリに対して異なる権限を割り当てる必要がある場合、これは十分ではありません。次のセクションでは、そのケースをカバーするためにモデルを拡張する方法を示します。

組織間のロール

コラボレーションソフトウェア(GitClubなど)では、ユーザーが個人プロジェクト用のアカウントを持ち、他の組織にも参加することが一般的です。

ユーザーを単一の組織に関連付ける代わりに、1人のユーザーが複数の組織に所属できるようにします。ユーザーは所属する組織に対してロールを持つ必要があります。

これをサポートするために、前の1対多のユーザーから組織への関係多対多の関係に変更します。

モデルの実装方法

多対多の関係の場合のように、結合テーブルを作成し、そのテーブルにロール名の列を含めます。

ユーザーと組織の多対多関係を示すデータベース構造

これにより、組織に所属することは組織内でロールに割り当てられることと同じ意味になります。ユーザーがロールを持たずに組織に所属することはできず、その逆も同様です。

ロジックについては、前述の権限を使用します。ただし、ユーザーのすべてのロールを見つけ、それらをターゲットの組織に対してチェックするようにロジックを更新する必要があります。

認可決定フロー

ロジックは少し複雑になっています。データベースからロールをフィルタリングして、適用可能なロールを確認する必要があります。しかし、基本的な構造はまだ存在します。ロールを取得し、それが組織と一致するかどうかを確認し、正しい権限があるかどうかを確認します。

いつこのモデルを使用するのか

ユーザーが複数の組織にアカウントを持つ必要がある場合、このモデルは前述のシンプルなモデルに対する大きな改善です!

これに対する代替手段として、Slackのような状況があります(執筆時点)。Slackでは、参加する各ワークスペースごとに別々のアカウントとログインが必要です。これにより、ユーザーがアカウントを持っているかどうかを忘れてしまったり、ログイン情報を忘れてしまったり、新しい組織に参加するための障壁が高くなります。

将来に備えるために、この努力をする価値がある場合もあります。アプリケーションが明らかに1ユーザー1組織モデルに適合しているように見えても、将来的に組織間のユーザーを追加する必要があるかもしれません。より柔軟なモデルを使用することで、後でこの新機能を追加する際にマイグレーションが不要になります。この柔軟性のコストは、わずかに複雑なデータモデルです。

リソース固有のロール

GitClubでは、より大きな組織が製品を使用し始めたとしましょう。顧客から、特定のリポジトリに誰がアクセスできるかをカスタマイズする必要があるとの要望が聞こえてきます。たとえば、ある顧客はすべてのインフラストラクチャをコードとしてリポジトリに保存しています。このリポジトリには、すべてのチームメンバーが書き込みアクセスを持つべきではありません。

多くのB2B企業は、この機能要求のバリエーションに遭遇します。組織のロールは、すべてのリソースに対してアクセスを一様に扱う限りうまく機能しますが、使用する組織の規模が大きくなるにつれて、単一のロールセットではすべてのユーザーができることを十分に説明できません。顧客はフォルダを作成し、フォルダにロールを割り当ててファイルへのアクセスを管理し、リソースをプロジェクトや部門ごとにグループ化し、個々のリソースに対するアクセスを明示的に制御できるようにしたいかもしれません。

これに対処するために、リソース固有のロールを導入します。

認可モデルとは何か

リソース固有のロールは、組織のロールのより一般的な形式です。ロールを特定の組織に関連付けるのではなく、任意の種類のリソースにロールを関連付けます。組織自体もリソースの一種であるため、組織のロールはリソースロールの特定の形式です。

GitClubでは、リポジトリも別の種類のリソースです。組織のロールに加えて、リポジトリにスコープされたロールを導入します。

モデルの実装方法

このモデルの良い点は、組織のロールと同じ構造を持つことです。

実際、組織自体がリソースです。組織にアクセス(またはメンバーになる)することは、他のリソースにアクセスすることと同じ方法でゲートされます。ここではそれを明示的にしています。組織は特別なケースではなくなります。組織とリポジトリなどのリソースはすべて同等のデータモデルを持ちます。

組織とリポジトリのロールに対するデータベース構造

ベストプラクティスとして、ユーザーが同じリソースにアクセスするための異なる方法の数を最小限に抑えます。したがって、リポジトリにアクセスするためには、リポジトリのロールを持っている必要があります。組織ロールにリポジトリとやり取りすることを許可する権限を割り当てることはしません。

GitClubでは、2つの組織ロール「管理者」と「メンバー」を保持し、リポジトリ用に「メンテナー」と「コントリビューター」の2つの新しいロールを定義します。メンテナーはリポジトリに対して何でもでき、ブランチを作成し、コードをプッシュし、プルリクエストをマージすることができます。コントリビューターはコードを読み取ることができ、問題を開き、プルリクエストを開くことができます。

リポジトリを作成する人には自動的に「メンテナー」ロールを割り当てます。しかし、組織内の全員にアクセス権を与えるために、手動で全員をリポジトリに追加するのは面倒です。組織内の全員に適用される基本的なアクセス権を持つことが望ましいです。

これを行うには、デフォルトのロールを持つことができます。たとえば、リポジトリと同じ組織内の誰もが明示的にロールが割り当てられていない限り、「コントリビューター」のデフォルトロールを持ちます。

リソースと組織の両方のロールに対する認可決定フロー

一方、組織レベルのアクションに対するアクセスをチェックする場合は、引き続き組織のロールのみをチェックします。これには、新しいユーザーを組織に追加するなどのアクションが含まれます。

いつこのモデルを使用するのか

リソース固有のロールは、多くのユースケースに対応するのに十分な細かい権限をサポートする非常に強力なモデルです。ここではリポジトリと組織だけのいくつかのシンプルなリソースから始めましたが、同じロジックを他の種類のリソースに適用し、細かいアクセス制御を持つことができます。

このレベルの成熟度に達した場合、ロールベースのアプローチでほとんどのニーズに対応できると確信できます。

カスタムロール

さらに1つの複雑な例があります。GitClubのユーザーの1つにCI/CDチームがあり、彼らの唯一の仕事はコミットをメインにマージし、CIジョブが実行されることを確実にすることです。彼らはコードを読むことはできませんが、ワークフローをトリガーする必要があります。これは既存のロールには当てはまりません。

さまざまなユースケースとカスタマイズをサポートするために最善を尽くしても、正確に何をできるかについて細かい制御を必要とする少数のエンタープライズユーザーがいます。最後の手段として、顧客が独自のロールを作成し、それらをカスタマイズできるようにします。これにはデータベースの複雑さが追加され、UIが必要です。ほとんどのシステムにはお勧めしません。

認可モデルとは何か

カスタムロールは、エンドユーザー(つまりアプリケーションのユーザー)が独自のロールを作成し、それに権限を割り当てることができるモデルです。これらのカスタムロールは、通常のロールと同様に使用できます。カスタムロールは、既存のロールと併用できます。

モデルの実装方法

カスタムロールのモデルは、以前に使用したものと実質的に同じですが、ロールから権限のリストへのマップが動的であり、動的に作成されたロールとユーザーを関連付ける方法が必要です。

これを実現するために、ロールを管理するための追加のテーブルを作成します。各ロールは特定のリソースに関連付けられている必要があります。組織がそのロールをカスタマイズできるようにする場合、ロールを組織に関連付けます。

ロールは、ロール名と権限のリストで構成されます。

カスタムロールのデータベース構造

抽象的な観点から見ると、これまでに書いたロジックとほぼ同じに見えます。「ロールが持つ権限をチェックする」という同じステップがあります。実装の観点からの違いは、これらの権限がメモリ内ではなくデータベースに保存されていることです。

いつこのモデルを使用するのか

ユーザーが独自のロールを作成できるようにすることで、あらゆるユースケースを事前に予測してそのためのロールを定義する必要なく、幅広いユースケースをサポートできます。

ただし、これをサポートすることを選択する際には非常に注意が必要です。カスタムロールを機能させるためには、権限の定義を公開することに同意する必要があります。これには、ドキュメントを作成し、変更があった場合にはユーザーに通知する必要があります。新しい機能を追加する際やアプリケーション内での移動により予期しない副作用が生じる可能性があります。

実際、私たちの主なインスピレーションの2つ、GitHubとGitLabのどちらも、カスタムロールをサポートしていません。

GitHubは2020年にパブリックロードマップに問題を開きましたが、それ以来、それを移動し続けています:

https://github.com/github/roadmap/issues/111

GitHubのカスタムロールの問題履歴

一方、GitLabではこの機能について非常に多くの議論があり、最終的にサポートしないことを選択しました:https://gitlab.com/gitlab-org/gitlab-foss/-/issues/12736#note_50662947

では、いつカスタムロールを構築するのが理にかなっているのでしょうか?1つの可能性は、プラットフォームとしてのサービスのようなアプリケーションで、広範な設定可能性が必要な場合です。この場合、ユーザーがチームをどのように整理し、製品を使用するかについて制約を課したくないかもしれません。

エンドユーザーが設定可能なロールの世界に足を踏み入れると、AWS Identity and Access Management(IAM)システムのような完全に設定可能な権限システムに向かうことになります。これについては今後の章で詳しく説明します。

3. 認可モデルの選択に関する簡単なリファレンス

4つのバリエーションのロールベース認可を、最も単純(最も柔軟性が低い)から最も複雑(最も柔軟性が高い)まで示しました。

  • ユーザーに組織ごとに「メンバー」や「管理者」などのロールを付与することは、ほとんどのアプリケーションにとって素晴らしいスタートです。
  • ユーザーが個人アカウントを持つ必要がある場合や、組織間でコラボレーションする必要がある場合、組織間ロールを追加できます。これは複雑さを増しますが、より柔軟です。
  • B2Bアプリケーションで大規模な組織にサービスを提供している場合、顧客はリソース固有のロールを必要とするかもしれません。データモデルはより複雑ですが、特定のリソースに対する認可を制御できます。
  • ユーザー自身が独自の権限スキームを作成および設定できるようにする必要がある場合、カスタムロールの実装方法を説明しました。これが必要な企業は非常に少ないです。お勧めしません!

ここで説明したロールは、本番環境で見る多くの状況をカバーします。ただし、すべてをカバーしているわけではありません。次のような状況はロールでは対応できません:

  • ユーザーは自分が作成したコメントを削除できます
  • ユーザーは自分と共有されたコードスニペットを読むことができます
  • ユーザーは許可されたリポジトリに属する問題を読むことができます

次章では、これらのケースに対処するための関係ベースの認可を紹介します。

もちろん、すべての組織には認可設計に関する異なるニーズがあります。前述のように、Oso Slackコミュニティに参加することをお勧めします!あなたが取り組んでいることについて話し合い、質問にお答えすることができます。認可をアプリケーションに組み込むプロセスを開始したい場合は、Osoを使用し、Osoドキュメントで詳細を学ぶことができます。