AWS上の静的WebサイトにBasic認証(パスワード認証)をかける(S3+CloudFront+Lambda@Edge)

どうも、Tです。

前回の記事で、AWSのS3+CloudFrontを使って静的Webサイトを公開することができました。

AWSで独自ドメインのhttpsな静的Webサイトを構築する(ACM+S3+CloudFront+Route53)
どうも、Tです。 静的なWebサイト(HTML+CSS+JavaScript)を構築する必要があり、AWSで公開することにした...

正式な公開時期までWebを公開しない方法を探したところ、AWSサービスではIPアドレスによる制限、もしくはBasic認証(ユーザー・パスワードによる認証)ができるようです。

今回は、よく使われるであろうBasic認証を試してみました。なお、Basic認証は、CloudFrontだけではできないので、Lambada@Edgeを使っての実装になります。

やりたいこと

やることは非常にシンプルです。アクセスしたユーザーのWebブラウザにユーザー名とパスワード認証のダイアログボックスを表示させて認証できたら、Webサイトが表示させたいです。

使うサービス

Lambda@Edgeとは

Lambda@Edgeという名前のサービスがあるわけでなく、サービスとしてはLambdaです。

LambdaとCloudFrontエッジのイベントによってトリガーして使う形態をLambda@Edgeの位置づけになるようです。

Lambda@Edge を使用してエッジでカスタマイズする - Amazon CloudFront
CloudFront ディストリビューションで Lambda@Edge を使用して、エッジでコンテンツをカスタマイズします。

Node.js および Python の Lambda 関数を実行して CloudFront が発信するコンテンツをカスタマイズし、ビューワーに近い AWS 地域でこの関数を実行できます。この関数は、プロビジョニングや管理の必要なく、CloudFront イベントに応答を実行します。Lambda 関数を使用して、次の時点で CloudFront リクエストとレスポンスを変更できます。

Lambda@Edgeのできること

Lambda@Edgeは、CloudFrontと連携して動くLambdaです。そのタイミング(トリガー)として下記があります。

下記にわかりやすいく書いてくれてました。

【速報】AWS Lambda@Edgeが発表されました! #reinvent | DevelopersIO

Viewer Request
クライアントがCloudFrontにHTTP(S)でアクセスし、そのリクエストがエッジロケーションに到達した時に発生するイベント

Viewer Response
リクエストをしたクライアントへエッジロケーションからレスポンスを返す際に発生するイベント

Origin Request
エッジロケーションにキャッシュが残っていない状態で、クライアントからのリクエストがオリジンに送信される際に発生するイベント

Origin Response
オリジンからエッジロケーションにレスポンスがある場合に発生するイベント

Basic認証の際は、クライアント(Webブラウザ)がアクセスしてエッジロケーション(CloudFront)に届いたときなので、Viewer Requestを利用します。

Lambda@Edgの制限

Lambda@Edgeを利用する上ので、制限事項が色々ありました。

エッジ関数に対する制限 - Amazon CloudFront
CloudFront Functions と Lambda@Edge は、以下の制限の対象となります。

数年前の記事など見比べると、内容は多少変わっているようですが今回の構成でハマった部分を書き出しておきます。(2020年7月現在)

トリガーを追加できるのは番号付きバージョンのみです。$LATEST やエイリアスには追加できません。

Lambdaで関数を作成したときに、バージョンを付けないとエラーができました・・・。

トリガーを追加できるのは、米国東部(バージニア北部) リージョンの関数のみです。

Lambdaのリージョンをバージニア北部にする必要があります。

トリガーを追加するには、Lambda 関数に関連付けられている IAM 実行ロールをサービスプリンシパルの lambda.amazonaws.com および edgelambda.amazonaws.com が引き受けられる必要があります。

ロール作成時に手動でプリシンパルを追加する必要がありました。

他にもLambda@Edgeを使う場合に注意が色々書かれているので目を通しておくことをお勧めします。

Lambda@Edgeの料金

サービスとしてはLambdaになるので、領域んはLambdaの料金になります。

料金 - AWS Lambda |AWS
AWS Lambda では、使用した分の料金が発生します。料金は、関数に対するリクエストの数とコードの実行時間に応じて請求されます。

Lambdaは、無料枠として1 か月ごとに 100 万件の無料リクエスト、および 40 万 GB-秒のコンピューティング時間があります。

これは1年間の無料枠ではなく無期限の無料枠になります。

超過分は、下記になります。

事前準備

S3+CloudFrontの静的Webサイトホスティングは行ている前提で記載しています。

設定方法

設定の流れ

以下の流れで進めていきます。

  1. Lambda@Edge用のロール作成
  2. Lambadaの設定
  3. CloudFrontにLambda関数の関連付け

Lambda@Edge用のロール作成

Lambdaを実行するためのロールを作成します。

サービスからIAMをクリックします。

「アクセス管理」->「ロール」->「ロールの作成」をクリックします。

AWSサービスから「Lambda」をクリックします。

「次のステック:アクセス権限」をクリックします。

ポリシーのフィルタにAWSLambdaを入力して、「AWSLambdaBasicExecutionRole」にチェックを入れて、「次のステップ:タグ」をクリックします。

「次のステップ:確認」をクリックします。

「ロール名」に任意の名前を付けて「ロールの作成」をクリックします。

Lambda実行用のロールが作成されました。作成したロールをクリックします。

Lambda@Edgeを利用するためのサービスプリシンパルの追加します。

「信頼関係」タブの「信頼関係の編集」をクリックします。

下は編集前の状態です。Serviceエレメントを編集していきます。

下は、編集後の状態です。「edgelambda.amazonaws.com」を追加しています。

Serviceエレメントの中に[]をつけ足しているので注意してください。

編集後、「信頼ポリシーの更新」をクリックします。

信頼されたエンティティに「edgelambda.amazonaws.com」が追加されていることを確認します。

Lambda関数の準備

Lambdaで実行するBasic認証を行うための関数を準備します。

色々みなさん作られているので、今回は下記を利用させていただきました。

Basic HTTP Authentication for CloudFront with Lambda@Edge
Basic HTTP Authentication for CloudFront with Lambda@Edge - lambda-basic-auth.js

アクセスして「Donwload ZIP」をクリックしてダウンロードします。

ZIPファイルを展開して「lambda-basic-auth.js」ファイルがあることを確認します。

Lambdaの設定

Lambdaの関数を作成していきます。

Lambdaサービスを開いて、リージョンをバージニア北部にします。

「一から作成」から基本的な情報を設定し、右下の「関数の作成」をクリックします。

関数名:任意の関数名を設定

ランタイム:Node.js12.x

実行ロール:既存のロールを使用する

既存のロール:Role_Lambda@Edge(作成したロールを選択)

関数が作成されました。スクロールして画面を下に移動していきます。

関数コードのindex.jsがデフォルトで作成されています。中身を全部消します。

ファイル名をindex.js以外で作成すると後で追加で設定が必要なので注意ください。

ダウンロードした「lambda-basic-auth.js」をテキストエディタで開きすべてコピーして、index.jsの中にペーストします。authUserの「user」とauthPassの「pass」をBasic認証するユーザー名とパスワードになるので修正して、「Save」をクリックします。

さらにスクロールすると基本設定があります。index.jsを名前を変えている場合、ハンドラの設定を<変更した名前>.handlerに変更してください。

「保存」をクリックします。

Lambda関数をCloudFront関連付け

Lambdaから紐づけ操作

引き続きLambdaの画面から設定を続けます。作ったLambda関数をCloudFrontにデプロイしていきます。

アクションから「Lambda@Edgeへのデプロイ」をクリックします。

Lambda関数をバージニア北部以外のリージョンで作成してる場合、「Lambda@Edgeへのデプロイ」メニューが表示されません。

下記の設定を行って、「デプロイ」をクリックします。

ディストリビューション:デプロイするCloudFrontのディストリビューション

CloudFrontイベント:ビューアーリクエスト

Lambda@Edgeへのデプロイを確認:チェックする

デプロイされるとデザイナー画面で作成したLambda関数にCloudFrontが紐づきました。

また、関数に:1が付与されました。Lambda@Edge利用時は、番号付きバージョンが必要という要件がありましたが、自動で番号を付けてくれています。

右上のARNは、この後の確認に使うためコピーしておきます。

Lambda関数のバージョン管理は、バージョンのリストから参照することができます。

CloudFrontで紐づけの確認

CloudFron側からLambda@Edgeの設定を確認しておきます。

CloudFrontサービスを開き、設定したディストリビューションをクリックします。

「Behaiviors」タブを開き、対象のOriginにチェックを付けて「Edit」をクリックします。

画面したのLambda Function AssociationsのViwer Requestで先ほどLambdaで確認したARNがLambda Function ARNに設定されていることが確認できます。

ここの設定は手動でもできますが、LambdaでバージョンのついていないARNを設定すると下記のようなエラーが発生します。

com.amazonaws.services.cloudfront.model.InvalidLambdaFunctionAssociationException: The function ARN must reference a specific function version. (The ARN must end with the version number.) ARN: arn:aws:lambda:us-east-1:xxxxxxxxxxxxxxxxxxxxxxxxxxxx:function:lambda_CloudFront_Basic-auth (Service: AmazonCloudFront; Status Code: 400; Error Code: InvalidLambdaFunctionAssociation; Request ID: xxxxxxxxxxxxxxxxxxxxxxxxxxxx; Proxy: null)

動作確認

設定が完了したので、CroudFrontで設定しているURLにアクセスすると下記のようにBasic認証のダイアログボックスが表示され、設定したユーザー名・パスワードを入力するとWebサイトが表示されます。

ちょっとはまったところ

Lambda@Edgeの関数が削除できない

作成したLambda関数は、CroudFrontから設定を消すと削除できるはずですが、下記のエラーが発生しました。

関数の削除時にエラー Lambda was unable to delete arn:aws:lambda:us-east-1:xxxxxxxxxxx:function:test1:1 because it is a replicated function. Please see our documentation for Deleting Lambda@Edge Functions and Replicas. が発生しました。

これは、Lambda@Edgeの仕様のようで、CloudFrontから設定を外した後も数時間は削除できないようです。CloudFrontから紐づけ設定を削除後に、1時間ほど放置した後にLambda関数を手動で削除することができました。

Lambda@Edge 関数とレプリカを削除する - Amazon CloudFront
Lambda@Edge 関数のレプリカを削除し、CloudFront から関数の関連付けを削除する方法について説明します。

すべての CloudFront ディストリビューションから関数の最後の関連付けを削除した後。複数のディストリビューションで関数が使用されている場合、最後のディストリビューションから関数の関連付けを削除した後にのみ、レプリカが削除されます。

関数が関連付けられた最後のディストリビューションを削除した後。

レプリカは通常、数時間以内に削除されます。Lambda@Edge 関数のレプリカを手動で削除することはできません。これにより、まだ使用中のレプリカが削除され、エラーが発生する状況を防ぐことができます。

Lambada@Edgeを適用した直後のWebページ参照でエラーがでる

CloudFrontと紐づけた後に、すぐにアクセスすると50xのエラーで接続できませんでした。

※細かいステータス番号取得し忘れた・・・・。

テスト時に1回だけ発生したのですが、Lambda@Edge関数のレプリカ配置に時間がかかっているかと思うので、CloudFront紐づけ後、数分程度経過後に再度試すと正常に接続されてBasic認証のダイアログボックスが表示されました。

参考

Lambda@Edge で CloudFront に Basic 認証をかける | クロジカ
## Lambda@Edge とは CloudFront へのリクエスト時に Lambda 関数を実行して何か
https://tech.recruit-mp.co.jp/infrastructure/post-16386/
S3+CloudFrontによる静的サイトにLambda@Edgeを設定(1)
S3+CloudFrontといった構成で、静的Webサイトを構築した場合、通常のWebサーバーなら簡単に利用できる、URL(URI)の書き換えやBasic認証といった機能が、そのままでは利用できません。今回は、Lambda@Edgeを使って、URLの書き換えを試してみました。

まとめ

やっぱりAWSのサービスは、最初の理解までに時間を要しますなぁ・・・・。