Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preserve original header value in a new header #12

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,18 @@ files and with other plugins.
realip [cidr] {
header name
from cidr [cidr... ]
preserve
strict
}
```

name is the name of the header containing the actual IP address. Default is X-Forwarded-For.
`name` is the name of the header containing the actual IP address. Default is `X-Forwarded-For`.

cidr is the address range of expected proxy servers. As a security measure, IP headers are only accepted from known proxy servers. Must be a valid cidr block notation. This may be specified multiple times.
`cidr` is the address range of expected proxy servers. As a security measure, IP headers are only accepted from known proxy servers. Must be a valid cidr block notation. This may be specified multiple times.

strict, if specified, will reject requests from unkown proxy IPs with a 403 status. If not specified, it will simply leave the original IP in place.
`preserve`, if specified, will retain the original `RemoteAddr` value in an `Original-Remote-Address` header value, which will be passed to other middleware or to upstream servers in a proxy configuration.

`strict`, if specified, will reject requests from unkown proxy IPs with a 403 status. If not specified, it will simply leave the original IP in place.

## CIDR blocks

Expand Down
11 changes: 10 additions & 1 deletion module.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package realip

import (
"fmt"
"github.com/mholt/caddy/caddyhttp/httpserver"
"net"
"net/http"
"strings"

"github.com/mholt/caddy/caddyhttp/httpserver"
)

type module struct {
Expand All @@ -19,6 +20,10 @@ type module struct {
// The default is 5, -1 to disable. If set to 0, any request with a forward header will be rejected
MaxHops int
Strict bool
// Preserves the original value of the RemoteAddr request property in a header
// which is passed on to other middleware and/or upstream servers in the case
// of a proxy configuration.
Preserve bool
}

func (m *module) validSource(addr string) bool {
Expand All @@ -35,6 +40,10 @@ func (m *module) validSource(addr string) bool {
}

func (m *module) ServeHTTP(w http.ResponseWriter, req *http.Request) (int, error) {
originalRemoteAddr := req.RemoteAddr
if m.Preserve {
req.Header.Set("Original-Remote-Address", originalRemoteAddr)
}
host, port, err := net.SplitHostPort(req.RemoteAddr)
if err != nil || !m.validSource(host) {
if m.Strict {
Expand Down
54 changes: 54 additions & 0 deletions realip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"bytes"
"fmt"

"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
)
Expand Down Expand Up @@ -124,6 +125,59 @@ func TestCidrAndPresets(t *testing.T) {
}
}

func TestPreserve(t *testing.T) {
for i, test := range []struct {
actualIP string
headerVal string
expectedIP string
}{
{"1.2.3.4:123", "", "1.2.3.4:123"},
{"4.4.255.255:123", "", "4.4.255.255:123"},
{"4.5.0.0:123", "1.2.3.4", "1.2.3.4:123"},

// because 111.111.111.111 is NOT in a trusted subnet, the next in the chain should not be trusted
{"4.5.2.3:123", "1.2.6.7,5.6.7.8,111.111.111.111", "111.111.111.111:123"},
{"4.5.5.5:123", "NOTANIP", "4.5.5.5:123"},
{"aaaaaa", "1.2.3.4", "aaaaaa"},
{"aaaaaa:123", "1.2.3.4", "aaaaaa:123"},

{"4.5.2.3:123", "1.2.6.7,5.6.7.8,4.5.6.7", "5.6.7.8:123"},
} {
originalRemoteAddr := ""
_, ipnet, err := net.ParseCIDR("4.5.0.0/16") // "4.5.x.x"
if err != nil {
t.Fatal(err)
}

he := &module{
next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
originalRemoteAddr = r.Header.Get("Original-Remote-Address")
return 0, nil
}),
Header: "X-Real-IP",
MaxHops: 5,
Preserve: true,
From: []*net.IPNet{ipnet},
}

req, err := http.NewRequest("GET", "http://foo.tld/", nil)
if err != nil {
t.Fatalf("Test %d: Could not create HTTP request: %v", i, err)
}
req.RemoteAddr = test.actualIP
if test.headerVal != "" {
req.Header.Set("X-Real-IP", test.headerVal)
}

rec := httptest.NewRecorder()
he.ServeHTTP(rec, req)

if test.expectedIP != "" && test.actualIP != originalRemoteAddr {
t.Errorf("Test %d: Expected '%s', but found '%s'", i, test.actualIP, originalRemoteAddr)
}
}
}

func parseCidrs(i int, values []string) ([]*net.IPNet, error) {
var cidrs []*net.IPNet
for _, value := range values {
Expand Down
2 changes: 2 additions & 0 deletions setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ func parse(m *module, c *caddy.Controller) (err error) {
m.Strict, err = BoolArg(c)
case "maxhops":
m.MaxHops, err = IntArg(c)
case "preserve":
m.Preserve, err = BoolArg(c)
default:
return c.Errf("Unknown realip arg: %s", c.Val())
}
Expand Down