AWS Amplify と Amazon CloudFront に HTTP レスポンスヘッダーを設定する [AWS CloudFormation 使用]

こんにちは、広野です。

セキュリティ対策で HTTP レスポンスヘッダーを設定することはよくあると思います。私は WEB アプリを AWS Amplify か Amazon CloudFront で公開することが多いのですが、AWS CloudFormation でプロビジョニングする際に設定を入れ込んでいます。

ホスティングしているものがサーバーレス WEB アプリ (SPA) の場合、外部 API を呼び出さないとアプリとして成り立ちません。ヘッダーによってはアプリから API へのリクエストを阻害してしまうものがあるので、必要なものは許可する記述も紹介したいと思います。

やりたいこと

以下の AWS ドキュメントに書かれている情報を参考に、セキュリティ対策でよく使用される HTTP レスポンスヘッダーの追加をしたいと思います。

  • AWS Amplify
Custom headers - AWS Amplify Hosting
Provides instructions for adding custom HTTP headers to an app either by using the AWS Management Console or by editing ...
  • Amazon CloudFront
Using the managed response headers policies - Amazon CloudFront
Use the managed response headers policies to add a predefined set of HTTP headers to the responses that Amazon CloudFron...

いずれも AWS が一般的に必要な設定例を用意してくれているので、助かります。もちろん会社やシステムのポリシーによって書き換える必要はあります。

AWS CloudFormation による仕込み (仮)

(仮) と書いているのは、多くの場合、そのままではアプリが機能しないからです。最初は厳し目に設定を入れて、動作テストをしていく中で設定をチューニングしていくことがよろしいかと思います。

まずは、何も考えずに入れてみます。AWS CloudFormation テンプレートの抜粋です。※ヘッダーの内容は例です。

  • AWS Amplify (AWS::Amplify::App の抜粋)
CustomHeaders: |-
  customHeaders:
    - pattern: '**'
      headers:
        - key: 'Strict-Transport-Security'
          value: 'max-age=31536000; includeSubDomains'
        - key: 'X-Frame-Options'
          value: 'DENY'
        - key: 'X-XSS-Protection'
          value: '1; mode=block'
        - key: 'X-Content-Type-Options'
          value: 'nosniff'
        - key: 'Content-Security-Policy'
          value: "default-src 'self'"
        - key: 'Cache-Control'
          value: 'no-store'
  • Amazon CloudFront (AWS::CloudFront::ResponseHeadersPolicy)
    オリジンから送られてきたヘッダーをオーバーライド(上書き)する設定になっています。
CloudFrontResponseHeadersPolicyApp:
  Type: AWS::CloudFront::ResponseHeadersPolicy
  Properties:
    ResponseHeadersPolicyConfig:
      Name: !Sub ResponseHeadersPolicy-app
      Comment: !Sub CloudFront Response Headers Policy for app
      SecurityHeadersConfig:
        ContentSecurityPolicy:
          ContentSecurityPolicy: "default-src 'self'"
          Override: true
        ContentTypeOptions:
          Override: true
        FrameOptions:
          FrameOption: DENY
          Override: true
        ReferrerPolicy:
          Override: true
          ReferrerPolicy: strict-origin-when-cross-origin
        StrictTransportSecurity:
          AccessControlMaxAgeSec: 31536000
          IncludeSubdomains: true
          Override: true
          Preload: true
        XSSProtection:
          ModeBlock: true
          Override: true
          Protection: true
      CustomHeadersConfig:
        Items:
          - Header: Cache-Control
            Value: no-store
            Override: true

これら設定のほとんどは単純なパラメータなのですが、Content-Security-Policy ヘッダーだけはこのままではアプリが動かないケースが多いです。

Content-Security-Policy は、そのアプリがアクセスを許可する API やスクリプト等のドメインを設定するもので、イメージ図になりますがこのままだと以下のような状況に陥ります。

自分が管理しているリソース、そうでないもの、悪意のあるなし、に関わらず、アプリからのアクセスがブラウザ側で拒否されてしまいます。この状況は、ブラウザの開発者コンソールでエラーが出るのですぐにわかります。そのエラーメッセージをもとに、アクセスを許可するドメインを Content-Security-Policy ヘッダーに追加していく作業が発生します。

最終的に何を Content-Security-Policy に設定するかはアプリによって変わるのですが、厳格に1つ1つのアクセス先を設定するのは現実的ではなく、ワイルドカードを使って大きく許可するぐらいまでが関の山だと思います。そうなると、Content-Security-Policy の設定自体の意味を感じられず、設定そのものをしないケースも多いと考えられます。しかしながら、設定しないよりはした方がセキュリティレベルが上がるのは間違いないので、管理オーバーヘッドとのトレードオフになるでしょう。

修正したテンプレート抜粋

以下は自分の管理するドメインに加えて、AWS サービスのエンドポイントや Google Fonts、ストリーミング動画、AWS AppSync へのサブスクリプションを許可してみた Content-Security-Policy 部分のテンプレート記述例です。

自分が管理するドメイン名、例えば example.com は ${DomainName} パラメータにして置換 (!Sub) しています。

  • AWS Amplify
- key: 'Content-Security-Policy'
  value: >-
    default-src 'self' *.${DomainName};
    img-src 'self' *.${DomainName} data: blob:;
    style-src 'self' *.${DomainName} 'unsafe-inline' fonts.googleapis.com;
    font-src 'self' *.${DomainName} fonts.gstatic.com;
    script-src-elem 'self' *.${DomainName} cdn.jsdelivr.net;
    connect-src 'self' *.${AWS::Region}.amazonaws.com *.${DomainName} wss:;
    media-src 'self' *.${DomainName} data: blob:;
    worker-src 'self' *.${DomainName} data: blob:;
  • Amazon CloudFront
ContentSecurityPolicy:
  ContentSecurityPolicy: !Sub >-
    default-src 'self' *.${DomainName};
    img-src 'self' *.${DomainName} data: blob:;
    style-src 'self' *.${DomainName} 'unsafe-inline' fonts.googleapis.com;
    font-src 'self' *.${DomainName} fonts.gstatic.com;
    script-src-elem 'self' *.${DomainName} cdn.jsdelivr.net;
    connect-src 'self' *.${DomainName} *.${AWS::Region}.amazonaws.com wss:;
    media-src 'self' *.${DomainName} data: blob:;
    worker-src 'self' *.${DomainName} data: blob:;
  Override: true
  • 画像にリンクする場合、img-src に data: や blob: を入れないと動かないケースがありました。
  • Google fonts を使用する場合は、style-src に fonts.googleapis.com を、font-src に fonts.gstatic.com を入れます。
  • AWS リソースのエンドポイントにアクセスする場合は、それぞれサービスごとに用意されたエンドポイントのドメイン名を connect-src に入れます。が、あまりにも多すぎてここではワイルドカードにしています。
  • AWS AppSync のサブスクリプションを使用する場合は、connect-src に wss: を入れます。
  • ストリーミング動画データを埋め込む場合には、media-src と worker-src に data: blob: を入れます。これは環境によって変わるかもしれません。

まとめ

いかがでしたでしょうか?

レスポンスヘッダーを設定するのに、AWS マネジメントコンソールからであれば画面に従って簡単に入れられるのですが、AWS CloudFormation だったらどうやって記述すればいいのかな?と少し調べることになったので、記事にしてみました。

各ヘッダーの意味は説明しませんでしたので、興味ある方はお調べ頂けたらと思います。

本記事が皆様のお役に立てれば幸いです。

著者について
広野 祐司

AWS サーバーレスアーキテクチャを駆使して社内クラウド人材育成アプリとコンテンツづくりに勤しんでいます。React で SPA を書き始めたら快適すぎて、他の言語には戻れなくなりました。サーバーレス & React 仲間を増やしたいです。AWSは好きですが、それよりもAWSすげー!って気持ちの方が強いです。
取得資格:AWS 認定は12資格、ITサービスマネージャ、ITIL v3 Expert 等
2020 - 2023 Japan AWS Top Engineer 受賞
2022 - 2023 Japan AWS Ambassador 受賞
2023 当社初代フルスタックエンジニア認定
好きなAWSサービス:AWS Amplify / AWS AppSync / Amazon Cognito / AWS Step Functions / AWS CloudFormation

広野 祐司をフォローする
クラウドに強いによるエンジニアブログです。
SCSKは専門性と豊富な実績を活かしたクラウドサービス USiZE(ユーサイズ)を提供しています。
USiZEサービスサイトでは、お客様のDX推進をワンストップで支援するサービスの詳細や導入事例を紹介しています。
AWSアプリケーション開発クラウドクラウドセキュリティソリューション
シェアする
タイトルとURLをコピーしました