gitkadoの気まぐれ日記

島根在住エンジニアが何かに興味を持ったらブログを更新します

S3静的ホスティングをそれっぽい構成にしてみた

あらすじ

S3の静的ホスティング機能を用いてindex.htmlを公開した。
が、本来はS3に直接アクセスさせるのはよろしくないみたい。

そこで本来あるべき構成で静的サイト公開をしてみようと思います。

やること

  • S3へ直接アクセスさせない
    • CloudFrontを経由
  • オブジェクト未指定アクセスの場合はindexアクセスとして処理する
    • Lambda@EdgeのオリジンリクエストをトリガーにURL書換

事前知識

CloudFrontとは

  • CDNコンテンツデリバリネットワーク)サービス
  • コンテンツファイルをサーバから直接配信せず、CDNを介してユーザに配信
  • グローバルに無数に配置されているキャッシュサーバやプロキシサーバ

Amazon CloudFrontとは

Lambda@Edgeとは

  • CloudFrontへのアクセスや応答時に機能制限されたLambda関数を実行可能
  • リクエストやレスポンス自体を変えることが可能
  • Lambda ‥ サーバレスでコード実行が可能なAWSの機能
  • リージョン「バージニア北部のみ」で使用可能(2019/2/6時点)

Lambda@Edge ユーザーに近いロケーションでコードを実行

やってみた

S3バケット作成(手順省略)

# バケット構成
[対象とするバケット]
    L index.html
    L subdir
        L index.html

CloudFront作成

  1. CloudFront>CreateDestribution クリック
  2. Web: GetStarted クリック
  3. フォームを入力してCreateDestribution クリック
    • OriginDomainName: 事前に作成したS3バケット選択
    • RestrictBucketAccess: Yes
    • OriginAccessIdentity: Create a New Identity
    • GrantReadPermissionsOnBucket: Yes, Update Bucket Policy

Lambda用ロール作成

  1. IAM>ロール ロールの作成 クリック
  2. サービス: Lambdaを選択 次のステップ クリック
  3. ポリシーの作成 クリック
    1. jsonを選択して以下を入力 ポリシーの確認 クリック
    2. ポリシーの名前を入力 ポリシーの作成 クリック
{
    "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": "*"
        }
    ]
}

 4. 作成したポリシーを選択 次のステップ クリック
 5. タグの追加は入力を行わない 次のステップ クリック
 6. ロール名(AWSLambdaEdgeDevRole)を入力 ロールの作成 クリック
 7. IAM>ロール>AWSLambdaEdgeDevRole クリック
 8. 信頼関係の編集で以下の内容を入力 信頼ポリシーの更新 クリック

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

Lambda@Edge関数作成

リージョンがus-east-1(米国東部:バージニア北部)であることを確認してください。
Lambda@Edgeはバージニア北部のみで使用可能(2019/2/6時点)

  1. Lambda>関数の作成 クリック
  2. 一から作成フォームを入力して関数の作成 クリック
    • 名前: cloudfront-redirect-indexhtml
    • ランタイム: node.js 8.10
    • 既存のロール: 事前に作成しているロール(AWSLambdaEdgeDevRole)
  3. index.jsを以下の内容に書換て保存 クリック
    • バージョンが$LATESTでないと保存できません。
'use strict';
exports.handler = (event, context, callback) => {
    var request = event.Records[0].cf.request;
    var olduri = request.uri;
    var newuri = olduri.replace(/\/$/, '\/index.html');
    console.log("Old URI: " + olduri);
    console.log("New URI: " + newuri);
    request.uri = newuri;
    // Return to CloudFront
    return callback(null, request);
};

トリガー設定

Lambda関数のバージョンが$LATESTだとトリガー設定ができません。

  1. アクション>新しいバージョンを発行 クリック
  2. バージョン説明を入力して発行 クリック
  3. トリガー追加 (画面を下にスクロール)
    1. トリガーの選択からCloudFrontを選択
    2. CloudFrontイベントからオリジンリクエストを選択
    3. Lambda@Edgeへのデプロイを確認 をチェック
    4. 追加 クリック
    5. 保存 クリック

※ CloudFrontのデプロイに少々時間がかかります (In Progress..)

確認

ブラウザで以下のアクセスをすると、index.htmlが表示されます。

ブラウザ 出力
http://CloudFrontのdomain/ バケット/index.html
http://CloudFrontのdomain/subdir/ バケット/subdir/index.html

まとめ

CloudFrontやLambdaは非常に便利でよく使われるサービスの1つです。
Lambdaについてはサーバレスで幅広く使えるサービスなのでIotButtonとかと連動させてみたい。
とりあえず、静的ホスティングから進化させることには成功!

追記(2019/02/13)

今回はindex.htmlのみの構成でした。
ですが、CSSやJSのファイル分けについても対応可能です。
静的と言っていますがJSが使えないわけではないのでご安心を!

参考

できた!S3 オリジンへの直接アクセス制限と、インデックスドキュメント機能を共存させる方法
Lambda@Edge 用の IAM アクセス権限とロールの設定