ServerlessでCloudFrontプライベートWebサイトを提供する


シナリオ

  • コンテンツはS3へアップロードし、CloudFront経由で配信します
  • 具体的には下記URLアクセスで参照できるようにします
  • ただし、アクセスするためには署名付きCookieが必要とします
  • Cookieなしの場合は閲覧できません

  • 下記URLへアクセスした場合は5分間有効な署名付きCookieを発行します
  • さらに、上記URLへリダイレクトします
  • つまり、このルートでのみコンテンツを参照できることになります

ポイント(何がしたかったのか)

  • 署名付きURLだと単一ファイル(コンテンツ)の応答しかできない
    • Webページの場合、1ファイルで構成するのは現実的ではない
  • 署名付きURLだとURLが長くなる
    • パラメータたくさんなのが美しくない
  • 動的に生成したい
    • 期限付きにしたい(ある程度コントロール可能にしておきたい)
    • しかし、署名用サーバーを設置するほど大げさにしたくはない

構成

自前構成のWebサーバーは設置せずに実現します

  • CloudFront
  • S3
  • LambdaEdge

教材ソース

これに沿った説明にします

$ git clone https://github.com/shinsaka/aws-cf-signedcookie.git
$ cd aws-cf-signedcookie

必要なもの

  • AWSアカウント
    • ルートユーザーでAWSコンソールへログインできる必要があります
  • 手元環境はLinux想定です(私はWSL Ubuntuで実行しています)
  • 教材を使う場合
    • githubへアクセス必要です
    • gitコマンドが必要です
  • 手順中でAWS CLIを使います(コンソールでも可)
  • Serverless FrameWork を使います
    • nodejsが必要になります

デプロイ

コンテンツをS3へアップロード

下記のような構成とします

/index.html
/403.html
/css/style.css

バケット作成してコンテンツをアップロードします

$ aws s3 mb s3://aws-cf-signedcookie-test
make_bucket: aws-cf-signedcookie-test
$ aws s3 sync www/ s3://aws-cf-signedcookie-test/
upload: www/403.html to s3://aws-cf-signedcookie-test/403.html
upload: www/index.html to s3://aws-cf-signedcookie-test/index.html
upload: www/css/style.css to s3://aws-cf-signedcookie-test/css/style.css

CloudFrontで配信設定

AWSコンソールからCloudFront→Distributionを作成します
署名付きCookieしたいので Restrict Viewer Access(Use Signed URLs or Signed Cookies) を Yesにするところがポイントです
Deployedまで10分~15分ぐらいかかると思います


参考ページ: CloudFrontの署名付きURLでS3にアクセスする方法
参考ページ: https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html#private-content-creating-oai

CloudFront エラーページを設定

アクセスできなかった場合に表示するエラーページを設定します

CloudFrontキーペア設定

下記ページの手順通り、ルートユーザーでログインし、CloudFrontキーペアを作成します
- AWS マネジメントコンソールで CloudFront キーペアを作成するには

  • ダウンロードしたプライベートキーファイルを教材ディレクトリへコピーします

設定ファイル記述

sample_settings.jsonsettings.json というファイルにコピーして編集します

{
    "keypairId": "APKXXXXXXXXXXXXX",        キーペアIDです。プライベートキーファイル名と同じはずです
    "privatekeyFile": "pk-APKXXXXXXXXXXXXXXXXX.pem",    プライベートキーファイル名
    "resource": "https://dxxxxxxxxxxxxx.cloudfront.net/*"  デプロイしたCloudFront URL+* (署名付きCookieを保持している状態でアクセスできるパスになります)
}

Lambda Functionをデプロイする

  • (注意)この仕組みではプライベートキーファイルをLambda内にデプロイします
  • LambdaEdgeにするため、必ず us-east-1 へデプロイします(教材では serverless.ymlで指定してあります)
$ serverless deploy --pkfile pk-APKXXXXXXXXXXXXXXXXX.pem

CloudFront Behaviors作成

/enter というパスでアクセスされた場合にLambdaを動作させるための設定を行います

  • Path Pattern
    • /enter
  • Lambda Function Associations
    • CloudFront Event
      • Viewer Request
    • Lamda Function ARN
      • 上記でデプロイしたLambda関数のARNをバージョン付きで設定します

LambdaFunctionの設定部分はLambda側の画面からも操作可能です
設定すると、またCloudFrontはデプロイを始めるので、完了するまで待ちます

動作確認

直接アクセス

  • アクセスできません

正規入り口からアクセス

https://***.cloudfront.net/enter へアクセスするとページが表示されます

Cookieも来ています

5分経過後、リロードすると「アクセスできません」ページが表示されます

curlで確認

直接アクセス

  • 403エラー
$ curl -I https://dxxxxxxxxxxxxx.cloudfront.net/index.html
HTTP/2 403
content-type: text/html
content-length: 161
date: Sat, 18 Jul 2020 09:03:02 GMT
last-modified: Sat, 18 Jul 2020 07:13:32 GMT
etag: "6cadxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
accept-ranges: bytes
server: AmazonS3
x-cache: Error from cloudfront
via: 1.1 19069xxxxxxxxxxxxxxxxxxxxxxxxxxx.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT57-C1
x-amz-cf-id: 67Bxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
age: 362

正規入り口からアクセス

  • Set-Cookie
  • 302リダイレクト
  • location: /index.html
$ curl -I --cookie-jar cookie.txt https://dxxxxxxxxxxxxx.cloudfront.net/enter
HTTP/2 302
content-length: 0
server: CloudFront
date: Sat, 18 Jul 2020 09:06:53 GMT
location: /index.html
set-cookie: CloudFront-Policy=eyJTdGF0ZW1lbnQXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX0_; Path=/; Secure; HttpOnly
set-cookie: CloudFront-Key-Pair-Id=APKXXXXXXXXXXXXXXXXX; Path=/; Secure; HttpOnly
set-cookie: CloudFront-Signature=KxgiTZXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX__; Path=/; Secure; HttpOnly
x-cache: LambdaGeneratedResponse from cloudfront
via: 1.1 adxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT57-C1
x-amz-cf-id: fxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  • cookie付きでリダイレクト先へアクセス
  • 200正常応答
$ curl -I --cookie cookie.txt https://dxxxxxxxxxxxxx.cloudfront.net/index.html
HTTP/2 200
content-type: text/html
content-length: 531
date: Sat, 18 Jul 2020 09:07:04 GMT
last-modified: Sat, 18 Jul 2020 07:13:33 GMT
etag: "fdxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
accept-ranges: bytes
server: AmazonS3
x-cache: Miss from cloudfront
via: 1.1 axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT57-C1
x-amz-cf-id: hxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

まとめ

  • できるだけ簡単にするためにかなり手抜きしていますが、一応やりたいことは実現できたと思います
    • /enter で許可していますが、実際はユーザーごとの長めのハッシュ文字列を想定しています
  • LambdaEdgeは制限が多いのですが、別の(not Edge)Lambdaを実行することはできますので、他のサービスとAPI連携などすることはできそうですね
    • 5秒制限、Layers使えない、環境変数使えない等
    • 長時間かかる処理は避けるべきとは思います