はじめに
本日は Cognito ユーザープールを API Gateway のオーソライザーとして使ってみようと思います。
Cognito ユーザープール とは
以下のエントリーをご参照ください。
オーソライザーを使うメリット
API Gatewayのエンドポイントはグローバルに公開されているおり、誰でもアクセス可能です。IPアドレスなどでアクセスを制御することもできますが、認証されたユーザだけがアクセス可能な API としてデプロイすることでセキュアな状態を維持することができます。
今回のゴール
Cognito 経由で REST API を使って S3 のバケット一覧を取得します
アーキテクチャ
手順
1. ユーザープールの作成
以下の設定でユーザープールを作成します。
項目 | 値 |
---|---|
プロバイダーのタイプ | Cognito ユーザープール |
Cognito ユーザープールのサインインオプション | E メール |
パスワードポリシーモード | Cognito のデフォルト |
多要素認証 | MFAなし |
ユーザーアカウントの復旧 | デフォルト |
セルフサービスのサインアップ | デフォルト |
属性検証とユーザーアカウントの確認 | デフォルト |
属性変更の確認 | デフォルト |
必須の属性 | |
属性変更の確認 | デフォルト |
E メール | Cognito で E メールを送信 |
ユーザープール名 | 任意のユーザープール名 |
ホストされた認証ページ | デフォルト |
アプリケーションタイプ | 秘密クライアント |
ホストされた認証ページ | デフォルト |
アプリケーションクライアント名 | 任意のアプリケーションクライアント名 |
クライアントのシークレット | クライアントのシークレットを生成しない |
高度なアプリケーションクライアントの設定 | デフォルト |
属性の読み取りおよび書き込み許可 | デフォルト |
2. ユーザ登録
以下のようにユーザを作成します。
3. IDトークンの取得
このIDトークンを利用して API Gateway へアクセスしますので事前に取得します。
import boto3 import json # CognitoIdentityProviderクライアント(Using Amazon Cognito user pools API) idp_client = boto3.client('cognito-idp') response = idp_client.admin_initiate_auth( UserPoolId = "xxx", ClientId = "xxx", AuthFlow = "ADMIN_NO_SRP_AUTH", AuthParameters={ "USERNAME": "xxx", "PASSWORD": "xxx", } ) print(json.dumps(response, indent=2))
以下のようなイメージでIDトークンが取得できます(以下は値を加工しています)
{ "ChallengeParameters": {}, "AuthenticationResult": { "AccessToken": "eyJrlWcQT0_LDihkbBhWf_zaddMc6k1JEcd1hGlsyOV3D4n2qClnxlFaTDTFXZvM8gco8QX0W0_ib_Ium_4wnORiUAQQeoQK6hEZruY7IJ7EGfkTictJyQl5iOdxCnk7TYA", "ExpiresIn": 3600, "TokenType": "Bearer", "RefreshToken": "eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAifQ.Io5wUP2Aw9ZTCnxO_xmzG-q6riAKfpdhmTLjnT6BGtRH2fowrNIg7XjueMFjztVlkXGqRr2zG1HJLe4azmomdJe1d7_-N61zgiBYG9MZI_1sF8Z", "IdToken": "eyJraWQiOiJiV3hrV3FOZ2lCdSsrZDJLWGwxa0JVdjlCSUVjRHZuUUg4YmlaTklSQWVJPSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiIwNTYyNDVkOS03MzIwLTQzZDgtYWRiNC1kMzY3NDEyNGVjMzciLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiaX99rEU6uHLA" }, "ResponseMetadata": { "RequestId": "ed94d428-8120-4838-9429-49320925d7e6", "HTTPStatusCode": 200, "HTTPHeaders": { "date": "Sat, 19 Nov 2022 05:14:08 GMT", "content-type": "application/x-amz-json-1.1", "content-length": "4132", "connection": "keep-alive", "x-amzn-requestid": "ed94d428-8120-4838-9429-49320925d7e6" }, "RetryAttempts": 0 } }
4. IAMロール 作成
「Lambda-S3Access」という AWS Systems Manager への権限を付与したロールを作成します。
5. Lambda 関数の作成
以下のように Lambda 関数を作成します。
項目 | 値 |
---|---|
オプション | 一から作成 |
関数名] | get-s3-bucket-list |
ランタイム] | Python 3.9 |
アーキテクチャ] | 86_64 |
既存のロール]] | Lambda-S3Access |
ソースコード
S3 のバケット一覧を取得します。
import json import boto3 s3 = boto3.resource('s3') def lambda_handler(event, context): bucket_list = [] for bucket in s3.buckets.all(): bucket_list.append(bucket.name) return bucket_list
6. API Gateway作成
項目 | 値 |
---|---|
API タイプを選択 | REST API |
プロトコルを選択する] | REST |
新しい API の作成 ] | 新しい API |
API 名 | cognito-authorizer |
説明 | 任意 |
エンドポイントタイプ | デフォルト |
API 作成後
リソースを作成します。
「5. Lambda関数の作成」で作成した関数名を入力しメソッドを作成します。
Lambda 関数に自動的に API Gateway のトリガーが設定されます。
API をデプロイします。
デプロイ後にエンドポイントの URLが表示されます。
以下のようにオーソライザーを作成します。
「認可トークン」項目にIDトークンをコピーしてテストすると正常終了します。
リソース > メソッドリクエストを選択します。
作成したオーソライザーを選択し更新します。
再度API をデプロイします。
7. 動作確認
IDトークンなし
以下のように REST API を実行します。
curl https://m158t0jime.execute-api.ap-northeast-1.amazonaws.com/development
認証されていない旨のメッセージが返されました。
{"message":"Missing Authentication Token"}
IDトークンあり
以下のように REST API を実行します。(IDトークンは加工しています)
curl https://m158t0jime.execute-api.ap-northeast-1.amazonaws.com/development -H "Authorization: eyJraWQiOiJiV3hrV3"
S3 のバケット一覧を取得することができました。
["20220514-cloudfront-test", "20220521-web-hosting", "20220807-sqs-test", "20220809-endpoint-gateway", "20220809-vpc-endpoint", "20220810-s3-gateway-endpoint", "20220810-test-bucket", "20220812-rekognition-test", "aws-athena-query-results-370560102364-ap-northeast-1", "aws-cloudtrail-logs-370560102364-44d0a6db", "aws-presigned-url", "cf-templates-lxrja6wfp65n-ap-northeast-1", "data-lake-20210331", "log-from-kinesis-sanvarie", "test-queue-sanvarie"]
さいごに
少しとっつきにくい Cognito ですが、使いこなせば非常に便利なサービスですね。まだまだ触りのところしかわからないので引き続き勉強しようと思います。