Skip to content

Commit

Permalink
Add rejectStatusCode option to IPAllowList middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
jfly committed Jan 9, 2024
1 parent fea94a3 commit ccf3a99
Show file tree
Hide file tree
Showing 12 changed files with 108 additions and 12 deletions.
42 changes: 42 additions & 0 deletions docs/content/middlewares/http/ipallowlist.md
Expand Up @@ -207,3 +207,45 @@ http:
[http.middlewares.test-ipallowlist.ipAllowList.ipStrategy]
excludedIPs = ["127.0.0.1/32", "192.168.1.7"]
```

### `rejectStatusCode`

The `rejectStatusCode` option sets HTTP status code for refused requests. If not set, the default is 403 (Forbidden).

```yaml tab="Docker & Swarm"
# Reject requests with a 404 rather than a 403
labels:
- "traefik.http.middlewares.test-ipallowlist.ipallowlist.rejectstatuscode=404"
```

```yaml tab="Kubernetes"
# Reject requests with a 404 rather than a 403
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: test-ipallowlist
spec:
ipAllowList:
rejectStatusCode: 404
```

```yaml tab="Consul Catalog"
# Reject requests with a 404 rather than a 403
- "traefik.http.middlewares.test-ipallowlist.ipallowlist.rejectstatuscode=404"
```

```yaml tab="File (YAML)"
# Reject requests with a 404 rather than a 403
http:
middlewares:
test-ipallowlist:
ipAllowList:
rejectStatusCode: 404
```

```toml tab="File (TOML)"
# Reject requests with a 404 rather than a 403
[http.middlewares]
[http.middlewares.test-ipallowlist.ipAllowList]
rejectStatusCode = 404
```
Expand Up @@ -68,6 +68,7 @@
- "traefik.http.middlewares.middleware11.ipallowlist.ipstrategy.depth=42"
- "traefik.http.middlewares.middleware11.ipallowlist.ipstrategy.excludedips=foobar, foobar"
- "traefik.http.middlewares.middleware11.ipallowlist.sourcerange=foobar, foobar"
- "traefik.http.middlewares.middleware11.ipallowlist.rejectstatuscode=404"
- "traefik.http.middlewares.middleware12.inflightreq.amount=42"
- "traefik.http.middlewares.middleware12.inflightreq.sourcecriterion.ipstrategy.depth=42"
- "traefik.http.middlewares.middleware12.inflightreq.sourcecriterion.ipstrategy.excludedips=foobar, foobar"
Expand Down
1 change: 1 addition & 0 deletions docs/content/reference/dynamic-configuration/file.toml
Expand Up @@ -199,6 +199,7 @@
[http.middlewares.Middleware11]
[http.middlewares.Middleware11.ipAllowList]
sourceRange = ["foobar", "foobar"]
rejectStatusCode = 404
[http.middlewares.Middleware11.ipAllowList.ipStrategy]
depth = 42
excludedIPs = ["foobar", "foobar"]
Expand Down
1 change: 1 addition & 0 deletions docs/content/reference/dynamic-configuration/file.yaml
Expand Up @@ -225,6 +225,7 @@ http:
isDevelopment: true
Middleware11:
ipAllowList:
rejectStatusCode: 404
sourceRange:
- foobar
- foobar
Expand Down
Expand Up @@ -1181,6 +1181,10 @@ spec:
type: string
type: array
type: object
rejectStatusCode:
description: RejectStatusCode defines the HTTP status code used
for refused requests. If not set, the default is 403 (Forbidden).
type: integer
sourceRange:
description: SourceRange defines the set of allowed IPs (or ranges
of allowed IPs by using CIDR notation).
Expand Down
1 change: 1 addition & 0 deletions docs/content/reference/dynamic-configuration/kv-ref.md
Expand Up @@ -81,6 +81,7 @@
| `traefik/http/middlewares/Middleware11/ipAllowList/ipStrategy/depth` | `42` |
| `traefik/http/middlewares/Middleware11/ipAllowList/ipStrategy/excludedIPs/0` | `foobar` |
| `traefik/http/middlewares/Middleware11/ipAllowList/ipStrategy/excludedIPs/1` | `foobar` |
| `traefik/http/middlewares/Middleware11/ipAllowList/rejectStatusCode` | `404` |
| `traefik/http/middlewares/Middleware11/ipAllowList/sourceRange/0` | `foobar` |
| `traefik/http/middlewares/Middleware11/ipAllowList/sourceRange/1` | `foobar` |
| `traefik/http/middlewares/Middleware12/inFlightReq/amount` | `42` |
Expand Down
Expand Up @@ -606,6 +606,10 @@ spec:
type: string
type: array
type: object
rejectStatusCode:
description: RejectStatusCode defines the HTTP status code used
for refused requests. If not set, the default is 403 (Forbidden).
type: integer
sourceRange:
description: SourceRange defines the set of allowed IPs (or ranges
of allowed IPs by using CIDR notation).
Expand Down
4 changes: 4 additions & 0 deletions integration/fixtures/k8s/01-traefik-crd.yml
Expand Up @@ -1181,6 +1181,10 @@ spec:
type: string
type: array
type: object
rejectStatusCode:
description: RejectStatusCode defines the HTTP status code used
for refused requests. If not set, the default is 403 (Forbidden).
type: integer
sourceRange:
description: SourceRange defines the set of allowed IPs (or ranges
of allowed IPs by using CIDR notation).
Expand Down
3 changes: 3 additions & 0 deletions pkg/config/dynamic/middlewares.go
Expand Up @@ -383,6 +383,9 @@ type IPAllowList struct {
// SourceRange defines the set of allowed IPs (or ranges of allowed IPs by using CIDR notation).
SourceRange []string `json:"sourceRange,omitempty" toml:"sourceRange,omitempty" yaml:"sourceRange,omitempty"`
IPStrategy *IPStrategy `json:"ipStrategy,omitempty" toml:"ipStrategy,omitempty" yaml:"ipStrategy,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"`
// RejectStatusCode defines the HTTP status code used for refused requests.
// If not set, the default is 403 (Forbidden).
RejectStatusCode int `json:"rejectStatusCode,omitempty" toml:"rejectStatusCode,omitempty" yaml:"rejectStatusCode,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"`
}

// +k8s:deepcopy-gen=true
Expand Down
1 change: 1 addition & 0 deletions pkg/config/label/label_test.go
Expand Up @@ -1254,6 +1254,7 @@ func TestEncodeConfiguration(t *testing.T) {
"traefik.HTTP.Middlewares.Middleware8.Headers.STSSeconds": "42",
"traefik.HTTP.Middlewares.Middleware9.IPAllowList.IPStrategy.Depth": "42",
"traefik.HTTP.Middlewares.Middleware9.IPAllowList.IPStrategy.ExcludedIPs": "foobar, fiibar",
"traefik.HTTP.Middlewares.Middleware9.IPAllowList.RejectStatusCode": "0",
"traefik.HTTP.Middlewares.Middleware9.IPAllowList.SourceRange": "foobar, fiibar",
"traefik.HTTP.Middlewares.Middleware10.InFlightReq.Amount": "42",
"traefik.HTTP.Middlewares.Middleware10.InFlightReq.SourceCriterion.IPStrategy.Depth": "42",
Expand Down
32 changes: 20 additions & 12 deletions pkg/middlewares/ipallowlist/ip_allowlist.go
Expand Up @@ -20,10 +20,11 @@ const (

// ipAllowLister is a middleware that provides Checks of the Requesting IP against a set of Allowlists.
type ipAllowLister struct {
next http.Handler
allowLister *ip.Checker
strategy ip.Strategy
name string
next http.Handler
allowLister *ip.Checker
strategy ip.Strategy
name string
rejectStatusCode int
}

// New builds a new IPAllowLister given a list of CIDR-Strings to allow.
Expand All @@ -35,6 +36,14 @@ func New(ctx context.Context, next http.Handler, config dynamic.IPAllowList, nam
return nil, errors.New("sourceRange is empty, IPAllowLister not created")
}

rejectStatusCode := config.RejectStatusCode
// If RejectStatusCode is not given, default to Forbidden (403).
if rejectStatusCode == 0 {
rejectStatusCode = http.StatusForbidden
} else if http.StatusText(rejectStatusCode) == "" {
return nil, fmt.Errorf("invalid HTTP status code %d", rejectStatusCode)
}

checker, err := ip.NewChecker(config.SourceRange)
if err != nil {
return nil, fmt.Errorf("cannot parse CIDRs %s: %w", config.SourceRange, err)
Expand All @@ -48,10 +57,11 @@ func New(ctx context.Context, next http.Handler, config dynamic.IPAllowList, nam
logger.Debug().Msgf("Setting up IPAllowLister with sourceRange: %s", config.SourceRange)

return &ipAllowLister{
strategy: strategy,
allowLister: checker,
next: next,
name: name,
strategy: strategy,
allowLister: checker,
next: next,
name: name,
rejectStatusCode: rejectStatusCode,
}, nil
}

Expand All @@ -69,17 +79,15 @@ func (al *ipAllowLister) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
msg := fmt.Sprintf("Rejecting IP %s: %v", clientIP, err)
logger.Debug().Msg(msg)
tracing.SetStatusErrorf(req.Context(), msg)
reject(ctx, rw)
reject(ctx, al.rejectStatusCode, rw)
return
}
logger.Debug().Msgf("Accepting IP %s", clientIP)

al.next.ServeHTTP(rw, req)
}

func reject(ctx context.Context, rw http.ResponseWriter) {
statusCode := http.StatusForbidden

func reject(ctx context.Context, statusCode int, rw http.ResponseWriter) {
rw.WriteHeader(statusCode)
_, err := rw.Write([]byte(http.StatusText(statusCode)))
if err != nil {
Expand Down
26 changes: 26 additions & 0 deletions pkg/middlewares/ipallowlist/ip_allowlist_test.go
Expand Up @@ -30,6 +30,14 @@ func TestNewIPAllowLister(t *testing.T) {
SourceRange: []string{"10.10.10.10"},
},
},
{
desc: "invalid HTTP status code",
allowList: dynamic.IPAllowList{
SourceRange: []string{"10.10.10.10"},
RejectStatusCode: 600,
},
expectedError: true,
},
}

for _, test := range testCases {
Expand Down Expand Up @@ -73,6 +81,24 @@ func TestIPAllowLister_ServeHTTP(t *testing.T) {
remoteAddr: "20.20.20.21:1234",
expected: 403,
},
{
desc: "authorized with remote address, reject 404",
allowList: dynamic.IPAllowList{
SourceRange: []string{"20.20.20.20"},
RejectStatusCode: 404,
},
remoteAddr: "20.20.20.20:1234",
expected: 200,
},
{
desc: "non authorized with remote address, reject 404",
allowList: dynamic.IPAllowList{
SourceRange: []string{"20.20.20.20"},
RejectStatusCode: 404,
},
remoteAddr: "20.20.20.21:1234",
expected: 404,
},
}

for _, test := range testCases {
Expand Down

0 comments on commit ccf3a99

Please sign in to comment.