Skip to content

Commit

Permalink
improve remote addr parsing as requested at: #1453
Browse files Browse the repository at this point in the history
Former-commit-id: e5fde988eda9bf582b04285a1c77ba123910a699
  • Loading branch information
kataras committed Apr 20, 2020
1 parent c5392ed commit 6c6de6b
Show file tree
Hide file tree
Showing 8 changed files with 297 additions and 50 deletions.
2 changes: 2 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ Other Improvements:

- Hero Handlers (and `app.ConfigureContainer().Handle`) do not have to require `iris.Context` just to call `ctx.Next()` anymore, this is done automatically now.

- Improve Remote Address parsing as requested at: https://github.com/kataras/iris/issues/1453. Add `Configuration.RemoteAddrPrivateSubnets` to exclude those addresses when fetched by `Configuration.RemoteAddrHeaders` through `context.RemoteAddr() string`.

New Context Methods:

- `context.UpsertCookie(*http.Cookie, cookieOptions ...context.CookieOption)` upserts a cookie, fixes [#1485](https://github.com/kataras/iris/issues/1485) too
Expand Down
114 changes: 88 additions & 26 deletions _examples/configuration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,21 @@ func main() {
## Builtin Configurators

```go

// WithGlobalConfiguration will load the global yaml configuration file
// from the home directory and it will set/override the whole app's configuration
// to that file's contents. The global configuration file can be modified by user
// and be used by multiple iris instances.
//
// This is useful when we run multiple iris servers that share the same
// configuration, even with custom values at its "Other" field.
//
// Usage: `app.Configure(iris.WithGlobalConfiguration)` or `app.Run([iris.Runner], iris.WithGlobalConfiguration)`.
WithGlobalConfiguration

// variables for configurators don't need any receivers, functions
// for them that need (helps code editors to recognise as variables without parenthesis completion).

// WithoutServerError will cause to ignore the matched "errors"
// from the main application's `Run` function.
//
Expand All @@ -147,81 +162,128 @@ func main() {
// See `Configuration#IgnoreServerErrors []string` too.
//
// Example: https://github.com/kataras/iris/tree/master/_examples/http-listening/listen-addr/omit-server-errors
func WithoutServerError(errors ...error) Configurator
WithoutServerError(errors ...error) Configurator

// WithoutStartupLog turns off the information send, once, to the terminal when the main server is open.
var WithoutStartupLog
WithoutStartupLog

// WithoutInterruptHandler disables the automatic graceful server shutdown
// when control/cmd+C pressed.
var WithoutInterruptHandler
WithoutInterruptHandle

// WithoutPathCorrection disables the PathCorrection setting.
//
// See `Configuration`.
var WithoutPathCorrection
WithoutPathCorrectio

// WithoutPathCorrectionRedirection disables the PathCorrectionRedirection setting.
//
// See `Configuration`.
WithoutPathCorrectionRedirection

// WithoutBodyConsumptionOnUnmarshal disables BodyConsumptionOnUnmarshal setting.
//
// See `Configuration`.
var WithoutBodyConsumptionOnUnmarshal
WithoutBodyConsumptionOnUnmarshal

// WithoutAutoFireStatusCode disables the AutoFireStatusCode setting.
//
// See `Configuration`.
var WithoutAutoFireStatusCode
WithoutAutoFireStatusCode

// WithPathEscape enanbles the PathEscape setting.
// WithPathEscape enables the PathEscape setting.
//
// See `Configuration`.
var WithPathEscape
WithPathEscape

// WithOptimizations can force the application to optimize for the best performance where is possible.
//
// See `Configuration`.
var WithOptimizations
WithOptimizations

// WithFireMethodNotAllowed enanbles the FireMethodNotAllowed setting.
// WithFireMethodNotAllowed enables the FireMethodNotAllowed setting.
//
// See `Configuration`.
var WithFireMethodNotAllowed
WithFireMethodNotAllowed

// WithTimeFormat sets the TimeFormat setting.
//
// See `Configuration`.
func WithTimeFormat(timeformat string) Configurator
WithTimeFormat(timeformat string) Configurator

// WithCharset sets the Charset setting.
//
// See `Configuration`.
func WithCharset(charset string) Configurator
WithCharset(charset string) Configurator

// WithPostMaxMemory sets the maximum post data size
// that a client can send to the server, this differs
// from the overral request body size which can be modified
// by the `context#SetMaxRequestBodySize` or `iris#LimitRequestBodySize`.
//
// Defaults to 32MB or 32 << 20 if you prefer.
WithPostMaxMemory(limit int64) Configurator

// WithRemoteAddrHeader enables or adds a new or existing request header name
// that can be used to validate the client's real IP.
//
// Existing values are:
// "X-Real-Ip": false,
// "X-Forwarded-For": false,
// "CF-Connecting-IP": false
// By-default no "X-" header is consired safe to be used for retrieving the
// client's IP address, because those headers can manually change by
// the client. But sometimes are useful e.g., when behind a proxy
// you want to enable the "X-Forwarded-For" or when cloudflare
// you want to enable the "CF-Connecting-IP", inneed you
// can allow the `ctx.RemoteAddr()` to use any header
// that the client may sent.
//
// Defaults to an empty map but an example usage is:
// WithRemoteAddrHeader("X-Forwarded-For")
//
// Look `context.RemoteAddr()` for more.
func WithRemoteAddrHeader(headerName string) Configurator
WithRemoteAddrHeader(headerName string) Configurator

// WithoutRemoteAddrHeader disables an existing request header name
// that can be used to validate the client's real IP.
// that can be used to validate and parse the client's real IP.
//
// Existing values are:
// "X-Real-Ip": false,
// "X-Forwarded-For": false,
// "CF-Connecting-IP": false
//
// Keep note that RemoteAddrHeaders is already defaults to an empty map
// so you don't have to call this Configurator if you didn't
// add allowed headers via configuration or via `WithRemoteAddrHeader` before.
//
// Look `context.RemoteAddr()` for more.
func WithoutRemoteAddrHeader(headerName string) Configurator
WithoutRemoteAddrHeader(headerName string) Configurator

// WithRemoteAddrPrivateSubnet adds a new private sub-net to be excluded from `context.RemoteAddr`.
// See `WithRemoteAddrHeader` too.
WithRemoteAddrPrivateSubnet(startIP, endIP string) Configurator

// WithOtherValue adds a value based on a key to the Other setting.
//
// See `Configuration`.
func WithOtherValue(key string, val interface{}) Configurator
// See `Configuration.Other`.
WithOtherValue(key string, val interface{}) Configurator

// WithSitemap enables the sitemap generator.
// Use the Route's `SetLastMod`, `SetChangeFreq` and `SetPriority` to modify
// the sitemap's URL child element properties.
//
// It accepts a "startURL" input argument which
// is the prefix for the registered routes that will be included in the sitemap.
//
// If more than 50,000 static routes are registered then sitemaps will be splitted and a sitemap index will be served in
// /sitemap.xml.
//
// If `Application.I18n.Load/LoadAssets` is called then the sitemap will contain translated links for each static route.
//
// If the result does not complete your needs you can take control
// and use the github.com/kataras/sitemap package to generate a customized one instead.
//
// Example: https://github.com/kataras/iris/tree/master/_examples/sitemap.
WithSitemap(startURL string) Configurator

// WithTunneling is the `iris.Configurator` for the `iris.Configuration.Tunneling` field.
// It's used to enable http tunneling for an Iris Application, per registered host
//
// Alternatively use the `iris.WithConfiguration(iris.Configuration{Tunneling: iris.TunnelingConfiguration{ ...}}}`.
WithTunneling
```

## Custom Configurator
Expand Down
60 changes: 53 additions & 7 deletions configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"os"
"os/exec"
Expand Down Expand Up @@ -350,6 +351,17 @@ func WithoutRemoteAddrHeader(headerName string) Configurator {
}
}

// WithRemoteAddrPrivateSubnet adds a new private sub-net to be excluded from `context.RemoteAddr`.
// See `WithRemoteAddrHeader` too.
func WithRemoteAddrPrivateSubnet(startIP, endIP string) Configurator {
return func(app *Application) {
app.config.RemoteAddrPrivateSubnets = append(app.config.RemoteAddrPrivateSubnets, netutil.IPRange{
Start: net.IP(startIP),
End: net.IP(endIP),
})
}
}

// WithOtherValue adds a value based on a key to the Other setting.
//
// See `Configuration.Other`.
Expand Down Expand Up @@ -857,6 +869,19 @@ 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 `Request.RemoteAddr`.
// For details please navigate through: https://github.com/kataras/iris/issues/1453
// Defaults to an empty slice, usage:
//
// RemoteAddrPrivateSubnets {
// {Start: "10.0.0.0", End: "10.255.255.255"},
// {Start: "100.64.0.0", End: "100.127.255.255"},
// }
//
// Look `context.RemoteAddr()` for more.
RemoteAddrPrivateSubnets []netutil.IPRange `json:"remoteAddrPrivateSubnets" yaml:"RemoteAddrPrivateSubnets" toml:"RemoteAddrPrivateSubnets"`
// Other are the custom, dynamic options, can be empty.
// This field used only by you to set any app's options you want.
//
Expand Down Expand Up @@ -993,6 +1018,22 @@ func (c Configuration) GetRemoteAddrHeaders() map[string]bool {
return c.RemoteAddrHeaders
}

// GetRemoteAddrPrivateSubnets returns the configuration's private sub-networks.
// They are used to be compared against
// IP Addresses fetched through `RemoteAddrHeaders` or `Request.RemoteAddr`.
// For details please navigate through: https://github.com/kataras/iris/issues/1453
// Defaults to an empty slice, usage:
//
// RemoteAddrPrivateSubnets {
// {Start: "10.0.0.0", End: "10.255.255.255"},
// {Start: "100.64.0.0", End: "100.127.255.255"},
// }
//
// Look `context.RemoteAddr()` for more.
func (c Configuration) GetRemoteAddrPrivateSubnets() []netutil.IPRange {
return c.RemoteAddrPrivateSubnets
}

// GetOther returns the Configuration#Other map.
func (c Configuration) GetOther() map[string]interface{} {
return c.Other
Expand Down Expand Up @@ -1087,6 +1128,10 @@ func WithConfiguration(c Configuration) Configurator {
}
}

if v := c.RemoteAddrPrivateSubnets; len(v) > 0 {
main.RemoteAddrPrivateSubnets = v
}

if v := c.Other; len(v) > 0 {
if main.Other == nil {
main.Other = make(map[string]interface{}, len(v))
Expand Down Expand Up @@ -1116,12 +1161,13 @@ func DefaultConfiguration() Configuration {
// The request body the size limit
// can be set by the middleware `LimitRequestBodySize`
// or `context#SetMaxRequestBodySize`.
PostMaxMemory: 32 << 20, // 32MB
LocaleContextKey: "iris.locale",
ViewLayoutContextKey: "iris.viewLayout",
ViewDataContextKey: "iris.viewData",
RemoteAddrHeaders: make(map[string]bool),
EnableOptimizations: false,
Other: make(map[string]interface{}),
PostMaxMemory: 32 << 20, // 32MB
LocaleContextKey: "iris.locale",
ViewLayoutContextKey: "iris.viewLayout",
ViewDataContextKey: "iris.viewData",
RemoteAddrHeaders: make(map[string]bool),
RemoteAddrPrivateSubnets: []netutil.IPRange{},
EnableOptimizations: false,
Other: make(map[string]interface{}),
}
}
5 changes: 4 additions & 1 deletion configuration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,13 @@ func createGlobalConfiguration(t *testing.T) {
}

func TestConfigurationGlobal(t *testing.T) {
t.Cleanup(func() {
os.Remove(homeConfigurationFilename(".yml"))
})

createGlobalConfiguration(t)

testConfigurationGlobal(t, WithGlobalConfiguration)
// globalConfigurationKeyword = "~""
testConfigurationGlobal(t, WithConfiguration(YAML(globalConfigurationKeyword)))
}

Expand Down
18 changes: 17 additions & 1 deletion context/configuration.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package context

import (
"github.com/kataras/iris/v12/core/netutil"
)

// ConfigurationReadOnly can be implemented
// by Configuration, it's being used inside the Context.
// All methods that it contains should be "safe" to be called by the context
Expand Down Expand Up @@ -91,7 +95,19 @@ type ConfigurationReadOnly interface {
//
// Look `context.RemoteAddr()` for more.
GetRemoteAddrHeaders() map[string]bool

// GetRemoteAddrPrivateSubnets returns the configuration's private sub-networks.
// They are used to be compared against
// IP Addresses fetched through `RemoteAddrHeaders` or `Request.RemoteAddr`.
// For details please navigate through: https://github.com/kataras/iris/issues/1453
// Defaults to an empty slice, usage:
//
// RemoteAddrPrivateSubnets {
// {Start: "10.0.0.0", End: "10.255.255.255"},
// {Start: "100.64.0.0", End: "100.127.255.255"},
// }
//
// Look `context.RemoteAddr()` for more.
GetRemoteAddrPrivateSubnets() []netutil.IPRange
// GetOther returns the configuration.Other map.
GetOther() map[string]interface{}
}
27 changes: 12 additions & 15 deletions context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"unsafe"

"github.com/kataras/iris/v12/core/memstore"
"github.com/kataras/iris/v12/core/netutil"

"github.com/Shopify/goreferrer"
"github.com/fatih/structs"
Expand Down Expand Up @@ -358,7 +359,8 @@ type Context interface {
//
// Look `Configuration.RemoteAddrHeaders`,
// `Configuration.WithRemoteAddrHeader(...)`,
// `Configuration.WithoutRemoteAddrHeader(...)` for more.
// `Configuration.WithoutRemoteAddrHeader(...)`and
// `Configuration.RemoteAddrPrivateSubnets` for more.
RemoteAddr() string
// GetHeader returns the request header's value based on its name.
GetHeader(name string) string
Expand Down Expand Up @@ -1753,25 +1755,20 @@ const xForwardedForHeaderKey = "X-Forwarded-For"
//
// Look `Configuration.RemoteAddrHeaders`,
// `Configuration.WithRemoteAddrHeader(...)`,
// `Configuration.WithoutRemoteAddrHeader(...)` for more.
// `Configuration.WithoutRemoteAddrHeader(...)` and
// `Configuration.RemoteAddrPrivateSubnets` for more.
func (ctx *context) RemoteAddr() string {
remoteHeaders := ctx.Application().ConfigurationReadOnly().GetRemoteAddrHeaders()
privateSubnets := ctx.Application().ConfigurationReadOnly().GetRemoteAddrPrivateSubnets()

for headerName, enabled := range remoteHeaders {
if enabled {
headerValue := ctx.GetHeader(headerName)
// exception needed for 'X-Forwarded-For' only , if enabled.
if headerName == xForwardedForHeaderKey {
idx := strings.IndexByte(headerValue, ',')
if idx >= 0 {
headerValue = headerValue[0:idx]
}
}
if !enabled {
continue
}

realIP := strings.TrimSpace(headerValue)
if realIP != "" {
return realIP
}
ipAddresses := strings.Split(ctx.GetHeader(headerName), ",")
if ip, ok := netutil.GetIPAddress(ipAddresses, privateSubnets); ok {
return ip
}
}

Expand Down
Loading

0 comments on commit 6c6de6b

Please sign in to comment.