Repository: labstack/echo
Pull request #1247 (2019-01-03 16:52:43)

Enable adding `preload` tag to HSTS header
Resolves https://github.com/labstack/echo/issues/1248

We would like our domain to be listed on [Chrome's HSTS preload list](https://hstspreload.org/?domain=api.yin.bi), which requires that the `preload` tag be appended to the HSTS header like so, and as described [here](https://www.owasp.org/index.php/HTTP_Strict_Transport_Security_Cheat_Sheet):

```
max-age=3600; includeSubdomains; preload
```




codecov[bot]: # [Codecov](https://codecov.io/gh/labstack/echo/pull/1247?src=pr&el=h1) Report
> Merging [#1247](https://codecov.io/gh/labstack/echo/pull/1247?src=pr&el=desc) into [master](https://codecov.io/gh/labstack/echo/commit/3f8b45c8d0dd840198a6b24b3393e5ec553dcb85?src=pr&el=desc) will **increase** coverage by `0.01%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/labstack/echo/pull/1247/graphs/tree.svg?width=650&token=vqnT76l79i&height=150&src=pr)](https://codecov.io/gh/labstack/echo/pull/1247?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #1247      +/-   ##
==========================================
+ Coverage   81.57%   81.59%   +0.01%     
==========================================
  Files          26       26              
  Lines        1927     1929       +2     
==========================================
+ Hits         1572     1574       +2     
  Misses        248      248              
  Partials      107      107
```


| [Impacted Files](https://codecov.io/gh/labstack/echo/pull/1247?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [middleware/secure.go](https://codecov.io/gh/labstack/echo/pull/1247/diff?src=pr&el=tree#diff-bWlkZGxld2FyZS9zZWN1cmUuZ28=) | `92.59% <100%> (+0.59%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/labstack/echo/pull/1247?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/labstack/echo/pull/1247?src=pr&el=footer). Last update [3f8b45c...22a9859](https://codecov.io/gh/labstack/echo/pull/1247?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

vishr: @hchood Could you please resolve the conflict?
hchood: Done.  Thanks for reviewing @vishr !

Patch:
diff --git a/middleware/secure.go b/middleware/secure.go
index eded54f..77a1487 100644
--- a/middleware/secure.go
+++ b/middleware/secure.go
@@ -60,6 +60,12 @@ type (
 		// have occurred instead of blocking the resource.
 		// Optional. Default value false.
 		CSPReportOnly bool `yaml:"csp_report_only"`
+
+		// HSTSPreloadEnabled will add the preload tag in the `Strict Transport Security`
+		// header, which enables the domain to be included in the HSTS preload list
+		// maintained by Chrome (and used by Firefox and Safari): https://hstspreload.org/
+		// Optional.  Default value false.
+		HSTSPreloadEnabled bool `yaml:"hsts_preload_enabled"`
 	}
 )
 
@@ -70,6 +76,7 @@ var (
 		XSSProtection:      "1; mode=block",
 		ContentTypeNosniff: "nosniff",
 		XFrameOptions:      "SAMEORIGIN",
+		HSTSPreloadEnabled: false,
 	}
 )
 
@@ -112,6 +119,9 @@ func SecureWithConfig(config SecureConfig) echo.MiddlewareFunc {
 				if !config.HSTSExcludeSubdomains {
 					subdomains = "; includeSubdomains"
 				}
+				if config.HSTSPreloadEnabled {
+					subdomains = fmt.Sprintf("%s; preload", subdomains)
+				}
 				res.Header().Set(echo.HeaderStrictTransportSecurity, fmt.Sprintf("max-age=%d%s", config.HSTSMaxAge, subdomains))
 			}
 			if config.ContentSecurityPolicy != "" {


Test patch:
diff --git a/middleware/secure_test.go b/middleware/secure_test.go
index 47121fb..96245fe 100644
--- a/middleware/secure_test.go
+++ b/middleware/secure_test.go
@@ -62,4 +62,25 @@ func TestSecure(t *testing.T) {
 	assert.Equal(t, "max-age=3600; includeSubdomains", rec.Header().Get(echo.HeaderStrictTransportSecurity))
 	assert.Equal(t, "default-src 'self'", rec.Header().Get(echo.HeaderContentSecurityPolicyReportOnly))
 	assert.Equal(t, "", rec.Header().Get(echo.HeaderContentSecurityPolicy))
+
+	// Custom, with preload option enabled
+	req.Header.Set(echo.HeaderXForwardedProto, "https")
+	rec = httptest.NewRecorder()
+	c = e.NewContext(req, rec)
+	SecureWithConfig(SecureConfig{
+		HSTSMaxAge:         3600,
+		HSTSPreloadEnabled: true,
+	})(h)(c)
+	assert.Equal(t, "max-age=3600; includeSubdomains; preload", rec.Header().Get(echo.HeaderStrictTransportSecurity))
+
+	// Custom, with preload option enabled and subdomains excluded
+	req.Header.Set(echo.HeaderXForwardedProto, "https")
+	rec = httptest.NewRecorder()
+	c = e.NewContext(req, rec)
+	SecureWithConfig(SecureConfig{
+		HSTSMaxAge:            3600,
+		HSTSPreloadEnabled:    true,
+		HSTSExcludeSubdomains: true,
+	})(h)(c)
+	assert.Equal(t, "max-age=3600; preload", rec.Header().Get(echo.HeaderStrictTransportSecurity))
 }