Amazon Verified Permissionsで、APIの認可処理を実装する
はじめに
2023年6月にGAされたAmazon Verified Permissionsを試します。
最初に書いておくと、このサービスはけっこう難解です。
どのくらい難解かというとGAされて2週間以上経ちましたが、やってみた系の記事が見つかりません。ほぼ情報がない中を手探りで触ってみました。心が折れないようにあまり深堀はせず、単純なAPI呼び出しをVerified Permissionsを使って認可する処理のみを試してみます。
Amazon Verified Permissionsとは
認可(承認)を提供するAWSのサービスです。
ここで早速、注意しておくべきポイントが2つあります。
- 認証の機能はなく、認可だけを提供するサービスです
- IAMとは全く関係ありません
1つ目は書いたとおりです。Verified Permissionsは認可のサービスです。認証のサービスではありません。
認証は本人であるかを確認することであり、認可は 権限があるかを確認すること です。この記事ではそのあたりの説明はこれ以上しませんので、この時点で何を言っているかよくわからないと感じた方は「認証認可」で検索して違いを確認することをお勧めします。
余談
我々の世界では、認証と認可は別の意味ですが、認可と承認は同じ意味です。
しかし法律の世界では、認可と承認は意味が違うらしく、ググるとその話が出てきて混乱するので気をつけましょう。役所のIT部門の人は混乱して大変なのではないかなと思いました。
2つ目について。AWSで認可といえばIAMを連想する方が多いと思います。しかしVerified PermissionsはIAMとは全く無関係です。つまり、Verified PermissionsではAWSのリソースへのアクセス権限を付与することは出来ません。たとえば、S3へのアクセスに対する認可処理にVerified Permissionsを使用する場合、アクセスできる(ALLOW) or アクセスできない(DENY)の判断は出来ますが実際のアクセス権限の付与は別途実施する必要があります。
実際に試す
この時点で難解そうだなと感じて頂いたと思いますが、実際に試してみたいと思います。
今回試すケースの状況設定
まずは、今回やることを整理します。
- 許可されたユーザーにだけに
HelloWorld
を返すAPIを作ります。- APIのエンドポイントは
/hello
とします。
- APIのエンドポイントは
- ユーザーの管理や認証は、Amazon Cognitoで実施します。深掘りはしないとは書きましたが、認証なしで認可だけを構築しても実用性が皆無ですので最低限Cognitoと連携して認証する機能は試します。
- 権限の有無はCognitoのカスタム属性
custom:role
で判別することにします。
- 権限の有無はCognitoのカスタム属性
Cognitoを作る
ユーザーを管理するCognitoを作ります。Cognitoの記事ではないので作り方は説明しません。
テスト用にユーザーを2人用意しました。
Aさんにだけ、カスタム属性としてcustom:role=hello
を設定しました。
custom:role=hello
の人だけがAPIを実行できることとして、この後Verified Permissionsを構築します。
Verified Permissionsを作る
とりあえずIaCとかは考えずに、ブラウザからコンソールを開いて作ります。
細かい解説はしない(解説できるほど詳しくない)ので、ドキュメントを貼っておきます。
- Verified Permissionsのドキュメント
また、Verified PermissionsではCedarポリシー言語というものを使います。チュートリアルとドキュメントを貼っておきます。
- Cedarのチュートリアル
- Cedarのドキュメント
principalとactionとresource
Cedar言語ではこの3要素が重要です。今回のケースでは次のように定義します。
- principal
- Cognitoのユーザー(Aさん,Bさん)
- action
- APIのメソッド(GET)
- resource
- APIのエンドポイント(hello)
スキーマを定義
ようやくここから実際にVerified Permissionsを構築します。まずはスキーマを定義します。
スキーマ定義とはつまりprincipal
とresource
とaction
を定義することです。
ブラウザでポチポチと追加したりJSONを編集することで定義できます。最終的にこんな感じになりました。
principal
とresource
は両方とも、エンティティとして定義されます。
action
を定義する際に、principal
とresource
をエンティティからそれぞれ選びます。
実際に定義したJSONをGitHubにおきました。画面で触ってみてよく分からない時は、参考に使ってください。
注意事項
前述のJSONでスキーマを定義するとGUIでUserエンティティが一部編集できなくなります。
これは、以下の理由によるものです。
- CognitoのユーザーをVerified Permissionsにマッピングする際に
custom:role
は、おそらくこのように展開されます。
"custom" : {
"role" : "hello"
}
- 見て分かる通り、このcustom属性はオブジェクトです。
- 一方でVerified Permissionsのコンソールからの編集はstringなどのリテラルにしか対応しいないようです。
- 対応してないので、編集できません。
JSONに直接書けば編集できますのでJSONを書きましょう。書き方はCedarのドキュメントを読みましょう。
お気持ち
「おそらく」と書いたのは特にドキュメントに載っていないからです。試行錯誤の結果たぶんこうなんだろうなぁという感じです。Cognitoとそういう感じのマッピングがされるなら、GUIも対応しておけと思ってしまいます。せめて、Cedarのドキュメント読んで察してではなくVerified Permissionsのドキュメントに記載が欲しかったです。
Verified PermissionsとCognitoを連携する
UserエンティティをCognitoと紐づけます。画面からIDソースを作成
してください。
お気持ち
バグなのか仕様なのか分かりませんが、バグでないならドキュメントに書いておいてほしかったです。
ポリシーを定義する
スキーマの定義とCognitoとの連携が出来ましたので、いよいよ実際に許可を出すポリシーを定義します。ポリシーの作成から作ることが出来ます。
ポリシーはこんな感じに書きました。
-
permit
は許可のポリシーになります。- principalの条件は細かい属性指定が必要なので、条件指定は後回しにします。
- actionはGETを指定します。今後POSTなども同時に指定することを考えて配列で条件を書いています。
- resourceは、helloを指定します。
-
when
で更に細かい条件を追加します。- custom.roleが
hello
の場合のみ許可します。複数のroleに許可を与える拡張性を考慮して配列で指定しています。
- custom.roleが
Verified Permissionsの設定は以上です。
API側のコードを書く
TypeScript + Expressで書いたHelloWorldに、先程構築したVerified Permissionsを使った認可を実装します。
作成物はGitHubに置きましたので、詳細はそちらを参照してください。
AWS-SDKのインストール
VerifiedPermissions用のSDKがリリースされているので使います。
npm i @aws-sdk/client-verifiedpermissions
- npm
- document
使うコマンド
IsAuthorizedWithTokenを使います。
IsAuthorizedWithTokenを実行する
こんな感じでコードを書いています。
引数の今回使用する項目は、次のようになっています。
- policyStoreId
- VerifiedPermissionsのストアID
- identityToken
- Cognito認証後に取得できるIDトークン
- action
- VerifiedPermissionsのアクション
- 今回はAPIのメソッドである
GET
- resource
- VerifiedPermissionsのリソース
- 今回はAPIのエンドポイントである
hello
許可されたかどうかは、response.decision
の中にALLOW
かDENY
が入っているので、それで判断します。文字列比較で条件分岐を書くのが面倒なので、booleanにして欲しかったです。
お気持ち
引数に関してドキュメントが不十分で、特に以下のようなことがよく分からなくて苦戦しました。
- 引数のactionとかresourceのTypeとId、どっちがどっちなのか。分かりやすい引数名をつけて欲しい。
- Typeはどの範囲の文字列を入れればよいのか。Actionなのか?Action::GETなのか?ストア名は付くのか?
SDKのソースコードのバリデーションに使われている正規表現から正しい入力を予測してTry&Errorしました。ドキュメントにもう少し分かりやすいサンプルを載せてほしかったです。
また、このコマンドはエラー時に、エラーの本当の内容に関係なくほとんどすべてを400バリデーションエラーで返してきます。対処方法が分からなくて途方に暮れました。エラーメッセージは正しく返して欲しいです。
ちなみに、identityTokenにアクセストークンを入れてもエラーにならずに処理が動作するので注意が必要です。本格的にアプリに組み込む場合は、ポリシーでtoken_use
の項目をチェックしてトークンの種類が違ったらDENYにしたほうが良さそうです。
お気持ち
引数の指定がidentityToken
とaccessToken
に分かれているのですが、どちらを指定しても同じ挙動しているとしか思えないです。
その挙動がバグじゃないのなら、パラメータをtoken
に統一して、ドキュメントに注意書きを残したほうが良いと思います。紛らわしいですし、気づかないで実装したらセキュリティ上の問題が発生する可能性があります。
実際にAPIを実行してみる
Aさん(許可)
AさんでCognitoにログインして、idTokenを取得します。
idTokenをAuthorizationヘッダーにつけて、先程作成したAPIを呼び出します。
200 OK
Bさん(拒否)
BさんでCognitoにログインして、idTokenを取得します。
idTokenをAuthorizationヘッダーにつけて、APIを呼び出します。
403 Forbidden
ユーザー以外(拒否)
idTokenなしで、APIを呼び出します。
401 Unauthorized
以上で、Amazon Verified Permissionsを使ってAPIの認可処理を実装できました。
最後に
GAされたばかりのサービスということもあり、かなりツラいというのが正直な感想です。認可のサービスということでただでさえ概念的に難解なのに、ドキュメントが不十分だったりエラーメッセージが意味不明だったり挙動が怪しいところがあったりします。
ただ、思っていたよりCognitoとの連携は便利に感じました。正直なところ、認証なしの認可だけのサービスだったら自力で作っても変わらなくない?とも思っていたのですが、実際にやってみるとCognitoと連携することで実質認証もカバーできるので、ありなのかなと思いました。
また、製品紹介ページの図によるとAPI GatewayやAppSyncとの連携が示唆されているので、そのあたりが統合されてかつ面倒くさい設定が自動化されれば、使いやすくなるのかなと思います。
NCDC株式会社( ncdc.co.jp/ )のエンジニアチームです。 募集中のエンジニアのポジションや、採用している技術スタックの紹介などはこちら( github.com/ncdcdev/recruitment )をご覧ください! ※エンジニア以外も記事を投稿することがあります
Discussion