こんにちは、広野です。
セキュリティ対策で HTTP レスポンスヘッダーを設定することはよくあると思います。私は WEB アプリを AWS Amplify か Amazon CloudFront で公開することが多いのですが、AWS CloudFormation でプロビジョニングする際に設定を入れ込んでいます。
ホスティングしているものがサーバーレス WEB アプリ (SPA) の場合、外部 API を呼び出さないとアプリとして成り立ちません。ヘッダーによってはアプリから API へのリクエストを阻害してしまうものがあるので、必要なものは許可する記述も紹介したいと思います。
やりたいこと
以下の AWS ドキュメントに書かれている情報を参考に、セキュリティ対策でよく使用される HTTP レスポンスヘッダーの追加をしたいと思います。
- AWS Amplify
- Amazon CloudFront
いずれも 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 だったらどうやって記述すればいいのかな?と少し調べることになったので、記事にしてみました。
各ヘッダーの意味は説明しませんでしたので、興味ある方はお調べ頂けたらと思います。
本記事が皆様のお役に立てれば幸いです。