From c1ba4dddd4347fd90ee246fac4f14d1d105f6111 Mon Sep 17 00:00:00 2001 From: Jeevanandam M Date: Fri, 1 Sep 2017 15:30:10 -0700 Subject: [PATCH] Secure headers #88 (#105) --- engine.go | 40 ++++++++++++++++++++++++++++++++++++---- engine_test.go | 6 ++++++ security.go | 13 ++----------- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/engine.go b/engine.go index f05a2ef3..444d9c03 100644 --- a/engine.go +++ b/engine.go @@ -27,7 +27,6 @@ const ( const ( gzipContentEncoding = "gzip" - hstsHeaderValue = "max-age=31536000; includeSubDomains" ) var ( @@ -337,10 +336,43 @@ func (e *engine) writeHeaders(ctx *Context) { ctx.Res.Header().Set(ahttp.HeaderServer, e.serverHeader) } - // Set the HSTS if SSL is enabled on aah server - // Know more: https://www.owasp.org/index.php/HTTP_Strict_Transport_Security_Cheat_Sheet + // Write application security headers with many safe defaults and + // configured header values. + secureHeaders := AppSecurityManager().SecureHeaders + + // Write common secure headers for all request + for header, value := range secureHeaders.Common { + ctx.Res.Header().Set(header, value) + } + + // Applied to all HTML Content-Type + if ahttp.ContentTypeHTML.IsEqual(ctx.Reply().ContType) { + // X-XSS-Protection + ctx.Res.Header().Set(ahttp.HeaderXXSSProtection, secureHeaders.XSSFilter) + + // Content-Security-Policy (CSP) and applied only to environment `prod` + if appIsProfileProd && len(secureHeaders.CSP) > 0 { + if secureHeaders.CSPReportOnly { + ctx.Res.Header().Set(ahttp.HeaderContentSecurityPolicy+"-Report-Only", secureHeaders.CSP) + } else { + ctx.Res.Header().Set(ahttp.HeaderContentSecurityPolicy, secureHeaders.CSP) + } + } + } + + // Apply only if HTTPS (SSL) if AppIsSSLEnabled() { - ctx.Res.Header().Set(ahttp.HeaderStrictTransportSecurity, hstsHeaderValue) + // Public-Key-Pins PKP (aka HPKP) and applied only to environment `prod` + if appIsProfileProd && len(secureHeaders.PKP) > 0 { + if secureHeaders.PKPReportOnly { + ctx.Res.Header().Set(ahttp.HeaderPublicKeyPins+"-Report-Only", secureHeaders.PKP) + } else { + ctx.Res.Header().Set(ahttp.HeaderPublicKeyPins, secureHeaders.PKP) + } + } + + // Strict-Transport-Security (STS, aka HSTS) + ctx.Res.Header().Set(ahttp.HeaderStrictTransportSecurity, secureHeaders.STS) } } diff --git a/engine_test.go b/engine_test.go index 29c56b55..86cea509 100644 --- a/engine_test.go +++ b/engine_test.go @@ -249,6 +249,9 @@ func TestEngineServeHTTP(t *testing.T) { assert.Equal(t, "http://localhost:8080/testdata/", resp5.Header.Get(ahttp.HeaderLocation)) // Directory Listing + appIsSSLEnabled = true + appIsProfileProd = true + AppSecurityManager().SecureHeaders.CSP = "default-erc 'self'" r6 := httptest.NewRequest("GET", "http://localhost:8080/testdata/", nil) r6.Header.Add(e.requestIDHeader, "D9391509-595B-4B92-BED7-F6A9BE0DFCF2") r6.Header.Add(ahttp.HeaderAcceptEncoding, "gzip, deflate, sdch, br") @@ -259,6 +262,9 @@ func TestEngineServeHTTP(t *testing.T) { body6 := getResponseBody(resp6) assert.True(t, strings.Contains(body6, "Listing of /testdata/")) assert.True(t, strings.Contains(body6, "config/")) + AppSecurityManager().SecureHeaders.CSP = "" + appIsSSLEnabled = false + appIsProfileProd = false // Custom Headers r7 := httptest.NewRequest("GET", "http://localhost:8080/credits", nil) diff --git a/security.go b/security.go index 390b5479..0b5da0a1 100644 --- a/security.go +++ b/security.go @@ -198,17 +198,8 @@ func (e *engine) doAuthcAndAuthz(ascheme scheme.Schemer, ctx *Context) flowResul //___________________________________ func initSecurity(appCfg *config.Config) error { - if err := appSecurityManager.Init(appCfg); err != nil { - return err - } - - // Based on aah server SSL configuration `http.Cookie.Secure` value is set, even - // though it's true in aah.conf at `security.session.secure = true`. - if AppSessionManager() != nil { - AppSessionManager().Options.Secure = AppIsSSLEnabled() - } - - return nil + appSecurityManager.IsSSLEnabled = AppIsSSLEnabled() + return appSecurityManager.Init(appCfg) } func isFormAuthLoginRoute(ctx *Context) bool {