Add rejectStatusCode
option to IPAllowList
middleware
This commit is contained in:
parent
fea94a3393
commit
ccf3a9995a
12 changed files with 108 additions and 12 deletions
|
@ -207,3 +207,45 @@ http:
|
||||||
[http.middlewares.test-ipallowlist.ipAllowList.ipStrategy]
|
[http.middlewares.test-ipallowlist.ipAllowList.ipStrategy]
|
||||||
excludedIPs = ["127.0.0.1/32", "192.168.1.7"]
|
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
|
||||||
|
```
|
||||||
|
|
|
@ -68,6 +68,7 @@
|
||||||
- "traefik.http.middlewares.middleware11.ipallowlist.ipstrategy.depth=42"
|
- "traefik.http.middlewares.middleware11.ipallowlist.ipstrategy.depth=42"
|
||||||
- "traefik.http.middlewares.middleware11.ipallowlist.ipstrategy.excludedips=foobar, foobar"
|
- "traefik.http.middlewares.middleware11.ipallowlist.ipstrategy.excludedips=foobar, foobar"
|
||||||
- "traefik.http.middlewares.middleware11.ipallowlist.sourcerange=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.amount=42"
|
||||||
- "traefik.http.middlewares.middleware12.inflightreq.sourcecriterion.ipstrategy.depth=42"
|
- "traefik.http.middlewares.middleware12.inflightreq.sourcecriterion.ipstrategy.depth=42"
|
||||||
- "traefik.http.middlewares.middleware12.inflightreq.sourcecriterion.ipstrategy.excludedips=foobar, foobar"
|
- "traefik.http.middlewares.middleware12.inflightreq.sourcecriterion.ipstrategy.excludedips=foobar, foobar"
|
||||||
|
|
|
@ -199,6 +199,7 @@
|
||||||
[http.middlewares.Middleware11]
|
[http.middlewares.Middleware11]
|
||||||
[http.middlewares.Middleware11.ipAllowList]
|
[http.middlewares.Middleware11.ipAllowList]
|
||||||
sourceRange = ["foobar", "foobar"]
|
sourceRange = ["foobar", "foobar"]
|
||||||
|
rejectStatusCode = 404
|
||||||
[http.middlewares.Middleware11.ipAllowList.ipStrategy]
|
[http.middlewares.Middleware11.ipAllowList.ipStrategy]
|
||||||
depth = 42
|
depth = 42
|
||||||
excludedIPs = ["foobar", "foobar"]
|
excludedIPs = ["foobar", "foobar"]
|
||||||
|
|
|
@ -225,6 +225,7 @@ http:
|
||||||
isDevelopment: true
|
isDevelopment: true
|
||||||
Middleware11:
|
Middleware11:
|
||||||
ipAllowList:
|
ipAllowList:
|
||||||
|
rejectStatusCode: 404
|
||||||
sourceRange:
|
sourceRange:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
|
|
|
@ -1181,6 +1181,10 @@ spec:
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
type: object
|
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:
|
sourceRange:
|
||||||
description: SourceRange defines the set of allowed IPs (or ranges
|
description: SourceRange defines the set of allowed IPs (or ranges
|
||||||
of allowed IPs by using CIDR notation).
|
of allowed IPs by using CIDR notation).
|
||||||
|
|
|
@ -81,6 +81,7 @@
|
||||||
| `traefik/http/middlewares/Middleware11/ipAllowList/ipStrategy/depth` | `42` |
|
| `traefik/http/middlewares/Middleware11/ipAllowList/ipStrategy/depth` | `42` |
|
||||||
| `traefik/http/middlewares/Middleware11/ipAllowList/ipStrategy/excludedIPs/0` | `foobar` |
|
| `traefik/http/middlewares/Middleware11/ipAllowList/ipStrategy/excludedIPs/0` | `foobar` |
|
||||||
| `traefik/http/middlewares/Middleware11/ipAllowList/ipStrategy/excludedIPs/1` | `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/0` | `foobar` |
|
||||||
| `traefik/http/middlewares/Middleware11/ipAllowList/sourceRange/1` | `foobar` |
|
| `traefik/http/middlewares/Middleware11/ipAllowList/sourceRange/1` | `foobar` |
|
||||||
| `traefik/http/middlewares/Middleware12/inFlightReq/amount` | `42` |
|
| `traefik/http/middlewares/Middleware12/inFlightReq/amount` | `42` |
|
||||||
|
|
|
@ -606,6 +606,10 @@ spec:
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
type: object
|
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:
|
sourceRange:
|
||||||
description: SourceRange defines the set of allowed IPs (or ranges
|
description: SourceRange defines the set of allowed IPs (or ranges
|
||||||
of allowed IPs by using CIDR notation).
|
of allowed IPs by using CIDR notation).
|
||||||
|
|
|
@ -1181,6 +1181,10 @@ spec:
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
type: object
|
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:
|
sourceRange:
|
||||||
description: SourceRange defines the set of allowed IPs (or ranges
|
description: SourceRange defines the set of allowed IPs (or ranges
|
||||||
of allowed IPs by using CIDR notation).
|
of allowed IPs by using CIDR notation).
|
||||||
|
|
|
@ -383,6 +383,9 @@ type IPAllowList struct {
|
||||||
// SourceRange defines the set of allowed IPs (or ranges of allowed IPs by using CIDR notation).
|
// 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"`
|
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"`
|
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
|
// +k8s:deepcopy-gen=true
|
||||||
|
|
|
@ -1254,6 +1254,7 @@ func TestEncodeConfiguration(t *testing.T) {
|
||||||
"traefik.HTTP.Middlewares.Middleware8.Headers.STSSeconds": "42",
|
"traefik.HTTP.Middlewares.Middleware8.Headers.STSSeconds": "42",
|
||||||
"traefik.HTTP.Middlewares.Middleware9.IPAllowList.IPStrategy.Depth": "42",
|
"traefik.HTTP.Middlewares.Middleware9.IPAllowList.IPStrategy.Depth": "42",
|
||||||
"traefik.HTTP.Middlewares.Middleware9.IPAllowList.IPStrategy.ExcludedIPs": "foobar, fiibar",
|
"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.Middleware9.IPAllowList.SourceRange": "foobar, fiibar",
|
||||||
"traefik.HTTP.Middlewares.Middleware10.InFlightReq.Amount": "42",
|
"traefik.HTTP.Middlewares.Middleware10.InFlightReq.Amount": "42",
|
||||||
"traefik.HTTP.Middlewares.Middleware10.InFlightReq.SourceCriterion.IPStrategy.Depth": "42",
|
"traefik.HTTP.Middlewares.Middleware10.InFlightReq.SourceCriterion.IPStrategy.Depth": "42",
|
||||||
|
|
|
@ -20,10 +20,11 @@ const (
|
||||||
|
|
||||||
// ipAllowLister is a middleware that provides Checks of the Requesting IP against a set of Allowlists.
|
// ipAllowLister is a middleware that provides Checks of the Requesting IP against a set of Allowlists.
|
||||||
type ipAllowLister struct {
|
type ipAllowLister struct {
|
||||||
next http.Handler
|
next http.Handler
|
||||||
allowLister *ip.Checker
|
allowLister *ip.Checker
|
||||||
strategy ip.Strategy
|
strategy ip.Strategy
|
||||||
name string
|
name string
|
||||||
|
rejectStatusCode int
|
||||||
}
|
}
|
||||||
|
|
||||||
// New builds a new IPAllowLister given a list of CIDR-Strings to allow.
|
// New builds a new IPAllowLister given a list of CIDR-Strings to allow.
|
||||||
|
@ -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")
|
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)
|
checker, err := ip.NewChecker(config.SourceRange)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot parse CIDRs %s: %w", config.SourceRange, err)
|
return nil, fmt.Errorf("cannot parse CIDRs %s: %w", config.SourceRange, err)
|
||||||
|
@ -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)
|
logger.Debug().Msgf("Setting up IPAllowLister with sourceRange: %s", config.SourceRange)
|
||||||
|
|
||||||
return &ipAllowLister{
|
return &ipAllowLister{
|
||||||
strategy: strategy,
|
strategy: strategy,
|
||||||
allowLister: checker,
|
allowLister: checker,
|
||||||
next: next,
|
next: next,
|
||||||
name: name,
|
name: name,
|
||||||
|
rejectStatusCode: rejectStatusCode,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +79,7 @@ func (al *ipAllowLister) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
msg := fmt.Sprintf("Rejecting IP %s: %v", clientIP, err)
|
msg := fmt.Sprintf("Rejecting IP %s: %v", clientIP, err)
|
||||||
logger.Debug().Msg(msg)
|
logger.Debug().Msg(msg)
|
||||||
tracing.SetStatusErrorf(req.Context(), msg)
|
tracing.SetStatusErrorf(req.Context(), msg)
|
||||||
reject(ctx, rw)
|
reject(ctx, al.rejectStatusCode, rw)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Debug().Msgf("Accepting IP %s", clientIP)
|
logger.Debug().Msgf("Accepting IP %s", clientIP)
|
||||||
|
@ -77,9 +87,7 @@ func (al *ipAllowLister) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
al.next.ServeHTTP(rw, req)
|
al.next.ServeHTTP(rw, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func reject(ctx context.Context, rw http.ResponseWriter) {
|
func reject(ctx context.Context, statusCode int, rw http.ResponseWriter) {
|
||||||
statusCode := http.StatusForbidden
|
|
||||||
|
|
||||||
rw.WriteHeader(statusCode)
|
rw.WriteHeader(statusCode)
|
||||||
_, err := rw.Write([]byte(http.StatusText(statusCode)))
|
_, err := rw.Write([]byte(http.StatusText(statusCode)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -30,6 +30,14 @@ func TestNewIPAllowLister(t *testing.T) {
|
||||||
SourceRange: []string{"10.10.10.10"},
|
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 {
|
for _, test := range testCases {
|
||||||
|
@ -73,6 +81,24 @@ func TestIPAllowLister_ServeHTTP(t *testing.T) {
|
||||||
remoteAddr: "20.20.20.21:1234",
|
remoteAddr: "20.20.20.21:1234",
|
||||||
expected: 403,
|
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 {
|
for _, test := range testCases {
|
||||||
|
|
Loading…
Reference in a new issue