Amazon Web Services Advent Calendar 20172日目です。大変遅くなってしまい、申し訳ありません…

前書き

re:Invent は今年も盛り上がりましたね。キーノート中ずっと喋りっぱなしの Andy Jassy の喉が心配になるほど大量の新サービスが発表されて、個人的には EKS, Fargate, Comprehend, Lambda の Go 対応あたりが興味深いと思いましたが、本記事では既存サービスについて書きます。

ちょうど1年前、2016年12月に Cognito にロールベースのアクセス制御機能が追加されました。

チュートリアルとしては上の記事の通りなのですが、 API の仕様理解と構築自動化のため、これをすべて CloudFormation で構築した上で簡単なデモを動かしてみます。

デモの仕様

こんな感じのものを作ってみました。

  • Cognito User Pool に登録されたユーザーの username/password でログインできる
  • ユーザー登録はここでは実装しない
  • ログインすると Cognito Identity が発行した temporaly credentials を使って S3 バケットに置いてあるオブジェクトにアクセスできる
    • ここでは固定された1つのオブジェクト CONTENT.md のみとする
  • ユーザーのカスタム属性 role の値に従って異なる IAM Role が assume され、 S3 バケットにどのようなアクセスができるかが変化する
logged in? user attribute: custom:role S3 (read) S3 (write)
No No No
Yes viewer Yes No
Yes editor Yes Yes

早速触ってみる

Angular で実装してみました!

デプロイ済みのデモはこちらです。そのコードはこちらです。

hello-viewer, hello-editor というユーザーでログインできます。どちらもパスワードは Password1234 です。

ログインすると、 View のページから S3 に置かれたオブジェクトの内容が見られます。せっかくなので Markdown をパースして表示しています。

hello-editor でログインした場合はこの内容を編集して更新できます。

また、 Identity のページでは、現在払い出されているロールを確認できます。ログイン前とログイン後、そしてログインしているユーザーによって異なるロールが払い出されていることを確認できると思います。

CFn による構築

ほとんど CloudFormation で Cognito で書いたままです。

後述する理由によりカスタムリソースが必要なので、 Serverless Framework で一式をデプロイすることにしました。 serverless.yml にゴリゴリとリソースを並べていきますserverless.yml が500行超えるやつは大体友達。

各リソースについて、この要件に必要な部分をざらっとおさらいします。

UserPool

ロールベースアクセス制御に使うカスタム属性を Schema で定義しています。今回は role というカスタム属性を追加しました。

なお、カスタム属性は必須ではなく、例えば email などデフォルトの属性を使ってロール割り当てを切り替えることも可能です。

UserPoolClient

今回はクライアントが Web なので、 GenerateSecretfalse です。

IdentityPool

特になし。IdentityPoolName にハイフンを含めないのが地味に不便です。

CognitoUserBasePolicy, CognitoUserAuthenticatedPolicy

コンソールからポチポチして作ると付けられるポリシーを元に作ってます。

今回はポリシーについては authed/unauthed という分け方ではなく、差分を追加していくという方法で分けてみました。

UnauthenticatedRole, AuthenticatedRole

これもコンソールポチポチロールを元に作ってます。 AssumeRolePolicyDocument が複雑なのでがんばりましょう。

AuthenticatedRole には今回の要件で必要な S3 へのアクセス権限などは一切追加しません(重要)。

ViewablePolicy, EditablePolicy

S3 へのアクセス権をこちらで定義しました。

ViewerRole, EditorRole

上の ViewablePolicy, EditablePolicy を付けたロールです。これらが今回のデモでログインした時に払い出されます。

RoleAttachment

UnauthenticatedRole, AuthenticatedRole をここでIdentityPool に紐付けます。

本来であれば、このリソースの RoleMappingsViewerRole, EditorRoleも紐付けたいのですが、そうしていません。

なぜなら CloudFormation は KeyValue の Key を動的に定義できない という問題があるため、 RoleMappings のキー(cognito-idp.<region>.amazonaws.com/<userPool>:<userPoolClient> という形式)をどうやってもハードコードする必要が生じてしまうからです。

この問題はフォーラムでも話題に上がっています。

IdentityPoolRoleMapping

というわけで、上の問題を無理矢理に解決するためのカスタムリソースです。

cognito-identity:SetIdentityPoolRoles を行うには iam:PassRole の権限も必要です。地味にハマリポイント。

属性のマッチ条件は完全一致、部分一致、不一致、前方一致が選べるようです。後方一致があれば email のドメイン名で…ができて便利そうなのですが。

Bucket

ユーザーが編集できるオブジェクトの他に、今回はデモそれ自体の動作に必要なファイルもここに置きます。

ブラウザー上で動作する SDK から直接叩くので、 CORS 設定が必要です。

BucketPolicy

public/* のみ全開放とします。

AwsConfiguration

どうせカスタムリソースを使うならということで、 Cognito の認証に必要な情報も JSON にしてバケットに置いておきます。

こうすることでクライアントのコードに Cognito 関係のリソースの ID をハードコードしなくてよくなりますね。

AssetBundle

デモが動作するために必要なファイルをバケットに置くカスタムリソースです。

どうやって全自動でこれを置くか? と思案した結果、下のような流れになりました。

  1. ng build --prod でクライアントのコードをバンドルする
  2. serverless-webpack copy-webpack-plugin を使い、クライアントのアセットバンドルを Lambda のバンドルの中に含める
  3. カスタムリソースでそれをバケットに置く

yarn run deploy を叩くとこの流れで一切合切がバケットに上がります。

その他

カスタムリソースを書くのが面倒すぎて、以前作った cfn-custom-resource-helper という npm パッケージを使っています。これの紹介はまたどこかで。

最後に

遅刻すみませんでした。

元記事はこちら

Cognito User Pool – ロールベースアクセス制御を CFn で構築する