CloudFront+S3で配信しているSPAにセキュリティヘッダーを追加

WebアプリケーションではXSS防止、クリックジャッキングなどを防ぐために、
レスポンスヘッダーに X-Frame-OptionsContent-Security-Policy などのセキュリティヘッダーを設定する必要があります。

ApacheやNginxといったWebサーバーを利用していれば、
サーバーの設定でレスポンスにセキュリティヘッダーを含めることができます。
X-Frame-Optionsのサーバー設定例

SPAのWebサイトをS3にデプロイし、CloudFrontでHTTPS対応するのはよくみる構成ですが、
Webサーバーに該当するものがないため、別の方法で設定する必要があります。

Lambda@Edgeを利用することで同じようにセキュリティヘッダーを追加することができます。

アーキテクチャ

※ S3のコンテンツをCloudFrontで配信する構成は構築済みという前提です。

Lambda@EdgeはCloudFrontのエッジロケーションでコードを実行できるLambdaのことです。
ビューワーリクエスト, オリジンリクエスト, オリジンレスポンス, ビューワーレスポンスの4つのイベントをLambda@Edge実行のトリガとして選ぶことができます。


出典

今回は、オリジンレスポンスをトリガにしてレスポンスにセキュリティヘッダーを追加します。こうすることで、セキュリティヘッダーが追加されたコンテンツがCloudFrontにキャッシュされます。

設定手順

AWSのマネジメントコンソールで「Lambda」を指定します。
※ Lambda@Edgeは「バージニア北部」のみとなるため、リージョンの指定を確認してください。

1. 関数の作成

関数を作成を選び、関数を作成します。

  • 関数名: 任意
  • アーキテクチャ: デフォルト
  • デフォルトの実行ロールの変更: AWSポリシーテンプレートから新しいロールを作成
  • ロール名: 任意
  • ポリシーテンプレート: 基本的なLambda@Edgeのアクセス権限(CloudFrontトリガーの場合)

2. コードを編集

以下は、レスポンスヘッダーにContent-Security-Policyのframe-ancestorsを設定する例です。

exports.handler = async (event, context, callback) => {
    const response = event.Records[0].cf.response;
    const headers = response.headers;

    headers['content-security-policy'] = [{key: 'Content-Security-Policy', value: "frame-ancestors 'none'"}]; 

    //Return modified response
    callback(null, response);
};

編集後、Deployボタンを押して反映します。

3. Lamdba@Edgeへデプロイ

右上のアクションからLambda@Edge へのデプロイ を選びます。
Lambda@Edgeへのデプロイ

どのCloudFrontディストリビューションのどのイベントに紐付けるかを設定します。
イベントは「オリジンレスポンス」を選びます。

動作確認

デプロイ後、サイトにアクセスしてみます。
Chromeのdeveloper toolのNetworkタブを開き、アクセス先のDocを取得している通信を確認します。
Response Headerscontent-security-policy: frame-ancestors 'none' が設定されていれば成功です。

少し時間が経っても反映されない場合は、CloudFrontのディストリビューションのキャッシュを削除してみてください。