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 | |
|---|---|---|---|
|
|
de7e382fde | ||
|
|
abae855d9e | ||
|
|
4bb6e830dc | ||
|
|
78869ecf77 | ||
|
|
7f99266f99 |
+1
-1
@@ -15,7 +15,7 @@ linters-settings:
|
||||
locale: US
|
||||
funlen:
|
||||
lines: -1
|
||||
statements: 50
|
||||
statements: 60
|
||||
godox:
|
||||
keywords:
|
||||
- FIXME
|
||||
|
||||
@@ -419,7 +419,7 @@ _By careful when you upgrade to >1.4.x_
|
||||
- string
|
||||
- default: ""
|
||||
- Database selection for the Redis service
|
||||
- RedisUnreachableBlock
|
||||
- RedisCacheUnreachableBlock
|
||||
- bool
|
||||
- default: true
|
||||
- Block request when Redis is unreachable (if Redis is unreachable, 1-second delay is added to each request)
|
||||
@@ -439,6 +439,10 @@ _By careful when you upgrade to >1.4.x_
|
||||
- int64
|
||||
- default: 60
|
||||
- Used only in `live` mode, maximum decision duration
|
||||
- RemediationStatusCode
|
||||
- int
|
||||
- default: 403
|
||||
- HTTP status code for banned user (not captcha)
|
||||
- CrowdsecCapiMachineId
|
||||
- string
|
||||
- Used only in `alone` mode, login for Crowdsec CAPI
|
||||
@@ -518,6 +522,7 @@ http:
|
||||
updateIntervalSeconds: 60
|
||||
updateMaxFailure: 0
|
||||
defaultDecisionSeconds: 60
|
||||
remediationStatusCode: 403
|
||||
httpTimeoutSeconds: 10
|
||||
crowdsecMode: live
|
||||
crowdsecAppsecEnabled: false
|
||||
@@ -527,7 +532,6 @@ http:
|
||||
crowdsecAppsecUnreachableBlock: true
|
||||
crowdsecAppsecBodyLimit: 10485760
|
||||
crowdsecLapiKey: privateKey-foo
|
||||
crowdsecLapiKeyFile: /etc/traefik/cs-privateKey-foo
|
||||
crowdsecLapiScheme: http
|
||||
crowdsecLapiHost: crowdsec:8080
|
||||
crowdsecLapiPath: "/"
|
||||
@@ -556,7 +560,6 @@ http:
|
||||
...
|
||||
Q0veeNzBQXg1f/JxfeA39IDIX1kiCf71tGlT
|
||||
-----END CERTIFICATE-----
|
||||
crowdsecLapiTLSCertificateAuthorityFile: /etc/traefik/crowdsec-certs/ca.pem
|
||||
crowdsecLapiTLSCertificateBouncer: |-
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEHjCCAwagAwIBAgIUOBTs1eqkaAUcPplztUr2xRapvNAwDQYJKoZIhvcNAQEL
|
||||
@@ -564,14 +567,12 @@ http:
|
||||
RaXAnYYUVRblS1jmePemh388hFxbmrpG2pITx8B5FMULqHoj11o2Rl0gSV6tHIHz
|
||||
N2U=
|
||||
-----END CERTIFICATE-----
|
||||
crowdsecLapiTLSCertificateBouncerFile: /etc/traefik/crowdsec-certs/bouncer.pem
|
||||
crowdsecLapiTLSCertificateBouncerKey: |-
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEogIBAAKCAQEAtYQnbJqifH+ZymePylDxGGLIuxzcAUU4/ajNj+qRAdI/Ux3d
|
||||
...
|
||||
ic5cDRo6/VD3CS3MYzyBcibaGaV34nr0G/pI+KEqkYChzk/PZRA=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
crowdsecLapiTLSCertificateBouncerKeyFile: /etc/traefik/crowdsec-certs/bouncer-key.pem
|
||||
captchaProvider: hcaptcha
|
||||
captchaSiteKey: FIXME
|
||||
captchaSecretKey: FIXME
|
||||
@@ -582,7 +583,7 @@ http:
|
||||
|
||||
#### Fill variable with value of file
|
||||
|
||||
`CrowdsecLapiTlsCertificateBouncerKey`, `CrowdsecLapiTlsCertificateBouncer`, `CrowdsecLapiTlsCertificateAuthority`, `CrowdsecCapiMachineId`, `CrowdsecCapiPassword`, `CrowdsecLapiKey`, `CaptchaSiteKey` and `CaptchaSecretKey` can be provided with the content as raw or through a file path that Traefik can read.
|
||||
`CrowdsecLapiTlsCertificateBouncerKey`, `CrowdsecLapiTlsCertificateBouncer`, `CrowdsecLapiTlsCertificateAuthority`, `CrowdsecCapiMachineId`, `CrowdsecCapiPassword`, `CrowdsecLapiKey`, `CaptchaSiteKey`, `CaptchaSecretKey` and `RedisCachePassword` can be provided with the content as raw or through a file path that Traefik can read.
|
||||
The file variable will be used as preference if both content and file are provided for the same variable.
|
||||
|
||||
Format is:
|
||||
|
||||
+6
-3
@@ -77,6 +77,7 @@ type Bouncer struct {
|
||||
updateInterval int64
|
||||
updateMaxFailure int
|
||||
defaultDecisionTimeout int64
|
||||
remediationStatusCode int
|
||||
remediationCustomHeader string
|
||||
forwardedCustomHeader string
|
||||
crowdsecStreamRoute string
|
||||
@@ -93,6 +94,7 @@ type Bouncer struct {
|
||||
|
||||
// New creates the crowdsec bouncer plugin.
|
||||
func New(_ context.Context, next http.Handler, config *configuration.Config, name string) (http.Handler, error) {
|
||||
config.LogLevel = strings.ToUpper(config.LogLevel)
|
||||
log := logger.New(config.LogLevel, config.LogFilePath)
|
||||
err := configuration.ValidateParams(config)
|
||||
if err != nil {
|
||||
@@ -169,6 +171,7 @@ func New(_ context.Context, next http.Handler, config *configuration.Config, nam
|
||||
remediationCustomHeader: config.RemediationHeadersCustomName,
|
||||
forwardedCustomHeader: config.ForwardedHeadersCustomName,
|
||||
defaultDecisionTimeout: config.DefaultDecisionSeconds,
|
||||
remediationStatusCode: config.RemediationStatusCode,
|
||||
redisUnreachableBlock: config.RedisCacheUnreachableBlock,
|
||||
banTemplateString: banTemplateString,
|
||||
crowdsecStreamRoute: crowdsecStreamRoute,
|
||||
@@ -354,11 +357,11 @@ func handleBanServeHTTP(bouncer *Bouncer, rw http.ResponseWriter) {
|
||||
rw.Header().Set(bouncer.remediationCustomHeader, "ban")
|
||||
}
|
||||
if bouncer.banTemplateString == "" {
|
||||
rw.WriteHeader(http.StatusForbidden)
|
||||
rw.WriteHeader(bouncer.remediationStatusCode)
|
||||
return
|
||||
}
|
||||
rw.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
rw.WriteHeader(http.StatusForbidden)
|
||||
rw.WriteHeader(bouncer.remediationStatusCode)
|
||||
_, err := fmt.Fprint(rw, bouncer.banTemplateString)
|
||||
if err != nil {
|
||||
bouncer.log.Error("handleBanServeHTTP could not write template to ResponseWriter")
|
||||
@@ -427,7 +430,7 @@ func handleNoStreamCache(bouncer *Bouncer, remoteIP string) (string, error) {
|
||||
Scheme: bouncer.crowdsecScheme,
|
||||
Host: bouncer.crowdsecHost,
|
||||
Path: bouncer.crowdsecPath + crowdsecLapiRoute,
|
||||
RawQuery: fmt.Sprintf("ip=%v&banned=true", remoteIP),
|
||||
RawQuery: fmt.Sprintf("ip=%v", remoteIP),
|
||||
}
|
||||
body, err := crowdsecQuery(bouncer, routeURL.String(), false)
|
||||
if err != nil {
|
||||
|
||||
@@ -6,6 +6,7 @@ logs:
|
||||
level: DEBUG
|
||||
access:
|
||||
enabled: true
|
||||
format: json
|
||||
fields:
|
||||
headers:
|
||||
defaultmode: keep
|
||||
|
||||
@@ -68,6 +68,7 @@ type Config struct {
|
||||
UpdateIntervalSeconds int64 `json:"updateIntervalSeconds,omitempty"`
|
||||
UpdateMaxFailure int `json:"updateMaxFailure,omitempty"`
|
||||
DefaultDecisionSeconds int64 `json:"defaultDecisionSeconds,omitempty"`
|
||||
RemediationStatusCode int `json:"remediationStatusCode,omitempty"`
|
||||
HTTPTimeoutSeconds int64 `json:"httpTimeoutSeconds,omitempty"`
|
||||
RemediationHeadersCustomName string `json:"remediationHeadersCustomName,omitempty"`
|
||||
ForwardedHeadersCustomName string `json:"forwardedHeadersCustomName,omitempty"`
|
||||
@@ -119,6 +120,7 @@ func New() *Config {
|
||||
UpdateIntervalSeconds: 60,
|
||||
UpdateMaxFailure: 0,
|
||||
DefaultDecisionSeconds: 60,
|
||||
RemediationStatusCode: http.StatusForbidden,
|
||||
HTTPTimeoutSeconds: 10,
|
||||
CaptchaProvider: "",
|
||||
CaptchaSiteKey: "",
|
||||
@@ -268,8 +270,8 @@ func ValidateParams(config *Config) error {
|
||||
}
|
||||
|
||||
// Check logging configuration
|
||||
|
||||
if !contains([]string{LogERROR, LogDEBUG, LogINFO}, config.LogLevel) {
|
||||
// to upper allow of anycase of log level
|
||||
if !contains([]string{LogERROR, LogDEBUG, LogINFO}, strings.ToUpper(config.LogLevel)) {
|
||||
return fmt.Errorf("LogLevel should be one of (%s,%s,%s)", LogDEBUG, LogINFO, LogERROR)
|
||||
}
|
||||
if config.LogFilePath != "" {
|
||||
@@ -355,6 +357,9 @@ func validateParamsRequired(config *Config) error {
|
||||
if config.CrowdsecAppsecBodyLimit < 0 {
|
||||
return errors.New("CrowdsecAppsecBodyLimit: cannot be less than 0")
|
||||
}
|
||||
if config.RemediationStatusCode < 100 || config.RemediationStatusCode >= 600 {
|
||||
return errors.New("RemediationStatusCode: cannot be less than 100 and more than 600")
|
||||
}
|
||||
|
||||
if !contains([]string{NoneMode, LiveMode, StreamMode, AloneMode, AppsecMode}, config.CrowdsecMode) {
|
||||
return errors.New("CrowdsecMode: must be one of 'none', 'live', 'stream', 'alone' or 'appsec'")
|
||||
|
||||
@@ -85,8 +85,14 @@ func Test_ValidateParams(t *testing.T) {
|
||||
cfg6 := getMinimalConfig()
|
||||
cfg6.CrowdsecLapiScheme = HTTPS
|
||||
cfg6.CrowdsecLapiTLSInsecureVerify = true
|
||||
cfg7 := getMinimalConfig()
|
||||
cfg7.CrowdsecLapiScheme = HTTPS
|
||||
cfg8 := getMinimalConfig()
|
||||
cfg8.CrowdsecLapiScheme = HTTPS
|
||||
cfg8.LogLevel = LogINFO
|
||||
cfg9 := getMinimalConfig()
|
||||
cfg9.LogLevel = "info"
|
||||
cfg10 := getMinimalConfig()
|
||||
cfg10.LogLevel = "Warning"
|
||||
type args struct {
|
||||
config *Config
|
||||
}
|
||||
@@ -104,7 +110,10 @@ func Test_ValidateParams(t *testing.T) {
|
||||
{name: "Not validate a bad clients ips", args: args{config: cfg5}, wantErr: true},
|
||||
// HTTPS enabled
|
||||
{name: "Validate https config with insecure verify", args: args{config: cfg6}, wantErr: false},
|
||||
{name: "Not validate https without cert authority", args: args{config: cfg8}, wantErr: true},
|
||||
{name: "Not validate https without cert authority", args: args{config: cfg7}, wantErr: true},
|
||||
{name: "Valid log level uppercase INFO", args: args{config: cfg8}, wantErr: false},
|
||||
{name: "Valid log level lowercase info", args: args{config: cfg9}, wantErr: false},
|
||||
{name: "Invalid log level Warning", args: args{config: cfg10}, wantErr: true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user