本日も乙

ただの自己満足な備忘録。

S3でサーバレスなBasic認証を実現する(CloudFront + Lambda@Edgeの活用)

S3バケットに配置しているオブジェクトファイルに対してBasic認証をかけたい場合があると思います。しかし、S3自体にBasic認証機能は提供されていません。そのため、今まではリバースプロキシとしてEC2を前段に置くことで実現していました。

最近、Lambda@Edgeという機能がGAリリースされました。この機能を使うことで、サーバレスにBasic認証をかけることができるため、その方法について紹介します。

[toc]

Lambda@Edge とは

cloudfront-events-that-trigger-lambda-functions

CloudFrontのイベントに対してLambda関数を実行できる機能です。Lambda@Edgeが実行できるCloudFrontのイベントは、以下の4つがあります。

  • CloudFrontがエンドユーザからHTTPリクエストを受信(ビューワーリクエスト)
  • CloudFrontが受け取ったHTTPリクエストをオリジンサーバに転送する前(オリジンリクエスト)
  • CloudFrontがオリジンサーバからレスポンスを受信(オリジンレスポンス)
  • CloudFrontがエンドユーザにレスポンスを返す前(ビューワーレスポンス)

Lambda@Edgeのユースケースとして以下の用途がドキュメントに書かれていました(一部抜粋)。

  • A/B テスト用に、異なるバージョンのサイトに URL を書き換えるために Cookie を検査します
  • リクエストを送信したデバイスに関する情報が含まれている、User-Agent ヘッダーに基づいてユーザーにさまざまなオブジェクトを送信します。たとえば、ユーザーのデバイスに応じて、さまざまな解像度のイメージをユーザーに送信できます。
  • ヘッダーまたは認証トークンを検査し、対応するヘッダーを挿入して、リクエストをオリジンに転送する前にアクセス制御を有効にします。
  • ヘッダーの追加、削除、変更、および URL パスの書き換えを行い、キャッシュの異なるオブジェクトにユーザーをダイレクトします。
  • 未認証ユーザーをログインページにリダイレクトしたり、エッジから直に静的ウェブページを作成して配信したりといったことを行う新しい HTTP レスポンスを生成します。
AWS Lambda@Edge - AWS Lambda

構成

以下のサービスを組み合わせて実現させます。

  • S3
    • 取りに行きたいファイルを置く
  • CloudFront
    • Lambda@Edgeとの連携
    • キャッシュ、TTLは無効化する
  • Lambda@Edge
    • Basic認証を設定(Authorizationヘッダを追加する)

S3

S3バケットの作成を行います。今回はマネジメントコンソールから行いました。 CloudFrontとの連携のためにWebホスティングを有効化します。 s3_basic_1.png

IAM

Lambda@Edge用のIAMロールを作成します。 lambda_edge_exection という名前で作成します。

信頼関係(Trust Relationship)は(元々の)LambdaとLambda@Edgeの両方を追加します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": [
          "lambda.amazonaws.com",
          "edgelambda.amazonaws.com"
        ]
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

インラインポリシーは以下のようにします。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "lambda:GetFunction",
                "lambda:EnableReplication*",
                "iam:CreateServiceLinkedRole",
                "cloudfront:CreateDistribution",
                "cloudfront:UpdateDistribution"
            ],
            "Resource": "*"
        }
    ]
}

Lambda

Lambda@Edgeの関数はバージニアリージョンで行う必要があります。 Lambdaのページに飛んだら右上のリージョン一覧から「米国頭部(バージニア北部)」を選択します。

s3_basic_2.png

関数の作成

「関数の作成」->「一から作成」を選択、以下の図のように作成します。 関数名を「S3BasicAuthentication」にし、ロールは先ほど作成したもの(lambda_edge_execution)を選択します。

s3_basic_3.png

関数のコードは以下を参考にしました。ランタイムは「Node.js 6.10」にします。

Basic HTTP Authentication for CloudFront with Lambda@Edge - Gist

注意点として、「基本設定」でメモリ(MB)を「128(MB)」、タイムアウトを「1(秒)」にします。

CloudFront

CloudFrontのページから「Distributions」->「Create Distributions」をクリックします。 「Select a delivery method for your content.」で「Web」を選択します。

s3_basic_4.png

以下の図のように設定していきます。

s3_basic_5.png

s3_basic_6.png

「Create Distribution」をクリックすると、作成されます。

s3_basic_7.png

エラーページのキャッシュ無効化

先ほど作成したDistributionを選択し、「Error Pages」タブをクリック、「Create Custom Error Response」をクリックします。

s3_basic_8.png

s3_basic_9.png

今回は400、403、404で設定してみました。

s3_basic_10.png

CloudFrontとLambda@Edgeの連携

Lambdaの画面に戻ります。 Lambda@Edgeでは指定されたバージョンに対して有効にする必要があります($LATESTは指定できない)。 「アクション」から「新しいバージョンを発行」をクリックします。

s3_basic_11.png

「バージョンの説明」には任意の説明(空でも良い)を入力し、「発行」をクリックします。

s3_basic_12.png

作成したバージョンにおいて、「トリガー」タブをクリックし、「トリガーを追加」をクリックします。

s3_basic_13.png

トリガーとしてCloudFrontを選択し、先ほど作成したDistribution IDを指定します。 CloudFrontイベントを「ビューアーリクエスト(Viewer Request)」を選択し、「トリガーとレプリケートの有効化」をチェックします。

送信ボタンを押すと、CloudFrontの設定変更も行われます。Statusが「Deployed」になれば完了です。

s3_basic_14.png

確認

CloudFrontのURLをブラウザからアクセスするとBasic認証ダイアログが表示されるはずです。

s3_basic_15.png

ログ

Lambda関数が実行したログはCloudWach Logsに格納されます。 ただ、他のLambda関数と異なる点として、「関数が実行される場所に最も近いCloudWatch Logsリージョン」でCloudWatch Logs ログストリームを作成します。各ログストリームの名前の形式は、 /aws/lambda/us-east-1.function-name です」 (参考: Lambda 関数の CloudWatch メトリクスと CloudWatch Logs - Amazon CloudFront)

日本国内からアクセスすると、東京リージョンのCloudWatch Logsで以下の図のようにログストリームが作成されます。

s3_basic_16.png

最後に

Lambda@Edgeを活用することでS3バケットのファイルにBasic認証をかけることができました。今回はCloudFrontのデフォルトURL(https://xxxxxxx.cloudfront.net)でしたが、独自ドメインで設定することも可能です。また、ユーザのIDとパスワードをLambda関数内に直接書いてしまっているので、KMSで暗号化したり、DynamoDBなどで管理することでより実践的な仕組みを構築することができます。 Lambdaを使いこなしてサーバレスなAWSライフを送りましょう。

参考