Add Security Header to Your SPA Hosted from S3
我們有個 SPA 網站是存放在 S3,直接用 Cloud Front 來 host,所以不需要任何 server。然而最近在調整 security header 時遇到了一些困難,記錄一下所遇到的狀況和解決辦法。
用 WebPageTest 測了一下看到 F 覺得看了很不爽😠😠😠
因此研究了一下 Security Score,順便做了一點筆記。查了一下可以用 Lambda@Edge 來處理,看起來只要在 viewer response 加上這些 security header 即可。但是因為 SPA(Single Page Application) 的特性,會將任何網址導到 index.html,結果導致只有 index.html 會吃到 viewer response。例如:
curl -v https://example.spa > /dev/nullcontent-type: text/html
strict-transport-security: max-age=63072000;
content-security-policy: frame-ancestors 'self';
x-content-type-options: nosniff
x-frame-options: DENY
x-xss-protection: 1; mode=block
但
curl -v https://example.spa/item/12345 > /dev/nullcontent-type: text/html
卻沒有拿到我在 lambda@edge 設定的 header。這讓我困惑了許久,我一直以為是我 lambda 沒有寫正確,最後終於追到問題。
404 處理:
其實一切的根源在 404 的處理,在 SPA 中,由於要將所有的 url 交給 index.html 處理,在 nginx 中,我們可以這樣寫即可:
location / {
try_files $uri $uri/ /index.html;
}
就可以將所有的 url 導向到 index.html。
我之前參考了這篇文章藉由 Cloud Front 的 error pages 設定將所有 request 導到 index.html,沒想到是因為這條 rule,讓 404 的路徑不會跑到 lambda@edge。
處理方式:
- 將 404 的處理交還給 S3:
2. 移除 Cloud Front 的 Error Pages 設定
3. 自己處理 404 redirect
這邊超雷,首先我解釋一下為何一開始不用 S3 的 error page handle。因為 S3 的 error page 其實還是在處理 404 Not Found,所以原本的用意其實是提供一個客製化 404 頁面的方式,所以即使你在 S3 設定 not found page,他的 status code 依然是 404。這也是為什麼這篇文章是要在 CloudFront 來設定 error page。但….這條路通不到 lambda@edge 啊啊啊~
我查到 AWS 的範例可以透過 lambda@edge 處理 error,可以參考這篇文章。這個範例是將錯誤導到 plan-b/path
'use strict';exports.handler = (event, context, callback) => {
const response = event.Records[0].cf.response;
const request = event.Records[0].cf.request; if (response.status >= 400 && response.status <= 599) {
const redirect_path = `/plan-b/path?${request.querystring}`; response.status = 302;
response.statusDescription = 'Found'; /* Drop the body, as it is not required for redirects */
response.body = '';
response.headers['location'] = [{ key: 'Location', value: redirect_path }];
} callback(null, response);
};
但是很重要的是,因為從 S3 拿到的是 404,在 origin-response 時只要將 response.status 轉成 200 就好了。(實際上這樣也才符合 SPA 的意義)。
if (response.status == 404) {
response.status = 200;
}
結論:
- 原來 Error Pages 的 rule 不會進到 Lambda@Edge,這個是我之前都沒想到的(官方也不提醒一下)。
- 雖然只改到幾行程式,不過這個尋根的過程很難解釋特此紀錄一下。我認為這個是要將 SPA 放到 S3 來跑的一個重大的關鍵。
- 看起來很不爽的 Security Score 終於過了🎉🎉🎉🎉🎉。