mattintosh note

どこかのエンジニアモドキの備忘録

CloudFront Functionsで遊ぶ

旧サイトから新サイトにリダイレクトする

EC2 が稼働しているなら Apache やら Nginx でリダイレクトしてもいいが、移転やサービス終了に伴い Web サーバを解体することになった場合に有用。

毎月 200 万リクエストまでは無料枠に収まるし、それを超えても 1 リクエストあたり 0.0000001 USD しかかからない。

最低でも CloudFront にデフォルトオリジンが必要になるため空の S3 バケットを作成しておく必要がある。バケットはオリジンとして指定できればいいだけなのでパブリックにしたり OAI(Origin Access Identity)を設定する必要も無い。

なお、CloudFront を構築したあとにバケットを削除すればバケットすら不要になる。

function handler(event) {
    return {
        statusCode: 301,
        statusDescription: 'Moved Permanently',
        headers: {
            location: {
                value: 'https://example.com',
            },
        },
    };
}

ビューワーを新しい URL にリダイレクトさせる - Amazon CloudFront

index.html なしでドキュメントを表示して URL の正規化もする

CloudFront と S3 で静的コンテンツを配信している場合、CloudFront のデフォルトルートオブジェクトを指定すればルートディレクトリだけは index.html 無しでも表示できるがサブディレクトリは index.html 等が必要になる。

index.html 無しでオリジンにリクエストする例については下記にサンプルが掲載されている。

index.html を追加してファイル名を含まない URL をリクエストする - Amazon CloudFront

このサンプルを使うと index.html あり・なしどちらでもアクセスができるようになるのだけど恐らく検索エンジンに両方登録されてしまうので SEO 対策として正規化して index.html なしに統一する。

event.requestscheme は無いのでこの部分はハードコーディングになりそう?

function handler(event) {
    var request = event.request;
    var uri = request.uri;

    // uri が /index.html で終了している場合は /index.html 無しに 301 リダイレクト
    if (uri.endsWith('/index.html')) {
        return {
            statusCode: 301,
            statusDescription: 'Moved Permanently',
            location: {
                value:
                    'https://' +
                    request.headers.host.value +
                    uri.replace(/\/index\.html$/, ''),
            },
        };
    }

    // uri が / で終了している場合は index.html を付与してオリジンにリクエストする
    if (uri.endsWith('/')) {
        request.uri += 'index.html';
    // uri が . を含まない(≒拡張子が存在しない)場合は /index.html を付与してオリジンにリクエストする
    // 上の 301 リダイレクトしたものはここで処理される
    } else if (!uri.includes('.')) {
        request.uri += '/index.html';
    }

    return request;
}

IP 制限をかける

CloudFront で 403 のカスタムエラーページを設定している場合、WAF で IP 制限を設定するとカスタムエラーレスポンスのページを返却してしまう。CloudFront Functions であればステータスコードだけを返却できる。CloudFront Functions では body はいじれないらしく、その場合は Lambda@Edge を使わなければならない模様。

CloudFront Functions で HTTP レスポンスを生成する場合、レスポンス本文を含めることはできません。生成された HTTP 応答にレスポンス本文を含める必要がある場合は、Lambda@Edge を使います 。

event の構造は下記に記載がある。

CloudFront Functions のイベント構造 - Amazon CloudFront

function handler(event) {
    var allowedIps = [
        '1.2.3.4',
        '5.6.7.8',
    ];

    if (!allowedIps.includes(event.viewer.ip)) {
        return {
            statusCode: 403,
            statusDescription: 'Forbidden',
        };
    }

    return event.request;
}

Basic 認証をかける

クラスメソッドさんの記事で紹介されていたもの。

WAF でも同じようなことが出来るのだけど authorization ヘッダーが無いか Basic 認証の Base64 文字列と一致しなければステータス 401 と www-authenticate: Basic ヘッダーを返却して Basic 認証を要求する。

反応が早い分総当り攻撃で突破されてしまいそうな気がしないでもない。

function handler(event) {
    var authString = 'Basic dXNlcjpwYXNzd29yZA==';

    var request = event.request;

    if (
        typeof request.headers.authorization === 'undefined' ||
        request.headers.authorization.value !== authString
    ) {
        return {
            statusCode: 401,
            statusDescription: 'Unauthorized',
            headers: {
                'www-authenticate': {
                    value: 'Basic',
                },
            },
        };
    }

    return request;
}

オリジンの 403 Forbidden を 404 Not Found に書き換える

中身無しのシンプルな書き換えが出来ると思ったがビューワーレスポンスに関連付けされている場合はレスポンスコードを書き換えることは出来ないらしい。(関数のテストでは出来る)

関数がビューワーレスポンスイベントタイプに関連付けられている場合 、関数コードは受信した statusCode を変更できません。関数がビューワーリクエストイベントタイプに関連付けられ、HTTP レスポンスを生成する場合は、関数コードで statusCode を設定できます。

function handler(event) {
    var response = event.response;

    if (response.statusCode == 403) {
        return {
            statusCode: 404,
            statusDescription: 'Not Found',
        };
    }

    return response;
}

CloudFront Functions は ES 5.1 準拠で一部 ES 6.x 以降の機能や非標準機能も含まれている。

docs.aws.amazon.com