AWS S3/CloudFrontにLambda@EdgeとTerraformを使用してセキュリティヘッダーを追加する
公開: 2021-04-24https://app.sendgrid.comにログインしてアカウントの詳細を確認するか、 https://mc.sendgrid.comにアクセスしてマーケティングキャンペーンを行うと、AWSS3バケットでホストされているフロントエンドWebアプリケーションにアクセスします。それらの上にCloudFrontディストリビューションがあります。
当社のWebアプリケーションは、基本的に、縮小化されコード分割されたJavaScriptファイルとCSSファイル、HTMLファイル、およびCloudFrontキャッシュとしてS3バケットにアップロードされた画像ファイルで構成され、ユーザーに提供されます。 テスト、ステージング、本番環境に至るまでの各Webアプリケーションの環境には、個別のS3バケットとCloudFrontディストリビューションがあります。
このAWSS3およびCloudFrontインフラストラクチャは、コンテンツ配信ネットワークを介してファイルをホストする大規模なWebアプリケーションで適切に機能しますが、初期構成にはセキュリティヘッダーの形式でのより厳密な保護がありませんでした。
これらのセキュリティヘッダーを追加すると、クロスサイトスクリプティング、MIMEスニッフィング、クリックジャッキング、コードインジェクション、安全でないプロトコルに関連する中間者攻撃などの攻撃からユーザーを防ぐことができます。 放置すると、これらはお客様のデータと、Web上で安全なエクスペリエンスを提供できるという当社の信頼に重大な影響を及ぼします。
これらのヘッダーを追加する方法を調査する前に、まず一歩下がって自分がどこにいるかを確認しました。 セキュリティヘッダースキャンWebサイトを介してWebアプリケーションのURLを実行した後、当然のことながら不合格のグレードを受け取りましたが、以下に示すように、調べるのに役立つヘッダーのリストが表示されました。
ご覧のとおり、改善の余地はたくさんありました。 上記のリスクと脆弱性を軽減するために、セキュリティヘッダーで応答するようにAWSS3とCloudFrontリソースを構成する方法を調査しました。
大まかに言うと、これは、Webアプリのファイルがユーザーのブラウザーに戻る前に、オリジン応答ヘッダーを変更して目的のセキュリティヘッダーを追加するLambda@Edge関数を作成することで実現できます。
戦略は、最初にAWSコンソールを介して手動で接続をテストすることです。 次に、これらの構成をTerraformに配置して、インフラストラクチャのこの部分をコードに保存し、将来の参照と他のチームやアプリケーション間での共有を可能にします。
どのような種類のセキュリティヘッダーを追加しますか?
製品セキュリティチームからの推奨事項の一部として、「Strict-Transport-Security」や「X-Frame-Options」などのセキュリティヘッダーを追加するという任務がありました。 MDN Webセキュリティチートシートなどのリソースもチェックして、最新情報を入手することをお勧めします。 これは、Webアプリケーションに適用できるセキュリティヘッダーの簡単な要約です。
Strict-Transport-Security(HSTS)
これは、HTTPではなくHTTPSを介してWebアプリケーションにアクセスするためのヒントをブラウザに提供するためです。
コンテンツ-セキュリティ-ポリシー(CSP)
これは、スクリプト、画像、スタイル、フォント、ネットワークリクエスト、iframeなど、Webアプリケーションでロードまたは接続するリソースの種類に明示的な許可リストを設定するためのものです。 これらのポリシーに明示的に記録するサードパーティのスクリプト、イメージ、スタイル、およびAPIエンドポイントがあったため、これはセットアップが最も困難でした。
ヒント: Content-Security-Policy-Report-Onlyヘッダーを使用して、特定の環境でのテストに役立ててください。 特定のリソースがポリシーに違反している場合、ポリシーで許可する必要のあるリソースの有用なコンソール出力を確認しました。
空白の画面やWebアプリの読み込みに失敗するというおかしな事故を避けたい場合は、最初にレポート専用モードでポリシーを試し、徹底的なテストを行ってから、これらのセキュリティポリシーを本番環境に展開するのに十分な自信を持ってください。
X-Content-Type-Options
これは、Webページで正しいMIMEタイプのアセットを維持およびロードするためです。
Xフレーム-オプション
これは、Webアプリケーションがiframeにロードされる可能性のある方法に関するルールを提供するためのものです。
X-XSS-保護
これにより、特定のブラウザでクロスサイトスクリプティング攻撃が検出された場合にページの読み込みが停止します。
リファラー-ポリシー
これは、外部サイトまたはリソースへのリンクをたどるときに、リクエストの発信元に関する情報を含む「Referrer」ヘッダーがどのように渡されるかを管理します。
これらのセキュリティヘッダーを念頭に置いて、今日のCloudFrontディストリビューションのセットアップ方法と、Lambda@Edge関数が目標の達成にどのように役立つかを振り返ってみましょう。
CloudFrontディストリビューションでのLambda@Edgeの使用
CloudFrontディストリビューションでは、次のように設定します。
- https://app.sendgrid.comのようなCloudFrontURLの上にアタッチするドメインのSSL証明書
- S3バケットの起源
- 自動フェイルオーバー用のプライマリバケットとレプリカバケットを備えたオリジングループ
- キャッシュの動作
特に、これらのキャッシュ動作により、特定のタイプのパスおよびファイルの応答を世界中のエッジサーバーにキャッシュする期間を制御できます。 さらに、キャッシュの動作は、オリジンリクエストやオリジンレスポンスなどのさまざまなイベントに応答してAWSLambda関数をトリガーする方法も提供します。 AWS Lambda関数は、特定のイベントに応答して実行される、定義した特定のコードと考えることができます。
この場合、エッジサーバーによってキャッシュされる前に、オリジン応答ヘッダーを変更できます。 Lambda @ Edge関数は、最終的にエッジサーバーに戻る前、およびエンドユーザーがこれらのヘッダーを含むJavaScript、CSS、およびHTMLファイルを受け取る前に、オリジン応答にカスタムセキュリティヘッダーを追加します。
このAWSブログ投稿に基づいてアプローチをモデル化し、特定のコンテンツセキュリティポリシーに簡単に変更できるように拡張しました。 スクリプト、スタイル、接続ソースのリストを生成する際に設定した方法に従って、Lambda@Edge関数をモデル化できます。 この関数は、CloudFrontオリジンレスポンスヘッダーを効果的に変更し、以下に示すように提供されるコールバック関数を呼び出して戻る前に、各セキュリティヘッダーに特定の値を追加します。
このLambda@Edge関数をどのようにテストしますか?
アセットがセキュリティヘッダーを使用して返される方法を正式に変更する前に、AWSコンソールを使用してすべてを手動で設定した後、関数が機能することを確認する必要があります。 ネットワーク応答に追加されたセキュリティヘッダーを使用して、Webアプリケーションをロードして適切に機能できるようにすることが重要です。 最後に聞きたいのは、セキュリティヘッダーが原因で予期しない停止が発生していることです。そのため、開発環境で徹底的にテストしてください。
この構成をコードベースに保存するために、後でTerraformコードで何を正確に記述するかを知ることも重要です。 Terraformについて知らない場合は、コードを使用してクラウドインフラストラクチャを記述および管理する方法を提供します。
ヒント: Terraformのドキュメントを見て、クラウドコンソールで行ったすべての手順を覚えていなくても、複雑な構成を維持できるかどうかを確認してください。
AWSコンソールで開始する方法
AWSコンソールを使用して手動で設定する方法から始めましょう。
- まず、「us-east-1」リージョンにLambda@Edge関数を作成する必要があります。 Lambdaサービスページに移動し、[Create Function]をクリックして、「testSecurityHeaders1」のような名前を付けます。
2.アクセス許可を持つ既存のロールを使用してエッジサーバーで関数を実行するか、「Basic Lambda @ EdgePermissions…」などのロールポリシーテンプレートの1つを使用して、「lambdaedgeroletest」という名前を付けることができます。
3.テストLambda関数とロールを作成すると、このような関数の[トリガーの追加]ボタンが表示されます。 これは、最終的にLambdaをオリジンレスポンスイベントでトリガーされるCloudFrontディストリビューションのキャッシュ動作に関連付ける場所です。
4.次に、前に作成したセキュリティヘッダーコードを使用して機能コードを編集し、[保存]をクリックする必要があります。
5.関数コードを保存した後、上にスクロールして[テスト]ボタンを押すことで、Lambda関数が機能するかどうかをテストしましょう。 「cloudfront-modify-response-header」イベントテンプレートを使用して「samplecloudfrontresponse」という名前のテストイベントを作成し、実際のCloudFrontオリジンレスポンスイベントをモックし、関数がそれに対してどのように実行されるかを確認します。
Lambda関数コードが変更する「cf.response」ヘッダーオブジェクトのようなものに気付くでしょう。
6.テストイベントを作成した後、[テスト]ボタンをもう一度クリックすると、Lambda関数がどのように実行されたかを確認できます。 このようなセキュリティヘッダーが追加された結果の応答を表示するログで正常に実行されるはずです。
Lambda関数は、応答にセキュリティヘッダーを正しく追加したように見えます。
7.「Designer」エリアに戻り、「Add Trigger」ボタンをクリックして、Lambda関数をオリジンレスポンスイベントでのCloudFrontディストリビューションのキャッシュ動作に関連付けることができるようにします。 必ず「CloudFront」トリガーを選択し、「Deploy to Lambda@Edge」ボタンをクリックしてください。
8.次に、CloudFrontディストリビューション(この例では、セキュリティ上の理由からここで入力をクリアしました)とそれに関連付けるキャッシュ動作を選択します。
次に、「*」キャッシュ動作を選択し、「オリジンレスポンス」イベントを選択して、CloudFrontディストリビューションへのすべてのリクエストパスで一致させ、すべてのオリジンレスポンスに対してLambda関数が常に実行されるようにします。
次に、[デプロイ]をクリックしてLambda関数を正式にデプロイする前に、確認応答をチェックします。
9. Lambda関数を関連するCloudFrontディストリビューションのすべてのキャッシュ動作に正常に関連付けると、CloudFrontトリガーを表示し、それらを表示または削除するオプションが表示されるLambdaダッシュボードの「デザイナー」領域にこれに似たものが表示されます。
Lambdaコードに変更を加える
Lambdaコードに変更を加える必要がある場合は、次のことをお勧めします。
- [アクション]ボタンのドロップダウンから新しいバージョンを公開します
- 古いバージョンのトリガーを削除します([修飾子]ドロップダウンをクリックすると、Lambdaのすべてのバージョンが表示されます)
- トリガーを最近公開した最新のバージョン番号に関連付けます
Lambdaを初めてデプロイするとき、またはLambdaの新しいバージョンを公開し、トリガーを新しいLambdaバージョンに関連付けた後、Webアプリケーションの応答にセキュリティヘッダーがすぐに表示されない場合があります。 これは、CloudFrontのエッジサーバーが応答をキャッシュする方法が原因です。 キャッシュ動作で存続時間を設定する時間によっては、影響を受けるCloudFrontディストリビューションでキャッシュの無効化を行わない限り、新しいセキュリティヘッダーが表示されるまでしばらく待つ必要がある場合があります。
Lambda関数への変更を再デプロイした後、応答がセキュリティヘッダーに最新の調整を加える前に、キャッシュがクリアされるまでに時間がかかることがよくあります(CloudFrontキャッシュ設定によって異なります)。
ヒント:ページを頻繁に更新したり、変更が機能したかどうかわからなくなったりしないようにするには、CloudFrontキャッシュの無効化を開始して、キャッシュをクリアするプロセスを高速化し、更新されたセキュリティヘッダーを確認できるようにします。
CloudFrontサービスページに移動し、CloudFrontディストリビューションのステータスがデプロイされるのを待ちます。つまり、すべてのラムダアソシエーションが完了してデプロイされ、[無効化]タブに移動します。 「無効化の作成」をクリックし、オブジェクトパスとして「/ *」を入力して、キャッシュ内のすべてのものを無効化し、「無効化」をクリックします。 これにはそれほど時間はかからないはずです。完了後、Webアプリケーションを更新すると、最新のセキュリティヘッダーの変更が表示されます。
Webアプリケーションで違反またはエラーとして検出したものに基づいてセキュリティヘッダーを反復処理するときに、次のプロセスを繰り返すことができます。
- 新しいLambda関数バージョンの公開
- 古いLambdaバージョンのトリガーを削除する
- 新しいバージョンでのトリガーの関連付け
- CloudFrontディストリビューションを無効にするキャッシュ
- Webアプリケーションのテスト
- 空白のページ、失敗したAPIリクエスト、コンソールのセキュリティエラーが発生することなく、自信を持って安全に動作できると感じるまで繰り返します
物事が安定したら、オプションで、TerraformがAWSアカウントに統合されていることを前提として、手動で行ったことをコード構成にテラフォーミングに移すことができます。 最初からTerraformを設定する方法については説明しませんが、Terraformコードがどのようになるかについてのスニペットを示します。
CloudFrontディストリビューションによってトリガーされたLambda@Edgeのテラフォーミング
「us-east-1」リージョンのセキュリティヘッダーのLambda@Edge関数を繰り返し処理した後、コードの保守性とバージョン管理のために、これをTerraformコードベースに追加したいと考えました。
すでに実装したすべてのキャッシュ動作について、キャッシュ動作を、オリジン応答イベントがトリガーしたLambda@Edge関数に関連付ける必要がありました。
次の手順は、Terraformを介してほとんどのCloudFrontディストリビューションとS3バケットがすでに設定されていることを前提としています。 Lambda @ Edgeに関連するメインモジュールとプロパティに焦点を当て、CloudFrontディストリビューションのキャッシュ動作にトリガーを追加します。 S3バケットやその他のCloudFrontディストリビューション設定を最初からTerraformを介してセットアップする方法については説明しませんが、これを自分で達成するための努力のレベルを確認できることを願っています。
現在、AWSリソースを個別のモジュールフォルダーに分割し、構成の柔軟性を高めるためにそれらのモジュールに変数を渡します。 development
およびproduction
サブフォルダーを含むapply
フォルダーがあり、それぞれに独自のmain.tf
ファイルがあり、特定の入力変数を使用してこれらのモジュールを呼び出し、AWSリソースをインスタンス化または変更します。
これらのサブフォルダーにはそれぞれ、 security_headers_lambda.js
ファイルなどのLambdaコードを保持するlambdas
フォルダーもあります。 security_headers_lambda.js
には、手動でテストしたときにLambda関数で使用していたものと同じコードがありますが、Terraformを介してzipおよびアップロードするためにコードベースに保存している点が異なります。
1.まず、Lambda @ Edge関数の別のバージョンとしてアップロードおよび公開する前に、Lambdaファイルを圧縮するための再利用可能なモジュールが必要です。 これにより、最終的なNode.jsLambda関数を保持するLambdaフォルダーへのパスが取得されます。
2.次に、既存のCloudFrontモジュールに追加します。このモジュールは、圧縮されたLambdaファイルから構築されたLambdaリソースも作成することで、S3バケット、ポリシー、CloudFrontディストリビューションリソースをラップします。 Lambda zipモジュールの出力は変数としてCloudFrontモジュールに渡され、Lambdaリソースを設定するため、AWSプロバイダーリージョンを「us-east-1」として指定し、このようなワーキングロールポリシーを使用する必要があります。
3. CloudFrontモジュール内で、次に示すように、このLambda@Edge関数をCloudFrontディストリビューションのキャッシュ動作に関連付けます。
4.最後に、 apply/development
またはapply/production
productフォルダーのmain.tf
ファイルにすべてをまとめて、これらすべてのモジュールを呼び出し、ここに示すように、適切な出力を変数としてCloudFrontモジュールに入れます。
これらの設定の調整は、基本的に、前のセクションでLambdaコードを更新し、新しいバージョンをCloudFrontのキャッシュ動作とオリジンレスポンスイベントのトリガーに関連付けるために行った手動の手順を処理します。 ウーフー! これらの変更をリソースに適用する限り、AWSコンソールの手順を実行したり覚えたりする必要はありません。
さまざまな環境でこれを安全に展開するにはどうすればよいですか?
Lambda @ Edge関数をテスト用のCloudFrontディストリビューションに最初に関連付けたとき、Webアプリケーションが正しくロードされなくなることにすぐに気付きました。 これは主に、Content-Security-Policyヘッダーを実装した方法と、アプリケーションにロードしたすべてのリソースをカバーしなかったことが原因でした。 他のセキュリティヘッダーは、アプリケーションの読み込みをブロックするという点でリスクが少なくなりました。 将来的には、Content-Security-Policyヘッダーを微調整するために、さらなる反復を念頭に置いてセキュリティヘッダーの展開に焦点を当てます。
前述のように、各ポリシーに追加するリソースドメインをさらに収集するときに、リスクを最小限に抑えるために、代わりにContent-Security-Policy-Report-Onlyヘッダーを利用する方法を発見しました。
このレポートのみのモードでは、ポリシーは引き続きブラウザで実行され、ポリシーへの違反に関するコンソールエラーメッセージが出力されます。 ただし、これらのスクリプトとソースを完全にブロックすることはないため、Webアプリケーションは通常どおり実行できます。 ポリシーの重要な情報源を見逃さないように、Webアプリケーション全体を引き続き確認する必要があります。そうしないと、お客様やサポートチームに悪影響を及ぼします。
環境ごとに、次のようにセキュリティヘッダーLambdaを展開できます。
- 手動またはTerraformプランを介してLambdaに変更を公開し、最初に他のセキュリティヘッダーとContent-Security-Policy-Report-Onlyヘッダーを使用して環境への変更を適用します。
- CloudFrontディストリビューションステータスが、キャッシュ動作に関連付けられたLambdaで完全にデプロイされるのを待ちます。
- 以前のセキュリティヘッダーが引き続き表示される場合、または現在の変更がブラウザーに表示されるまでに時間がかかりすぎる場合は、CloudFrontディストリビューションでキャッシュの無効化を実行してください。
- 開発者ツールを開いた状態でWebアプリケーションのページにアクセスしてツアーし、コンソールをスキャンして「レポートのみ…」コンソールエラーメッセージを探し、Content-Security-Policyヘッダーを改善します。
- 報告された違反を考慮に入れるために、Lambdaコードに変更を加えます。
- ヘッダーをContent-Security-Policy-Report-OnlyからContent-Security-Policyに変更するのに十分な自信が持てるようになるまで、最初のステップから繰り返します。これは、環境がそれを強制することを意味します。
セキュリティヘッダースコアの改善
Terraformの変更を環境に正常に適用し、CloudFrontキャッシュを無効にした後、Webアプリケーションのページを更新しました。 開発者ツールを開いたままにして、HSTS、CSPなどのセキュリティヘッダー、および以下に示すセキュリティヘッダーなどのネットワーク応答内の他のヘッダーを確認しました。
また、このサイトにあるようなセキュリティヘッダースキャンレポートを介してWebアプリケーションを実行しました。 その結果、以前は失敗したグレードから大幅な改善(A評価!)が見られました。S3/ CloudFrontのセットアップを変更してセキュリティヘッダーを設定した後でも、同様の改善を実現できます。
セキュリティヘッダーで前進
AWSコンソールを介してセキュリティヘッダーを手動で設定するか、ソリューションを正常にテラフォーミングして各環境に変更を適用すると、将来的に既存のセキュリティヘッダーをさらに反復して改善するための優れた基盤が得られます。
Webアプリケーションの進化によっては、セキュリティを強化するために許可されるリソースに関して、Content-Security-Policyヘッダーをより具体的にする必要がある場合があります。 または、別の目的で、または別のセキュリティホールを埋めるために、新しいヘッダーを完全に追加する必要がある場合があります。
Lambda @ Edge関数のセキュリティヘッダーに対するこれらの将来の変更により、環境ごとに同様のリリース戦略に従って、WebアプリケーションがWebに対する悪意のある攻撃から保護され、ユーザーが違いに気付くことなく動作するようにすることができます。
Alfred Luceroが書いたその他の記事については、彼のブログ作成者ページにアクセスしてください: https ://sendgrid.com/blog/author/alfred/