mirror of
https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin.git
synced 2026-07-21 11:38:59 +02:00
Rework variables name, and logic to check IPs with strategy
This commit is contained in:
@@ -32,7 +32,7 @@ run_cacheredis:
|
|||||||
docker-compose -f exemples/redis-cache/docker-compose.redis.yml up -d --remove-orphans
|
docker-compose -f exemples/redis-cache/docker-compose.redis.yml up -d --remove-orphans
|
||||||
|
|
||||||
run_trustedips:
|
run_trustedips:
|
||||||
docker-compose -f exemples/trusted-ips/docker-compose.redis.yml up -d --remove-orphans
|
docker-compose -f exemples/trusted-ips/docker-compose.trusted.yml up -d --remove-orphans
|
||||||
|
|
||||||
run:
|
run:
|
||||||
docker-compose -f docker-compose.yml up -d --remove-orphans
|
docker-compose -f docker-compose.yml up -d --remove-orphans
|
||||||
@@ -46,6 +46,15 @@ restart_local:
|
|||||||
restart:
|
restart:
|
||||||
docker-compose -f docker-compose.yml restart
|
docker-compose -f docker-compose.yml restart
|
||||||
|
|
||||||
|
restart_behindproxy:
|
||||||
|
docker-compose -f exemples/behind-proxy/docker-compose.cloudflare.yml restart
|
||||||
|
|
||||||
|
restart_cacheredis:
|
||||||
|
docker-compose -f exemples/redis-cache/docker-compose.redis.yml restart
|
||||||
|
|
||||||
|
restart_trustedips:
|
||||||
|
docker-compose -f exemples/trusted-ips/docker-compose.trusted.yml restart
|
||||||
|
|
||||||
show_logs:
|
show_logs:
|
||||||
docker-compose -f docker-compose.yml restart
|
docker-compose -f docker-compose.yml restart
|
||||||
|
|
||||||
|
|||||||
@@ -89,10 +89,10 @@ make run
|
|||||||
- string
|
- string
|
||||||
- default: "redis:6379"
|
- default: "redis:6379"
|
||||||
- hostname and port for the redis service
|
- hostname and port for the redis service
|
||||||
- TrustedIPs
|
- ClientTrustedIPs
|
||||||
- string
|
- string
|
||||||
- default: []
|
- default: []
|
||||||
- List of IPs we trust, they will bypass any more check to the bouncer or cache (usefull for LAN or VPN IP)
|
- List of client IPs we trust, they will bypass any more check to the bouncer or cache (usefull for LAN or VPN IP)
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
@@ -142,7 +142,7 @@ http:
|
|||||||
forwardedHeadersTrustedIPs:
|
forwardedHeadersTrustedIPs:
|
||||||
- 10.0.10.23/32
|
- 10.0.10.23/32
|
||||||
- 10.0.20.0/24
|
- 10.0.20.0/24
|
||||||
trustedIPs:
|
clientTrustedIPs:
|
||||||
- 192.168.1.0/24
|
- 192.168.1.0/24
|
||||||
forwardedHeadersCustomName: X-Custom-Header
|
forwardedHeadersCustomName: X-Custom-Header
|
||||||
redisCacheEnabled: false
|
redisCacheEnabled: false
|
||||||
@@ -229,6 +229,7 @@ You need to configure your Traefik to trust Forwarded headers by your front prox
|
|||||||
In the exemple we use another instance of traefik with the container named cloudflare to simulate a front proxy
|
In the exemple we use another instance of traefik with the container named cloudflare to simulate a front proxy
|
||||||
|
|
||||||
The "internal" Traefik instance is configured to trust the cloudflare forward headers
|
The "internal" Traefik instance is configured to trust the cloudflare forward headers
|
||||||
|
This helps Traefik choose the right IP of the client: see https://doc.traefik.io/traefik/routing/entrypoints/#forwarded-headers
|
||||||
```yaml
|
```yaml
|
||||||
- "--entrypoints.web.forwardedheaders.trustedips=172.21.0.5"
|
- "--entrypoints.web.forwardedheaders.trustedips=172.21.0.5"
|
||||||
```
|
```
|
||||||
@@ -261,12 +262,14 @@ make run_cacheredis
|
|||||||
You need to configure your Traefik to trust Forwarded headers by your front proxy
|
You need to configure your Traefik to trust Forwarded headers by your front proxy
|
||||||
In the exemple we use a whoami container protected by crowdsec, and we ban or IP before allowing using TrustedIPs
|
In the exemple we use a whoami container protected by crowdsec, and we ban or IP before allowing using TrustedIPs
|
||||||
|
|
||||||
|
If you are using another proxy in front, you need to add it's IP in the trusted IP for the forwarded headers.
|
||||||
|
This helps Traefik choose the right IP of the client: see https://doc.traefik.io/traefik/routing/entrypoints/#forwarded-headers
|
||||||
The "internal" Traefik instance is configured to trust the forward headers
|
The "internal" Traefik instance is configured to trust the forward headers
|
||||||
```yaml
|
```yaml
|
||||||
- "--entrypoints.web.forwardedheaders.trustedips=172.21.0.5"
|
- "--entrypoints.web.forwardedheaders.trustedips=172.21.0.5"
|
||||||
```
|
```
|
||||||
|
|
||||||
We configure the middleware to trust as well the IP:
|
We configure the middleware to trust as well the IP of the intermediate proxy if needed:
|
||||||
```yaml
|
```yaml
|
||||||
- "traefik.http.middlewares.crowdsec.plugin.bouncer.forwardedheaderstrustedips=172.21.0.5"
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.forwardedheaderstrustedips=172.21.0.5"
|
||||||
```
|
```
|
||||||
@@ -279,14 +282,14 @@ You should get a 403 on http://localhost/foo
|
|||||||
|
|
||||||
> Replace *10.0.10.30* by your IP
|
> Replace *10.0.10.30* by your IP
|
||||||
|
|
||||||
And the IPS that will not be filtered by the plugin
|
Add the IPs that will not be filtered by the plugin
|
||||||
```yaml
|
```yaml
|
||||||
- "traefik.http.middlewares.crowdsec.plugin.bouncer.trustedips=10.0.10.30/32"
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.clientTrustedips=10.0.10.30/32"
|
||||||
```
|
```
|
||||||
|
|
||||||
> Replace *10.0.10.30/32* by your IP or IP range, so it's not getting checked against ban cache of crowdsec
|
> Replace *10.0.10.30/32* by your IP or IP range, so it's not getting checked against ban cache of crowdsec
|
||||||
|
|
||||||
You should get a 200 on http://localhost/foo
|
You should get a 200 on http://localhost/foo even if you are on the ban cache
|
||||||
|
|
||||||
To play the demo environnement run:
|
To play the demo environnement run:
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
+17
-39
@@ -45,7 +45,7 @@ type Config struct {
|
|||||||
DefaultDecisionSeconds int64 `json:"defaultDecisionSeconds,omitempty"`
|
DefaultDecisionSeconds int64 `json:"defaultDecisionSeconds,omitempty"`
|
||||||
ForwardedHeadersCustomName string `json:"forwardedheaderscustomheader,omitempty"`
|
ForwardedHeadersCustomName string `json:"forwardedheaderscustomheader,omitempty"`
|
||||||
ForwardedHeadersTrustedIPs []string `json:"forwardedHeadersTrustedIps,omitempty"`
|
ForwardedHeadersTrustedIPs []string `json:"forwardedHeadersTrustedIps,omitempty"`
|
||||||
TrustedIPs []string `json:"TrustedIps,omitempty"`
|
ClientTrustedIPs []string `json:"clientTrustedIps,omitempty"`
|
||||||
RedisCacheEnabled bool `json:"redisCacheEnabled,omitempty"`
|
RedisCacheEnabled bool `json:"redisCacheEnabled,omitempty"`
|
||||||
RedisCacheHost string `json:"redisCacheHost,omitempty"`
|
RedisCacheHost string `json:"redisCacheHost,omitempty"`
|
||||||
}
|
}
|
||||||
@@ -54,7 +54,7 @@ type Config struct {
|
|||||||
func CreateConfig() *Config {
|
func CreateConfig() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
LogLevel: "INFO",
|
LogLevel: "DEBUG",
|
||||||
CrowdsecMode: liveMode,
|
CrowdsecMode: liveMode,
|
||||||
CrowdsecLapiScheme: "http",
|
CrowdsecLapiScheme: "http",
|
||||||
CrowdsecLapiHost: "crowdsec:8080",
|
CrowdsecLapiHost: "crowdsec:8080",
|
||||||
@@ -62,7 +62,7 @@ func CreateConfig() *Config {
|
|||||||
UpdateIntervalSeconds: 60,
|
UpdateIntervalSeconds: 60,
|
||||||
DefaultDecisionSeconds: 60,
|
DefaultDecisionSeconds: 60,
|
||||||
ForwardedHeadersTrustedIPs: []string{},
|
ForwardedHeadersTrustedIPs: []string{},
|
||||||
TrustedIPs: []string{},
|
ClientTrustedIPs: []string{},
|
||||||
ForwardedHeadersCustomName: "X-Forwarded-For",
|
ForwardedHeadersCustomName: "X-Forwarded-For",
|
||||||
RedisCacheEnabled: false,
|
RedisCacheEnabled: false,
|
||||||
RedisCacheHost: "redis:6379",
|
RedisCacheHost: "redis:6379",
|
||||||
@@ -83,9 +83,8 @@ type Bouncer struct {
|
|||||||
updateInterval int64
|
updateInterval int64
|
||||||
defaultDecisionTimeout int64
|
defaultDecisionTimeout int64
|
||||||
customHeader string
|
customHeader string
|
||||||
poolStrategy *ip.PoolStrategy
|
serverPoolStrategy *ip.PoolStrategy
|
||||||
poolStrategyTrusted *ip.PoolStrategy
|
clientPoolStrategy *ip.PoolStrategy
|
||||||
CheckerTrusted *ip.Checker
|
|
||||||
client *http.Client
|
client *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,16 +97,8 @@ func New(ctx context.Context, next http.Handler, config *Config, name string) (h
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
checker, err := ip.NewChecker(config.ForwardedHeadersTrustedIPs)
|
serverChecker, _ := ip.NewChecker(config.ForwardedHeadersTrustedIPs)
|
||||||
if err != nil {
|
clientChecker, _ := ip.NewChecker(config.ClientTrustedIPs)
|
||||||
logger.Debug("Checker == nil")
|
|
||||||
logger.Debug(err.Error())
|
|
||||||
}
|
|
||||||
checkerTrusted, err := ip.NewChecker(config.TrustedIPs)
|
|
||||||
if err != nil {
|
|
||||||
logger.Debug("CheckerTrusted == nil")
|
|
||||||
logger.Debug(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
bouncer := &Bouncer{
|
bouncer := &Bouncer{
|
||||||
next: next,
|
next: next,
|
||||||
@@ -122,13 +113,12 @@ func New(ctx context.Context, next http.Handler, config *Config, name string) (h
|
|||||||
updateInterval: config.UpdateIntervalSeconds,
|
updateInterval: config.UpdateIntervalSeconds,
|
||||||
customHeader: config.ForwardedHeadersCustomName,
|
customHeader: config.ForwardedHeadersCustomName,
|
||||||
defaultDecisionTimeout: config.DefaultDecisionSeconds,
|
defaultDecisionTimeout: config.DefaultDecisionSeconds,
|
||||||
poolStrategy: &ip.PoolStrategy{
|
serverPoolStrategy: &ip.PoolStrategy{
|
||||||
Checker: checker,
|
Checker: serverChecker,
|
||||||
},
|
},
|
||||||
poolStrategyTrusted: &ip.PoolStrategy{
|
clientPoolStrategy: &ip.PoolStrategy{
|
||||||
Checker: checkerTrusted,
|
Checker: clientChecker,
|
||||||
},
|
},
|
||||||
CheckerTrusted: checkerTrusted,
|
|
||||||
client: &http.Client{
|
client: &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
MaxIdleConns: 10,
|
MaxIdleConns: 10,
|
||||||
@@ -157,36 +147,24 @@ func (bouncer *Bouncer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Here we check for the trusted IPs in the customHeader
|
// Here we check for the trusted IPs in the customHeader
|
||||||
remoteHost, err := ip.GetRemoteIP(req, bouncer.poolStrategyTrusted, bouncer.customHeader)
|
remoteHost, err := ip.GetRemoteIP(req, bouncer.serverPoolStrategy, bouncer.customHeader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Info(err.Error())
|
logger.Info(err.Error())
|
||||||
bouncer.next.ServeHTTP(rw, req)
|
bouncer.next.ServeHTTP(rw, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if bouncer.CheckerTrusted == nil {
|
|
||||||
logger.Debug("CheckerTrusted == nil")
|
trusted, err := bouncer.clientPoolStrategy.Checker.Contains(remoteHost)
|
||||||
} else {
|
|
||||||
trusted, err := bouncer.CheckerTrusted.Contains(remoteHost)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Info(err.Error())
|
logger.Info(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// if our IP is in the trusted list we bypass the next checks
|
// if our IP is in the trusted list we bypass the next checks
|
||||||
|
logger.Debug(fmt.Sprintf("ServeHTTP ip:%v isTrusted:%v", remoteHost, trusted))
|
||||||
if trusted {
|
if trusted {
|
||||||
logger.Debug(fmt.Sprintf("IP %v is trusted", remoteHost))
|
|
||||||
bouncer.next.ServeHTTP(rw, req)
|
bouncer.next.ServeHTTP(rw, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Here we get the IP from the trusted header customHeader
|
|
||||||
remoteHost, err = ip.GetRemoteIP(req, bouncer.poolStrategy, bouncer.customHeader)
|
|
||||||
if err != nil {
|
|
||||||
logger.Info(err.Error())
|
|
||||||
bouncer.next.ServeHTTP(rw, req)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
logger.Debug(fmt.Sprintf("ServeHTTP ip:%v", remoteHost))
|
|
||||||
|
|
||||||
healthy := crowdsecStreamHealthy
|
healthy := crowdsecStreamHealthy
|
||||||
if bouncer.crowdsecMode != noneMode {
|
if bouncer.crowdsecMode != noneMode {
|
||||||
@@ -423,8 +401,8 @@ func validateParams(config *Config) error {
|
|||||||
} else {
|
} else {
|
||||||
logger.Debug("No IP provided for ForwardedHeadersTrustedIPs")
|
logger.Debug("No IP provided for ForwardedHeadersTrustedIPs")
|
||||||
}
|
}
|
||||||
if len(config.TrustedIPs) > 0 {
|
if len(config.ClientTrustedIPs) > 0 {
|
||||||
_, err = ip.NewChecker(config.TrustedIPs)
|
_, err = ip.NewChecker(config.ClientTrustedIPs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("TrustedIPs must be a list of IP/CIDR :%w", err)
|
return fmt.Errorf("TrustedIPs must be a list of IP/CIDR :%w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ version: "3.8"
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
cloudflare:
|
cloudflare:
|
||||||
image: "traefik:v2.9.1"
|
image: "traefik:v2.9.4"
|
||||||
container_name: "cloudflare"
|
container_name: "cloudflare"
|
||||||
command:
|
command:
|
||||||
# - "--log.level=DEBUG"
|
# - "--log.level=DEBUG"
|
||||||
@@ -21,7 +21,7 @@ services:
|
|||||||
- 8080:8080
|
- 8080:8080
|
||||||
|
|
||||||
traefik:
|
traefik:
|
||||||
image: "traefik:v2.9.1"
|
image: "traefik:v2.9.4"
|
||||||
container_name: "traefik"
|
container_name: "traefik"
|
||||||
command:
|
command:
|
||||||
# - "--log.level=DEBUG"
|
# - "--log.level=DEBUG"
|
||||||
|
|||||||
+1
-1
@@ -42,7 +42,7 @@ services:
|
|||||||
# crowdseclapikey must be uniq to the middleware attached to the service
|
# crowdseclapikey must be uniq to the middleware attached to the service
|
||||||
- "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdseclapikey=40796d93c2958f9e58345514e67740e5"
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdseclapikey=40796d93c2958f9e58345514e67740e5"
|
||||||
# Replace 10.0.10.30/32 by your IP range which is "trusted"
|
# Replace 10.0.10.30/32 by your IP range which is "trusted"
|
||||||
- "traefik.http.middlewares.crowdsec.plugin.bouncer.trustedips=10.0.10.30/32"
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.clienttrustedips=10.0.50.30/32"
|
||||||
|
|
||||||
|
|
||||||
crowdsec:
|
crowdsec:
|
||||||
@@ -20,10 +20,6 @@ type Checker struct {
|
|||||||
|
|
||||||
// NewChecker builds a new Checker given a list of CIDR-Strings to trusted IPs.
|
// NewChecker builds a new Checker given a list of CIDR-Strings to trusted IPs.
|
||||||
func NewChecker(trustedIPs []string) (*Checker, error) {
|
func NewChecker(trustedIPs []string) (*Checker, error) {
|
||||||
if len(trustedIPs) == 0 {
|
|
||||||
return nil, errors.New("no trusted IPs provided")
|
|
||||||
}
|
|
||||||
|
|
||||||
checker := &Checker{}
|
checker := &Checker{}
|
||||||
|
|
||||||
for _, ipMask := range trustedIPs {
|
for _, ipMask := range trustedIPs {
|
||||||
|
|||||||
Reference in New Issue
Block a user