From 3c526c00676ef526b0be539651910f9b27d51e09 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Thu, 11 Jun 2020 18:52:35 +0300 Subject: [PATCH] add 'HostProxyHeaders' Configuration field --- HISTORY.md | 2 +- .../from-yaml-file/configs/iris.yml | 10 +++- configuration.go | 49 ++++++++++++++++--- configuration_test.go | 34 ++++++++++++- context/configuration.go | 3 +- context/context.go | 12 +++++ 6 files changed, 98 insertions(+), 12 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 8a74be3954..9c9ead6c1e 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -417,7 +417,7 @@ New Package-level Variables: New Context Methods: -- `Context.IsSSL() bool` reports whether the request is under HTTPS SSL (New `Configuration.SSLProxyHeaders` field too). +- `Context.IsSSL() bool` reports whether the request is under HTTPS SSL (New `Configuration.SSLProxyHeaders` and `HostProxyHeaders` fields too). - `Context.GzipReader(enable bool)` method and `iris.GzipReader` middleware to enable future request read body calls to decompress data using gzip, [example](_examples/request-body/read-gzip). - `Context.RegisterDependency(v interface{})` and `Context.RemoveDependency(typ reflect.Type)` to register/remove struct dependencies on serve-time through a middleware. - `Context.SetID(id interface{})` and `Context.GetID() interface{}` added to register a custom unique indetifier to the Context, if necessary. diff --git a/_examples/configuration/from-yaml-file/configs/iris.yml b/_examples/configuration/from-yaml-file/configs/iris.yml index 78113e5414..c0c764d104 100644 --- a/_examples/configuration/from-yaml-file/configs/iris.yml +++ b/_examples/configuration/from-yaml-file/configs/iris.yml @@ -4,5 +4,13 @@ FireMethodNotAllowed: true DisableBodyConsumptionOnUnmarshal: true TimeFormat: Mon, 01 Jan 2006 15:04:05 GMT Charset: UTF-8 +SSLProxyHeaders: + X-Forwarded-Proto: https +HostProxyHeaders: + X-Host: true +RemoteAddrHeaders: + X-Real-Ip: true + X-Forwarded-For: true + CF-Connecting-IP: true Other: - Addr: :8080 \ No newline at end of file + Addr: :8080 diff --git a/configuration.go b/configuration.go index 250c4d7872..2fa0f20609 100644 --- a/configuration.go +++ b/configuration.go @@ -354,15 +354,18 @@ func WithPostMaxMemory(limit int64) Configurator { // that the client may sent. // // Defaults to an empty map but an example usage is: -// WithRemoteAddrHeader("X-Forwarded-For") +// WithRemoteAddrHeader("X-Forwarded-For", "CF-Connecting-IP") // // Look `context.RemoteAddr()` for more. -func WithRemoteAddrHeader(headerName string) Configurator { +func WithRemoteAddrHeader(header ...string) Configurator { return func(app *Application) { if app.config.RemoteAddrHeaders == nil { app.config.RemoteAddrHeaders = make(map[string]bool) } - app.config.RemoteAddrHeaders[headerName] = true + + for _, k := range header { + app.config.RemoteAddrHeaders[k] = true + } } } @@ -408,6 +411,20 @@ func WithSSLProxyHeader(headerKey, headerValue string) Configurator { } } +// WithHostProxyHeader sets a HostProxyHeaders key value pair. +// Example: WithHostProxyHeader("X-Host"). +// See `Context.Host` for more. +func WithHostProxyHeader(headers ...string) Configurator { + return func(app *Application) { + if app.config.HostProxyHeaders == nil { + app.config.HostProxyHeaders = make(map[string]bool) + } + for _, k := range headers { + app.config.HostProxyHeaders[k] = true + } + } +} + // WithOtherValue adds a value based on a key to the Other setting. // // See `Configuration.Other`. @@ -880,7 +897,7 @@ type Configuration struct { // context.UnmarshalBody/ReadJSON/ReadXML will be not consumed. DisableBodyConsumptionOnUnmarshal bool `json:"disableBodyConsumptionOnUnmarshal,omitempty" yaml:"DisableBodyConsumptionOnUnmarshal" toml:"DisableBodyConsumptionOnUnmarshal"` // FireEmptyFormError returns if set to tue true then the `context.ReadBody/ReadForm` - // will return an `iris.ErrEmptyForm` on empty request form data. + // will return an `iris.ErrEmptyForm` on empty request form data. FireEmptyFormError bool `json:"fireEmptyFormError,omitempty" yaml:"FireEmptyFormError" yaml:"FireEmptyFormError"` // TimeFormat time format for any kind of datetime parsing @@ -956,7 +973,6 @@ type Configuration struct { // // Look `context.RemoteAddr()` for more. RemoteAddrHeaders map[string]bool `json:"remoteAddrHeaders,omitempty" yaml:"RemoteAddrHeaders" toml:"RemoteAddrHeaders"` - // RemoteAddrPrivateSubnets defines the private sub-networks. // They are used to be compared against // IP Addresses fetched through `RemoteAddrHeaders` or `Context.Request.RemoteAddr`. @@ -987,14 +1003,18 @@ type Configuration struct { // End: net.ParseIP("198.19.255.255"), // } // - // Look `context.RemoteAddr()` for more. + // Look `Context.RemoteAddr()` for more. RemoteAddrPrivateSubnets []netutil.IPRange `json:"remoteAddrPrivateSubnets" yaml:"RemoteAddrPrivateSubnets" toml:"RemoteAddrPrivateSubnets"` // SSLProxyHeaders defines the set of header key values - // that would indicate a valid https Request (look `context.IsSSL()`). + // that would indicate a valid https Request (look `Context.IsSSL()`). // Example: `map[string]string{"X-Forwarded-Proto": "https"}`. // // Defaults to empty map. SSLProxyHeaders map[string]string `json:"sslProxyHeaders" yaml:"SSLProxyHeaders" toml:"SSLProxyHeaders"` + // HostProxyHeaders defines the set of headers that may hold a proxied hostname value for the clients. + // Look `Context.Host()` for more. + // Defaults to empty map. + HostProxyHeaders map[string]bool `json:"hostProxyHeaders" yaml:"HostProxyHeaders" toml:"HostProxyHeaders"` // Other are the custom, dynamic options, can be empty. // This field used only by you to set any app's options you want. // @@ -1124,6 +1144,11 @@ func (c Configuration) GetRemoteAddrPrivateSubnets() []netutil.IPRange { return c.RemoteAddrPrivateSubnets } +// GetHostProxyHeaders returns the HostProxyHeaders field. +func (c Configuration) GetHostProxyHeaders() map[string]bool { + return c.HostProxyHeaders +} + // GetOther returns the Other field. func (c Configuration) GetOther() map[string]interface{} { return c.Other @@ -1259,6 +1284,15 @@ func WithConfiguration(c Configuration) Configurator { } } + if v := c.HostProxyHeaders; len(v) > 0 { + if main.HostProxyHeaders == nil { + main.HostProxyHeaders = make(map[string]bool, len(v)) + } + for key, value := range v { + main.HostProxyHeaders[key] = value + } + } + if v := c.Other; len(v) > 0 { if main.Other == nil { main.Other = make(map[string]interface{}, len(v)) @@ -1325,6 +1359,7 @@ func DefaultConfiguration() Configuration { }, }, SSLProxyHeaders: make(map[string]string), + HostProxyHeaders: make(map[string]bool), EnableOptimizations: false, Other: make(map[string]interface{}), } diff --git a/configuration_test.go b/configuration_test.go index c7f0e48340..24c552a0ce 100644 --- a/configuration_test.go +++ b/configuration_test.go @@ -153,12 +153,14 @@ EnableOptimizations: true DisableBodyConsumptionOnUnmarshal: true TimeFormat: "Mon, 02 Jan 2006 15:04:05 GMT" Charset: "utf-8" - RemoteAddrHeaders: X-Real-Ip: true X-Forwarded-For: true CF-Connecting-IP: true - +HostProxyHeaders: + X-Host: true +SSLProxyHeaders: + X-Forwarded-Proto: https Other: MyServerName: "Iris: https://github.com/kataras/iris" ` @@ -224,6 +226,34 @@ Other: } } + expectedHostProxyHeaders := map[string]bool{ + "X-Host": true, + } + + if expected, got := len(c.HostProxyHeaders), len(expectedHostProxyHeaders); expected != got { + t.Fatalf("error on TestConfigurationYAML: Expected HostProxyHeaders' len(%d) and got(%d), len is not the same", expected, got) + } + + for k, v := range c.HostProxyHeaders { + if expected, got := expectedHostProxyHeaders[k], v; expected != got { + t.Fatalf("error on TestConfigurationYAML: Expected HostProxyHeaders[%s] = %t but got %t", k, expected, got) + } + } + + expectedSSLProxyHeaders := map[string]string{ + "X-Forwarded-Proto": "https", + } + + if expected, got := len(c.SSLProxyHeaders), len(c.SSLProxyHeaders); expected != got { + t.Fatalf("error on TestConfigurationYAML: Expected SSLProxyHeaders' len(%d) and got(%d), len is not the same", expected, got) + } + + for k, v := range c.SSLProxyHeaders { + if expected, got := expectedSSLProxyHeaders[k], v; expected != got { + t.Fatalf("error on TestConfigurationYAML: Expected SSLProxyHeaders[%s] = %s but got %s", k, expected, got) + } + } + if len(c.Other) == 0 { t.Fatalf("error on TestConfigurationYAML: Expected Other to be filled") } diff --git a/context/configuration.go b/context/configuration.go index b182480001..5f09f0cba3 100644 --- a/context/configuration.go +++ b/context/configuration.go @@ -60,7 +60,8 @@ type ConfigurationReadOnly interface { GetRemoteAddrPrivateSubnets() []netutil.IPRange // GetSSLProxyHeaders returns the SSLProxyHeaders field. GetSSLProxyHeaders() map[string]string - + // GetHostProxyHeaders returns the HostProxyHeaders field. + GetHostProxyHeaders() map[string]bool // GetOther returns the Other field. GetOther() map[string]interface{} } diff --git a/context/context.go b/context/context.go index 2dc0da91aa..8bbf4b7994 100644 --- a/context/context.go +++ b/context/context.go @@ -346,6 +346,7 @@ type Context interface { // based on the 'escape'. RequestPath(escape bool) string // Host returns the host part of the current url. + // This method makes use of the `Configuration.HostProxyHeaders` field too. Host() string // Subdomain returns the subdomain of this request, if any. // Note that this is a fast method which does not cover all cases. @@ -1813,7 +1814,18 @@ func (ctx *context) RequestPath(escape bool) string { // } no, it will not work because map is a random peek data structure. // Host returns the host part of the current URI. +// This method makes use of the `Configuration.HostProxyHeaders` field too. func (ctx *context) Host() string { + for header, ok := range ctx.app.ConfigurationReadOnly().GetHostProxyHeaders() { + if !ok { + continue + } + + if host := ctx.GetHeader(header); host != "" { + return host + } + } + return GetHost(ctx.request) }