こんにちは、ひるたんぬです。
最近は自分が食べたいと思ったものを、レシピを参考に作ることにハマっています。
レシピを見る際に、必ず「○人前」と書かれていますが、あれは何を基準としているのでしょうか?
調べてみても具体的な基準などは見当たらないのですが、大体乾麺のパスタだと、80〜100gというレシピが多い印象です。
※ ご存知の方がいらっしゃいましたら、何らかの手段や媒体でご教示・発信いただけると幸いです。。。
私はこの量では満足することができないので、「私は人じゃない…?」と疑いかけますが、それは穿った見方ですね。
ただ、「一人前」と言う表現は個人差が大きいと思うので、もっと良い表現があってもいいのになぁ…と思った今日このごろです。
さて、今回はタイトルにもある通り、Amazon CloudFrontとAmazon S3を用いた静的コンテンツ配信基盤において、cognito-at-edgeで特定のパスにのみ認証を設定する方法をご紹介します。
やりたいこと
静的コンテンツを公開する場合、nginxやapacheを利用してWebサーバーを立てて公開する方法もありますが、クラウドネイティブな方法としてCloudFrontとS3を利用する方法が挙げられます。
これらを利用することにより、需要に応じたリソースのスケールや料金負担が実現できるほか、サーバー(インフラ)のメンテナンスを自身で行う必要がなくなるなど、コンテンツ配信の事業者の方々にとっても大きなメリットがあります。
一方、コンテンツの中には、特定のユーザーに公開を絞りたいケースもあるかと思います。
今回は、一つのサイト(ドメイン)の中で、全体に公開したいコンテンツと、公開を限定したいコンテンツを制御したいという事例を考えてみます。
使うもの
配信基盤として用いるものはAmazon CloudFrontとAmazon S3です。
また、認証にはAmazon Cognitoを利用し、CloudFrontと認証機能を連携させる手段としてLambda@Edgeを利用します。
具体的なLambda@Edge内の処理プログラムについては、AWS (AWS Labs)より提供されているcognito-at-edgeを利用します。
事前準備・確認
まずは、特定のパスに限定させず、全てのコンテンツに対して公開を制限したいと思います。
コンテンツ格納用バケットの用意
コンテンツを格納するためのS3バケットを用意します。今回は東京リージョンにデプロイしたいと思います。
バケット名以外の設定はデフォルトで問題ありません。(今回は「202509-web-contents-bucket」を作成しました。)
作成が終わったら、何かしらコンテンツを格納しておきましょう。
こういうときに生成AIを使うとサクッと作れていいですね。
Cognitoのデプロイ
Cognitoをデプロイします。今回は東京リージョンにデプロイしたいと思います。
CloudFormationより、以下のテンプレートをデプロイします。
パラメータのCognitoCallbackURLについては、ひとまずこのままでもOKです。
AWSTemplateFormatVersion: 2010-09-09 Description: Create Cognito. Please deploy at ap-northeast-1 region. # Parameters Parameters: ## CognitoコールバックURL CognitoCallbackURL: ### cognito-at-edgeの仕様上、最後は"/"をつけない Description: Cognito callback URL Type: String Default: https://example.com # Resources Resources: # Cognito関連リソース ## Cognito User Pool CognitoUserPool: Type: AWS::Cognito::UserPool Properties: UserPoolName: pathauth-cognitouserpool UserPoolTier: PLUS ### Cognitoのプラン DeletionProtection: INACTIVE ### 検証のため(削除できるように) UserPoolTags: Name: pathauth-cognitouserpool ## Cognito User Pool Client CognitoUserPoolClient: Type: AWS::Cognito::UserPoolClient Properties: ClientName: pathauth-cognitouserpool-client UserPoolId: !Ref CognitoUserPool GenerateSecret: false AllowedOAuthFlowsUserPoolClient: true AllowedOAuthScopes: - openid AllowedOAuthFlows: - code CallbackURLs: - !Ref CognitoCallbackURL SupportedIdentityProviders: - COGNITO ExplicitAuthFlows: - ALLOW_USER_SRP_AUTH ## Cognito User Pool Domain CognitoUserPoolDomain: Type: AWS::Cognito::UserPoolDomain Properties: UserPoolId: !Ref CognitoUserPool Domain: pathauth-userpool-domain ManagedLoginVersion: 2 ## Cognito Managed Login Page CognitoManagedLogin: Type: AWS::Cognito::ManagedLoginBranding Properties: UserPoolId: !Ref CognitoUserPool ClientId: !Ref CognitoUserPoolClient UseCognitoProvidedValues: true # Outputs Outputs: CognitoUserPoolId: Description: Cognito User Pool ID Value: !Ref CognitoUserPool CognitoUserPoolClientId: Description: Cognito User Pool Client ID Value: !Ref CognitoUserPoolClient CognitoUserPoolDomain: Description: Cognito User Pool Domain Value: !Sub "pathauth-userpool-domain.auth.${AWS::Region}.amazoncognito.com"
Lambda@Edgeのコード準備
続いて、Lambda@Edgeにデプロイするためのcognito-at-edgeを準備します。
事前にバージニア北部リージョンに、完成したコードを格納するためのバケットを用意しておきます。バケット名以外の設定はデフォルトで問題ありません。(今回は「202509-cognito-at-edge-bucket」を作成しました。)
続いて、任意のコードエディタなどで、以下のファイルを作成し、「index.js」という名前で保存します。
userPoolId、userPoolAppId、userPoolDomainについては、先ほど作成したスタックの出力を参照してください。
const { Authenticator } = require('cognito-at-edge'); const authenticator = new Authenticator({ // Replace these parameter values with those of your own environment region: 'ap-northeast-1', // user pool region userPoolId: 'ap-northeast-1_abcdefgh1', // user pool ID userPoolAppId: '1examp1ec1ient1d', // user pool app client ID userPoolDomain: 'pathauth-userpool-domain.auth.ap-northeast-1.amazoncognito.com', // user pool domain }); exports.handler = async (request) => authenticator.handle(request);
作成が完了したらCloudShellで以下のコマンドを実行します。
[ ]内については適宜作業・ご自身の環境に置き換えてください。
[index.jsをアップロード] mkdir cognito-at-edge cd cognito-at-edge npm install cognito-at-edge mv ~/index.js ./ npx esbuild --bundle index.js --minify --outfile=bundle/index.js --platform=node cd bundle zip -r lambda-edge-auth.zip ./index.js aws s3 cp lambda-edge-auth.zip s3://[宛先S3バケット名]/lambda-edge-auth.zipさ
最終的にS3に「lambda-edge-auth.zip」というファイルがアップロードされていればOKです。
CloudFront・Lambda@Edgeのデプロイ
Cognitoと同じようにCloudFormationでデプロイします。
バージニア北部リージョンでデプロイするようにしてください。
パラメータについては、それぞれ作成したバケット名、Lambda@Edgeで用いるコードのファイル名(上記に従っていればlambda-edge-auth.zipになっているはずです。)を入力してください。
AWSTemplateFormatVersion: 2010-09-09 Description: Create CloudFront and Lambda@Edge. Please deploy at us-east-1 region. Transform: AWS::Serverless-2016-10-31 # Mappings Mappings: # CachePolicyIdは以下の記事を参照 # https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/using-managed-cache-policies.html CachePolicyIds: # Recommended for S3 CachingOptimized: Id: 658327ea-f89d-4fab-a63d-7e88639e58f6 # Parameters Parameters: # コンテンツの格納先(S3バケット名) OriginS3Bucket: Description: S3 bucket for contents Type: String Default: s3-bucket-name-for-contents # Lambda@Edgeのコード格納先(S3バケット名) LambdaEdgeCodeS3Bucket: Description: S3 bucket for Lambda@Edge code Type: String Default: s3-bucket-name-for-cognito-at-edge # Lambda@Edgeのコード格納先(S3キー名) LambdaEdgeCodeS3Key: Description: S3 key for Lambda@Edge code Type: String Default: lambda-edge-auth.zip # Resources Resources: # CloudFront周辺リソース ## CloudFront Distribution CloudFrontDistribution: Type: AWS::CloudFront::Distribution Properties: DistributionConfig: Origins: - Id: "S3Origin" DomainName: !Sub "${OriginS3Bucket}.s3.ap-northeast-1.amazonaws.com" S3OriginConfig: OriginAccessIdentity: "" OriginAccessControlId: !Ref CloudFrontOriginAccessControl DefaultCacheBehavior: TargetOriginId: "S3Origin" ViewerProtocolPolicy: redirect-to-https # Recommended for S3 CachePolicyId: !FindInMap [CachePolicyIds, CachingOptimized, Id] # cognito-at-edge LambdaFunctionAssociations: - EventType: viewer-request LambdaFunctionARN: !Ref LambdaEdgeFunctionForAuth.Version Enabled: true IPV6Enabled: false Tags: - Key: Name Value: !Sub "pathauth-cloudfront" ## CloudFront OriginAccessControl CloudFrontOriginAccessControl: Type: AWS::CloudFront::OriginAccessControl Properties: OriginAccessControlConfig: Description: "Origin Access Control For S3 Static Website Hosting" Name: !Sub "pathauth-s3oac" OriginAccessControlOriginType: s3 SigningBehavior: always SigningProtocol: sigv4 # Lambda@Edge周辺リソース ## Lambda@Edge Function for authentication LambdaEdgeFunctionForAuth: Type: AWS::Serverless::Function Properties: FunctionName: !Sub "pathauth-lambdaedge-auth" AutoPublishAlias: pathauth Handler: index.handler Runtime: nodejs22.x MemorySize: 128 Timeout: 5 CodeUri: Bucket: !Ref LambdaEdgeCodeS3Bucket Key: !Ref LambdaEdgeCodeS3Key Role: !GetAtt RoleForLambdaEdge.Arn ## CloudWatch Log Group for Lambda@Edge ### 実際の実行ログは各エッジロケーションに作成される LogGroupForLambdaEdge: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub "/aws/lambda/${LambdaEdgeFunctionForAuth}" RetentionInDays: 30 ## IAM Role for Lambda@Edge RoleForLambdaEdge: Type: AWS::IAM::Role Properties: RoleName: cognito-at-edge-role AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com - edgelambda.amazonaws.com Action: sts:AssumeRole Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole - !Ref PolicyForLambdaEdge ## IAM Policy for Lambda@Edge PolicyForLambdaEdge: Type: AWS::IAM::ManagedPolicy Properties: ManagedPolicyName: cognito-at-edge-policy Description: Policy for Lambda@Edge Function Path: / PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - iam:CreateServiceLinkedRole Resource: "*" - Effect: Allow Action: - lambda:GetFunction - lambda:EnableReplication Resource: ### Allow access to all Lambda functions in the account at the specified region(us-east-1) - !Sub "arn:aws:lambda:us-east-1:${AWS::AccountId}:function:*:*" - Effect: Allow Action: - cloudfront:UpdateDistribution Resource: ### Allow access to all CloudFront distributions in the account - !Sub "arn:aws:cloudfront::${AWS::AccountId}:distribution/*"
コンテンツバケットのバケットポリシー設定
コンテンツバケットにCloudFrontからのアクセス(OAC)を許可します。
まずは、CloudFrontの当該ディストリビューションを開き、「オリジン」タブを選択します。
「S3Origin」を選択し、「編集」を押下します。
画面中段にあるOACに関する設定項目から、「ポリシーをコピー」を押下し、その下にある「S3 バケットアクセス許可に移動」を押下します。
すると、コンテンツバケットのページが開くので、「アクセス許可」からバケットポリシーを変更します。
CognitoのコールバックURL変更
今回は検証のため、CloudFrontのデフォルトドメイン(*.cloudfront.net)を使用します。
それに対応するよう、CognitoのコールバックURLを変更します。今回はコンソールより手動で変更します。
Cognitoのユーザープールを開いたら、「アプリケーションクライアント」から作成したアプリケーションクライアント(pathauth-cognitouserpool-client)を開きます。
次に、「ログインページ」タブを押下し、「マネージドログインページの設定」を編集します。
編集画面の一番上にある「許可されているコールバックURL」に、CloudFrontのURLを貼り付けます。
動作確認
では、きちんと認証機能が働くか確認をします。
任意のWebブラウザを開き、CloudFrontのドメイン名と、表示させたいコンテンツのパス(例:examp1edoma1n.cloudfront.net.index.html)を入力しアクセスします。
きちんとサインインページに遷移しましたね。実際にユーザー名などを入力しサインインすると…
コンテンツがしっかり表示されました。
検証
…ここからが本番です。
特定のパスのみ認証を設定していきます。今回は以下のようなフォルダ構成を仮定し、limited配下のみ公開に制限をかけます。
コンテンツバケット ├ limited/ │ ├ index.html │ └ style.css ├ index.html └ style.css
CloudFrontディストリビューションのビヘイビア編集
CloudFrontのコンソール画面から「ビヘイビア」タブを選択し、「ビヘイビアの作成」を押下します。
パスパターンに制限をかけたいパスにアスタリスクを追加(/limited*)、オリジンには宛先のS3オリジン(S3Origin)を選択し、他の箇所はデフォルトで「Create behavior」を押下します。
次に、デフォルトビヘイビア(*)を編集します。編集画面の一番下、「関数の関連付け」を「関連付けなし」に設定します。
CognitoのコールバックURL編集
CognitoのコールバックURLをパスつきのURLに変更します。
詳細な手順は事前準備のときとほとんど同じなので省略しますが、以下のようになっていればOKです。
Lambda@Edgeのコード編集
Lambda@Edgeのコードを特定のパスに対してのみ認証できるよう変更します。
先ほど作成したindex.jsに一部追記をします。
const { Authenticator } = require('cognito-at-edge'); const authenticator = new Authenticator({ // Replace these parameter values with those of your own environment region: 'ap-northeast-1', // user pool region userPoolId: 'ap-northeast-1_abcdefgh1', // user pool ID userPoolAppId: '1examp1ec1ient1d', // user pool app client ID userPoolDomain: 'pathauth-userpool-domain.auth.ap-northeast-1.amazoncognito.com', // user pool domain parseAuthPath: '/limited', // 追記① cookiePath: '/limited' // 追記② }); exports.handler = async (request) => authenticator.handle(request);
追記が終わったら先程と同じようにCloudShellからzip化し、S3にアップロードします。名前は変えておくと分かりやすいかと思います。今回は「lambda-edge-auth-path.zip」としました。
Lambda@Edgeの更新
作成したコードでLambda@Edgeを更新します。今回はコンソールより更新します。
まず、先程アップロードしたS3バケットから、当該コンテンツのオブジェクトURLをコピーします。
次にLambdaのコンソールから、認証に用いているLambda(pathauth-lambdaedge-auth)を探し開きます。
コード編集画面の右上にある「アップロード元」から「Amazon S3の場所」を選択し、先程コピーしたURLを貼り付けます。
関数の更新が完了したら、Lambda@Edgeにデプロイします。
右上の「アクション」から「Lambda@Edgeへのデプロイ」を選択します。
ディストリビューションは作成したもの、キャッシュ動作には先程作成したビヘイビア(/limited*)を選択します。CloudFrontイベントについては「ビューアーリクエスト」に変更してください。
最後に一番下の「Lambda@Edge へのデプロイを確認」に✅️を入れてデプロイをします。
以上で変更作業は完了です。
動作確認
早速想定の挙動になるか見ていきます。
まずは、公開されているコンテンツ(ルートのindex.html)にアクセスします。
問題なく表示されました。認証もありません。
では、次に限定コンテンツ(/limited/index.html)にアクセスします。
きちんと認証画面に移りましたね!良かったです。認証情報を入れてサインインしてみると…
いかにも特別感のあるページが表示されました。ありがとう、チャッピー。
終わりに
事前準備が少し多かったので長くなってしまいましたが、パスごとの認証は思ったよりもシンプルな手順でできたので良かったです。
本記事の内容が、どなたかの参考になることを願っております。
余談ですが、先日生まれて初めてオイルマッサージなるものを体験してきました。
担当の方に「全身ボロボロですね…」と遠回しに言われ少し傷ついたので、折を見て通おうかなと思ったりしています。