🌶️

Amazon Verified Permissionsで、APIの認可処理を実装する

2023/07/02に公開

はじめに

2023年6月にGAされたAmazon Verified Permissionsを試します。

https://aws.amazon.com/jp/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とします。
  • ユーザーの管理や認証は、Amazon Cognitoで実施します。深掘りはしないとは書きましたが、認証なしで認可だけを構築しても実用性が皆無ですので最低限Cognitoと連携して認証する機能は試します。
    • 権限の有無はCognitoのカスタム属性custom:roleで判別することにします。

Cognitoを作る

ユーザーを管理するCognitoを作ります。Cognitoの記事ではないので作り方は説明しません。

テスト用にユーザーを2人用意しました。

Aさんにだけ、カスタム属性としてcustom:role=helloを設定しました。
custom:role=helloの人だけがAPIを実行できることとして、この後Verified Permissionsを構築します。

Verified Permissionsを作る

とりあえずIaCとかは考えずに、ブラウザからコンソールを開いて作ります。
細かい解説はしない(解説できるほど詳しくない)ので、ドキュメントを貼っておきます。

  • Verified Permissionsのドキュメント

https://docs.aws.amazon.com/verifiedpermissions/latest/apireference/Welcome.html

また、Verified PermissionsではCedarポリシー言語というものを使います。チュートリアルとドキュメントを貼っておきます。

  • Cedarのチュートリアル

https://www.cedarpolicy.com/en/tutorial

  • Cedarのドキュメント

https://docs.cedarpolicy.com/

principalとactionとresource

Cedar言語ではこの3要素が重要です。今回のケースでは次のように定義します。

  • principal
    • Cognitoのユーザー(Aさん,Bさん)
  • action
    • APIのメソッド(GET)
  • resource
    • APIのエンドポイント(hello)

スキーマを定義

ようやくここから実際にVerified Permissionsを構築します。まずはスキーマを定義します。
スキーマ定義とはつまりprincipalresourceactionを定義することです。

ブラウザでポチポチと追加したりJSONを編集することで定義できます。最終的にこんな感じになりました。

principalresourceは両方とも、エンティティとして定義されます。
actionを定義する際に、principalresourceをエンティティからそれぞれ選びます。

実際に定義したJSONをGitHubにおきました。画面で触ってみてよく分からない時は、参考に使ってください。
https://github.com/k-ibaraki/sample-verified-permissions/blob/2023-07-01/verified_permissions_resouces/Schema.json

注意事項

前述の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との連携が出来ましたので、いよいよ実際に許可を出すポリシーを定義します。ポリシーの作成から作ることが出来ます。

ポリシーはこんな感じに書きました。
https://github.com/k-ibaraki/sample-verified-permissions/blob/2023-07-01/verified_permissions_resouces/Policy.cedar

  • permitは許可のポリシーになります。
    • principalの条件は細かい属性指定が必要なので、条件指定は後回しにします。
    • actionはGETを指定します。今後POSTなども同時に指定することを考えて配列で条件を書いています。
    • resourceは、helloを指定します。
  • whenで更に細かい条件を追加します。
    • custom.roleがhelloの場合のみ許可します。複数のroleに許可を与える拡張性を考慮して配列で指定しています。

Verified Permissionsの設定は以上です。

API側のコードを書く

TypeScript + Expressで書いたHelloWorldに、先程構築したVerified Permissionsを使った認可を実装します。

作成物はGitHubに置きましたので、詳細はそちらを参照してください。
https://github.com/k-ibaraki/sample-verified-permissions/tree/2023-07-01

AWS-SDKのインストール

VerifiedPermissions用のSDKがリリースされているので使います。

 npm i @aws-sdk/client-verifiedpermissions

使うコマンド

IsAuthorizedWithTokenを使います。
https://docs.aws.amazon.com/verifiedpermissions/latest/apireference/API_IsAuthorizedWithToken.html

IsAuthorizedWithTokenを実行する

こんな感じでコードを書いています。
https://github.com/k-ibaraki/sample-verified-permissions/blob/2023-07-01/src/verifiedPermissionsSample.ts#L31-L55

引数の今回使用する項目は、次のようになっています。

  • policyStoreId
    • VerifiedPermissionsのストアID
  • identityToken
    • Cognito認証後に取得できるIDトークン
  • action
    • VerifiedPermissionsのアクション
    • 今回はAPIのメソッドであるGET
  • resource
    • VerifiedPermissionsのリソース
    • 今回はAPIのエンドポイントであるhello

許可されたかどうかは、response.decisionの中にALLOWDENYが入っているので、それで判断します。文字列比較で条件分岐を書くのが面倒なので、booleanにして欲しかったです。

お気持ち

引数に関してドキュメントが不十分で、特に以下のようなことがよく分からなくて苦戦しました。

  • 引数のactionとかresourceのTypeとId、どっちがどっちなのか。分かりやすい引数名をつけて欲しい。
  • Typeはどの範囲の文字列を入れればよいのか。Actionなのか?Action::GETなのか?ストア名は付くのか?

SDKのソースコードのバリデーションに使われている正規表現から正しい入力を予測してTry&Errorしました。ドキュメントにもう少し分かりやすいサンプルを載せてほしかったです。

また、このコマンドはエラー時に、エラーの本当の内容に関係なくほとんどすべてを400バリデーションエラーで返してきます。対処方法が分からなくて途方に暮れました。エラーメッセージは正しく返して欲しいです。

ちなみに、identityTokenにアクセストークンを入れてもエラーにならずに処理が動作するので注意が必要です。本格的にアプリに組み込む場合は、ポリシーでtoken_useの項目をチェックしてトークンの種類が違ったらDENYにしたほうが良さそうです。

お気持ち

引数の指定がidentityTokenaccessTokenに分かれているのですが、どちらを指定しても同じ挙動しているとしか思えないです。
その挙動がバグじゃないのなら、パラメータを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エンジニアブログ

Discussion