From 2f63a31f4e8e585477e790374e660f2b84df8117 Mon Sep 17 00:00:00 2001 From: maxlerebourg Date: Thu, 11 Dec 2025 19:46:17 +0100 Subject: [PATCH 1/8] :bug: fix start up config error for appsec --- bouncer.go | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/bouncer.go b/bouncer.go index 22d9173..e2aa0f7 100644 --- a/bouncer.go +++ b/bouncer.go @@ -134,17 +134,24 @@ func New(_ context.Context, next http.Handler, config *configuration.Config, nam serverChecker, _ := ip.NewChecker(log, config.ForwardedHeadersTrustedIPs) clientChecker, _ := ip.NewChecker(log, config.ClientTrustedIPs) - tlsAppsecConfig, err := configuration.GetTLSConfigCrowdsec(config, log, true) - if err != nil { - log.Error("New:getTLSConfigCrowdsec fail to get tlsAppsecConfig " + err.Error()) - return nil, err - } - apiAppsecKey, errAppsecKey := configuration.GetVariable(config, "CrowdsecAppsecKey") - if errAppsecKey != nil && len(tlsAppsecConfig.Certificates) == 0 { - log.Error("New:crowdsecLapiKey fail to get CrowdsecAppsecKey and no client certificate setup " + errAppsecKey.Error()) - return nil, errAppsecKey + + var tlsAppsecConfig *tls.Config + if config.CrowdsecAppsecEnabled { + if config.CrowdsecAppsecScheme == "" { + config.CrowdsecAppsecScheme = config.CrowdsecLapiScheme + } + tlsAppsecConfig, err := configuration.GetTLSConfigCrowdsec(config, log, true) + if err != nil { + log.Error("New:getTLSConfigCrowdsec fail to get tlsAppsecConfig " + err.Error()) + return nil, err + } + apiAppsecKey, errAppsecKey := configuration.GetVariable(config, "CrowdsecAppsecKey") + if errAppsecKey != nil && len(tlsAppsecConfig.Certificates) == 0 { + log.Info("New:crowdsecLapiKey fail to get CrowdsecAppsecKey and no client certificate setup " + errAppsecKey.Error()) + } + config.CrowdsecAppsecKey = apiAppsecKey } - config.CrowdsecAppsecKey = apiAppsecKey + var tlsConfig *tls.Config crowdsecStreamRoute := "" @@ -155,7 +162,6 @@ func New(_ context.Context, next http.Handler, config *configuration.Config, nam config.CrowdsecLapiScheme = configuration.HTTPS config.CrowdsecLapiHost = crowdsecCapiHost config.CrowdsecLapiPath = "/" - config.CrowdsecAppsecEnabled = config.CrowdsecAppsecEnabled && config.CrowdsecAppsecScheme != "" config.UpdateIntervalSeconds = 7200 // 2 hours crowdsecStreamRoute = crowdsecCapiStreamRoute crowdsecHeader = crowdsecCapiHeader @@ -173,6 +179,9 @@ func New(_ context.Context, next http.Handler, config *configuration.Config, nam return nil, errKey } config.CrowdsecLapiKey = apiKey + if config.CrowdsecAppsecKey == "" { + config.CrowdsecAppsecKey = apiKey + } } var banTemplate *htmltemplate.Template From 36a25306c1314c13cb47a4f290d33ddb6789bcff Mon Sep 17 00:00:00 2001 From: mhx Date: Fri, 12 Dec 2025 08:38:11 +0100 Subject: [PATCH 2/8] :doc: add documentation on appsec variables and missing conf parameter --- README.md | 35 ++++++-- pkg/configuration/configuration.go | 131 +++++++++++++++-------------- 2 files changed, 95 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 2a54171..f26d4e2 100644 --- a/README.md +++ b/README.md @@ -310,17 +310,16 @@ make run ### Note > [!IMPORTANT] -> Some of the behaviours and configuration parameters are shared globally across *all* crowdsec middlewares even if you declare different middlewares with different settings. +> Some of the behaviours and configuration parameters are shared globally across _all_ crowdsec middlewares even if you declare different middlewares with different settings. > > **Cache is shared by all services**: This means if an IP is banned, all services which are protected by an instance of the plugin will deny requests from that IP > > If you define different caches for different middlewares, only the first one to be instantiated will be bound to the crowdsec stream. > -> Overall, this middleware is designed in such a way that **only one instance of the plugin is *possible*.** You can have multiple crowdsec middlewares in the same cluster, the key parameters must be aligned (MetricsUpdateIntervalSeconds, CrowdsecMode, CrowdsecAppsecEnabled, etc.) +> Overall, this middleware is designed in such a way that **only one instance of the plugin is _possible_.** You can have multiple crowdsec middlewares in the same cluster, the key parameters must be aligned (MetricsUpdateIntervalSeconds, CrowdsecMode, CrowdsecAppsecEnabled, etc.) > [!WARNING] -> **Appsec maximum body limit is defaulted to 10MB** -> *Be careful when you upgrade to >1.4.x* +> **Appsec maximum body limit is defaulted to 10MB** > _Be careful when you upgrade to >1.4.x_ ### Variables @@ -351,7 +350,26 @@ make run - CrowdsecAppsecHost - string - default: "crowdsec:7422" - - Crowdsec Appsec Server available on which host and port. The scheme will be handled by the CrowdsecLapiScheme var. + - Crowdsec Appsec Server available on which host and port. +- CrowdsecAppsecTlsInsecureVerify + - bool + - default: false + - Disable verification of certificate presented by Appsec +- CrowdsecAppsecTlsCertificateAuthority + - string + - default: "" + - PEM-encoded Certificate Authority of Appsec +- CrowdsecAppsecTlsCertificateBouncer + - string + - default: "" + - PEM-encoded client Certificate of Appsec +- CrowdsecAppsecTlsCertificateBouncerKey + - string + - default: "" + - PEM-encoded client private key of Appsec +- CrowdsecAppsecScheme + - string + - default: value of `CrowdsecLapiScheme`, expected values are: `http`, `https` - CrowdsecAppsecPath - string - default: "/" @@ -368,6 +386,10 @@ make run - int64 - default: 10485760 (= 10MB) - Transmit only the first number of bytes to Crowdsec Appsec Server. +- CrowdsecAppsecKey + - string + - default: value of `CrowdsecLapiKey` + - Crowdsec AppSec key for the bouncer. - CrowdsecLapiScheme - string - default: `http`, expected values are: `http`, `https` @@ -614,7 +636,8 @@ http: #### Fill variable with value of file -`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. +`CrowdsecLapiTlsCertificateBouncerKey`, `CrowdsecLapiTlsCertificateBouncer`, `CrowdsecLapiTlsCertificateAuthority`, +`CrowdsecAppsecTlsCertificateBouncerKey`, `CrowdsecAppsecTlsCertificateBouncer`, `CrowdsecAppsecTlsCertificateAuthority`, `CrowdsecCapiMachineId`, `CrowdsecCapiPassword`, `CrowdsecLapiKey`, `CrowdsecAppsecKey`, `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: diff --git a/pkg/configuration/configuration.go b/pkg/configuration/configuration.go index 5becb98..5051cfa 100644 --- a/pkg/configuration/configuration.go +++ b/pkg/configuration/configuration.go @@ -42,71 +42,72 @@ const ( // Config the plugin configuration. type Config struct { - Enabled bool `json:"enabled,omitempty"` - LogLevel string `json:"logLevel,omitempty"` - LogFilePath string `json:"logFilePath,omitempty"` - CrowdsecMode string `json:"crowdsecMode,omitempty"` - CrowdsecAppsecEnabled bool `json:"crowdsecAppsecEnabled,omitempty"` - CrowdsecAppsecScheme string `json:"crowdsecAppsecScheme,omitempty"` - CrowdsecAppsecHost string `json:"crowdsecAppsecHost,omitempty"` - CrowdsecAppsecPath string `json:"crowdsecAppsecPath,omitempty"` - CrowdsecAppsecKey string `json:"crowdsecAppsecKey,omitempty"` - CrowdsecAppsecKeyFile string `json:"crowdsecAppsecKeyFile,omitempty"` - CrowdsecAppsecTLSInsecureVerify bool `json:"crowdsecAppsecTlsInsecureVerify,omitempty"` - CrowdsecAppsecTLSCertificateAuthority string `json:"crowdsecAppsecTlsCertificateAuthority,omitempty"` - CrowdsecAppsecTLSCertificateAuthorityFile string `json:"crowdsecAppsecTlsCertificateAuthorityFile,omitempty"` - CrowdsecAppsecTLSCertificateBouncer string `json:"crowdsecAppsecTlsCertificateBouncer,omitempty"` - CrowdsecAppsecTLSCertificateBouncerFile string `json:"crowdsecAppsecTlsCertificateBouncerFile,omitempty"` - CrowdsecAppsecTLSCertificateBouncerKey string `json:"crowdsecAppsecTlsCertificateBouncerKey,omitempty"` - CrowdsecAppsecFailureBlock bool `json:"crowdsecAppsecFailureBlock,omitempty"` - CrowdsecAppsecUnreachableBlock bool `json:"crowdsecAppsecUnreachableBlock,omitempty"` - CrowdsecAppsecBodyLimit int64 `json:"crowdsecAppsecBodyLimit,omitempty"` - CrowdsecLapiScheme string `json:"crowdsecLapiScheme,omitempty"` - CrowdsecLapiHost string `json:"crowdsecLapiHost,omitempty"` - CrowdsecLapiPath string `json:"crowdsecLapiPath,omitempty"` - CrowdsecLapiKey string `json:"crowdsecLapiKey,omitempty"` - CrowdsecLapiKeyFile string `json:"crowdsecLapiKeyFile,omitempty"` - CrowdsecLapiTLSInsecureVerify bool `json:"crowdsecLapiTlsInsecureVerify,omitempty"` - CrowdsecLapiTLSCertificateAuthority string `json:"crowdsecLapiTlsCertificateAuthority,omitempty"` - CrowdsecLapiTLSCertificateAuthorityFile string `json:"crowdsecLapiTlsCertificateAuthorityFile,omitempty"` - CrowdsecLapiTLSCertificateBouncer string `json:"crowdsecLapiTlsCertificateBouncer,omitempty"` - CrowdsecLapiTLSCertificateBouncerFile string `json:"crowdsecLapiTlsCertificateBouncerFile,omitempty"` - CrowdsecLapiTLSCertificateBouncerKey string `json:"crowdsecLapiTlsCertificateBouncerKey,omitempty"` - CrowdsecLapiTLSCertificateBouncerKeyFile string `json:"crowdsecLapiTlsCertificateBouncerKeyFile,omitempty"` - CrowdsecCapiMachineID string `json:"crowdsecCapiMachineId,omitempty"` - CrowdsecCapiMachineIDFile string `json:"crowdsecCapiMachineIdFile,omitempty"` - CrowdsecCapiPassword string `json:"crowdsecCapiPassword,omitempty"` - CrowdsecCapiPasswordFile string `json:"crowdsecCapiPasswordFile,omitempty"` - CrowdsecCapiScenarios []string `json:"crowdsecCapiScenarios,omitempty"` - UpdateIntervalSeconds int64 `json:"updateIntervalSeconds,omitempty"` - MetricsUpdateIntervalSeconds int64 `json:"metricsUpdateIntervalSeconds,omitempty"` - UpdateMaxFailure int64 `json:"updateMaxFailure,omitempty"` - DefaultDecisionSeconds int64 `json:"defaultDecisionSeconds,omitempty"` - RemediationStatusCode int `json:"remediationStatusCode,omitempty"` - HTTPTimeoutSeconds int64 `json:"httpTimeoutSeconds,omitempty"` - TraceHeadersCustomName string `json:"traceHeadersCustomName,omitempty"` - RemediationHeadersCustomName string `json:"remediationHeadersCustomName,omitempty"` - ForwardedHeadersCustomName string `json:"forwardedHeadersCustomName,omitempty"` - ForwardedHeadersTrustedIPs []string `json:"forwardedHeadersTrustedIps,omitempty"` - ClientTrustedIPs []string `json:"clientTrustedIps,omitempty"` - RedisCacheEnabled bool `json:"redisCacheEnabled,omitempty"` - RedisCacheHost string `json:"redisCacheHost,omitempty"` - RedisCachePassword string `json:"redisCachePassword,omitempty"` - RedisCachePasswordFile string `json:"redisCachePasswordFile,omitempty"` - RedisCacheDatabase string `json:"redisCacheDatabase,omitempty"` - RedisCacheUnreachableBlock bool `json:"redisCacheUnreachableBlock,omitempty"` - BanHTMLFilePath string `json:"banHtmlFilePath,omitempty"` - CaptchaHTMLFilePath string `json:"captchaHtmlFilePath,omitempty"` - CaptchaProvider string `json:"captchaProvider,omitempty"` - CaptchaCustomJsURL string `json:"captchaCustomJsUrl,omitempty"` - CaptchaCustomValidateURL string `json:"captchaCustomValidateUrl,omitempty"` - CaptchaCustomKey string `json:"captchaCustomKey,omitempty"` - CaptchaCustomResponse string `json:"captchaCustomResponse,omitempty"` - CaptchaSiteKey string `json:"captchaSiteKey,omitempty"` - CaptchaSiteKeyFile string `json:"captchaSiteKeyFile,omitempty"` - CaptchaSecretKey string `json:"captchaSecretKey,omitempty"` - CaptchaSecretKeyFile string `json:"captchaSecretKeyFile,omitempty"` - CaptchaGracePeriodSeconds int64 `json:"captchaGracePeriodSeconds,omitempty"` + Enabled bool `json:"enabled,omitempty"` + LogLevel string `json:"logLevel,omitempty"` + LogFilePath string `json:"logFilePath,omitempty"` + CrowdsecMode string `json:"crowdsecMode,omitempty"` + CrowdsecAppsecEnabled bool `json:"crowdsecAppsecEnabled,omitempty"` + CrowdsecAppsecScheme string `json:"crowdsecAppsecScheme,omitempty"` + CrowdsecAppsecHost string `json:"crowdsecAppsecHost,omitempty"` + CrowdsecAppsecPath string `json:"crowdsecAppsecPath,omitempty"` + CrowdsecAppsecKey string `json:"crowdsecAppsecKey,omitempty"` + CrowdsecAppsecKeyFile string `json:"crowdsecAppsecKeyFile,omitempty"` + CrowdsecAppsecTLSInsecureVerify bool `json:"crowdsecAppsecTlsInsecureVerify,omitempty"` + CrowdsecAppsecTLSCertificateAuthority string `json:"crowdsecAppsecTlsCertificateAuthority,omitempty"` + CrowdsecAppsecTLSCertificateAuthorityFile string `json:"crowdsecAppsecTlsCertificateAuthorityFile,omitempty"` + CrowdsecAppsecTLSCertificateBouncer string `json:"crowdsecAppsecTlsCertificateBouncer,omitempty"` + CrowdsecAppsecTLSCertificateBouncerFile string `json:"crowdsecAppsecTlsCertificateBouncerFile,omitempty"` + CrowdsecAppsecTLSCertificateBouncerKey string `json:"crowdsecAppsecTlsCertificateBouncerKey,omitempty"` + CrowdsecAppsecTLSCertificateBouncerKeyFile string `json:"crowdsecAppsecTlsCertificateBouncerKeyFile,omitempty"` + CrowdsecAppsecFailureBlock bool `json:"crowdsecAppsecFailureBlock,omitempty"` + CrowdsecAppsecUnreachableBlock bool `json:"crowdsecAppsecUnreachableBlock,omitempty"` + CrowdsecAppsecBodyLimit int64 `json:"crowdsecAppsecBodyLimit,omitempty"` + CrowdsecLapiScheme string `json:"crowdsecLapiScheme,omitempty"` + CrowdsecLapiHost string `json:"crowdsecLapiHost,omitempty"` + CrowdsecLapiPath string `json:"crowdsecLapiPath,omitempty"` + CrowdsecLapiKey string `json:"crowdsecLapiKey,omitempty"` + CrowdsecLapiKeyFile string `json:"crowdsecLapiKeyFile,omitempty"` + CrowdsecLapiTLSInsecureVerify bool `json:"crowdsecLapiTlsInsecureVerify,omitempty"` + CrowdsecLapiTLSCertificateAuthority string `json:"crowdsecLapiTlsCertificateAuthority,omitempty"` + CrowdsecLapiTLSCertificateAuthorityFile string `json:"crowdsecLapiTlsCertificateAuthorityFile,omitempty"` + CrowdsecLapiTLSCertificateBouncer string `json:"crowdsecLapiTlsCertificateBouncer,omitempty"` + CrowdsecLapiTLSCertificateBouncerFile string `json:"crowdsecLapiTlsCertificateBouncerFile,omitempty"` + CrowdsecLapiTLSCertificateBouncerKey string `json:"crowdsecLapiTlsCertificateBouncerKey,omitempty"` + CrowdsecLapiTLSCertificateBouncerKeyFile string `json:"crowdsecLapiTlsCertificateBouncerKeyFile,omitempty"` + CrowdsecCapiMachineID string `json:"crowdsecCapiMachineId,omitempty"` + CrowdsecCapiMachineIDFile string `json:"crowdsecCapiMachineIdFile,omitempty"` + CrowdsecCapiPassword string `json:"crowdsecCapiPassword,omitempty"` + CrowdsecCapiPasswordFile string `json:"crowdsecCapiPasswordFile,omitempty"` + CrowdsecCapiScenarios []string `json:"crowdsecCapiScenarios,omitempty"` + UpdateIntervalSeconds int64 `json:"updateIntervalSeconds,omitempty"` + MetricsUpdateIntervalSeconds int64 `json:"metricsUpdateIntervalSeconds,omitempty"` + UpdateMaxFailure int64 `json:"updateMaxFailure,omitempty"` + DefaultDecisionSeconds int64 `json:"defaultDecisionSeconds,omitempty"` + RemediationStatusCode int `json:"remediationStatusCode,omitempty"` + HTTPTimeoutSeconds int64 `json:"httpTimeoutSeconds,omitempty"` + TraceHeadersCustomName string `json:"traceHeadersCustomName,omitempty"` + RemediationHeadersCustomName string `json:"remediationHeadersCustomName,omitempty"` + ForwardedHeadersCustomName string `json:"forwardedHeadersCustomName,omitempty"` + ForwardedHeadersTrustedIPs []string `json:"forwardedHeadersTrustedIps,omitempty"` + ClientTrustedIPs []string `json:"clientTrustedIps,omitempty"` + RedisCacheEnabled bool `json:"redisCacheEnabled,omitempty"` + RedisCacheHost string `json:"redisCacheHost,omitempty"` + RedisCachePassword string `json:"redisCachePassword,omitempty"` + RedisCachePasswordFile string `json:"redisCachePasswordFile,omitempty"` + RedisCacheDatabase string `json:"redisCacheDatabase,omitempty"` + RedisCacheUnreachableBlock bool `json:"redisCacheUnreachableBlock,omitempty"` + BanHTMLFilePath string `json:"banHtmlFilePath,omitempty"` + CaptchaHTMLFilePath string `json:"captchaHtmlFilePath,omitempty"` + CaptchaProvider string `json:"captchaProvider,omitempty"` + CaptchaCustomJsURL string `json:"captchaCustomJsUrl,omitempty"` + CaptchaCustomValidateURL string `json:"captchaCustomValidateUrl,omitempty"` + CaptchaCustomKey string `json:"captchaCustomKey,omitempty"` + CaptchaCustomResponse string `json:"captchaCustomResponse,omitempty"` + CaptchaSiteKey string `json:"captchaSiteKey,omitempty"` + CaptchaSiteKeyFile string `json:"captchaSiteKeyFile,omitempty"` + CaptchaSecretKey string `json:"captchaSecretKey,omitempty"` + CaptchaSecretKeyFile string `json:"captchaSecretKeyFile,omitempty"` + CaptchaGracePeriodSeconds int64 `json:"captchaGracePeriodSeconds,omitempty"` } func contains(source []string, target string) bool { From 2d5e5bb97fd59640e30d1b41e7275c3bf3ef6e6b Mon Sep 17 00:00:00 2001 From: maxlerebourg Date: Fri, 12 Dec 2025 15:13:41 +0100 Subject: [PATCH 3/8] :bento: fix lint --- bouncer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bouncer.go b/bouncer.go index e2aa0f7..d6ca94e 100644 --- a/bouncer.go +++ b/bouncer.go @@ -140,7 +140,7 @@ func New(_ context.Context, next http.Handler, config *configuration.Config, nam if config.CrowdsecAppsecScheme == "" { config.CrowdsecAppsecScheme = config.CrowdsecLapiScheme } - tlsAppsecConfig, err := configuration.GetTLSConfigCrowdsec(config, log, true) + tlsAppsecConfig, err = configuration.GetTLSConfigCrowdsec(config, log, true) if err != nil { log.Error("New:getTLSConfigCrowdsec fail to get tlsAppsecConfig " + err.Error()) return nil, err From b5540a4012a004fd8971e7ba8413636837be2369 Mon Sep 17 00:00:00 2001 From: maxlerebourg Date: Fri, 12 Dec 2025 15:20:40 +0100 Subject: [PATCH 4/8] :bento: fix lint --- bouncer.go | 1 - 1 file changed, 1 deletion(-) diff --git a/bouncer.go b/bouncer.go index d6ca94e..6996c16 100644 --- a/bouncer.go +++ b/bouncer.go @@ -151,7 +151,6 @@ func New(_ context.Context, next http.Handler, config *configuration.Config, nam } config.CrowdsecAppsecKey = apiAppsecKey } - var tlsConfig *tls.Config crowdsecStreamRoute := "" From 67d47d684774e8c4f87c682cfd19c84d2cb816b1 Mon Sep 17 00:00:00 2001 From: maxlerebourg Date: Fri, 12 Dec 2025 15:27:43 +0100 Subject: [PATCH 5/8] :bento: fix lint --- bouncer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bouncer.go b/bouncer.go index 6996c16..0e8b4f9 100644 --- a/bouncer.go +++ b/bouncer.go @@ -179,7 +179,7 @@ func New(_ context.Context, next http.Handler, config *configuration.Config, nam } config.CrowdsecLapiKey = apiKey if config.CrowdsecAppsecKey == "" { - config.CrowdsecAppsecKey = apiKey + config.CrowdsecAppsecKey = apiKey } } From 9816bf34099f067697e1fedbc858604ba72a5977 Mon Sep 17 00:00:00 2001 From: maxlerebourg Date: Sat, 20 Dec 2025 12:05:54 +0100 Subject: [PATCH 6/8] :bento: fix after lot of tests --- README.md | 18 ++++++++---------- bouncer.go | 5 ++++- examples/tls-auth/docker-compose.yml | 1 - examples/tls-auth/gencerts.sh | 3 +++ 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index f26d4e2..fdda774 100644 --- a/README.md +++ b/README.md @@ -359,14 +359,6 @@ make run - string - default: "" - PEM-encoded Certificate Authority of Appsec -- CrowdsecAppsecTlsCertificateBouncer - - string - - default: "" - - PEM-encoded client Certificate of Appsec -- CrowdsecAppsecTlsCertificateBouncerKey - - string - - default: "" - - PEM-encoded client private key of Appsec - CrowdsecAppsecScheme - string - default: value of `CrowdsecLapiScheme`, expected values are: `http`, `https` @@ -636,8 +628,7 @@ http: #### Fill variable with value of file -`CrowdsecLapiTlsCertificateBouncerKey`, `CrowdsecLapiTlsCertificateBouncer`, `CrowdsecLapiTlsCertificateAuthority`, -`CrowdsecAppsecTlsCertificateBouncerKey`, `CrowdsecAppsecTlsCertificateBouncer`, `CrowdsecAppsecTlsCertificateAuthority`, `CrowdsecCapiMachineId`, `CrowdsecCapiPassword`, `CrowdsecLapiKey`, `CrowdsecAppsecKey`, `CaptchaSiteKey`, `CaptchaSecretKey` and `RedisCachePassword` can be provided with the content as raw or through a file path that Traefik can read. +`CrowdsecLapiTlsCertificateBouncerKey`, `CrowdsecLapiTlsCertificateBouncer`, `CrowdsecLapiTlsCertificateAuthority`, `CrowdsecAppsecTlsCertificateAuthority`, `CrowdsecCapiMachineId`, `CrowdsecCapiPassword`, `CrowdsecLapiKey`, `CrowdsecAppsecKey`, `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: @@ -700,6 +691,13 @@ Set the `crowdsecLapiScheme` to https. Crowdsec must be listening in HTTPS for this to work. Please see the [tls-auth example](https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/blob/main/examples/tls-auth/README.md) or the official documentation: [docs.crowdsec.net/docs/local_api/tls_auth/](https://docs.crowdsec.net/docs/local_api/tls_auth/) +#### Use HTTPS to communicate with the Appsec + +To communicate with the Appsec in HTTPS you need to either accept any certificates by setting the `crowdsecAppsecTLSInsecureVerify` to true or add the CA used by the server certificate of Crowdsec using `crowdsecAppsecTLSCertificateAuthority` or `crowdsecAppsecTLSCertificateAuthorityFile`. +Set the `crowdsecAppsecScheme` to https. + +Currently AppSec does not support mTLS authentication for the AppSec Component. + #### Manually add an IP to the blocklist (for testing purposes) ```bash diff --git a/bouncer.go b/bouncer.go index 0e8b4f9..3e0bc0c 100644 --- a/bouncer.go +++ b/bouncer.go @@ -137,10 +137,10 @@ func New(_ context.Context, next http.Handler, config *configuration.Config, nam var tlsAppsecConfig *tls.Config if config.CrowdsecAppsecEnabled { + tlsAppsecConfig, err = configuration.GetTLSConfigCrowdsec(config, log, true) if config.CrowdsecAppsecScheme == "" { config.CrowdsecAppsecScheme = config.CrowdsecLapiScheme } - tlsAppsecConfig, err = configuration.GetTLSConfigCrowdsec(config, log, true) if err != nil { log.Error("New:getTLSConfigCrowdsec fail to get tlsAppsecConfig " + err.Error()) return nil, err @@ -382,6 +382,9 @@ func (bouncer *Bouncer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { } } else { value, err := handleNoStreamCache(bouncer, remoteIP) + if err != nil { + bouncer.log.Debug("handleNoStreamCache:crowdsecQuery " + err.Error()) + } if value == cache.NoBannedValue { bouncer.handleNextServeHTTP(rw, req, remoteIP) } else { diff --git a/examples/tls-auth/docker-compose.yml b/examples/tls-auth/docker-compose.yml index 80bfc70..d10f106 100644 --- a/examples/tls-auth/docker-compose.yml +++ b/examples/tls-auth/docker-compose.yml @@ -17,7 +17,6 @@ services: # - "--experimental.localplugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin" volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - - ./LAPIKEY:/etc/traefik/LAPIKEY:ro - logs-tls-auth:/var/log/traefik - crowdsec-certs-tls-auth:/etc/traefik/crowdsec-certs # - ./../../:/plugins-local/src/github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin diff --git a/examples/tls-auth/gencerts.sh b/examples/tls-auth/gencerts.sh index 4003595..4ece92e 100644 --- a/examples/tls-auth/gencerts.sh +++ b/examples/tls-auth/gencerts.sh @@ -1,5 +1,8 @@ #!/bin/bash +if [ -f "/out/inter-key.pem" ]; then + exit 0 +fi stdout=/out/res.log cfssl gencert --initca /in/ca.json 2>${stdout} | cfssljson --bare "/out/ca" && \ # Generate an intermediate certificate that will be used to sign the client certificates From 650edf300586c14a38cb435f4b90787a36d0d88b Mon Sep 17 00:00:00 2001 From: mhx Date: Sun, 21 Dec 2025 21:45:39 +0100 Subject: [PATCH 7/8] update exemple tls with new variables tested --- Makefile | 2 +- ....appsec-enabled.yml => docker-compose.yml} | 10 +++--- examples/tls-auth/README.md | 36 +++++++++++++------ examples/tls-auth/config/acquis.yaml | 9 +++++ examples/tls-auth/docker-compose.yml | 33 ++++++++++------- examples/tls-auth/gencerts.sh | 1 - 6 files changed, 61 insertions(+), 30 deletions(-) rename examples/appsec-enabled/{docker-compose.appsec-enabled.yml => docker-compose.yml} (85%) diff --git a/Makefile b/Makefile index 5894fde..4785399 100644 --- a/Makefile +++ b/Makefile @@ -99,7 +99,7 @@ clean_all_docker: docker compose -f examples/redis-cache/docker-compose.yml down --remove-orphans docker compose -f examples/trusted-ips/docker-compose.yml down --remove-orphans docker compose -f examples/tls-auth/docker-compose.yml down --remove-orphans - docker compose -f examples/appsec-enabled/docker-compose.appsec-enabled.yml down --remove-orphans + docker compose -f examples/appsec-enabled/docker-compose.yml down --remove-orphans docker compose -f examples/captcha/docker-compose.yml down --remove-orphans docker compose -f examples/custom-captcha/docker-compose.yml down --remove-orphans docker compose -f examples/custom-ban-page/docker-compose.yml down --remove-orphans diff --git a/examples/appsec-enabled/docker-compose.appsec-enabled.yml b/examples/appsec-enabled/docker-compose.yml similarity index 85% rename from examples/appsec-enabled/docker-compose.appsec-enabled.yml rename to examples/appsec-enabled/docker-compose.yml index 6ee6372..8658dd5 100644 --- a/examples/appsec-enabled/docker-compose.appsec-enabled.yml +++ b/examples/appsec-enabled/docker-compose.yml @@ -1,6 +1,6 @@ services: traefik: - image: "traefik:v3.0.0" + image: "traefik:v3.5.0" container_name: "traefik" restart: unless-stopped command: @@ -12,13 +12,13 @@ services: - "--providers.docker.exposedbydefault=false" - "--entrypoints.web.address=:80" - - "--experimental.plugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin" - - "--experimental.plugins.bouncer.version=v1.3.0" - # - "--experimental.localplugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin" + # - "--experimental.plugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin" + # - "--experimental.plugins.bouncer.version=v1.3.0" + - "--experimental.localplugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin" volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - logs-appsec-enabled:/var/log/traefik - # - ./../../:/plugins-local/src/github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin + - ./../../:/plugins-local/src/github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin ports: - 8000:80 - 8080:8080 diff --git a/examples/tls-auth/README.md b/examples/tls-auth/README.md index dac4d03..7f8ba6e 100644 --- a/examples/tls-auth/README.md +++ b/examples/tls-auth/README.md @@ -1,7 +1,9 @@ # Example + ## Using https communication and tls authentication with Crowdsec ##### Summary + This example demonstrates the use of https between the Traefik plugin and the Crowdsec LAPI. It is possible to communicate with the LAPI in https and still authenticate with API key. @@ -17,7 +19,9 @@ In that case the setting **crowdsecLapiTLSInsecureVerify** must be set to true. It is recommended to validate the certificate presented by Crowdsec LAPI using the Certificate Authority which created it. You can provide the Certificate Authority using: -* A file path readable by Traefik + +- A file path readable by Traefik + ```yaml http: middlewares: @@ -26,9 +30,11 @@ http: bouncer: crowdsecLapiTlsCertificateAuthorityFile: /etc/traefik/certs/crowdsecCA.pem ``` -* The PEM encoded certificate as a text variable + +- The PEM encoded certificate as a text variable In the static file configuration of Traefik + ```yaml http: middlewares: @@ -36,15 +42,17 @@ http: plugin: bouncer: crowdsecLapiTlsCertificateAuthority: |- - -----BEGIN CERTIFICATE----- - MIIEBzCCAu+gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgZQxCzAJBgNVBAYTAlVT - MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK - ... - C6qNieSwcvWL7C03ri0DefTQMY54r5wP33QU5hJ71JoaZI3YTeT0Nf+NRL4hM++w - Q0veeNzBQXg1f/JxfeA39IDIX1kiCf71tGlT - -----END CERTIFICATE----- + -----BEGIN CERTIFICATE----- + MIIEBzCCAu+gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgZQxCzAJBgNVBAYTAlVT + MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK + ... + C6qNieSwcvWL7C03ri0DefTQMY54r5wP33QU5hJ71JoaZI3YTeT0Nf+NRL4hM++w + Q0veeNzBQXg1f/JxfeA39IDIX1kiCf71tGlT + -----END CERTIFICATE----- ``` + In a dynamic configuration of a provider (ex docker) as a Label + ```yaml services: whoami-foo: @@ -71,26 +79,34 @@ The service `whoami-foo` will authenticate with an **API key** over HTTPS after The service `whoami-bar` will authenticate with a **client certificate** signed by the CA. Access to a route that communicate via https and authenticate with API-key: + ``` curl http://localhost:8000/foo ``` + Access to a route that communicate via https and authenticate with a client certificate: + ``` curl http://localhost:8000/bar ``` + Access to the traefik dashboard + ``` curl http://localhost:8080/dashboard/#/ ``` To play the demo environnement run: + ```bash make run_tlsauth ``` Note: -> Traefik need to be restarted if certificates are regenerated after his launch + +> Traefik need to be restarted if certificates are regenerated after his launch, crowdsec also ## Separate LAPI and Appsec HTTP/S config + To separate TLS config for LAPI and Appsec, you can use all the TLS LAPI variable beginning with `CrowdsecLapi...` into `CrowdsecAppsec...`. Don't forget to set `CrowdsecAppsecScheme: HTTP` or `HTTPS` to trigger the separate setup. diff --git a/examples/tls-auth/config/acquis.yaml b/examples/tls-auth/config/acquis.yaml index 5d52554..d48a16b 100644 --- a/examples/tls-auth/config/acquis.yaml +++ b/examples/tls-auth/config/acquis.yaml @@ -2,3 +2,12 @@ filenames: - /var/log/traefik/access.log labels: type: traefik +--- +listen_addr: 0.0.0.0:7422 +appsec_config: crowdsecurity/virtual-patching +name: myAppSecComponent +source: appsec +labels: + type: appsec +cert_file: /etc/crowdsec/certs/server.pem +key_file: /etc/crowdsec/certs/server-key.pem \ No newline at end of file diff --git a/examples/tls-auth/docker-compose.yml b/examples/tls-auth/docker-compose.yml index d10f106..d1d6c48 100644 --- a/examples/tls-auth/docker-compose.yml +++ b/examples/tls-auth/docker-compose.yml @@ -1,6 +1,6 @@ services: traefik: - image: "traefik:v3.0.0" + image: "traefik:v3.5.0" container_name: "traefik" restart: unless-stopped command: @@ -13,14 +13,13 @@ services: - "--entrypoints.web.address=:80" - "--experimental.plugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin" - - "--experimental.plugins.bouncer.version=v1.3.0" + - "--experimental.plugins.bouncer.version=v1.5.0" # - "--experimental.localplugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin" volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - logs-tls-auth:/var/log/traefik - crowdsec-certs-tls-auth:/etc/traefik/crowdsec-certs # - ./../../:/plugins-local/src/github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin - ports: - 8000:80 - 8080:8080 @@ -28,7 +27,7 @@ services: - crowdsec - gencert -# Use HTTPS scheme but with lapikey authentication + # Use HTTPS scheme but with lapikey authentication # whoami-foo: # image: traefik/whoami # container_name: "simple-service-foo" @@ -37,7 +36,7 @@ services: # - "traefik.enable=true" # - "traefik.http.routers.router-foo.rule=Path(`/foo`)" # - "traefik.http.routers.router-foo.entrypoints=web" - # - "traefik.http.routers.router-foo.middlewares=crowdsec@docker" + # - "traefik.http.routers.router-foo.middlewares=crowdsec@docker" # - "traefik.http.services.service-foo.loadbalancer.server.port=80" # - "traefik.http.middlewares.crowdsec.plugin.bouncer.enabled=true" # - "traefik.http.middlewares.crowdsec.plugin.bouncer.loglevel=DEBUG" @@ -45,34 +44,41 @@ services: # - "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdseclapischeme=https" # - "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdsecLapiTLSCertificateAuthorityFile=/etc/traefik/crowdsec-certs/inter.pem" -# Use HTTPS scheme with TLS cert authentication + # Use HTTPS scheme with TLS cert authentication whoami-bar: image: traefik/whoami container_name: "simple-service-bar" restart: unless-stopped labels: - "traefik.enable=true" - - "traefik.http.routers.router-bar.rule=Path(`/bar`)" + - "traefik.http.routers.router-bar.rule=PathPrefix(`/bar`)" - "traefik.http.routers.router-bar.entrypoints=web" - "traefik.http.routers.router-bar.middlewares=crowdsec@docker" - "traefik.http.services.service-bar.loadbalancer.server.port=80" - "traefik.http.middlewares.crowdsec.plugin.bouncer.enabled=true" - "traefik.http.middlewares.crowdsec.plugin.bouncer.loglevel=DEBUG" + - "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdsecMode=none" - "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdseclapischeme=https" + - "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdsecappsecscheme=https" + - "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdsecAppsecTLSCertificateAuthorityFile=/etc/traefik/crowdsec-certs/inter.pem" + + - "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdsecLapikey=40796d93c2958f9e58345514e67740e5=" - "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdsecLapiTLSCertificateAuthorityFile=/etc/traefik/crowdsec-certs/inter.pem" - "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdsecLapiTLSCertificateBouncerFile=/etc/traefik/crowdsec-certs/bouncer.pem" - - "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdsecLapiTLSCertificateBouncerKeyFile=/etc/traefik/crowdsec-certs/bouncer-key.pem" - + - "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdsecLapiTLSCertificateBouncerKeyFile=/etc/traefik/crowdsec-certs/bouncer-key.pem" + # Enable AppSec + - "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdsecappsecenabled=true" + # Define AppSec host and port informations + - "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdsecappsechost=crowdsec:7422" crowdsec: - image: crowdsecurity/crowdsec:v1.6.1-2 + image: crowdsecurity/crowdsec:latest container_name: "crowdsec" restart: unless-stopped environment: - COLLECTIONS: crowdsecurity/traefik CUSTOM_HOSTNAME: crowdsec # whoami-foo is authenticating with api key over https # whoami-bar is authenticating with tls cert over https - BOUNCER_KEY_TRAEFIK_FOO: 40796d93c2958f9e58345514e67740e5 + BOUNCER_KEY_TRAEFIK_FOO: 40796d93c2958f9e58345514e67740e5= LOCAL_API_URL: https://127.0.0.1:8080 USE_TLS: "true" CERT_FILE: "/etc/crowdsec/certs/server.pem" @@ -87,6 +93,7 @@ services: # DISABLE_AGENT: "true" # Disabled for the examples DISABLE_ONLINE_API: "true" + COLLECTIONS: crowdsecurity/traefik crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-generic-rules volumes: - ./config/acquis.yaml:/etc/crowdsec/acquis.yaml # - ./config/config.yaml:/etc/crowdsec/config_local.yaml @@ -99,7 +106,7 @@ services: - "traefik.enable=false" depends_on: - gencert - + gencert: build: . volumes: diff --git a/examples/tls-auth/gencerts.sh b/examples/tls-auth/gencerts.sh index 4ece92e..67f9521 100644 --- a/examples/tls-auth/gencerts.sh +++ b/examples/tls-auth/gencerts.sh @@ -3,7 +3,6 @@ if [ -f "/out/inter-key.pem" ]; then exit 0 fi -stdout=/out/res.log cfssl gencert --initca /in/ca.json 2>${stdout} | cfssljson --bare "/out/ca" && \ # Generate an intermediate certificate that will be used to sign the client certificates cfssl gencert --initca /in/intermediate.json 2>${stdout} | cfssljson --bare "/out/inter" && \ From 2713a33da41c18450382cf732221b5c2a1c2b998 Mon Sep 17 00:00:00 2001 From: mhx Date: Sun, 21 Dec 2025 21:49:38 +0100 Subject: [PATCH 8/8] fix exemple appsec with release and not localplugin --- docker-compose.local.yml | 7 ++++--- examples/appsec-enabled/docker-compose.yml | 8 ++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/docker-compose.local.yml b/docker-compose.local.yml index 040496a..a746d4e 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -1,6 +1,6 @@ services: traefik: - image: "traefik:v3.0.0" + image: "traefik:v3.5.0" container_name: "traefik" restart: unless-stopped command: @@ -16,8 +16,8 @@ services: volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - logs-local:/var/log/traefik - - './ban.html:/ban.html:ro' - - './captcha.html:/captcha.html:ro' + - "./ban.html:/ban.html:ro" + - "./captcha.html:/captcha.html:ro" - ./:/plugins-local/src/github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin ports: - 8000:80 @@ -52,6 +52,7 @@ services: - "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdsecappsecenabled=true" - "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdsecmode=stream" - "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdseclapikey=40796d93c2958f9e58345514e67740e5=" + - "traefik.http.middlewares.crowdsec.plugin.bouncer.ForwardedHeadersTrustedIPs=172.21.0.1/8" bar2: image: traefik/whoami diff --git a/examples/appsec-enabled/docker-compose.yml b/examples/appsec-enabled/docker-compose.yml index 8658dd5..5834f2b 100644 --- a/examples/appsec-enabled/docker-compose.yml +++ b/examples/appsec-enabled/docker-compose.yml @@ -12,13 +12,13 @@ services: - "--providers.docker.exposedbydefault=false" - "--entrypoints.web.address=:80" - # - "--experimental.plugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin" - # - "--experimental.plugins.bouncer.version=v1.3.0" - - "--experimental.localplugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin" + - "--experimental.plugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin" + - "--experimental.plugins.bouncer.version=v1.5.0" + # - "--experimental.localplugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin" volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - logs-appsec-enabled:/var/log/traefik - - ./../../:/plugins-local/src/github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin + # - ./../../:/plugins-local/src/github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin ports: - 8000:80 - 8080:8080