~~Amazon Web Services Advent Calendar 2017 の2日目です。~~大変遅くなってしまい、申し訳ありません…
前書き
re:Invent は今年も盛り上がりましたね。キーノート中ずっと喋りっぱなしの Andy Jassy の喉が心配になるほど大量の新サービスが発表されて、個人的には EKS, Fargate, Comprehend, Lambda の Go 対応あたりが興味深いと思いましたが、本記事では既存サービスについて書きます。
ちょうど1年前、2016年12月に Cognito にロールベースのアクセス制御機能が追加されました。
- 新機能 – Amazon Cognito グループ、およびきめ細かなロールベースのアクセス制御 | Amazon Web Services ブログ
- ロールベースアクセスコントロール - Amazon Cognito
チュートリアルとしては上の記事の通りなのですが、 API の仕様理解と構築自動化のため、これをすべて CloudFormation で構築した上で簡単なデモを動かしてみます。
デモの仕様
こんな感じのものを作ってみました。
- Cognito User Pool に登録されたユーザーの username/password でログインできる
- ユーザー登録はここでは実装しない
- ログインすると Cognito Identity が発行した temporaly credentials を使って S3 バケットに置いてあるオブジェクトにアクセスできる
- ここでは固定された1つのオブジェクト
CONTENT.md
のみとする
- ここでは固定された1つのオブジェクト
- ユーザーのカスタム属性
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 なので、 GenerateSecret
は false
です。
IdentityPool
特になし。 IdentityPoolName
にハイフンを含めないのが地味に不便です。
CognitoUserBasePolicy
, CognitoUserAuthenticatedPolicy
コンソールからポチポチして作ると付けられるポリシーを元に作ってます。
今回はポリシーについては authed/unauthed という分け方ではなく、差分を追加していくという方法で分けてみました。
UnauthenticatedRole
, AuthenticatedRole
これもコンソールポチポチロールを元に作ってます。 AssumeRolePolicyDocument
が複雑なのでがんばりましょう。
AuthenticatedRole
には今回の要件で必要な S3 へのアクセス権限などは一切追加しません(重要)。
ViewablePolicy
, EditablePolicy
S3 へのアクセス権をこちらで定義しました。
ViewerRole
, EditorRole
上の ViewablePolicy
, EditablePolicy
を付けたロールです。これらが今回のデモでログインした時に払い出されます。
RoleAttachment
UnauthenticatedRole
, AuthenticatedRole
をここで IdentityPool
に紐付けます。
本来であれば、このリソースの RoleMappings
で ViewerRole
, 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
デモが動作するために必要なファイルをバケットに置くカスタムリソースです。
どうやって全自動でこれを置くか? と思案した結果、下のような流れになりました。
-
ng build --prod
でクライアントのコードをバンドルする -
serverless-webpack
とcopy-webpack-plugin
を使い、クライアントのアセットバンドルを Lambda のバンドルの中に含める - カスタムリソースでそれをバケットに置く
yarn run deploy
を叩くとこの流れで一切合切がバケットに上がります。
その他
カスタムリソースを書くのが面倒すぎて、以前作った cfn-custom-resource-helper という npm パッケージを使っています。これの紹介はまたどこかで。
最後に
遅刻すみませんでした。