Posts に戻る

権限管理基盤の記事を読んで、RBAC・ホワイトリスト・プロキシの役割を整理した

思考整理 設計 セキュリティ

TL;DR

freee Developers Hub の記事「freeeの認可基盤について」を読みながら、RBAC・ホワイトリスト・プロキシ・UI表示制御の役割を整理しました。

最初はそれぞれの責務が少し混ざっていましたが、最終的には次のように分けて理解しました。

RBAC
  ユーザーにロールを割り当て、そのロールが使える機能を決める

ホワイトリスト
  API / エンドポイントごとに必要な機能を定義する

プロキシ
  ユーザーが使える機能と、API が必要とする機能を照合して許可・拒否を決める

UI表示制御
  ユーザーが使える機能を見て、画面上の表示や操作を補助的に制御する

大事だと思ったのは、UI でボタンを隠すだけでは不十分で、最終的な許可・拒否はバックエンド手前のプロキシで判断する という点です。

背景

権限管理の記事を読む前、自分の中では RBAC、ホワイトリスト、プロキシ、UI表示制御の役割が少し混ざっていました。

たとえば、次のような理解です。

  • RBAC はロールを分けるもの
  • ホワイトリストは許可するものを書くもの
  • UI表示制御は使えない機能を隠すもの
  • プロキシは何かをチェックする場所

それぞれ単語としては知っているつもりでした。 なお、ここで扱うのは「誰であるか」を確認する認証ではなく、「何をしてよいか」を決める認可です。 ただ、実際に「リクエストが来たとき、どこで何を見て、どう許可・拒否するのか」を説明しようとすると曖昧でした。

この記事では、その曖昧だった部分を、自分なりに整理した流れを書きます。

最初に混ざっていたこと

最初に混ざっていたのは、主に次の2つです。

  1. ホワイトリストと UI表示制御の役割
  2. 最終的な許可・拒否をどこが決めるのか

UI表示制御は、ユーザーが使えない機能のボタンを隠すような仕組みです。 これはユーザー体験としては大事です。

ただし、UI でボタンを隠しても、API を直接呼び出されたら意味がありません。 そのため、UI表示制御はあくまで補助であり、最終的な防御線ではありません。

最終的に「この API を通してよいか」を判断するのは、プロダクトの手前に置かれたプロキシです。

ここを分けて考えると、理解しやすくなりました。

RBAC は「ユーザーが使える機能」を決める

RBAC は Role-Based Access Control の略です。 日本語では、ロールベースアクセス制御と呼ばれます。

大まかに言うと、ユーザーにロールを割り当て、そのロールに使える機能を紐づける考え方です。

たとえば、次のようなイメージです。

管理者ロール
  - 請求書を見る
  - 請求書を作る
  - 請求書を削除する

閲覧者ロール
  - 請求書を見る

この場合、閲覧者ロールのユーザーは「請求書を見る」はできますが、「請求書を作る」はできません。

つまり RBAC は、ユーザー側が何をできるか を決める仕組みです。

ここで大事なのは、ロール名だけを見るのではなく、最終的には「そのロールがどの機能を持っているか」を見るという点です。

ホワイトリストは「API が必要とする機能」を決める

次にホワイトリストです。

ホワイトリストは、API やエンドポイントごとに「この API を呼ぶには、どの機能が必要か」を定義するものです。

たとえば、次のようなイメージです。

GET /invoices
  必要な機能: 請求書を見る

POST /invoices
  必要な機能: 請求書を作る

DELETE /invoices
  必要な機能: 請求書を削除する

RBAC がユーザー側の機能を決めるのに対して、ホワイトリストは API 側の必要機能を決めます。

ここが理解のポイントでした。

RBAC: ユーザーは何ができるか
ホワイトリスト: API は何を要求するか

この2つは同じものではなく、照合するための別々の情報です。

プロキシが両者を照合する

では、実際にリクエストが来たとき、どこで許可・拒否を決めるのでしょうか。

そこで出てくるのがプロキシです。

プロキシはプロダクトの前段に置かれ、リクエストを本体アプリケーションへ通す前に権限を確認します。

流れは次のようになります。

ユーザーが API を呼ぶ
  ↓
プロキシがリクエストを受ける
  ↓
ユーザーのロールから「使える機能」を取得する
  ↓
API のホワイトリストから「必要な機能」を取得する
  ↓
両者を照合する
  ↓
API が必要とする機能が、ユーザーの機能集合に含まれていれば通す / 含まれていなければ拒否する

たとえば、閲覧者ロールのユーザーが POST /invoices を呼んだとします。

ユーザーが使える機能: 請求書を見る
API が必要な機能: 請求書を作る

API が必要とする「請求書を作る」は、ユーザーが使える機能集合に含まれていません。 そのため、プロキシはリクエストを拒否します。

このように、プロキシは ユーザーが使える機能API が必要とする機能 を比べる場所です。

ホワイトリスト方式が安全側に倒れる理由

ホワイトリスト方式の良いところは、未定義の API を拒否しやすいことです。 これは、未定義のものを通さない default deny の設計にしておくことで成り立ちます。

たとえば、新しく DELETE /invoices を追加したのに、必要な機能をホワイトリストへ登録し忘れたとします。

このとき、ホワイトリスト方式なら「必要機能が定義されていない API」として拒否できます。

DELETE /invoices
  ホワイトリスト未定義
  → 拒否

これは安全側の挙動です。

一方で、ブラックリスト方式は「危険なものだけを拒否する」考え方です。 この場合、危険な API をリストに書き忘れると通ってしまう可能性があります。

もちろんホワイトリスト方式にも欠点はあります。 新しい API を追加するたびに、必要な機能を定義しなければいけません。

ただ、権限管理のように失敗時の影響が大きい領域では、未定義のものを通すより、未定義のものを拒否するほうが安全です。

UI表示制御は補助であって、最終防衛線ではない

UI表示制御も権限管理の一部として大事です。

たとえば、閲覧者ロールのユーザーに「請求書を削除する」ボタンを表示しないようにできます。

これはユーザーにとって親切です。 使えない操作が画面に出てこないので、迷いにくくなります。

ただし、UI表示制御だけでは不十分です。 ブラウザの開発者ツールや curl などを使えば、画面上のボタンがなくても API を直接呼び出せる可能性があるからです。

そのため、UI はあくまで補助です。 最終的な許可・拒否は、プロキシやバックエンド側で必ず行う必要があります。

この点を整理すると、次のように分けられます。

UI表示制御
  ユーザー体験をよくするために、使えない操作を見せない

プロキシ
  API の直接呼び出しも含めて、リクエストを通すか拒否するかを決める

共通基盤化が必要になる理由

権限管理を各プロダクトがそれぞれ独自に実装すると、いくつか問題が起きます。

まず、ユーザー体験がばらつきます。 プロダクトごとに権限設定の考え方や画面が違うと、管理者は何をどう設定すればよいか迷いやすくなります。

次に、開発・保守コストが増えます。 似たような権限チェックを各プロダクトで何度も実装することになるからです。

さらに、安全性の問題もあります。 プロダクトごとに実装が分かれていると、どこかでチェック漏れや仕様のばらつきが起きやすくなります。

共通基盤化すると、次のようなメリットがあります。

  • ユーザー操作や設定の考え方を統一できる
  • 権限管理の重複実装を減らせる
  • 権限チェックの品質を共通化できる
  • プロダクトごとの実装漏れやばらつきを減らせる

権限管理は、動けばよいだけの機能ではありません。 失敗すると、本来見えてはいけない情報が見えたり、できてはいけない操作ができたりします。

だからこそ、共通基盤として責務を整理し、プロダクトごとの実装差分を減らす価値があるのだと思いました。

自分の中で整理した理解順序

これは一般的な実装手順というより、自分が理解するために整理した順序です。 最終的に、自分の中では次の順番で見るとわかりやすくなりました。

1. 権限管理したい操作を「機能」として定義する
2. ロールに使える機能を紐づける
3. ユーザーにロールを割り当てる
4. API / エンドポイントに必要機能を紐づける
5. プロキシをプロダクトの前段に置く
6. ユーザーが持つ機能と API が要求する機能を照合する
7. API が要求する機能がユーザーの機能集合に含まれていれば許可、含まれていなければ拒否する

この順番で見ると、RBAC、ホワイトリスト、プロキシの役割が混ざりにくくなります。

まとめ

今回の記事を読んで、一番大きかった学びは、権限管理を次のように分けて考えることでした。

RBAC
  ユーザーが使える機能を決める

ホワイトリスト
  API が必要とする機能を決める

プロキシ
  API が必要とする機能がユーザーの機能集合に含まれるかを照合して、許可・拒否を決める

UI表示制御
  画面上の表示や操作を補助的に制御する

最初は、それぞれの言葉をなんとなく理解しているだけでした。 しかし、具体例として POST /invoices を考えると、責務の違いが見えやすくなりました。

権限管理は、UI だけでも、ロール定義だけでも完結しません。 ユーザー側の権限、API 側の必要機能、それを照合する場所を分けて設計する必要があります。

今後、認可設計を見るときは、まず次の3点を確認したいです。

  • ユーザーが使える機能はどこで決まっているか
  • API が必要とする機能はどこで定義されているか
  • 最終的な許可・拒否はどこで行われているか

この3つを分けて見るだけで、権限管理の設計の見通しがよくなると感じました。