XACML日々権限設計で頭を抱えてます。この苦悩が終わることは無いと思ってますが、新しい課題にぶつかっていくうちに最初のころの課題を忘れていきそうなので、現時点での自分の中でぐちゃぐちゃになっている情報をまとめようと思い、記事にしました。所々で「メリット」「デメリット」に関連する情報がありますが、そのときそのときには色々と感じることがあっても、いざ記事にまとめるときに思い出せないものが多々ありました。フィードバックや自分の経験を思い出しながら随時更新する予定です。Automate権限設計はどこを目指したら良いのか最後にその他参考情報TL;DR(長すぎて読みたくない)権限設計は難しく、ベストプラクティスが広まっていないネット上に散らばっている情報をかき集めながらBetterなアプローチを追求し続けないといけない権限設計のノウハウがもっと公に共有されてほしい本当に長い記事なので目次に気になるトピックがある方だけ続きを読んでください。ところどころ感想文になっていたりする点に関してはあらかじめお詫び申し上げます。この記事へのフィードバックだったり、僕の継続的な苦悩から生まれる新たな知識をアップデートしていくことで、今後改善させるつもりではあります!想定する読者や前提知識API設計をしたことがある権限をどうやって実装するべきか苦しんだことがあるソフトウェアアーキテクチャについて意識しているこの記事での権限とはこの記事における権限とは、「誰が(Principal)」、「何に(Resource)」、「何を(Action)」して良い(Allow)のか、あるいはしてはいけない(Deny)のかを定義するものに関して述べています。Instagramを例にとると、フォローしている人の投稿を参照することができる自分の投稿を作成することができる(他人の投稿は作成できない)自分の投稿を削除することができる(他人の投稿は削除できない)自分の投稿のメニューには「削除」メニューがある(他人の投稿には無い)のようなものです(かなりシンプルな例を出しました)。アプリケーションを作る上でほとんどの場合こういう観点の権限が何かしら登場すると思います。この「権限」をどうやって設計・実装するべきなのか?ということがこの記事のメイントピックです。間違った理解で書いている内容もあるかと思いますが、何かしらの方法でご指摘いただけると幸いです。(その分また一つ権限レベルが上がる気がします)権限の種類まず、権限モデルには様々なものがあります。ABACについて軽く紹介したいと思います。dzone.com上の記事がわかりやすく説明しているのですが要点だけ言うと以下のとおりです。List)ACLは「ある対象(よく例に出てくるのはファイルだったりディレクトリだったり)」に対して利用者の何らかのアクセス権限をリスト化したものです。「勉強会で出席者名簿に名前があれば参加できる」というように、一覧に入っていればOKというようなシンプルな制御です。Control)RBACは、「ある対象(例えばユーザー)」に「ロール(一般ユーザー・管理者など)」という概念が割当てられるようにしたものです。このロールに対して、「何ができるか」というのが付与されるようになります。「勉強会の一般参加者はGUESTカードを渡され、運営側の人はSTAFFカードが渡される。STAFFカードを持っている人は特定の部屋に入ることができる。」というように特定のロールに、特定の行動許可が付与されるようなイメージです。Control)ABACはさらにもう一歩先にいきます。ABACのAがAttribute(属性)であるように、「ある対象の属性」も考慮して「何ができるか」を決めることができます。「AさんもBさんも勉強会に参加しました。Aさんは20歳なので懇親会でお酒が飲めますが、Bさんは18歳なので懇親会でお酒が飲めません。」というように、「ある対象(Aさん、Bさん)」の「属性(年齢)」に着目して「何ができるか(お酒が飲めるか)」を決めることができます。どの権限モデルを採用するべきか3つの権限モデルを紹介しましたが、他にも色々とあります。depends」です。上で紹介した中ではABACが最も柔軟ですが、その分実装するのも大変になります。要件がロールだけで完結するアプリケーションであれば、RBACで作る方がシンプルになると思います。下のようなケースが出てくるとだいたいの場合ABACになっちゃうのかなと思います:同じグループだったら○○できる友達だったら○○できる○○歳以上なら○○できるYYYY年〜ZZZZ年の間に加入した人なら○○できるなどなど要件を見極めながら権限モデルを決める必要があります。権限モデルについて触れたところで、次はどこで権限を適用するのか、という点について考えます。これには次の3つの観点が重要なのかなと思っています。対象項目の観点タイミング的にはAPIであれば「リクエスト到着時」、「データ取得時」、「データ返却時」に分類できると思っています。下図のそれぞれのポイントになります。Architecture的に言うと下図のポイントに該当します。それぞれをもう少し詳しくみていきます。機能的な観点というのは「そもそも対象となる機能が使えるのか」というかなり広い範囲の観点です。APIであれば、リクエストしてきた人が「何者かわかれば」判断を下すことができます。「一般ユーザーだったら管理者画面を閲覧できない」というようなケースがこれにあたります。対象範囲の観点というのは、「閲覧」のときであれば「どこまで見ていいか」という意味での範囲です。大きな病院で医者が使うシステムがあって、「患者一覧」を閲覧するAPIがあったとします。このとき、患者はきっとたくさんいますが、全ての患者の情報を全ての医者が問答無用に閲覧できてしまって良いでしょうか?答えはきっとNOです。単純な例だと、きっと医者が担当している患者の情報しか閲覧できない、みたいな制限があるはずです。これは下図のようなイメージになります。この観点は主にデータを取得する際に適用するものです。対象項目の観点とは、例えば「個人情報のクレジットカード番号は非表示にする」と言うような、「ある対象の具体的な項目」に関する観点です。この観点をどこで適用するのか、というのは未だに僕も悩んでいるのですが、APIであればデータを返却する直前(Presenter層)なのかなと今のところ思っています。Overflowでも述べられているので合わせて読んでみると知識が広がります。stackoverflow.com権限のソフトウェアアーキテクチャ何も考えずにアプリケーションの中に権限を実装すると、ビジネスルールとして実装しているのか、権限の関心事として実装しているのか訳がわからなくなってしまいます。Language)です。XACMLに関しては僕もほとんどわかっていないのですが、「権限制御用のXMLで標準化されている言語仕様」、という認識です。XACMLの詳細については説明することができないのですが、XACMLのアーキテクチャが権限における役割を理解するために非常にわかりやすかったです。以下、Axiomaticsの記事がわかりやすく説明しているのと、動画もあるので英語に抵抗が無い人はぜひ見てみてください。www.axiomatics.com記事内に登場する以下の図が特に重要です。図にあるように、権限のアーキテクチャにおいて重要な役割が4つあります。Point)この4つの役割を意識することで、権限のアーキテクチャは結構わかりやすく疎結合に組み立てることができます。僕が理解している範囲でそれぞれの役割をもうちょっと詳しく見てみます。権限を管理(Manage)する人ここは読んで字の如く、権限を管理する箇所です。上の図にあるようにPAPからPRPに、権限にまつわる情報が保存されます。ConsoleもPAPと言えます。権限の判断を下すサポート(Support)をする人ここは権限の判断に必要な付加情報を取得する箇所だと僕は思っています。PRPは権限の情報を取得・保持する箇所なので、権限判断で使う情報としてはサポートというよりは核となる部分だと思うのですが、PIPで取得する付加情報が権限の判断を下すサポートをするメインの箇所という認識です。PRPを表現せずに、PIPだけが表現されている図もよく見かけます。ここで言う付加情報とは、例えばAPIのリクエストにはJWTだけが送られてくるけど、そのJWTに紐づくユーザーの属性値(年齢・所属グループ)などのことです。これをPIPが外部リソース(API・DB・LDAPなど)から取得し、権限の判断のためのサポートを行うというわけです。権限の中ではここが一番中心となる部分でしょう。PAPで管理している権限がPRPに保持されていて、PIPからの付加情報と合わせて最終的な判断を下すのがここ(PDP)です。ここが「やっていいですか?」のような質問を受け付ける場所で、「いいよ」あるいは「だめ」という回答をする場所です。判断の回答は単純ではない上に「いいよ」と「だめ」といった単純な回答を例にしましたが、実際のところこのような単純な回答だけでは要件を満たせないことが多いと思います。先の例にも使った「医者は担当している患者を閲覧できる」という権限があったとします。PDPがもし「いいよ」と「だめ」しか回答できなかった場合、この要件を満たすためには患者レコードを全部取得して、一つずつに対して「患者Xのデータを閲覧していい?」という質問をPDPにしなければいけません。10,000レコードあれば、10,000回質問する必要があるということです。これではスケールしません。すればいいのか?それは、クローズドな質問ではなく、オープンな質問ができるようにする必要があります。クローズドな質問が「患者Xのデータを閲覧していい?」というのに対して、オープンな質問は「どの患者を閲覧していい?」というものです。この回答が「担当している患者であればいいよ」というものになります。回答は「いいよ」や「だめ」と言った単純なものではなく、もっと複雑なものになります。具体的には、例えばデータストアがRDBだった場合に、回答がSQLそのものである可能性もありえます。Query)が以下の動画で紹介されていて、非常に興味深いです。Agentの以下の記事に関しても似たようなことを実現しようとしていて興味深いです。blog.openpolicyagent.orgこの観点に関して、言っていることはわかったとしても「じゃあ実際どう実装するの?」という点に関しては結局僕も答えが見つかっておらず、模索中です。少なくともAxiomaticsのようなソリューションを自分たちで作るのは非現実的だと思います(Axiomaticsはそれを専門にビジネスにしている会社なので)。そもそも「回答をSQLにする」というのも、権限の内容とデータベースの項目が密結合すぎて繊細になりすぎるのでは、とも思っています。最後に重要なのが、権限の質問をして回答をもらった後に権限を適用する箇所で、それが上図でいうPEPとなります。Forbiddenを返す、といった処理が「権限を適用」していることになります。権限実装のアプローチ実際に権限はどうやって実装すると良いのか、というのもまた悩みの種です。「対象範囲の観点」で権限を適用するときを例にとって、実装のアプローチについて挙げてみます。ハードコードするアプローチ権限実装においてハードコードと言われて何のことなのかイメージできるでしょうか?僕は最初は全くイメージできていなかったです。僕の中での当初のハードコードというのは下のようなコードです。execute();}このイメージが一般的には多いんじゃないかと思います。権限実装におけるハードコードというのは少し毛色が異なります。現状の僕の理解は以下のとおりとなっています。権限実装における「ハードコードしている状態」とは、権限の内容が変わった場合に、権限の関心以外のコードが影響を受けてしまう状態ふわっとしててわかりづらいと思うのですが、具体例をあげます。先に述べた患者一覧を取得するAPIについて考えてみます。ようなものになるのが想像できると思います。このリソースをDBから取得する場合のコードはきっと下のようになります(JSっぽい擬似コードです)。patients');}ただし、これでは本当に「全患者を取得」してしまいます。「見て良い範囲の権限」が適用されていません。先の例では「担当している患者だけ閲覧して良い」という要件があるので、この権限をコードに適用すると下のようになります。書いた場所が権限実装における「ハードコードしている箇所」になります。動的に医者のIDが入るので、ハードコードしているようには見えないのですが、権限の内容が変わった場合のことを考えてみましょう。「担当している患者だけ閲覧して良い」という権限から「同じ診療科内の医者が担当している患者だけ閲覧して良い」に変わったとします。なると、下のようなコードに変える必要がありそうです。(${doctorIds.join(',')})`);}これが権限を「ハードコード」してしまっているがために、本来影響を受けてほしくないfindAllPatientsの実装に変更が発生してしまっている状態です。患者一覧を取得する関数(findAllPatients)のはずが、権限の関心が紛れているせいで権限の内容が変わった場合に影響を受けてしまう、というものです。「いや、要件が変わってるんだから影響受けてもいいでしょ」と、思うかもしれませんが、これが医者によって閲覧できる範囲が変わったりしたらどうなるでしょう?例えば「一般権限の医者は担当している患者を閲覧できる」が、「マネージャー権限をもつ医者は同じ診療科内の医者が担当している患者を閲覧できる」といった違いがある場合です。別々の関数を用意して、マネージャーなのか、そうじゃないのかで使い分けたりしそうな複雑な実装になりそうです。...}ユースケースとしては「患者一覧タブを表示したら患者一覧を取得する」というシンプルそうに聞こえるものなはずなのですが、findAllPatients関数の中身を見たら「なんだか思ったより複雑なことをしている」という状態になっているのが想像できると思います。患者一覧を表示する画面のイメージ(ユースケースはシンプルに見える)コアロジックから切り離すアプローチということでハードコードのアプローチは小規模〜中規模なアプリケーションまでなら問題無い気がしますが、中規模〜大規模なアプリケーションになってくるとコードの可読性がしんどくなってくるかと思います。そこで、権限の関心の分離をする方法がもっとスケールするのかなと思います。このアプローチには上の例で示しているような「生のSQLを書く」やり方は厳しいんじゃないかと現状の僕は思っています。Builderのような、生のSQLをWrapするようなものがあって、SQL実行前に介入しつつ、権限を適用する仕組みが必要だと思っています。Builderの力を借りると下のコードのように権限とコアロジックの分離ができそうです。App\Patient::enforcePolicy()->get();enforcePolicyというのがLaravelのScopeを使っていて、モデル内では下のように実装されています(「担当している患者」に絞り込んでいます)。権限の内容に変更があった場合は、下のscopeEnforcePolicyの中身を変えるだけで済み、コアロジック側には影響が無いという状態にすることができます。enforcePolicy()を呼び出している時点で明示的にORMを使う側が権限を意識しないといけないので、結構繊細なコードになってしまう(enforcePolicy呼び出し忘れたら全取得できてしまうセキュリティリスクがある)↑に似てますが、PatientモデルにscopeEnforcePolicyという実装を入れているので、Patientが権限を意識する必要がある。隠蔽化、あるいは処理を移譲してPatientが意識しないようにするべきということです。フレームワークレベルでさらにいい感じに実装できるものがありそうですが、現状では僕はそこまで把握できておらず、イメージ的にこのような分離ができたら素敵なのでは、と妄想しているだけです。Architectureだったり、ドメイン駆動設計にも挑戦している中、「権限はビジネスルールの関心事なのか」という疑問へのスッキリする答えが未だに見つかっていません。少なくとも上に述べている実装のアプローチは、ビジネスルールと権限の関心を分離しているアプローチのつもりです。権限の変更によってビジネスルール側(ドメイン)には影響が無いようにしています。Discussionにおいて以下のように発言しています。it.「セキュリティはアプリケーション特有の関心事であり、ビジネスオブジェクトはこのことについて意識しない」と言っています。この言葉をそのまま鵜呑みにすると、「権限の関心をビジネスルール内に持ってこない」考え方は間違っていないのかなと思っています(セキュリティに関連するアプリケーションを作っているのであれば話はきっと違う)。この観点に関しては色々な人の色々なアプリケーションでの色々な実装方法を聞きたいです。システムアーキテクチャどこまで権限の関心を分離するのか、と考えたときに意識しないといけないのがシステムアーキテクチャです。モノリシックとマイクロサービスなアプローチについて言及し、それぞれのメリット・デメリットについて述べたいと思います。モノリシックなアーキテクチャ迷った場合にまずとってみるアプローチがモノリシックなアーキテクチャかと思います。PAPをすべて同じアプリケーションに詰め込むことになります。それぞれの○が別々のアプリケーションサービスになっていくのがわかりやすい分け方かなと思っています。権限の実装をするにあたってスクラッチから全部作るのは大変なので、Casbinのようなライブラリの力を借りながら実装していけるのかなと思っています。casbin.orgマイクロサービスなアーキテクチャ運用上の懸念点が増える一方で、最も権限の関心の分離が行えるのがこのマイクロサービス的アプローチになるかと思います。PAP)の権限に関連する登場人物はすべて別アプリケーションにします。Agentがかなり興味深い存在です。boxでそのまま別アプリケーションとして使うことができます。そのままの機能では不十分だと感じたら、Goのライブラリとしても使うことができるので、強力な権限アプリケーションをGoで自作することも可能です。www.openpolicyagent.orgまた、権限用の独自の言語Regoを扱っていて、権限を宣言的に定義することができます。AgentやRegoに関してはこのブログでも何度か紹介しているので、気になる方はぜひ読んでみてください。kenfdev.hateblo.jpAuthzforce僕は使ったことがないのですがXACMLに準拠していてOSSなもので名が通っているものとしてAuthzforceというものがあるようです。Editionもあるみたいなので、評価してみる価値があると思います。authzforce-ce-fiware.readthedocs.ioAxiomatics有償にはなりますが、Axiomaticsのソリューションを使うという選択肢もありそうです。XACMLのラッパー言語としてALFAを使うことができ、宣言的に権限を定義することができます。Kubernetes上にもデプロイできるようで、PayPalも使っているようです。Brossard氏です。権限の知識を増やしたければフォローしておくとすごく勉強になります。忘れがちなのがフロントエンドの観点です。Application)登場前であればフロントの生成(HTML)もサーバーサイドが担当していたので、サーバーサイドが持っている権限の情報をフロント側にも使えば良いというものでした。LaravelのPolicyをBladeテンプレートで使う例がこのやりかたに当たります。他のフレームワークでも似たようなことができるはずです。AngularなどのSPAを使うことが増えてきたため、フロントとサーバーサイドが分離しています。いうことは、権限情報は標準でフロント側は持っていないということを意識しておく必要があります。やり方としては大きくは2つあるのかなと思っています。下図のような「編集機能付き患者一覧画面」を例にとって、どういうアプローチがあるか見てみます。編集機能付き患者一覧画面リソース自体に権限の内容を埋め込む方法この方法が一般的には多い印象があります。APIのレスポンスが以下のような形になります。}]ここに登場するeditableのキーを「権限情報がリソースに埋め込まれてる状態」と僕は呼んでいます。「患者」のリソースを取得できるAPIのはずが、患者とは関係ない「編集可能かどうか(editable)」という権限の情報もまぎれこんでいるのがわかります。考えられるメリットフロントは圧倒的に実装しやすい(上の例であればeditableを見るだけで編集して良いかどうかが判断できる)考えられるデメリットリソースに権限の情報が紛れ込んでしまう↑の影響でサーバーサイドの「患者一覧取得」APIは、患者の情報以外に権限の情報をJSONに付加する必要がある権限情報をリソースと切り離す方法この方法は馴染みがあまり無いという人が多いのではないかと思います。このアプローチの場合、APIから取得するリソースの情報に原則として「権限情報」を含めません。権限情報は別APIを用意することになります。患者一覧を取得するAPIのJSONは下のようになります。}]前の例との違いは、権限情報であるeditableが消えたことです。編集可能な患者はどのように見分けるのでしょう?そのために別途権限情報を取得するAPIを用意します。権限のAPIに対しては「私は何ができますか?」という問い合わせをするイメージです。以下は権限APIのレスポンスJSONの一例です。}]読み替えると、「すべてのpatientリソースの閲覧ができる」ことと「doctorNameが医者Aとなっているpatientリソースなら編集できる」、という内容になります。下図のように、2つのAPIから取得した情報を組み合わせることでUIを作ることになります。権限の分離を行っているアーキテクチャを支援してくれるライブラリだったり、ドキュメントがいくつかあるので紹介します。CASLCASLというライブラリが主要なJSフレームワークと連携して使うことができます。stalniy.github.io何に対して、どんなアクションを、どんな時に実施して良いかという権限の関心を独立して管理することができます。参考まで、以下が実際のJSONでの定義例です。updateのアクションを許可する」というものです。この情報をUI側から使って、例えば「編集ボタンを表示・非表示」にすることができます。AutomateのAuthorizationコンポーネントがあります。Introspectionの節がこれにあたります。権限設計含めて神がかっているドキュメントだと思うので、ぜひ一度読んでみることをおすすめします。ベストプラクティス」とググったところで「権限」のカバーしている領域が広すぎて求めている情報がなかなか見つかりません。その中で僕が今の所目指している方向性としては「AWSのIAMのような仕組み」です。Policyの共通のJSONフォーマットがわかりやすいし、先人の知恵がつまっているAWSのIAMに関するドキュメントが権限設計においてすごく勉強になるというように、権限設計・実装をするためのとっかかりがあった、という感じです。Agentにはもともと期待しているというバイアスが働いてるのも理由としては大きいと思います。完全に「AWSのIAM」を作るところを目指すのは非現実的ですが、現実的なラインを見極めながらシンプルで良いところを自分のシステムに組み込んでいくアプローチが良いんじゃないかと思っています。本当に強力な権限サービスが必要であれば、Axiomaticsのような有償サービスに移譲することも有用な選択肢の一つだと思います。これに関してはコストとユースケースのフィットを自分なりに見極めるしかないでしょう。最後にここまで読んでいただきありがとうございます。まとまりの悪い内容になっていることを再度お詫びいたします。権限設計に悩む誰かのヒントになったり、同じように悩んでいる人がいるんだよという共感を持てる内容にどこかしらなっていたら幸いです。今後もこの記事をアップデートしていくつもりですが、独りで苦悩していてもつらいので、権限設計Meetupとかあったら面白いんじゃないかな、と思いました。色々な人の泥臭い話が聞ける気しかしないです。Authorization」みたいな本が出てほしいなという気持ちもあります。AgentのSlackを見ながら、同じように苦悩している人の会話を日々追っています。興味がある人はぜひJoinしてみてください。slack.openpolicyagent.org長々と書いてしまいましたが、いったんここまでとして引き続き精進し続けたいと思います。(1)月別アーカイブはてなブログをはじめよう!kenevさんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。