Add Security Header to Your SPA Hosted from S3

blackbing Playground
6 min readSep 2, 2020

我們有個 SPA 網站是存放在 S3,直接用 Cloud Front 來 host,所以不需要任何 server。然而最近在調整 security header 時遇到了一些困難,記錄一下所遇到的狀況和解決辦法。

Photo by Kaitlyn Baker on Unsplash

用 WebPageTest 測了一下看到 F 覺得看了很不爽😠😠😠

WebPageTest

因此研究了一下 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。

處理方式:

  1. 將 404 的處理交還給 S3:
S3 Error document

2. 移除 Cloud Front 的 Error Pages 設定

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;
}

結論:

  1. 原來 Error Pages 的 rule 不會進到 Lambda@Edge,這個是我之前都沒想到的(官方也不提醒一下)。
  2. 雖然只改到幾行程式,不過這個尋根的過程很難解釋特此紀錄一下。我認為這個是要將 SPA 放到 S3 來跑的一個重大的關鍵。
  3. 看起來很不爽的 Security Score 終於過了🎉🎉🎉🎉🎉。
WebPageTest result

--

--

blackbing Playground

Bingo Yang。記錄一些生活與工作的雜事,偶爾會寫一些前端網頁開發的心得,過去的足跡在 http://blog.blackbing.net