From 590a0d67bb3a90997a41fc4fa1df05c5cc70cfd5 Mon Sep 17 00:00:00 2001 From: Eliel Goncalves Date: Mon, 7 Oct 2019 05:16:05 -0300 Subject: [PATCH] Fix Location response header http to https when SSL --- Gopkg.lock | 4 +- vendor/github.com/unrolled/secure/secure.go | 157 +++++++++++++++----- 2 files changed, 118 insertions(+), 43 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 35466f17a..6c3fab174 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1909,11 +1909,11 @@ [[projects]] branch = "v1" - digest = "1:60888cead16f066c948c078258b27f2885dce91cb6aadacf545b62a1ae1d08cb" + digest = "1:819d4566276aed820b412b7e72683edfe99f53d2ac54e5b13eda197b523a369b" name = "github.com/unrolled/secure" packages = ["."] pruneopts = "NUT" - revision = "a1cf62cc2159fff407728f118c41aece76c397fa" + revision = "232c938a6a69cfd83e26e2bfe100a20486d3a9a0" [[projects]] digest = "1:e84e99d5f369afaa9a5c41f55b57fa03047ecd3bac2a65861607882693ceea81" diff --git a/vendor/github.com/unrolled/secure/secure.go b/vendor/github.com/unrolled/secure/secure.go index 0f8c58017..92347bffa 100644 --- a/vendor/github.com/unrolled/secure/secure.go +++ b/vendor/github.com/unrolled/secure/secure.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net/http" + "regexp" "strings" ) @@ -11,7 +12,7 @@ type secureCtxKey string const ( stsHeader = "Strict-Transport-Security" - stsSubdomainString = "; includeSubdomains" + stsSubdomainString = "; includeSubDomains" stsPreloadString = "; preload" frameOptionsHeader = "X-Frame-Options" frameOptionsValue = "DENY" @@ -20,8 +21,11 @@ const ( xssProtectionHeader = "X-XSS-Protection" xssProtectionValue = "1; mode=block" cspHeader = "Content-Security-Policy" + cspReportOnlyHeader = "Content-Security-Policy-Report-Only" hpkpHeader = "Public-Key-Pins" referrerPolicyHeader = "Referrer-Policy" + featurePolicyHeader = "Feature-Policy" + expectCTHeader = "Expect-CT" ctxSecureHeaderKey = secureCtxKey("SecureResponseHeader") cspNonceSize = 16 @@ -61,6 +65,8 @@ type Options struct { STSPreload bool // ContentSecurityPolicy allows the Content-Security-Policy header value to be set with a custom value. Default is "". ContentSecurityPolicy string + // ContentSecurityPolicyReportOnly allows the Content-Security-Policy-Report-Only header value to be set with a custom value. Default is "". + ContentSecurityPolicyReportOnly string // CustomBrowserXssValue allows the X-XSS-Protection header value to be set with a custom value. This overrides the BrowserXssFilter option. Default is "". CustomBrowserXssValue string // nolint: golint // Passing a template string will replace `$NONCE` with a dynamic nonce value of 16 bytes for each request which can be later retrieved using the Nonce function. @@ -71,10 +77,15 @@ type Options struct { PublicKey string // ReferrerPolicy allows sites to control when browsers will pass the Referer header to other sites. Default is "". ReferrerPolicy string + // FeaturePolicy allows to selectively enable and disable use of various browser features and APIs. Default is "". + FeaturePolicy string // SSLHost is the host name that is used to redirect http requests to https. Default is "", which indicates to use the same host. SSLHost string // AllowedHosts is a list of fully qualified domain names that are allowed. Default is empty list, which allows any and all host names. AllowedHosts []string + // AllowedHostsAreRegex determines, if the provided slice contains valid regular expressions. If this flag is set to true, every request's + // host will be checked against these expressions. Default is false for backwards compatibility. + AllowedHostsAreRegex bool // HostsProxyHeaders is a set of header keys that may hold a proxied hostname value for the request. HostsProxyHeaders []string // SSLHostFunc is a function pointer, the return value of the function is the host name that has same functionality as `SSHost`. Default is nil. @@ -84,6 +95,8 @@ type Options struct { SSLProxyHeaders map[string]string // STSSeconds is the max-age of the Strict-Transport-Security header. Default is 0, which would NOT include the header. STSSeconds int64 + // ExpectCTHeader allows the Expect-CT header value to be set with a custom value. Default is "". + ExpectCTHeader string } // Secure is a middleware that helps setup a few basic security features. A single secure.Options struct can be @@ -94,6 +107,10 @@ type Secure struct { // badHostHandler is the handler used when an incorrect host is passed in. badHostHandler http.Handler + + // cRegexAllowedHosts saves the compiled regular expressions of the AllowedHosts + // option for subsequent use in processRequest + cRegexAllowedHosts []*regexp.Regexp } // New constructs a new Secure instance with the supplied options. @@ -106,13 +123,27 @@ func New(options ...Options) *Secure { } o.ContentSecurityPolicy = strings.Replace(o.ContentSecurityPolicy, "$NONCE", "'nonce-%[1]s'", -1) + o.ContentSecurityPolicyReportOnly = strings.Replace(o.ContentSecurityPolicyReportOnly, "$NONCE", "'nonce-%[1]s'", -1) - o.nonceEnabled = strings.Contains(o.ContentSecurityPolicy, "%[1]s") + o.nonceEnabled = strings.Contains(o.ContentSecurityPolicy, "%[1]s") || strings.Contains(o.ContentSecurityPolicyReportOnly, "%[1]s") - return &Secure{ + s := &Secure{ opt: o, badHostHandler: http.HandlerFunc(defaultBadHostHandler), } + + if s.opt.AllowedHostsAreRegex { + // Test for invalid regular expressions in AllowedHosts + for _, allowedHost := range o.AllowedHosts { + regex, err := regexp.Compile(fmt.Sprintf("^%s$", allowedHost)) + if err != nil { + panic(fmt.Sprintf("Error parsing AllowedHost: %s", err)) + } + s.cRegexAllowedHosts = append(s.cRegexAllowedHosts, regex) + } + } + + return s } // SetBadHostHandler sets the handler to call when secure rejects the host name. @@ -123,13 +154,10 @@ func (s *Secure) SetBadHostHandler(handler http.Handler) { // Handler implements the http.HandlerFunc for integration with the standard net/http lib. func (s *Secure) Handler(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if s.opt.nonceEnabled { - r = withCSPNonce(r, cspRandNonce()) - } - // Let secure process the request. If it returns an error, // that indicates the request should not continue. - err := s.Process(w, r) + responseHeader, r, err := s.processRequest(w, r) + addResponseHeaders(responseHeader, w) // If there was an error, do not continue. if err != nil { @@ -144,13 +172,9 @@ func (s *Secure) Handler(h http.Handler) http.Handler { // Note that this is for requests only and will not write any headers. func (s *Secure) HandlerForRequestOnly(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if s.opt.nonceEnabled { - r = withCSPNonce(r, cspRandNonce()) - } - // Let secure process the request. If it returns an error, // that indicates the request should not continue. - responseHeader, err := s.processRequest(w, r) + responseHeader, r, err := s.processRequest(w, r) // If there was an error, do not continue. if err != nil { @@ -167,13 +191,10 @@ func (s *Secure) HandlerForRequestOnly(h http.Handler) http.Handler { // HandlerFuncWithNext is a special implementation for Negroni, but could be used elsewhere. func (s *Secure) HandlerFuncWithNext(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { - if s.opt.nonceEnabled { - r = withCSPNonce(r, cspRandNonce()) - } - // Let secure process the request. If it returns an error, // that indicates the request should not continue. - err := s.Process(w, r) + responseHeader, r, err := s.processRequest(w, r) + addResponseHeaders(responseHeader, w) // If there was an error, do not call next. if err == nil && next != nil { @@ -184,13 +205,9 @@ func (s *Secure) HandlerFuncWithNext(w http.ResponseWriter, r *http.Request, nex // HandlerFuncWithNextForRequestOnly is a special implementation for Negroni, but could be used elsewhere. // Note that this is for requests only and will not write any headers. func (s *Secure) HandlerFuncWithNextForRequestOnly(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { - if s.opt.nonceEnabled { - r = withCSPNonce(r, cspRandNonce()) - } - // Let secure process the request. If it returns an error, // that indicates the request should not continue. - responseHeader, err := s.processRequest(w, r) + responseHeader, r, err := s.processRequest(w, r) // If there was an error, do not call next. if err == nil && next != nil { @@ -202,21 +219,44 @@ func (s *Secure) HandlerFuncWithNextForRequestOnly(w http.ResponseWriter, r *htt } } -// Process runs the actual checks and writes the headers in the ResponseWriter. -func (s *Secure) Process(w http.ResponseWriter, r *http.Request) error { - responseHeader, err := s.processRequest(w, r) - if responseHeader != nil { - for key, values := range responseHeader { - for _, value := range values { - w.Header().Add(key, value) - } +// addResponseHeaders Adds the headers from 'responseHeader' to the response. +func addResponseHeaders(responseHeader http.Header, w http.ResponseWriter) { + for key, values := range responseHeader { + for _, value := range values { + w.Header().Set(key, value) } } +} + +// Process runs the actual checks and writes the headers in the ResponseWriter. +func (s *Secure) Process(w http.ResponseWriter, r *http.Request) error { + responseHeader, _, err := s.processRequest(w, r) + addResponseHeaders(responseHeader, w) + return err } +// ProcessAndReturnNonce runs the actual checks and writes the headers in the ResponseWriter. +// In addition, the generated nonce for the request is returned as well as the error value. +func (s *Secure) ProcessAndReturnNonce(w http.ResponseWriter, r *http.Request) (string, error) { + responseHeader, newR, err := s.processRequest(w, r) + addResponseHeaders(responseHeader, w) + + return CSPNonce(newR.Context()), err +} + +// ProcessNoModifyRequest runs the actual checks but does not write the headers in the ResponseWriter. +func (s *Secure) ProcessNoModifyRequest(w http.ResponseWriter, r *http.Request) (http.Header, *http.Request, error) { + return s.processRequest(w, r) +} + // processRequest runs the actual checks on the request and returns an error if the middleware chain should stop. -func (s *Secure) processRequest(w http.ResponseWriter, r *http.Request) (http.Header, error) { +func (s *Secure) processRequest(w http.ResponseWriter, r *http.Request) (http.Header, *http.Request, error) { + // Setup nonce if required. + if s.opt.nonceEnabled { + r = withCSPNonce(r, cspRandNonce()) + } + // Resolve the host for the request, using proxy headers if present. host := r.Host for _, header := range s.opt.HostsProxyHeaders { @@ -229,16 +269,25 @@ func (s *Secure) processRequest(w http.ResponseWriter, r *http.Request) (http.He // Allowed hosts check. if len(s.opt.AllowedHosts) > 0 && !s.opt.IsDevelopment { isGoodHost := false - for _, allowedHost := range s.opt.AllowedHosts { - if strings.EqualFold(allowedHost, host) { - isGoodHost = true - break + if s.opt.AllowedHostsAreRegex { + for _, allowedHost := range s.cRegexAllowedHosts { + if match := allowedHost.MatchString(host); match { + isGoodHost = true + break + } + } + } else { + for _, allowedHost := range s.opt.AllowedHosts { + if strings.EqualFold(allowedHost, host) { + isGoodHost = true + break + } } } if !isGoodHost { s.badHostHandler.ServeHTTP(w, r) - return nil, fmt.Errorf("bad host name: %s", host) + return nil, nil, fmt.Errorf("bad host name: %s", host) } } @@ -265,11 +314,11 @@ func (s *Secure) processRequest(w http.ResponseWriter, r *http.Request) (http.He } http.Redirect(w, r, url.String(), status) - return nil, fmt.Errorf("redirecting to HTTPS") + return nil, nil, fmt.Errorf("redirecting to HTTPS") } if s.opt.SSLForceHost { - var SSLHost = host; + var SSLHost = host if s.opt.SSLHostFunc != nil { if h := (*s.opt.SSLHostFunc)(host); len(h) > 0 { SSLHost = h @@ -288,7 +337,7 @@ func (s *Secure) processRequest(w http.ResponseWriter, r *http.Request) (http.He } http.Redirect(w, r, url.String(), status) - return nil, fmt.Errorf("redirecting to HTTPS") + return nil, nil, fmt.Errorf("redirecting to HTTPS") } } @@ -343,12 +392,31 @@ func (s *Secure) processRequest(w http.ResponseWriter, r *http.Request) (http.He } } + // Content Security Policy Report Only header. + if len(s.opt.ContentSecurityPolicyReportOnly) > 0 { + if s.opt.nonceEnabled { + responseHeader.Set(cspReportOnlyHeader, fmt.Sprintf(s.opt.ContentSecurityPolicyReportOnly, CSPNonce(r.Context()))) + } else { + responseHeader.Set(cspReportOnlyHeader, s.opt.ContentSecurityPolicyReportOnly) + } + } + // Referrer Policy header. if len(s.opt.ReferrerPolicy) > 0 { responseHeader.Set(referrerPolicyHeader, s.opt.ReferrerPolicy) } - return responseHeader, nil + // Feature Policy header. + if len(s.opt.FeaturePolicy) > 0 { + responseHeader.Set(featurePolicyHeader, s.opt.FeaturePolicy) + } + + // Expect-CT header. + if len(s.opt.ExpectCTHeader) > 0 { + responseHeader.Set(expectCTHeader, s.opt.ExpectCTHeader) + } + + return responseHeader, r, nil } // isSSL determine if we are on HTTPS. @@ -369,6 +437,13 @@ func (s *Secure) isSSL(r *http.Request) bool { // Used by http.ReverseProxy. func (s *Secure) ModifyResponseHeaders(res *http.Response) error { if res != nil && res.Request != nil { + // Fix Location response header http to https when SSL is enabled. + location := res.Header.Get("Location") + if s.isSSL(res.Request) && strings.Contains(location, "http:") { + location = strings.Replace(location, "http:", "https:", 1) + res.Header.Set("Location", location) + } + responseHeader := res.Request.Context().Value(ctxSecureHeaderKey) if responseHeader != nil { for header, values := range responseHeader.(http.Header) {