在 AWS S3/CloudFront 中使用 Lambda@Edge 和 Terraform 添加安全標頭

已發表: 2021-04-24

當您登錄https://app.sendgrid.com查看您的帳戶詳細信息或訪問https://mc.sendgrid.com進行營銷活動時,您正在訪問我們在 AWS S3 存儲桶中託管的前端 Web 應用程序CloudFront 分佈在它們之上。

我們的 Web 應用程序本質上由縮小和代碼拆分的 JavaScript 和 CSS 文件、HTML 文件和圖像文件組成,這些文件作為 CloudFront 緩存上傳到 S3 存儲桶並將它們提供給我們的用戶。 我們的每個 Web 應用程序環境(包括測試、暫存和生產)都有單獨的 S3 存儲桶和 CloudFront 分配。

這種 AWS S3 和 CloudFront 基礎設施非常適合我們在內容交付網絡上託管文件的大規模 Web 應用程序,但我們的初始配置缺乏安全標頭形式的更嚴格的保護。

添加這些安全標頭可以防止用戶受到攻擊,例如跨站點腳本、MIME 嗅探、點擊劫持、代碼注入和與不安全協議相關的中間人攻擊。 如果置之不理,這些將對我們客戶的數據以及我們公司對能夠在網絡上提供安全體驗的信任產生嚴重後果。

在我們研究如何添加這些標題之前,我們首先退後一步看看我們在哪裡。 在通過安全標頭掃描網站運行我們的 Web 應用程序 URL 後,我們不出所料地收到了不及格的成績,但看到了一個有用的標頭列表,如下所示。

如您所見,還有很大的改進空間。 我們研究瞭如何配置我們的 AWS S3 和 CloudFront 資源以使用安全標頭進行響應,以減輕上述風險和漏洞。

在高層次上,我們可以通過創建一個 Lambda@Edge 函數來完成此操作,該函數會在 Web 應用程序的文件返回用戶瀏覽器之前更改原始響應標頭以附加所需的安全標頭。

該策略是首先通過 AWS 控制台手動測試連接。 然後,我們會將這些配置放入 Terraform 中,以將這部分基礎設施保存在代碼中,以供將來在其他團隊和應用程序之間參考和共享。

我們要添加什麼樣的安全標頭?

作為我們產品安全團隊建議的一部分,我們的任務是添加安全標頭,例如“Strict-Transport-Security”和“X-Frame-Options”。 我們建議您還查看MDN Web 安全備忘單等資源以了解最新情況。 以下是可應用於 Web 應用程序的安全標頭的簡短摘要。

嚴格的運輸安全 (HSTS)

這是為了提示瀏覽器通過 HTTPS 而不是 HTTP 訪問您的 Web 應用程序。

內容安全策略 (CSP)

這是為您在 Web 應用程序中加載或連接的資源類型設置明確的允許列表,例如腳本、圖像、樣式、字體、網絡請求和 iframe。 這對我們來說是最難設置的,因為我們有第三方腳本、圖像、樣式和 API 端點來明確記錄在這些策略中。

提示:使用Content-Security-Policy-Report-Only標頭可幫助您在某些環境中進行測試。 如果某些資源違反了政策,我們會觀察到我們需要在政策中允許的資源的有用控制台輸出。

如果您想避免出現空白屏幕和 Web 應用程序無法加載的有趣事故,我們強烈建議您先在僅報告模式下試驗您的策略並進行徹底測試,然後才能有足夠的信心在生產中部署這些安全策略。

X-Content-Type-Options

這是為了在您的網頁中維護和加載具有正確 MIME 類型的資產。

X 框架選項

這是為了提供有關您的 Web 應用程序如何在 iframe 中加載的規則。

X-XSS-保護

如果在某些瀏覽器中檢測到跨站點腳本攻擊,這會阻止頁面加載。

推薦人政策

這管理“Referrer”標頭在跟隨外部站點或資源的鏈接時如何傳遞有關請求來源的信息。

考慮到這些安全標頭,讓我們回到我們今天如何設置 CloudFront 分配以及 Lambda@Edge 函數將如何幫助我們實現目標。

將 Lambda@Edge 與我們的 CloudFront 分配一起使用

對於我們的 CloudFront 分配,我們進行瞭如下設置:

    • 要附加在 CloudFront URL 之上的域的 SSL 證書,例如https://app.sendgrid.com
    • S3 存儲桶來源
    • 具有用於自動故障轉移的主存儲桶和副本存儲桶的源組
    • 緩存行為

特別是,這些緩存行為使我們能夠控制我們希望某些類型的路徑和文件的響應在世界各地的邊緣服務器中緩存多長時間。 此外,緩存行為還為我們提供了一種觸發 AWS Lambda 函數以響應各種事件的方法,例如源請求和源響應。 您可以將 AWS Lambda 函數視為您定義的特定代碼,將運行以響應特定事件。

在我們的例子中,我們可以在原始響應頭被邊緣服務器緩存之前更改它。 我們的 Lambda@Edge 函數將在原始響應最終返回到邊緣服務器之前以及在最終用戶收到帶有這些標頭的 JavaScript、CSS 和 HTML 文件之前,將自定義安全標頭添加到原始響應中。

我們根據這篇AWS 博客文章對我們的方法進行了建模,並對其進行了擴展,以便更輕鬆地更改特定的內容安全策略。 您可以按照我們為腳本、樣式和連接源生成列表的設置方式為您的 Lambda@Edge 函數建模。 此函數有效地修改 CloudFront 原始響應標頭,並在通過調用提供的回調函數返回之前將具有特定值的每個安全標頭附加到響應中,如下所示。

我們如何測試這個 Lambda@Edge 函數?

在正式更改資產返回安全標頭的方式之前,您應該在通過 AWS 控制台手動配置所有內容後驗證該功能是否正常工作。 至關重要的是,您的 Web 應用程序應該能夠加載並正常運行,並將安全標頭添加到您的網絡響應中。 您最不想听到的是由於安全標頭而發生的意外中斷,因此請在您的開發環境中徹底測試它們。

了解稍後將在 Terraform 代碼中編寫的確切內容以將此配置保存在代碼庫中也很重要。 如果您不了解 Terraform,它為您提供了一種通過代碼編寫和管理雲基礎架構的方法。

提示:查看 Terraform文檔,看看它是否可以幫助您維護複雜的配置,而無需記住您在雲控制台中執行的所有步驟。

如何開始使用 AWS 控制台

讓我們開始了解如何通過 AWS 控制台手動進行設置。

  1. 首先,您需要在“us-east-1”區域創建 Lambda@Edge 函數。 轉到 Lambda 服務頁面,我們將單擊“創建函數”並將其命名為“testSecurityHeaders1”。

2.您可以使用具有權限的現有角色在邊緣服務器上運行該函數,或者您可以使用他們的角色策略模板之一,例如“Basic Lambda@Edge Permissions...”,並將其命名為“lambdaedgeroletest”。

3. 創建測試 Lambda 函數和角色後,您應該會看到類似這樣的內容,您會注意到該函數的“添加觸發器”按鈕。 這是您最終將 Lambda 與在源響應事件上觸發的 CloudFront 分配的緩存行為相關聯的地方。

4.接下來,您需要使用我們之前製作的安全標頭代碼編輯功能代碼,然後點擊“保存”。

5. 保存函數代碼後,讓我們通過滾動到頂部並點擊“測試”按鈕來測試您的 Lambda 函數是否有效。 您將使用“cloudfront-modify-response-header”事件模板創建一個名為“samplecloudfrontresponse”的測試事件,以模擬實際的 CloudFront 源響應事件並查看您的函數如何針對它運行。

您會注意到您的 Lambda 函數代碼將修改的“cf.response”標頭對象之類的內容。

6. 創建測試事件後,您將再次單擊“測試”按鈕,應該會看到 Lambda 函數是如何針對它運行的。 它應該成功運行,日誌顯示結果響應並添加了這樣的安全標頭。

太好了,Lambda 函數看起來像是正確地將安全標頭附加到響應中!

7. 讓我們回到“設計器”區域並單擊“添加觸發器”按鈕,以便您可以將 Lambda 函數與您的 CloudFront 分配在源響應事件上的緩存行為相關聯。 確保選擇“CloudFront”觸發器並單擊“部署到 Lambda@Edge”按鈕。

8. 接下來,選擇 CloudFront 分配(在我們的示例中,出於安全原因,我們在此處清除了輸入)和與之關聯的緩存行為。

然後,您選擇“*”緩存行為並選擇“源響應”事件以匹配到 CloudFront 分配的所有請求路徑,並確保 Lambda 函數始終針對所有源響應運行。

然後,您在單擊“部署”以正式部署您的 Lambda 函數之前檢查確認。

9. 將您的 Lambda 函數與所有相關 CloudFront 分配的緩存行為成功關聯後,您應該會在 Lambda 儀表板“設計器”區域中看到類似的內容,您可以在其中看到 CloudFront 觸發器並可以選擇查看或刪除它們。

更改您的 Lambda 代碼

每當您可能需要更改 Lambda 代碼時,我們建議:

    1. 通過“操作”按鈕下拉列表發布新版本
    2. 刪除舊版本上的觸發器(您可以單擊“限定符”下拉菜單以查看您的 Lambda 的所有版本)
    3. 將觸發器與您最近發布的最新版本號相關聯

首次部署 Lambda 或發布新版本的 Lambda 並將觸發器與較新的 Lambda 版本相關聯後,您可能不會立即在 Web 應用程序的響應中看到安全標頭。 這是因為 CloudFront 中的邊緣服務器緩存響應的方式。 根據您在緩存行為中設置的生存時間,您可能需要等待一段時間才能看到新的安全標頭,除非您在受影響的 CloudFront 分配中執行緩存失效。

在將您的更改重新部署到您的 Lambda 函數後,在您的響應對您的安全標頭進行最新調整之前,通常需要一些時間來清除緩存(取決於您的 CloudFront 緩存設置)。

提示:為避免頻繁刷新頁面或坐視您的更改是否有效,請啟動 CloudFront 緩存失效以加快清除緩存的過程,以便您可以看到更新的安全標頭。

轉到您的 CloudFront 服務頁面,等待部署 CloudFront 分配的狀態,這意味著所有 lambda 關聯都已完成並已部署,然後轉到“無效”選項卡。 單擊“創建無效”並將“/ *”作為對象路徑以使緩存中的所有內容無效,然後單擊“無效”。 這應該不會花費太長時間,完成後,刷新您的 Web 應用程序應該會看到最新的安全標頭更改。

當您根據您在 Web 應用程序中發現的違規或錯誤對安全標頭進行迭代時,您可以重複此過程:

    • 發布新的 Lambda 函數版本
    • 刪除舊 Lambda 版本上的觸發器
    • 在新版本上關聯觸發器
    • 緩存使您的 CloudFront 分配無效
    • 測試您的 Web 應用程序
    • 重複直到您感到自信和安全,事情按預期工作,沒有任何空白頁、失敗的 API 請求或控制台安全錯誤

一旦事情穩定下來,假設您已將 Terraform 與您的 AWS 賬戶集成,您可以選擇繼續將您剛剛手動執行的 Terraform 轉換為代碼配置。 我們不會從一開始就介紹如何設置 Terraform,但我們將向您展示 Terraform 代碼的片段。

對由我們的 CloudFront 分佈觸發的 Lambda@Edge 進行地形改造

在對“us-east-1”區域中的安全標頭的 Lambda@Edge 函數進行迭代之後,我們希望將其添加到我們的 Terraform 代碼庫中,以實現代碼的可維護性和版本控制。

對於我們已經實現的所有緩存行為,我們必須將緩存行為與原始響應事件觸發的 Lambda@Edge 函數相關聯。

以下步驟假設您已經通過 Terraform 配置了大​​部分 CloudFront 分配和 S3 存儲桶。 我們將重點關注與 Lambda@Edge 相關的主要模塊和屬性,並將觸發器添加到 CloudFront 分配的緩存行為。 我們不會介紹如何通過 Terraform 從頭開始設置您的 S3 存儲桶和其他 CloudFront 分配設置,但我們希望您能看到自己完成這項工作所付出的努力。

我們目前將 AWS 資源分解為單獨的模塊文件夾,並將變量傳遞到這些模塊中,以實現我們的配置靈活性。 我們有一個包含developmentproduction子文件夾的apply文件夾,每個文件夾都有自己的main.tf文件,我們在其中使用某些輸入變量調用這些模塊來實例化或修改我們的 AWS 資源。

這些子文件夾也都有一個lambdas文件夾,我們在其中保存 Lambda 代碼,例如security_headers_lambda.js文件。 security_headers_lambda.js與我們手動測試時在 Lambda 函數中使用的代碼相同,除了我們還將它保存在代碼庫中,以便我們通過 Terraform 壓縮和上傳。

1. 首先,我們需要一個可重用的模塊來壓縮我們的 Lambda 文件,然後將其上傳並發佈為我們的 Lambda@Edge 函數的另一個版本。 這需要一個指向我們的 Lambda 文件夾的路徑,該文件夾包含最終的 Node.js Lambda 函數。

2. 接下來,我們添加到我們現有的 CloudFront 模塊中,該模塊還通過創建一個從壓縮的 Lambda 文件構建的 Lambda 資源來包裝 S3 存儲桶、策略和 CloudFront 分配資源。 因為 Lambda zip 模塊的輸出作為變量傳遞到 CloudFront 模塊以設置 Lambda 資源,所以我們需要將 AWS 提供程序區域指定為“us-east-1”,並使用這樣的工作角色策略。

3. 在 CloudFront 模塊中,我們將這個 Lambda@Edge 函數與 CloudFront 分配的緩存行為相關聯,如下所示。

4. 最後,將它們放在我們的apply/developmentapply/production文件夾的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/