みなさま、認可の設計に苦しんでいるでしょうか?私は苦しんでいます。苦しまなかった瞬間などありません。昔「アプリケーションにおける権限設計の課題」を執筆しましたが、あれから3年以上が経ちます。
https://kenfdev.hateblo.jp/entry/2020/01/13/115032
当時は認可の設計に関する情報がうまくまとまっている記事などほとんど無く、調べに調べて得たナレッジを書き記したのが上記の記事です。3年以上経ちますが、苦悩が今も特に変わっていないことが驚きです。
ただし、世の中的には認可のライブラリであったりサービスというのは少しずつ増えてきている印象があります(Auth0のOpenFGA であったりOsoのOso Cloud 、AsertoのTopaz )。
認可の設計に関する記事も少しずつ増えている印象があり、その中でも本記事で紹介したいのがAuthorization Academyです。
これは認可サービスである Oso Cloud やOSSのライブラリ oso を提供しているOsoが出している認可に関するナレッジがシェアされている神記事 です。この記事が昔存在していたら、どれだけ自分が救われただろうか、と思えるくらいにすばらしくまとまっている記事です。
英語が読める人は本記事を読むのをやめて、Authorization Academyを一読することを強くおすすめします。本記事ではAuthorization Academyの中のII章にあたる「What is Authorization?」に関する概要と、自分の経験をもとにしたナレッジも混ぜ込んでシェアできたらと思います。
Authorization Academyでは架空のサービスGitClubを例に考えています。GitClubはGitHubを想像してもらえたらOKです。
www.gitclub.dev
でアクセスapi.gitclub.dev
でアクセスgit@git.gitclub.dev
のようにアクセスgit clone
するときのエンドポイントをイメージしてもらえたらOKGitClubのデータモデルはシンプルで、Organization、Git Repository、Userの3つが登場します。
関係は以下の通り:
この中で適用したい認可は:
「Userは、自分の所属しているOrganizationが持っているRepositoryにしかアクセスできない」
です。
認可は一箇所で適用するものではなく、様々な場所で様々な角度から適用するものです。そして、様々な場所で繰り返し問い続けるのが認可三大要素(Three main aspects of authorization)です。
認可三大要素は以下のとおり:
「誰が」リクエストしているか
「何を」しようとしているのか
「何に」しようとしているのか
GitClubの例で、「ユーザーがRepositoryを参照する」リクエストがWebアプリに来た時に、どこで、どのように認可が適用できるのか認可三大要素の観点から考えます。
これはロードバランサなど、リクエストのかなり初期フェーズの場所に該当します。
gitclub.dev
のポート443に対して補足
アプリエンジニアにとってはあまり意識されないレイヤーかもしれませんが、IP制限やAWSのセキュリティグループなども認可にあたります
ProxyはAWSでいうところのAPI Gatewayや、(筆者はあまり詳しくないですが)Nginxなどのレイヤにあたります。
alice@acme.org
など)GET
しようとしているかどうかなどが特定できるwww.gitclub.dev/acme/anvil
)alice@acme.org
は /acme/anvil
に GET
できる?補足
AWSを使ったことのある人はわかるかもしれませんが、API GatewayにはAuthorizerという仕組みがあり、そこでJWTの中身に応じて(例えばrole
や
これはWebアプリのいわゆるルーティング部分にあたります。例えばNode.jsのExpressで言うExpress Routerとか、渡ってきたリクエストを適切なハンドラーに委譲するレイヤーです。
GET
とか)isAdmin: true
があるなら、/admin
関連のパスへのアクセスを許可するかどうか認可できる補足
この記事では詳しく触れませんが、Roleによって認可を適用する(Role-Based Access Control)などは、このレイヤーで「admin
Roleを持っているから許可する」のようなことをよくやります
ここがアプリケーションのコアな部分にあたります。
view_repository
メソッドにマップしたと仮定acme/anvil
)anvil
Repositoryはacme
Organizationが持っていて、alice@acme.org
はacme
に所属している」ので「許可」されることになる補足
Controllerと言っているものの、レイヤーとしてはRouterが呼び出す先のアプリケーションのコア部分のレイヤーのことです。主にビジネスロジックが関わる部分になります
アプリケーションにおいてデータ取得をするレイヤーにおいても認可をかけることができます。
acme/anvil
)を絞り込んだものacme/anvil
リポジトリの情報を取得していたとするSELECT*FROM repositories rWHERE r.name="acme/anvil";
organization_member
テーブルがあったとして、JOINすることで「ユーザーは所属組織内のリポジトリのみ参照できる」認可を適用できる(具体的には以下)SELECT*FROM repositories rJOIN organization_member omON r.organization_id= om.organization_idWHERE r.name="acme/anvil"AND om.member_id= $1;-- 所属組織内のRepositoryに絞る
上記で認可の情報を「ハードコードしてしまっている」点については「アプリケーションにおける権限設計の課題」の「権限実装のアプローチ」でも紹介しています。「どこがハードコートなの?」と思う人はぜひ読んでみてください。
https://kenfdev.hateblo.jp/entry/2020/01/13/115032#権限実装のアプローチ
またOsoにはData Filterという仕組みがあり、これがなかなかおもしろいので興味がある人はこちらも深掘りしてみると良いです。
https://docs.osohq.com/node/guides/data_filtering.html
補足
簡単に書いていますが、このレイヤーでキレイに認可を適用するのはかなり難しいです。例えば上記でSQLに認可を適用していますが、「どのようなSQLなのか?」、「どんなテーブルをどんな名前でJOINしているのか?」、「カラム名はSQL内で変わっていないか?」など、不確定要素が多いからです。Prismaなど、型補完の効くORMなどと併用することである程度現実的に適用できますが、ORMの採用はそれはそれでデメリットもあったりするので、筆者はこのあたりのグッドプラクティスはまだ模索中です
以上で、「認可はどこでどのように適用できるのか」というのを各レイヤーの観点で理解できます。
if(!user.isAdmin)return;// ここが認可ロジックrunAdminProcess();
if(!user.isAdmin||(!resource.isOwner(user)&& resource.dueDate<now()))return;runAdminProcess();
!user.isAdmin || (!resource.isOwner(user) && resource.dueDate < now())
)をやり始めるこのままでは認可を関心を分離することができず、複雑性や冗長性が上がり、変更容易性が下がります。
認可の関心を分離させるのは難しいのですが、ここで認可三大要素がポイントとなります。
このactor, action, resourceを使ってアプリ側のロジックとの関心の分離を試みます。
)
認可において大事なフェーズが2つあって、それがEnforcement(認可の適用) とDecision(認可の判断) になります。この2つの境界となるのが認可のインターフェイスです。言ってる意味がわかりづらいですが下図のイメージ。
まだわかりづらいと思うので、上の方で出てきたコード例で考えてみます。
例えば以下のコードの場合、if文の結果(認可の判断)によって「処理を継続するか」「returnするか」ということが適用(Enforce)されているのです。
if(!user.isAdmin)return;// ここが認可ロジックrunAdminProcess();
「isAdmin だから許可」というのが認可の判断(Decision)です。この、認可の判断(Decision)と認可の適用(Enforcement)の間にインターフェイスを設けることができます。
そうすることで、例えばAuthorizerというServiceがあったとしたら、以下のようなコードが書けるようになります。
if(!authorizer.isAllowed(user,"write", resource))return;runAdminProcess();
user
が、resource
に対して、write
できるかどうか(Decision)がこれで共有できることがわかります。よって、別の場所でもauthorizer.isAllowed(user, "write", resource)
が呼べるようになるのです。(e.g. 403を返すのか、WHERE句を拡張するのか、など)
ではDecision(認可の判断)はどのように実装できるのでしょうか?Decisionを下すために必要なものは以下2つ:
そして実装のアプローチは大きく3つあります。
多くの場合とる方法がこの分散型になります。方針としては認可データもロジックもそれぞれのサービス内で実装する方法です。
補足
デメリットについては特にAuthorization Academyでは明示的に述べられていませんが、メリットの逆でサービスの数が多くなったときにスケールしづらくなっていきます
すべてのサービスのDecisionをCentralizedサービスに委譲
それぞれのサービスでDecisionができるようにして、そのDecisionのAPIも公開して、それぞれのサービス間で必要に応じてDecisionを委譲するやり方
まとめに関しては冒頭の「いきなりまとめ」の通りです。
本記事ではAuthorization Academyの中のII章にあたる「What is Authorization?」について概要をまとめました。認可についてや、認可のインターフェイス、アーキテクチャに関する考え方がこの記事でつかめるかと思います(原文を読むのが一番おすすめです)。
次はどうするかと言うと、Authorization AcademyのIII章以降の「Authorization Modeling」が重要になってきます。認可モデルの設計です。本記事の内容はあくまで技術的な側面が強かったのですが、認可モデルをしっかり設計しないと、いかにテクニカルにすばらしくても破綻します。
認可モデルを考える際にRBAC(Role-Based Access Control)、ABAC(Attribute-Based Access Control)、さらにReBAC(Relation-Based Access Control)という概念が登場します。このあたりに関してもしっかり抑えていくことをおすすめします。(要は「Authorization Academyを読んでみましょう」ってことです!)
ということでIII章は以下です!ぜひ読んでみましょう🚀
https://www.osohq.com/academy/role-based-access-control-rbac
エンジニア採用強化中
SHE株式会社では開発メンバーを積極採用中です。正社員はもちろん副業で入社も相談可能です!とりあえず話を聞いてみたい!という方は是非カジュアル面談にご応募いただけますと幸いです!
採用情報 ->https://bit.ly/3XxywnD
「SHElikes(シーライクス)」を運営するSHEの開発チームがお送りするテックブログです。私たちは社会的不均衡の解決を目指すインパクトスタートアップです。【エンジニア積極採用中】カジュアル面談、副業からのトライアル etc 承っております💪 採用情報 ->bit.ly/3XxywnD