Skip to content

Commit

Permalink
Add appendXForwardedFor option
Browse files Browse the repository at this point in the history
  • Loading branch information
jliebert committed Jan 13, 2024
1 parent 2b9bb94 commit 6e693e5
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 16 deletions.
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ Traefik plugin to handle traffic coming from Cloudflare.

### Plugin options

| Key | Type | Default | Description |
|:------------------------:|:---------------:|:-------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------:|
| `trustedCIDRs` | `[]string` | `[]` | Requests coming from a source not matching any of these CIDRs will be terminated with a 403. If empty, it is populated with Cloudflare's CIDRs. |
| `allowedCIDRs` | `[]string` | `[]` | Requests coming from a source matching any of these CIDRs will not be terminated with a 403 and no overwrite of request header append. |
| `refreshInterval` | `time.Duration` | `24h` | When `trustedCIDRs` is empty, Cloudflare's CIDRs will be refreshed after this duration. Using a value of 0 seconds disables the refresh. |
| `overwriteRequestHeader` | `bool` | `true` | When `true`, the request's header are rewrite. When `false` any header or traefik RemoteAddress are modified, filter only the request from Cloudflare IP. |
| `debug` | `bool` | `false` | Output debug message in traefik log. |
| Key | Type | Default | Description |
|:------------------------:|:---------------:|:--------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------:|
| `trustedCIDRs` | `[]string` | `[]` | Requests coming from a source not matching any of these CIDRs will be terminated with a 403. If empty, it is populated with Cloudflare's CIDRs. |
| `allowedCIDRs` | `[]string` | `[]` | Requests coming from a source matching any of these CIDRs will not be terminated with a 403 and no overwrite of request header append. |
| `refreshInterval` | `time.Duration` | `24h` | When `trustedCIDRs` is empty, Cloudflare's CIDRs will be refreshed after this duration. Using a value of 0 seconds disables the refresh. |
| `overwriteRequestHeader` | `bool` | `true` | When `true`, the request's header are rewrite. When `false` any header or traefik RemoteAddress are modified, filter only the request from Cloudflare IP. |
| `appendXForwardedFor` | `bool` | `false` | Work only when `overwriteRequestHeader` `true`, When `true` prepend Cloudflare IP to XForwardedFor instead of replace XForwardedFor first value. |
| `debug` | `bool` | `false` | Output debug message in traefik log. |

### Traefik static configuration

Expand Down
26 changes: 17 additions & 9 deletions cloudflare.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type Config struct {
AllowedCIDRs []string `json:"allowedCIDRs,omitempty"`
RefreshInterval string `json:"refreshInterval,omitempty"`
OverwriteRequestHeader bool `json:"overwriteRequestHeader,omitempty"`
AppendXForwardedFor bool `json:"appendXForwardedFor,omitempty"`
Debug bool `json:"debug,omitempty"`
}

Expand All @@ -31,6 +32,7 @@ func CreateConfig() *Config {
AllowedCIDRs: nil,
RefreshInterval: defaultRefresh,
OverwriteRequestHeader: true,
AppendXForwardedFor: false,
Debug: false,
}
}
Expand All @@ -41,6 +43,7 @@ type Cloudflare struct {
trustedChecker ipChecker
allowedChecker ipChecker
overwriteRequestHeader bool
appendXForwardedFor bool
debug bool
}

Expand All @@ -58,6 +61,7 @@ func New(ctx context.Context, next http.Handler, config *Config, name string) (h
next: next,
name: name,
overwriteRequestHeader: config.OverwriteRequestHeader,
appendXForwardedFor: config.AppendXForwardedFor,
debug: config.Debug,
}

Expand Down Expand Up @@ -187,7 +191,7 @@ func (c *Cloudflare) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}

if c.overwriteRequestHeader && trusted {
err = overwriteRequestHeader(r)
err = overwriteRequestHeader(r, c.appendXForwardedFor)
if err != nil {
if c.debug {
log.Println(fmt.Errorf("debug: %w", err))
Expand All @@ -201,7 +205,7 @@ func (c *Cloudflare) ServeHTTP(w http.ResponseWriter, r *http.Request) {
c.next.ServeHTTP(w, r)
}

func overwriteRequestHeader(r *http.Request) error {
func overwriteRequestHeader(r *http.Request, appendXForwardedFor bool) error {
ip := r.Header.Get("CF-Connecting-IP")
if ip == "" {
return errors.New("missing CF-Connecting-IP header")
Expand All @@ -216,12 +220,16 @@ func overwriteRequestHeader(r *http.Request) error {
}

ipList := XForwardedIpValues(r)
if len(ipList) == 0 {
r.RemoteAddr = ip
ipList = append(ipList, ip)
} else {
ipList[0] = ip
}
if appendXForwardedFor {
ipList = append([]string{ip}, ipList...)
} else {
if len(ipList) == 0 {
r.RemoteAddr = ip
ipList = []string{ip}
} else {
ipList[0] = ip
}
}

r.Header.Set("X-Forwarded-For", strings.Join(ipList, ", "))
r.Header.Set("X-Real-Ip", ip)
Expand All @@ -243,4 +251,4 @@ func XForwardedIpValues(r *http.Request) []string {
}

return list
}
}
66 changes: 66 additions & 0 deletions cloudflare_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,72 @@ func TestCloudflare(t *testing.T) {
})
})

t.Run("overwrite header request with append xff", func(t *testing.T) {
cfg := cloudflare.CreateConfig()
cfg.TrustedCIDRs = []string{"0.0.0.0/0"}
cfg.OverwriteRequestHeader = true
cfg.AppendXForwardedFor = true
cfg.Debug = true

ctx := context.Background()
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})

handler, err := cloudflare.New(ctx, next, cfg, "cloudflare")
require.NoError(t, err)

t.Run("valid", func(t *testing.T) {
req := &http.Request{
RemoteAddr: "172.16.1.1:42",
Header: makeHeaders(map[string]string{
"CF-Connecting-IP": "1.2.3.4",
"CF-Visitor": "{\"scheme\":\"https\"}",
}),
}

rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)

xff := strings.Join(req.Header.Values("X-Forwarded-For"), ",")
require.Equal(t, "1.2.3.4", xff)

xri := strings.Join(req.Header.Values("X-Real-Ip"), ",")
require.Equal(t, "1.2.3.4", xri)

xfp := strings.Join(req.Header.Values("X-Forwarded-Proto"), ",")
require.Equal(t, "https", xfp)

res := rr.Result()
require.Equal(t, http.StatusOK, res.StatusCode)
})

t.Run("overwrite", func(t *testing.T) {
req := &http.Request{
RemoteAddr: "172.16.1.1:42",
Header: makeHeaders(map[string]string{
"CF-Connecting-IP": "1.2.3.4",
"CF-Visitor": "{\"scheme\":\"https\"}",
"X-Forwarded-For": "1.1.1.1, 2.2.2.2",
"X-Real-Ip": "172.16.1.1",
}),
}

rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)

xff := strings.Join(req.Header.Values("X-Forwarded-For"), ",")
require.Equal(t, "1.2.3.4, 1.1.1.1, 2.2.2.2", xff)

xri := strings.Join(req.Header.Values("X-Real-Ip"), ",")
require.Equal(t, "1.2.3.4", xri)

xfp := strings.Join(req.Header.Values("X-Forwarded-Proto"), ",")
require.Equal(t, "https", xfp)

res := rr.Result()
require.Equal(t, http.StatusOK, res.StatusCode)
})
})

t.Run("no overwrite header request", func(t *testing.T) {
cfg := cloudflare.CreateConfig()
cfg.TrustedCIDRs = []string{"0.0.0.0/0"}
Expand Down

0 comments on commit 6e693e5

Please sign in to comment.