From 5323e20f7fa10a8c01220e7e368822386060d3b8 Mon Sep 17 00:00:00 2001 From: Franklin De Los Santos Date: Fri, 24 Jun 2022 20:00:34 -0700 Subject: [PATCH 01/10] add custom allowd hosts parameter to Options --- secure.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/secure.go b/secure.go index f8d3c8e..0df573b 100644 --- a/secure.go +++ b/secure.go @@ -36,6 +36,9 @@ const ( // SSLHostFunc a type whose pointer is the type of field `SSLHostFunc` of `Options` struct type SSLHostFunc func(host string) (newHost string) +// AllowedHostsFunc a custom function type that returns a list of strings used in place of AllowedHosts list +type AllowedHostsFunc func() []string + func defaultBadHostHandler(w http.ResponseWriter, r *http.Request) { http.Error(w, "Bad Host", http.StatusInternalServerError) } @@ -90,6 +93,8 @@ type Options struct { CrossOriginOpenerPolicy 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 + // AllowedHostsFunc is a custom function that returns a list of fully qualified domain names that are allowed. If set, AllowedHosts will be ignored + AllowedHostsFunc AllowedHostsFunc // 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 @@ -290,6 +295,10 @@ func (s *Secure) processRequest(w http.ResponseWriter, r *http.Request) (http.He } // Allowed hosts check. + if s.opt.AllowedHostsFunc != nil { + s.opt.AllowedHosts = s.opt.AllowedHostsFunc() + } + if len(s.opt.AllowedHosts) > 0 && !s.opt.IsDevelopment { isGoodHost := false if s.opt.AllowedHostsAreRegex { From 1005976b12155e61e52881ab6177c169631926b1 Mon Sep 17 00:00:00 2001 From: Franklin De Los Santos Date: Fri, 24 Jun 2022 20:01:01 -0700 Subject: [PATCH 02/10] tests for AllowedHostsFunc --- secure_test.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/secure_test.go b/secure_test.go index 0d4b129..f0ab8ef 100644 --- a/secure_test.go +++ b/secure_test.go @@ -1448,6 +1448,37 @@ func TestMultipleCustomSecureContextKeys(t *testing.T) { expect(t, s2Headers.Get(featurePolicyHeader), s2.opt.FeaturePolicy) } +func TestAllowHostsFunc(t *testing.T) { + s := New(Options{ + AllowedHostsFunc: func() []string { return []string{"www.example.com"} }, + }) + + res := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/foo", nil) + req.Host = "www.example.com" + + s.Handler(myHandler).ServeHTTP(res, req) + + expect(t, res.Code, http.StatusOK) + expect(t, res.Body.String(), `bar`) +} + +func TestAllowHostsFuncIgnoreAllowedHostsList(t *testing.T) { + s := New(Options{ + AllowedHosts: []string{"www.blocked.com"}, + AllowedHostsFunc: func() []string { return []string{"www.allow.com"} }, + }) + + res := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/foo", nil) + req.Host = "www.allow.com" + + s.Handler(myHandler).ServeHTTP(res, req) + + expect(t, res.Code, http.StatusOK) + expect(t, res.Body.String(), `bar`) +} + /* Test Helpers */ func expect(t *testing.T, a interface{}, b interface{}) { if a != b { From fb0a1746a64f0bc26138c66782b9fd36359c6145 Mon Sep 17 00:00:00 2001 From: Franklin De Los Santos Date: Wed, 29 Jun 2022 12:49:41 -0700 Subject: [PATCH 03/10] add seperate checks for AllowedHostsFunc to support AllowdHosts list in tandem + tests --- secure.go | 19 ++++++++++++------- secure_test.go | 6 +++--- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/secure.go b/secure.go index 0df573b..5ae81f7 100644 --- a/secure.go +++ b/secure.go @@ -295,12 +295,17 @@ func (s *Secure) processRequest(w http.ResponseWriter, r *http.Request) (http.He } // Allowed hosts check. - if s.opt.AllowedHostsFunc != nil { - s.opt.AllowedHosts = s.opt.AllowedHostsFunc() + isGoodHost := false + if s.opt.AllowedHostsFunc != nil && !s.opt.IsDevelopment { + for _, allowedHost := range s.opt.AllowedHostsFunc() { + if strings.EqualFold(allowedHost, host) { + isGoodHost = true + break + } + } } if len(s.opt.AllowedHosts) > 0 && !s.opt.IsDevelopment { - isGoodHost := false if s.opt.AllowedHostsAreRegex { for _, allowedHost := range s.cRegexAllowedHosts { if match := allowedHost.MatchString(host); match { @@ -316,11 +321,11 @@ func (s *Secure) processRequest(w http.ResponseWriter, r *http.Request) (http.He } } } + } - if !isGoodHost { - s.badHostHandler.ServeHTTP(w, r) - return nil, nil, fmt.Errorf("bad host name: %s", host) - } + if !isGoodHost { + s.badHostHandler.ServeHTTP(w, r) + return nil, nil, fmt.Errorf("bad host name: %s", host) } // Determine if we are on HTTPS. diff --git a/secure_test.go b/secure_test.go index f0ab8ef..d54db03 100644 --- a/secure_test.go +++ b/secure_test.go @@ -1463,10 +1463,10 @@ func TestAllowHostsFunc(t *testing.T) { expect(t, res.Body.String(), `bar`) } -func TestAllowHostsFuncIgnoreAllowedHostsList(t *testing.T) { +func TestAllowHostsFuncWithAllowedHostsList(t *testing.T) { s := New(Options{ - AllowedHosts: []string{"www.blocked.com"}, - AllowedHostsFunc: func() []string { return []string{"www.allow.com"} }, + AllowedHosts: []string{"www.allow.com"}, + AllowedHostsFunc: func() []string { return []string{"www.allowfunc.com"} }, }) res := httptest.NewRecorder() From 04de0ef4161709b2c43f6eece4d0b7561452170d Mon Sep 17 00:00:00 2001 From: Franklin De Los Santos Date: Wed, 29 Jun 2022 13:15:50 -0700 Subject: [PATCH 04/10] clean up, just append the values from AllowedHostsFunc to the AllowedHosts list --- secure.go | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/secure.go b/secure.go index 5ae81f7..8248bec 100644 --- a/secure.go +++ b/secure.go @@ -295,17 +295,12 @@ func (s *Secure) processRequest(w http.ResponseWriter, r *http.Request) (http.He } // Allowed hosts check. - isGoodHost := false if s.opt.AllowedHostsFunc != nil && !s.opt.IsDevelopment { - for _, allowedHost := range s.opt.AllowedHostsFunc() { - if strings.EqualFold(allowedHost, host) { - isGoodHost = true - break - } - } + s.opt.AllowedHosts = append(s.opt.AllowedHosts, s.opt.AllowedHostsFunc()...) } if len(s.opt.AllowedHosts) > 0 && !s.opt.IsDevelopment { + isGoodHost := false if s.opt.AllowedHostsAreRegex { for _, allowedHost := range s.cRegexAllowedHosts { if match := allowedHost.MatchString(host); match { @@ -321,11 +316,10 @@ func (s *Secure) processRequest(w http.ResponseWriter, r *http.Request) (http.He } } } - } - - if !isGoodHost { - s.badHostHandler.ServeHTTP(w, r) - return nil, nil, fmt.Errorf("bad host name: %s", host) + if !isGoodHost { + s.badHostHandler.ServeHTTP(w, r) + return nil, nil, fmt.Errorf("bad host name: %s", host) + } } // Determine if we are on HTTPS. From 8e525ab2cc6dfb4629b7a6850704a6d135cda4c3 Mon Sep 17 00:00:00 2001 From: Franklin De Los Santos Date: Wed, 29 Jun 2022 13:34:04 -0700 Subject: [PATCH 05/10] README update --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 4e90ab0..96890b1 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ Secure comes with a variety of configuration options (Note: these are not the de // ... s := secure.New(secure.Options{ AllowedHosts: []string{"ssl.example.com"}, // AllowedHosts is a list of fully qualified domain names that are allowed. Default is empty list, which allows any and all host names. + AllowedHostsFunc: func () { return []string{"example\\.com", ".*\\.example\\.com" } // AllowedHostsFunc is a custom function that returns a list of fully qualified domain names that are allowed. If set, values will be appended to AllowedHosts AllowedHostsAreRegex: false, // AllowedHostsAreRegex determines, if the provided AllowedHosts slice contains valid regular expressions. Default is false. HostsProxyHeaders: []string{"X-Forwarded-Hosts"}, // HostsProxyHeaders is a set of header keys that may hold a proxied hostname value for the request. SSLRedirect: true, // If SSLRedirect is set to true, then only allow HTTPS requests. Default is false. @@ -101,6 +102,7 @@ s := secure.New() l := secure.New(secure.Options{ AllowedHosts: []string, + AllowedHostsFunc: nil, AllowedHostsAreRegex: false, HostsProxyHeaders: []string, SSLRedirect: false, From c110cdc958e64d288b018de02354429cd8432ff0 Mon Sep 17 00:00:00 2001 From: Franklin De Los Santos Date: Wed, 29 Jun 2022 13:34:20 -0700 Subject: [PATCH 06/10] comment update --- secure.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/secure.go b/secure.go index 8248bec..dfb3ddb 100644 --- a/secure.go +++ b/secure.go @@ -93,7 +93,7 @@ type Options struct { CrossOriginOpenerPolicy 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 - // AllowedHostsFunc is a custom function that returns a list of fully qualified domain names that are allowed. If set, AllowedHosts will be ignored + // AllowedHostsFunc is a custom function that returns a list of fully qualified domain names that are allowed. If set, values will be appended to AllowedHosts AllowedHostsFunc AllowedHostsFunc // 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 From 4b2b6c956e56de27edc25e554244a8e1f62a4d4b Mon Sep 17 00:00:00 2001 From: Franklin De Los Santos Date: Wed, 29 Jun 2022 14:05:45 -0700 Subject: [PATCH 07/10] readme typo fix --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 96890b1..bbaa83c 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,8 @@ Secure comes with a variety of configuration options (Note: these are not the de // ... s := secure.New(secure.Options{ AllowedHosts: []string{"ssl.example.com"}, // AllowedHosts is a list of fully qualified domain names that are allowed. Default is empty list, which allows any and all host names. - AllowedHostsFunc: func () { return []string{"example\\.com", ".*\\.example\\.com" } // AllowedHostsFunc is a custom function that returns a list of fully qualified domain names that are allowed. If set, values will be appended to AllowedHosts + AllowedHostsFunc: func() []string { return []string{"example\\.com", ".*\\.example\\.com" } // + AllowedHostsFunc is a custom function that returns a list of fully qualified domain names that are allowed. If set, values will be appended to AllowedHosts AllowedHostsAreRegex: false, // AllowedHostsAreRegex determines, if the provided AllowedHosts slice contains valid regular expressions. Default is false. HostsProxyHeaders: []string{"X-Forwarded-Hosts"}, // HostsProxyHeaders is a set of header keys that may hold a proxied hostname value for the request. SSLRedirect: true, // If SSLRedirect is set to true, then only allow HTTPS requests. Default is false. From 7ca049e66e67d0c989ece506fd075cd532ec726f Mon Sep 17 00:00:00 2001 From: Franklin De Los Santos Date: Wed, 29 Jun 2022 14:06:45 -0700 Subject: [PATCH 08/10] readme typo fix --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index bbaa83c..0db633a 100644 --- a/README.md +++ b/README.md @@ -62,8 +62,7 @@ Secure comes with a variety of configuration options (Note: these are not the de // ... s := secure.New(secure.Options{ AllowedHosts: []string{"ssl.example.com"}, // AllowedHosts is a list of fully qualified domain names that are allowed. Default is empty list, which allows any and all host names. - AllowedHostsFunc: func() []string { return []string{"example\\.com", ".*\\.example\\.com" } // - AllowedHostsFunc is a custom function that returns a list of fully qualified domain names that are allowed. If set, values will be appended to AllowedHosts + AllowedHostsFunc: func() []string { return []string{"example\\.com", ".*\\.example\\.com" } // AllowedHostsFunc is a custom function that returns a list of fully qualified domain names that are allowed. If set, values will be appended to AllowedHosts AllowedHostsAreRegex: false, // AllowedHostsAreRegex determines, if the provided AllowedHosts slice contains valid regular expressions. Default is false. HostsProxyHeaders: []string{"X-Forwarded-Hosts"}, // HostsProxyHeaders is a set of header keys that may hold a proxied hostname value for the request. SSLRedirect: true, // If SSLRedirect is set to true, then only allow HTTPS requests. Default is false. From 7cd7827e3bad00e961d16171c07ef631050e45d6 Mon Sep 17 00:00:00 2001 From: Franklin De Los Santos Date: Thu, 30 Jun 2022 17:47:06 -0700 Subject: [PATCH 09/10] Update README.md Co-authored-by: Cory Jacobsen --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0db633a..056d73d 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ Secure comes with a variety of configuration options (Note: these are not the de // ... s := secure.New(secure.Options{ AllowedHosts: []string{"ssl.example.com"}, // AllowedHosts is a list of fully qualified domain names that are allowed. Default is empty list, which allows any and all host names. - AllowedHostsFunc: func() []string { return []string{"example\\.com", ".*\\.example\\.com" } // AllowedHostsFunc is a custom function that returns a list of fully qualified domain names that are allowed. If set, values will be appended to AllowedHosts + AllowedHostsFunc: func() []string { return []string{"example\\.com", ".*\\.example\\.com" } // AllowedHostsFunc is a custom function that returns a list of fully qualified domain names that are allowed. This can be used in combination with the above AllowedHosts. AllowedHostsAreRegex: false, // AllowedHostsAreRegex determines, if the provided AllowedHosts slice contains valid regular expressions. Default is false. HostsProxyHeaders: []string{"X-Forwarded-Hosts"}, // HostsProxyHeaders is a set of header keys that may hold a proxied hostname value for the request. SSLRedirect: true, // If SSLRedirect is set to true, then only allow HTTPS requests. Default is false. From 248ba83126e8f8a3c8a7e402522f9557700425f7 Mon Sep 17 00:00:00 2001 From: Franklin De Los Santos Date: Fri, 1 Jul 2022 09:33:35 -0700 Subject: [PATCH 10/10] combine the static a dynamic lists --- secure.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/secure.go b/secure.go index dfb3ddb..754e1cc 100644 --- a/secure.go +++ b/secure.go @@ -295,11 +295,13 @@ func (s *Secure) processRequest(w http.ResponseWriter, r *http.Request) (http.He } // Allowed hosts check. - if s.opt.AllowedHostsFunc != nil && !s.opt.IsDevelopment { - s.opt.AllowedHosts = append(s.opt.AllowedHosts, s.opt.AllowedHostsFunc()...) + combinedAllowedHosts := s.opt.AllowedHosts + + if s.opt.AllowedHostsFunc != nil { + combinedAllowedHosts = append(combinedAllowedHosts, s.opt.AllowedHostsFunc()...) } - if len(s.opt.AllowedHosts) > 0 && !s.opt.IsDevelopment { + if len(combinedAllowedHosts) > 0 && !s.opt.IsDevelopment { isGoodHost := false if s.opt.AllowedHostsAreRegex { for _, allowedHost := range s.cRegexAllowedHosts { @@ -309,7 +311,7 @@ func (s *Secure) processRequest(w http.ResponseWriter, r *http.Request) (http.He } } } else { - for _, allowedHost := range s.opt.AllowedHosts { + for _, allowedHost := range combinedAllowedHosts { if strings.EqualFold(allowedHost, host) { isGoodHost = true break