mirror of
https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin.git
synced 2026-09-02 20:28:50 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8fb0a016b6 | ||
|
|
45d5f38c4d | ||
|
|
f1de1c924e | ||
|
|
36e6043c32 |
@@ -51,7 +51,7 @@ There are 5 operating modes (CrowdsecMode) for this plugin:
|
||||
|
||||
The `streaming mode` is recommended for performance, decisions are updated every 60 sec by default and that's the only communication between Traefik and Crowdsec. Every request that happens hits the cache for quick decisions.
|
||||
|
||||
The cache can be local to Traefik using the filesystem, or a separate Redis instance.
|
||||
The cache can be local to Traefik in memory or using a separate Redis instance.
|
||||
|
||||
Below are Mermaid diagrams detailling how each mode work:
|
||||
|
||||
@@ -368,18 +368,22 @@ Only one instance of the plugin is *possible*.
|
||||
- string
|
||||
- default: []
|
||||
- List of client IPs to trust, they will bypass any check from the bouncer or cache (useful for LAN or VPN IP)
|
||||
- ForwardedHeadersTrustedIPs
|
||||
- []string
|
||||
- default: []
|
||||
- List of IPs of trusted Proxies that are in front of traefik (ex: Cloudflare)
|
||||
- RemediationHeadersCustomName
|
||||
- string
|
||||
- default: ""
|
||||
- Name of the header you want in response when request are cancelled (possible value of the header `ban` or `captcha`)
|
||||
- ForwardedHeadersCustomName
|
||||
- string
|
||||
- default: "X-Forwarded-For"
|
||||
- Name of the header where the real IP of the client should be retrieved
|
||||
- ForwardedHeadersTrustedIPs
|
||||
- []string
|
||||
- default: []
|
||||
- List of IPs of trusted Proxies that are in front of traefik (ex: Cloudflare)
|
||||
- RedisCacheEnabled
|
||||
- bool
|
||||
- default: false
|
||||
- enable Redis cache instead of filesystem cache
|
||||
- enable Redis cache instead of in-memory cache
|
||||
- RedisCacheHost
|
||||
- string
|
||||
- default: "redis:6379"
|
||||
@@ -508,6 +512,7 @@ http:
|
||||
clientTrustedIPs:
|
||||
- 192.168.1.0/24
|
||||
forwardedHeadersCustomName: X-Custom-Header
|
||||
remediationHeadersCustomName: cs-remediation
|
||||
redisCacheEnabled: false
|
||||
redisCacheHost: "redis:6379"
|
||||
redisCachePassword: password
|
||||
|
||||
+13
-5
@@ -1,5 +1,5 @@
|
||||
// Package crowdsec_bouncer_traefik_plugin implements a middleware that communicates with crowdsec.
|
||||
// It can cache results to filesystem or redis, or even ask crowdsec for every requests.
|
||||
// It can cache results in memory or using redis, or even ask crowdsec for every requests.
|
||||
package crowdsec_bouncer_traefik_plugin //nolint:revive,stylecheck
|
||||
|
||||
import (
|
||||
@@ -74,7 +74,8 @@ type Bouncer struct {
|
||||
updateInterval int64
|
||||
updateMaxFailure int
|
||||
defaultDecisionTimeout int64
|
||||
customHeader string
|
||||
remediationCustomHeader string
|
||||
forwardedCustomHeader string
|
||||
crowdsecStreamRoute string
|
||||
crowdsecHeader string
|
||||
banTemplateString string
|
||||
@@ -156,7 +157,8 @@ func New(_ context.Context, next http.Handler, config *configuration.Config, nam
|
||||
crowdsecScenarios: config.CrowdsecCapiScenarios,
|
||||
updateInterval: config.UpdateIntervalSeconds,
|
||||
updateMaxFailure: config.UpdateMaxFailure,
|
||||
customHeader: config.ForwardedHeadersCustomName,
|
||||
remediationCustomHeader: config.RemediationHeadersCustomName,
|
||||
forwardedCustomHeader: config.ForwardedHeadersCustomName,
|
||||
defaultDecisionTimeout: config.DefaultDecisionSeconds,
|
||||
banTemplateString: banTemplateString,
|
||||
crowdsecStreamRoute: crowdsecStreamRoute,
|
||||
@@ -202,6 +204,7 @@ func New(_ context.Context, next http.Handler, config *configuration.Config, nam
|
||||
config.CaptchaProvider,
|
||||
config.CaptchaSiteKey,
|
||||
config.CaptchaSecretKey,
|
||||
config.RemediationHeadersCustomName,
|
||||
config.CaptchaHTMLFilePath,
|
||||
config.CaptchaGracePeriodSeconds,
|
||||
)
|
||||
@@ -236,8 +239,8 @@ func (bouncer *Bouncer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Here we check for the trusted IPs in the customHeader
|
||||
remoteIP, err := ip.GetRemoteIP(req, bouncer.serverPoolStrategy, bouncer.customHeader)
|
||||
// Here we check for the trusted IPs in the forwardedCustomHeader
|
||||
remoteIP, err := ip.GetRemoteIP(req, bouncer.serverPoolStrategy, bouncer.forwardedCustomHeader)
|
||||
if err != nil {
|
||||
bouncer.log.Error(fmt.Sprintf("ServeHTTP:getRemoteIp ip:%s %s", remoteIP, err.Error()))
|
||||
handleBanServeHTTP(bouncer, rw)
|
||||
@@ -337,6 +340,9 @@ func handleBanServeHTTP(bouncer *Bouncer, rw http.ResponseWriter) {
|
||||
return
|
||||
}
|
||||
rw.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
if bouncer.remediationCustomHeader != "" {
|
||||
rw.Header().Set(bouncer.remediationCustomHeader, "ban")
|
||||
}
|
||||
rw.WriteHeader(http.StatusForbidden)
|
||||
fmt.Fprint(rw, bouncer.banTemplateString)
|
||||
}
|
||||
@@ -546,6 +552,8 @@ func crowdsecQuery(bouncer *Bouncer, stringURL string, isPost bool) ([]byte, err
|
||||
req, _ = http.NewRequest(http.MethodGet, stringURL, nil)
|
||||
}
|
||||
req.Header.Add(bouncer.crowdsecHeader, bouncer.crowdsecKey)
|
||||
req.Header.Add("User-Agent", "Crowdsec bouncer Traefik Plugin")
|
||||
|
||||
res, err := bouncer.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("crowdsecQuery:unreachable url:%s %w", stringURL, err)
|
||||
|
||||
+2
-2
@@ -75,7 +75,7 @@ func TestBouncer_ServeHTTP(t *testing.T) {
|
||||
crowdsecMode string
|
||||
updateInterval int64
|
||||
defaultDecisionTimeout int64
|
||||
customHeader string
|
||||
forwardedCustomHeader string
|
||||
clientPoolStrategy *ip.PoolStrategy
|
||||
serverPoolStrategy *ip.PoolStrategy
|
||||
httpClient *http.Client
|
||||
@@ -105,7 +105,7 @@ func TestBouncer_ServeHTTP(t *testing.T) {
|
||||
crowdsecMode: tt.fields.crowdsecMode,
|
||||
updateInterval: tt.fields.updateInterval,
|
||||
defaultDecisionTimeout: tt.fields.defaultDecisionTimeout,
|
||||
customHeader: tt.fields.customHeader,
|
||||
forwardedCustomHeader: tt.fields.forwardedCustomHeader,
|
||||
clientPoolStrategy: tt.fields.clientPoolStrategy,
|
||||
serverPoolStrategy: tt.fields.serverPoolStrategy,
|
||||
httpClient: tt.fields.httpClient,
|
||||
|
||||
@@ -20,6 +20,7 @@ type Client struct {
|
||||
provider string
|
||||
siteKey string
|
||||
secretKey string
|
||||
remediationCustomHeader string
|
||||
gracePeriodSeconds int64
|
||||
captchaTemplate *template.Template
|
||||
cacheClient *cache.Client
|
||||
@@ -48,14 +49,14 @@ var (
|
||||
},
|
||||
configuration.TurnstileProvider: {
|
||||
js: "https://challenges.cloudflare.com/turnstile/v0/api.js",
|
||||
key: "cf-captcha",
|
||||
key: "cf-turnstile",
|
||||
validate: "https://challenges.cloudflare.com/turnstile/v0/siteverify",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// New Initialize captcha client.
|
||||
func (c *Client) New(log *logger.Log, cacheClient *cache.Client, httpClient *http.Client, provider, siteKey, secretKey, captchaTemplatePath string, gracePeriodSeconds int64) error {
|
||||
func (c *Client) New(log *logger.Log, cacheClient *cache.Client, httpClient *http.Client, provider, siteKey, secretKey, remediationCustomHeader, captchaTemplatePath string, gracePeriodSeconds int64) error {
|
||||
c.Valid = provider != ""
|
||||
if !c.Valid {
|
||||
return nil
|
||||
@@ -63,6 +64,7 @@ func (c *Client) New(log *logger.Log, cacheClient *cache.Client, httpClient *htt
|
||||
c.siteKey = siteKey
|
||||
c.secretKey = secretKey
|
||||
c.provider = provider
|
||||
c.remediationCustomHeader = remediationCustomHeader
|
||||
html, _ := configuration.GetHTMLTemplate(captchaTemplatePath)
|
||||
c.captchaTemplate = html
|
||||
c.gracePeriodSeconds = gracePeriodSeconds
|
||||
@@ -87,6 +89,9 @@ func (c *Client) ServeHTTP(rw http.ResponseWriter, r *http.Request, remoteIP str
|
||||
return
|
||||
}
|
||||
rw.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
if c.remediationCustomHeader != "" {
|
||||
rw.Header().Set(c.remediationCustomHeader, "captcha")
|
||||
}
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
err = c.captchaTemplate.Execute(rw, map[string]string{
|
||||
"SiteKey": c.siteKey,
|
||||
|
||||
@@ -62,6 +62,7 @@ type Config struct {
|
||||
UpdateMaxFailure int `json:"updateMaxFailure,omitempty"`
|
||||
DefaultDecisionSeconds int64 `json:"defaultDecisionSeconds,omitempty"`
|
||||
HTTPTimeoutSeconds int64 `json:"httpTimeoutSeconds,omitempty"`
|
||||
RemediationHeadersCustomName string `json:"remediationHeadersCustomName,omitempty"`
|
||||
ForwardedHeadersCustomName string `json:"forwardedHeadersCustomName,omitempty"`
|
||||
ForwardedHeadersTrustedIPs []string `json:"forwardedHeadersTrustedIps,omitempty"`
|
||||
ClientTrustedIPs []string `json:"clientTrustedIps,omitempty"`
|
||||
@@ -113,6 +114,7 @@ func New() *Config {
|
||||
CaptchaGracePeriodSeconds: 1800,
|
||||
CaptchaHTMLFilePath: "/captcha.html",
|
||||
BanHTMLFilePath: "",
|
||||
RemediationHeadersCustomName: "",
|
||||
ForwardedHeadersCustomName: "X-Forwarded-For",
|
||||
ForwardedHeadersTrustedIPs: []string{},
|
||||
ClientTrustedIPs: []string{},
|
||||
|
||||
Reference in New Issue
Block a user