CloudFrontとLambda at EdgeとCognitoの認証をSAMLでIAM Identity Centerと連携したい
静的ウェブサイトホスティングに認証機能を開発なしで実装したいという想いはだいぶ前に成し遂げていた(下の記事)のですが、人間の欲は尽きないもので、最近IAM Identity Centerをしっかり使い始めたので、Cognitoと別でID管理したくないなぁという欲が出てきました。
今回はそんな欲をCognitoとIdentity CenterをSAML連携することで満たしていきたいと思います。
ちなみに、単にAWS Re:Postの記事とかをスクショ付きの手順にする以外は新しいこともあまりなのですが、あえてこうして筆をとっているのは「うまくいかなかったら、時間をおいてやり直してみるといいよ」というのを残しておきたかったからです。
私が検証した際に原因不明でうまくSAML連携できないことが続きてトラブルシューティングとかでいろいろ設定を変更したりしたのですが、結局再現性がなかったので、検証時に単に障害が起きていたか再現性のないバグを引いてしまったかだけだったのかなと思っています。
つまり、今後同じように沼にはまる人がいないようにこうして記事にしてあの苦労を成仏させようという想いです。
構成に関しての説明
簡単に構成をまとめてみました。ざっくりなので参考程度にしておいていただければと思います。詳細は参考のリンクを読めばわかると思います。(ざっくりなので、本当はもっといろいろ細かいことやっています。)
参考
Serverless Application Repositoryでデプロイされる機能部分
CognitoのSAMLのIdPの連携部分
設定手順
今回はS3とCognitoは設定を管理したいのでセルフで作成する形の手順にします。
大まかな流れは以下の通りです。
- S3の作成とテスト用のindex.htmlファイルの配置
- Cognito ユーザプールの作成(ついでにドメインとクライアントの作成)
- Identity Center アプリケーションの作成
- CognitoのSAML IdPの設定
- SAMLの動作確認
- Serverless Application Repositoryのデプロイ
- CloudFrontのOACの設定
- CloudFront Authentication at Edgeの動作確認
- Idenity CenterのスタートURLの設定と動作確認
- 後片付け
手順の参考としては以下のre:Postの記事です。
事前準備(1~2)
1. S3の作成とテスト用のindex.htmlファイルの配置
マネジメントコンソールでデフォルトの設定で作っていきます。
S3バケットの作成
パラメータ名 | デフォルト値 | 値 | 備考 |
---|---|---|---|
バケット名 | (なし) | jimatomo-test-1234 | 任意の名前でOK。設定例として記載 |
AWS リージョン | アジアパシフィック (東京) ap-northeast-1 | ← | |
既存のバケットから設定をコピー - オプション | (なし) | ← | |
オブジェクト所有者 | ACL 無効 (推奨) | ← | |
このバケットのブロックパブリックアクセス設定 | パブリックアクセスをすべて ブロック | ← | |
バケットのバージョニング | 無効にする | ← | |
タグ (0) - オプション | (なし) | ← | 設定してもいい |
暗号化タイプ | Amazon S3 マネージドキーを使用したサーバー側の暗号化 (SSE-S3) | ← | |
バケットキー | 有効にする | ← | |
オブジェクトロック | 無効にする | ← |
テスト用ファイルの配置
テスト用の [index.html] をルートのパスに配置しておきましょう
2. Cognitoユーザプールの作成
Cognitoユーザープールはマネジメントコンソールから作成していきます。
ユーザープールの作成
[サインインエクスペリエンスを設定]
パラメータ名 | デフォルト値 | 値 | 備考 |
---|---|---|---|
プロバイダーのタイプ | Cognito ユーザープール | ← | フェデレーテッドIdPは後で設定する |
Cognito ユーザープールのサインインオプション | (なし) | E メール |
[セキュリティ要件を設定]
パラメータ名 | デフォルト値 | 値 | 備考 |
---|---|---|---|
パスワードポリシーモード | Cognito のデフォルト | ← | |
MFA の強制 | MFA を必須にする - 推奨 | MFA なし | SAML連携しかしないので何でもいいが設定する箇所が少なくなるのでなしにする |
セルフサービスのアカウントの復旧 | ■セルフサービスのアカウントの復旧を有効化 - 推奨 | □セルフサービスのアカウントの復旧を有効化 - 推奨 | 同様にSAML連携しかしないので… |
[サインアップエクスペリエンスを設定]
パラメータ名 | デフォルト値 | 値 | 備考 |
---|---|---|---|
自己登録 | ■自己登録を有効化 | □自己登録を有効化 | 同様にSAML連携しかしないので… |
Cognito アシスト型の検証および確認 | ■Cognito が検証と確認のためにメッセージを自動的に送信することを許可 - 推奨 | □Cognito が検証と確認のためにメッセージを自動的に送信することを許可 - 推奨 | 同様にSAML連携しかしないので… |
以前の選択に基づく必須属性 | - | ← | |
追加の必須属性 | (なし) | ← | |
カスタム属性 - オプション | (なし) | ← |
[メッセージ配信を設定]
パラメータ名 | デフォルト値 | 値 | 備考 |
---|---|---|---|
E メールプロバイダー | Amazon SES で E メールを送信 - 推奨 | Cognito で E メールを送信 | |
送信元の E メールアドレス | no-reply@verificationemail.com |
← | |
返信先 E メールアドレス - オプション | (なし) | ← |
[アプリケーションを統合]
パラメータ名 | デフォルト値 | 値 | 備考 |
---|---|---|---|
ユーザープール名 | (なし) | test_userpool | 任意の名前でOK |
ホストされた認証ページ | □Cognito のホストされた UI を使用 | ■Cognito のホストされた UI を使用 | |
ドメインタイプ | Cognito ドメインを使用する | ← | |
Cognito ドメイン | https:// | https://jimatomo-test-1234 |
設定例として記載 |
アプリケーションタイプ | パブリッククライアント | ← | |
アプリケーションクライアント名 | (なし) | test_client | |
クライアントシークレット | クライアントのシークレットを生成しない | クライアントのシークレットを生成する | CloudFrontとLambda@Edgeの仕組みで利用するので生成しておく |
許可されているコールバック URL | https:// | https://example.com |
後で変えるので何でもいい |
高度なアプリケーションクライアントの設定 | (割愛) | ← | 後で修正するのでいじらなくてOK |
属性の読み取りおよび書き込み許可 | (割愛) | ← | |
タグ (0) - オプション | (なし) | ← | 設定してもいい |
以上で事前準備は完了です。
SAMLの設定(3~5)
SAMLの設定と動作確認をしていきます。
3. Identity Center アプリケーションの作成
Identity Centerのコンソールを開いて、ナビゲーションペインから [アプリケーション] を選択する。
[アプリケーションの追加] をクリックする。
[カスタムアプリケーション] で [カスタム SAML 2.0 アプリケーションの追加] を選択する。
[次へ] をクリックする。
アプリケーションを設定
以下の通りに設定を変更する
[アプリケーションを設定] のエリア
パラメータ名 | デフォルト値 | 値 | 備考 |
---|---|---|---|
表示名 | Custom SAML 2.0 application | ← | 変えてもいい |
説明 | Custom SAML 2.0 application | ← | 変えてもいい |
[アプリケーションメタデータ]
Cognitoの画面を確認しながら入力する。
ドメインは [アプリケーションの統合] のタブを開くと確認可能
ユーザープールIDは上部の [ユーザープールの概要] から確認可能
上記の情報をベースに入力していく。
パラメータ名 | デフォルト値 | 値 | 備考 |
---|---|---|---|
アプリケーションメタデータ | メタデータ値をマニュアルで入力する | ← | |
アプリケーション ACS URL | (なし) | https://jimatomo-test-1234.auth.ap-northeast-1.amazoncognito.com/saml2/idpresponse |
※1 |
アプリケーション SAML 対象者 | (なし) | urn:amazon:cognito:sp:ap-northeast-1_Muqcz2zSf |
※2 |
※1:設定例として記載。形式はhttps://<domain-prefix>.auth.<region>.amazoncognito.com/saml2/idpresponse
※2:設定例として記載。形式はurn:amazon:cognito:sp:<userpool-id>
最後に [IAM Identity Center メタデータ] の [IAM Identity Center SAML メタデータファイル] のURLをコピーしておく(後で設定変更画面で確認可能)
[送信] をクリックする
属性のマッピングを設定
アプリケーションの詳細画面の [アクション] から [属性マッピングを編集] をクリックする
以下の通りに設定
[Subject] 属性
パラメータ名 | デフォルト値 | 値 | 備考 |
---|---|---|---|
アプリケーションのユーザー属性 | Subject | ← | 変更不可 |
この文字列値または IAM Identity Center のユーザー属性にマッピング | (なし) | ${user:subject} | |
形式 | unspecified | persistent |
[新規属性マッピングの追加] をクリックする。
[email属性]
パラメータ名 | デフォルト値 | 値 | 備考 |
---|---|---|---|
アプリケーションのユーザー属性 | (なし) | ||
この文字列値または IAM Identity Center のユーザー属性にマッピング | (なし) | ${user:email} | |
形式 | unspecified | basic |
[設定の保存] をクリックする。
ユーザ・グループの割り当て
アプリケーションの詳細画面で [ユーザーを割り当て] をクリックする。
割り当てるユーサーもしくはグループを選択して [ユーザーを割り当て] をクリックする。
ちゃんとユーザーが割り当てられた状態はこちら
4. CognitoのSAML IdPの設定
Cognitoのコンソールから [サインインエクスペリエンス] タブを選択する
[フェデレーテッドアイデンティティプロバイダーのサインイン] から [アイデンティティプロバイダーを追加] をクリックする。
[アイデンティティプロバイダー] で SAML を選択する。
以下の通りに設定
パラメータ名 | デフォルト値 | 値 | 備考 |
---|---|---|---|
プロバイダー名 | (なし) | IdentityCenter | 命名規約に沿っていれば何でもOK |
識別子 - オプション | (なし) | ← | |
サインアウトフロー | □サインアウトフローを追加 | ← | |
メタデータドキュメントのソース | サインアウトフローを追加 | メタデータドキュメントのエンドポイント URL を入力 | |
メタデータドキュメントのエンドポイント URL を入力 | https:// | (長いので表の下に記載) | 先ほどコピーしたURLを入力 |
※メタデータドキュメント URL の例:https://portal.sso.ap-northeast-1.amazonaws.com/saml/metadata/MTQ3OTY3NDM4NTYwX2lucy02YzBjMjU3NTEyYjNjYTYx
[SAML プロバイダーとユーザープールの間で属性をマッピング] で [別の属性を追加] をクリックする
パラメータ名 | デフォルト値 | 値 | 備考 |
---|---|---|---|
ユーザープール属性 | (なし) | ||
SAML 属性 | (なし) |
5. SAMLの動作確認
Cognitoの [アプリケーションの統合] タブの [アプリケーションクライアントのリスト] のリンク(クライアントの名前)をクリックする。
[ホストされたUI] の [編集] をクリックする。
パラメータ名 | デフォルト値 | 値 | 備考 |
---|---|---|---|
ID プロバイダー | Cognito ユーザープール | IdentityCenter |
[変更を保存] をクリックする。
[ホストされたUIを表示] をクリックする。
IdentityCenterの認証画面に遷移した後に以下の画面が表示されれば成功です。
※URLの中にcode=***
がいることがポイントですね。
また、ユーザーが追加されているのでユーザープールを見てみるといいです。
CloudFront Authentication at Edgeのデプロイ(6~8)
6. Serverless Application Repositoryのデプロイ
以下のリンクを踏んでください。
Serverless Application Repositoryのリンク
以下のようなコンソールの画面に遷移します。
[アプリケーションの設定] で以下の通りに設定する。
※記載していないパラメータはデフォルト値を採用する。
パラメータ名 | デフォルト値 | 値 | 備考 |
---|---|---|---|
アプリケーション名 | cloudfront-authorization-at-edge | cloudfront-authorization-at-edge-sso | 任意の名前でOK |
EnableSPAMode | true | false | |
HttpHeaders | (長いので表の下に記載※1) | (長いので表の下に記載※1) | CSPが邪魔しがちなので削除 |
※1:HttpHeadersのデフォルト値と設定値はこちら
{
"Content-Security-Policy": "default-src 'none'; img-src 'self'; script-src 'self' https://code.jquery.com https://stackpath.bootstrapcdn.com; style-src 'self' 'unsafe-inline' https://stackpath.bootstrapcdn.com; object-src 'none'; connect-src 'self' https://*.amazonaws.com https://*.amazoncognito.com",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",
"Referrer-Policy": "same-origin",
"X-XSS-Protection": "1; mode=block",
"X-Frame-Options": "DENY",
"X-Content-Type-Options": "nosniff"
}
{
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",
"Referrer-Policy": "same-origin",
"X-XSS-Protection": "1; mode=block",
"X-Frame-Options": "DENY",
"X-Content-Type-Options": "nosniff"
}
今回の構成上特殊なパラメータ
パラメータ名 | デフォルト値 | 値 | 備考 |
---|---|---|---|
RewritePathWithTrailingSlashToIndex | false | true | 今回のパスの構成上trueが望ましい |
Cognito系のパラメータ
パラメータ名 | デフォルト値 | 値 | 備考 |
---|---|---|---|
UserPoolArn | (なし) | arn:aws:cognito-idp:ap-northeast-1:<account_id>:userpool/ap-northeast-1_Muqcz2zSf |
※2 |
UserPoolClientId | (なし) | 60bb9509t0rqlusma6f704m97v | 設定例を記載 |
※2:設定例を記載。arn:aws:cognito-idp:<region:<account_id>:userpool/<ユーザープールID>
の形式
S3(オリジン)系のパラメータ
パラメータ名 | デフォルト値 | 値 | 備考 |
---|---|---|---|
S3OriginDomainName | (なし) | jimatomo-test-1234.s3.ap-northeast-1.amazonaws.com |
※3 |
※3:設定例を記載。<bucket-name>.s3.<region>.amazonaws.com
の形式
[このアプリがカスタム IAM ロールとリソースポリシーを作成することを承認します。] にチェックを入れて、[デプロイ] をクリックする。
(10分以上放置してCloudformation Stackが作成されることを確認する)
しばらくするとアプリケーションの詳細画面に遷移するので、[デプロイ] タブを開き、[スタックイベントを表示] をクリックするとCloudformationスタックを確認できる
7. CloudFrontのOACの設定
OAIはレガシーなものになってしまったのでOACを利用していきます。
CloudFront側の設定
CloudformationスタックのリソースからCloudFrontのディストリビューションのリンクをクリックする。
[オリジン] タブを開き、オリジン名 [protected-origin] を選択し、[編集] をクリックする。
[オリジンアクセス] で [Origin access control settings (recommended)] を選択する。
[コントロール設定を作成] をクリックして、デフォルトの設定のまま作成する。
[ポリシーをコピー] をクリック [S3 バケットアクセス許可に移動] をクリックしてS3のバケットポリシーの画面が別のタブで開く。
[変更を保存] をクリックする。
S3バケット側の設定
バケットポリシーの [編集] をクリックして、コピーしたポリシーをペーストしたうえで保存する。
8. CloudFront + Lambda@Edge + Cognitoの認証機能の動作確認
1. CloudFrontのURLからのアクセス
Cloudformationスタックの [出力] タブから [WebsiteUrl] をクリックする。
index.htmlが表示されればOK(今回はhello workと表示されます)
2. CognitoのホストされたUIからのアクセス
これもリダイレクトされるので行けます。
3. Identity Centerのアプリケーションのリンクからアクセス
これはまだアクセスできません。残念ながらIdPからのアクセスはできず、SP側から認証をスタートさせないといけないです。一応試してみましょう。
Idetity Centerのポータルページを開き、[Custom SAML 2.0 application]をクリックする。
こんなエラー画面になります。
Idenity CenterのスタートURLの設定と動作確認(9)
ということで、スタートURLを設定していきます。
今度はIdentity Centerのアプリケーションの詳細画面から [アクション] > [設定を編集] をクリックする。
[アプリケーションのプロパティ] から [アプリケーション開始 URL - (オプション)] にCloudFrontの [WebsiteUrl] を入力する(Cloudformationの出力から確認可能)。
[送信] をクリックする。
これにより最初にCloudFrontのURLにアクセスするようになるのでIdentity Centerのポータルからもアクセスできるようになっています。確認してみて下さい。
後片付け(10)
逆順に削除していきます。
- CloudFrontのOACを削除する(ディストリビューションのオリジンアクセスを [public] に戻し、ナビゲーションペインから [オリジンアクセス] を選択し、作成したやつを削除)
- S3にserverless application repositoryが作成した
serverlessrepo-cloudfron-authedgedeploymentbucket-***
バケットを空にしておく - Cloudformationのスタックを削除する(ap-northeast-1で実行した場合はus-east-1二もスタックが残っているので忘れずに削除するのだが、数時間放置しないとLambda@Edgeの関数が削除できないので注意!とりあえず、私の環境は4時間後くらいに再チャレンジしたら削除できました。)
- Identity Centerのアプリケーションを削除する(ユーザーの割り当てを解除しないと削除できない)
- Cognitoユーザープールを削除する([ユーザープールのプロパティ] タブを開き、[削除保護] を非アクティブ化した上で削除する)
- S3バケットを削除する(index.htmlの削除をした上で削除する)
最後に
Serverlessの仕組みはいろいろつなぎ合わせて実装する必要があるので本来考えないといけないことが多いのですが、Serverless Application Repositoryを使うとそんな難しいことないのでありがたいです。
とは言いつつも簡単にできるということは裏側がよくわからないので何が原因でエラーになっているのか切り分けが難しいという欠点もあるなと感じました。(あのエラーは結局再現しなくなっちゃったからもう切り分けも何もないですが…)
これはマネージドサービスを利用する場合に共通する悩みですね。特に可用性が重要なサービスは採用するかどうかで少し慎重になってしまいますよね。
とは言いつつもこの仕組みは頑張ればほぼ無料枠で使えるので改めていい時代だなと思いました。
Discussion