From 291e1bcec1e26626bf2c46908e27ffe7f5b80636 Mon Sep 17 00:00:00 2001 From: maxlerebourg Date: Mon, 17 Nov 2025 13:31:45 +0100 Subject: [PATCH 1/9] :sparkles: Separate TLS conf for LAPI and Appsec --- README.md | 1 + bouncer.go | 42 +++-- examples/tls-auth/README.md | 6 +- pkg/configuration/configuration.go | 245 ++++++++++++++++------------- 4 files changed, 174 insertions(+), 120 deletions(-) diff --git a/README.md b/README.md index ce093594..e13a083b 100644 --- a/README.md +++ b/README.md @@ -550,6 +550,7 @@ http: httpTimeoutSeconds: 10 crowdsecMode: live crowdsecAppsecEnabled: false + crowdsecAppsecScheme: "" crowdsecAppsecHost: crowdsec:7422 crowdsecAppsecPath: "/" crowdsecAppsecFailureBlock: true diff --git a/bouncer.go b/bouncer.go index eff1d191..8825e829 100644 --- a/bouncer.go +++ b/bouncer.go @@ -85,8 +85,10 @@ type Bouncer struct { enabled bool appsecEnabled bool + appsecScheme string appsecHost string appsecPath string + appsecKey string appsecFailureBlock bool appsecUnreachableBlock bool appsecBodyLimit int64 @@ -111,6 +113,7 @@ type Bouncer struct { clientPoolStrategy *ip.PoolStrategy serverPoolStrategy *ip.PoolStrategy httpClient *http.Client + httpAppsecClient *http.Client cacheClient *cache.Client captchaClient *captcha.Client log *logger.Log @@ -130,6 +133,17 @@ 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 + } + config.CrowdsecAppsecKey = apiAppsecKey var tlsConfig *tls.Config crowdsecStreamRoute := "" @@ -140,22 +154,22 @@ func New(_ context.Context, next http.Handler, config *configuration.Config, nam config.CrowdsecLapiScheme = configuration.HTTPS config.CrowdsecLapiHost = crowdsecCapiHost config.CrowdsecLapiPath = "/" - config.CrowdsecAppsecEnabled = false + config.CrowdsecAppsecEnabled = config.CrowdsecAppsecEnabled && config.CrowdsecAppsecScheme != "" config.UpdateIntervalSeconds = 7200 // 2 hours crowdsecStreamRoute = crowdsecCapiStreamRoute crowdsecHeader = crowdsecCapiHeader } else { crowdsecStreamRoute = crowdsecLapiStreamRoute crowdsecHeader = crowdsecLapiHeader - tlsConfig, err = configuration.GetTLSConfigCrowdsec(config, log) + tlsConfig, err = configuration.GetTLSConfigCrowdsec(config, log, false) if err != nil { log.Error("New:getTLSConfigCrowdsec fail to get tlsConfig " + err.Error()) return nil, err } - apiKey, errAPIKey := configuration.GetVariable(config, "CrowdsecLapiKey") - if errAPIKey != nil && len(tlsConfig.Certificates) == 0 { - log.Error("New:crowdsecLapiKey fail to get CrowdsecLapiKey and no client certificate setup " + errAPIKey.Error()) - return nil, errAPIKey + apiKey, errKey := configuration.GetVariable(config, "CrowdsecLapiKey") + if errKey != nil && len(tlsConfig.Certificates) == 0 { + log.Error("New:crowdsecLapiKey fail to get CrowdsecLapiKey and no client certificate setup " + errKey.Error()) + return nil, errKey } config.CrowdsecLapiKey = apiKey } @@ -173,8 +187,10 @@ func New(_ context.Context, next http.Handler, config *configuration.Config, nam enabled: config.Enabled, crowdsecMode: config.CrowdsecMode, appsecEnabled: config.CrowdsecAppsecEnabled, + appsecScheme: config.CrowdsecAppsecScheme, appsecHost: config.CrowdsecAppsecHost, appsecPath: config.CrowdsecAppsecPath, + appsecKey: config.CrowdsecAppsecKey, appsecFailureBlock: config.CrowdsecAppsecFailureBlock, appsecUnreachableBlock: config.CrowdsecAppsecUnreachableBlock, appsecBodyLimit: config.CrowdsecAppsecBodyLimit, @@ -210,6 +226,14 @@ func New(_ context.Context, next http.Handler, config *configuration.Config, nam }, Timeout: time.Duration(config.HTTPTimeoutSeconds) * time.Second, }, + httpAppsecClient: &http.Client{ + Transport: &http.Transport{ + MaxIdleConns: 10, + IdleConnTimeout: 30 * time.Second, + TLSClientConfig: tlsAppsecConfig, + }, + Timeout: time.Duration(config.HTTPTimeoutSeconds) * time.Second, + }, cacheClient: &cache.Client{}, captchaClient: &captcha.Client{}, } @@ -656,7 +680,7 @@ func crowdsecQuery(bouncer *Bouncer, stringURL string, data []byte) ([]byte, err func appsecQuery(bouncer *Bouncer, ip string, httpReq *http.Request) error { routeURL := url.URL{ - Scheme: bouncer.crowdsecScheme, + Scheme: bouncer.appsecScheme, Host: bouncer.appsecHost, Path: bouncer.appsecPath, } @@ -681,14 +705,14 @@ func appsecQuery(bouncer *Bouncer, ip string, httpReq *http.Request) error { req.Header.Add(key, value) } } - req.Header.Set(crowdsecAppsecHeader, bouncer.crowdsecKey) + req.Header.Set(crowdsecAppsecHeader, bouncer.appsecKey) req.Header.Set(crowdsecAppsecIPHeader, ip) req.Header.Set(crowdsecAppsecVerbHeader, httpReq.Method) req.Header.Set(crowdsecAppsecHostHeader, httpReq.Host) req.Header.Set(crowdsecAppsecURIHeader, httpReq.URL.String()) req.Header.Set(crowdsecAppsecUserAgent, httpReq.Header.Get("User-Agent")) - res, err := bouncer.httpClient.Do(req) + res, err := bouncer.httpAppsecClient.Do(req) if err != nil { bouncer.log.Error("appsecQuery:unreachable") if bouncer.appsecUnreachableBlock { diff --git a/examples/tls-auth/README.md b/examples/tls-auth/README.md index 7cedaadd..96659f97 100644 --- a/examples/tls-auth/README.md +++ b/examples/tls-auth/README.md @@ -89,4 +89,8 @@ make run_tlsauth ``` Note: -> Traefik need to be restart if certificates are regenerated after his launch \ No newline at end of file +> Traefik need to be restart if certificates are regenerated after his launch + +## 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/pkg/configuration/configuration.go b/pkg/configuration/configuration.go index cbda4ca4..4e77995a 100644 --- a/pkg/configuration/configuration.go +++ b/pkg/configuration/configuration.go @@ -42,61 +42,70 @@ 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"` - CrowdsecAppsecHost string `json:"crowdsecAppsecHost,omitempty"` - CrowdsecAppsecPath string `json:"crowdsecAppsecPath,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"` - 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"` + 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"` + 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 { @@ -111,46 +120,49 @@ func contains(source []string, target string) bool { // New creates the default plugin configuration. func New() *Config { return &Config{ - Enabled: false, - LogLevel: LogINFO, - LogFilePath: "", - CrowdsecMode: LiveMode, - CrowdsecAppsecEnabled: false, - CrowdsecAppsecHost: "crowdsec:7422", - CrowdsecAppsecPath: "/", - CrowdsecAppsecFailureBlock: true, - CrowdsecAppsecUnreachableBlock: true, - CrowdsecAppsecBodyLimit: 10485760, - CrowdsecLapiScheme: HTTP, - CrowdsecLapiHost: "crowdsec:8080", - CrowdsecLapiPath: "/", - CrowdsecLapiKey: "", - CrowdsecLapiTLSInsecureVerify: false, - UpdateIntervalSeconds: 60, - MetricsUpdateIntervalSeconds: 600, - UpdateMaxFailure: 0, - DefaultDecisionSeconds: 60, - RemediationStatusCode: http.StatusForbidden, - HTTPTimeoutSeconds: 10, - CaptchaProvider: "", - CaptchaCustomJsURL: "", - CaptchaCustomValidateURL: "", - CaptchaCustomKey: "", - CaptchaCustomResponse: "", - CaptchaSiteKey: "", - CaptchaSecretKey: "", - CaptchaGracePeriodSeconds: 1800, - CaptchaHTMLFilePath: "/captcha.html", - BanHTMLFilePath: "", - RemediationHeadersCustomName: "", - ForwardedHeadersCustomName: "X-Forwarded-For", - ForwardedHeadersTrustedIPs: []string{}, - ClientTrustedIPs: []string{}, - RedisCacheEnabled: false, - RedisCacheHost: "redis:6379", - RedisCachePassword: "", - RedisCacheDatabase: "", - RedisCacheUnreachableBlock: true, + Enabled: false, + LogLevel: LogINFO, + LogFilePath: "", + CrowdsecMode: LiveMode, + CrowdsecAppsecEnabled: false, + CrowdsecAppsecFailureBlock: true, + CrowdsecAppsecUnreachableBlock: true, + CrowdsecAppsecBodyLimit: 10485760, + CrowdsecAppsecScheme: "", + CrowdsecAppsecHost: "crowdsec:7422", + CrowdsecAppsecPath: "/", + CrowdsecAppsecKey: "", + CrowdsecAppsecTLSInsecureVerify: false, + CrowdsecLapiScheme: HTTP, + CrowdsecLapiHost: "crowdsec:8080", + CrowdsecLapiPath: "/", + CrowdsecLapiKey: "", + CrowdsecLapiTLSInsecureVerify: false, + UpdateIntervalSeconds: 60, + MetricsUpdateIntervalSeconds: 600, + UpdateMaxFailure: 0, + DefaultDecisionSeconds: 60, + RemediationStatusCode: http.StatusForbidden, + HTTPTimeoutSeconds: 10, + CaptchaProvider: "", + CaptchaCustomJsURL: "", + CaptchaCustomValidateURL: "", + CaptchaCustomKey: "", + CaptchaCustomResponse: "", + CaptchaSiteKey: "", + CaptchaSecretKey: "", + CaptchaGracePeriodSeconds: 1800, + CaptchaHTMLFilePath: "/captcha.html", + BanHTMLFilePath: "", + RemediationHeadersCustomName: "", + ForwardedHeadersCustomName: "X-Forwarded-For", + ForwardedHeadersTrustedIPs: []string{}, + ClientTrustedIPs: []string{}, + RedisCacheEnabled: false, + RedisCacheHost: "redis:6379", + RedisCachePassword: "", + RedisCacheDatabase: "", + RedisCacheUnreachableBlock: true, } } @@ -412,26 +424,25 @@ func validateParamsRequired(config *Config) error { if !contains([]string{HTTP, HTTPS}, config.CrowdsecLapiScheme) { return errors.New("CrowdsecLapiScheme: must be one of 'http' or 'https'") } + if !contains([]string{HTTP, HTTPS, ""}, config.CrowdsecAppsecScheme) { + return errors.New("CrowdsecAppsecScheme: must be one of 'http' or 'https'") + } return nil } -// GetTLSConfigCrowdsec get TLS config from Config. -// -//nolint:nestif -func GetTLSConfigCrowdsec(config *Config, log *logger.Log) (*tls.Config, error) { +func getTLSConfig(config *Config, log *logger.Log, prefix, scheme string, insecureVerify bool) (*tls.Config, error) { tlsConfig := new(tls.Config) tlsConfig.RootCAs = x509.NewCertPool() - //nolint:gocritic - if config.CrowdsecLapiScheme != HTTPS { - log.Debug("getTLSConfigCrowdsec:CrowdsecLapiScheme https:no") + if scheme != HTTPS { + log.Debug("getTLSConfigCrowdsec:" + prefix + "Scheme https:no") return tlsConfig, nil - } else if config.CrowdsecLapiTLSInsecureVerify { + } else if insecureVerify { tlsConfig.InsecureSkipVerify = true - log.Debug("getTLSConfigCrowdsec:CrowdsecLapiTLSInsecureVerify tlsInsecure:true") + log.Debug("getTLSConfigCrowdsec:" + prefix + "TLSInsecureVerify tlsInsecure:true") // If we return here and still want to use client auth this won't work // return tlsConfig, nil } else { - certAuthority, err := GetVariable(config, "CrowdsecLapiTLSCertificateAuthority") + certAuthority, err := GetVariable(config, prefix + "TLSCertificateAuthority") if err != nil { return nil, err } @@ -439,17 +450,16 @@ func GetTLSConfigCrowdsec(config *Config, log *logger.Log) (*tls.Config, error) if !tlsConfig.RootCAs.AppendCertsFromPEM([]byte(certAuthority)) { // here we return because if CrowdsecLapiTLSInsecureVerify is false // and CA not load, we can't communicate with https - return nil, errors.New("getTLSConfigCrowdsec:cannot load CA and verify cert is enabled") + return nil, errors.New("getTLSConfigCrowdsec:" + prefix + "cannot load CA and verify cert is enabled") } - log.Debug("getTLSConfigCrowdsec:CrowdsecLapiTLSCertificateAuthority CA added successfully") + log.Debug("getTLSConfigCrowdsec:" + prefix + "TLSCertificateAuthority CA added successfully") } } - - certBouncer, err := GetVariable(config, "CrowdsecLapiTLSCertificateBouncer") + certBouncer, err := GetVariable(config, prefix + "TLSCertificateBouncer") if err != nil { return nil, err } - certBouncerKey, err := GetVariable(config, "CrowdsecLapiTLSCertificateBouncerKey") + certBouncerKey, err := GetVariable(config, prefix + "TLSCertificateBouncerKey") if err != nil { return nil, err } @@ -464,3 +474,18 @@ func GetTLSConfigCrowdsec(config *Config, log *logger.Log) (*tls.Config, error) return tlsConfig, nil } + +// GetTLSConfigCrowdsec get TLS config from Config. +func GetTLSConfigCrowdsec(config *Config, log *logger.Log, isAppsec bool) (*tls.Config, error) { + var prefix string + var tlsConfig *tls.Config + var err error + if isAppsec && config.CrowdsecAppsecScheme != "" { + prefix = "CrowdsecAppsec" + return getTLSConfig(config, log, prefix, config.CrowdsecAppsecScheme, config.CrowdsecAppsecTLSInsecureVerify) + } else { + prefix = "CrowdsecLapi" + return getTLSConfig(config, log, prefix, config.CrowdsecLapiScheme, config.CrowdsecLapiTLSInsecureVerify) + } + +} From cb1391f399eb8bf2d8c7e43301cb649e8f1eb7d5 Mon Sep 17 00:00:00 2001 From: maxlerebourg Date: Mon, 17 Nov 2025 13:47:27 +0100 Subject: [PATCH 2/9] :bento: fix lint --- pkg/configuration/configuration.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/configuration/configuration.go b/pkg/configuration/configuration.go index 4e77995a..b970e890 100644 --- a/pkg/configuration/configuration.go +++ b/pkg/configuration/configuration.go @@ -478,8 +478,6 @@ func getTLSConfig(config *Config, log *logger.Log, prefix, scheme string, insecu // GetTLSConfigCrowdsec get TLS config from Config. func GetTLSConfigCrowdsec(config *Config, log *logger.Log, isAppsec bool) (*tls.Config, error) { var prefix string - var tlsConfig *tls.Config - var err error if isAppsec && config.CrowdsecAppsecScheme != "" { prefix = "CrowdsecAppsec" return getTLSConfig(config, log, prefix, config.CrowdsecAppsecScheme, config.CrowdsecAppsecTLSInsecureVerify) From 7b56f691392846fbbeec53c9489c4e8704fedcb2 Mon Sep 17 00:00:00 2001 From: maxlerebourg Date: Mon, 17 Nov 2025 13:49:34 +0100 Subject: [PATCH 3/9] :bento: fix test --- pkg/configuration/configuration_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/configuration/configuration_test.go b/pkg/configuration/configuration_test.go index cc8885ac..2febdb37 100644 --- a/pkg/configuration/configuration_test.go +++ b/pkg/configuration/configuration_test.go @@ -242,7 +242,7 @@ func Test_GetTLSConfigCrowdsec(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := GetTLSConfigCrowdsec(tt.args.config, logger.New("INFO", "")) + got, err := GetTLSConfigCrowdsec(tt.args.config, logger.New("INFO", ""), false) if (err != nil) != tt.wantErr { t.Errorf("getTLSConfigCrowdsec() error = %v, wantErr %v", err, tt.wantErr) return From 5bced4117bf0e9eac433ef4e4a1334df288f084c Mon Sep 17 00:00:00 2001 From: maxlerebourg Date: Mon, 17 Nov 2025 18:06:58 +0100 Subject: [PATCH 4/9] :bento: fix lint --- pkg/configuration/configuration.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/pkg/configuration/configuration.go b/pkg/configuration/configuration.go index b970e890..7cff1ae7 100644 --- a/pkg/configuration/configuration.go +++ b/pkg/configuration/configuration.go @@ -433,9 +433,10 @@ func validateParamsRequired(config *Config) error { func getTLSConfig(config *Config, log *logger.Log, prefix, scheme string, insecureVerify bool) (*tls.Config, error) { tlsConfig := new(tls.Config) tlsConfig.RootCAs = x509.NewCertPool() + //nolint:gocritic if scheme != HTTPS { - log.Debug("getTLSConfigCrowdsec:" + prefix + "Scheme https:no") - return tlsConfig, nil + log.Debug("getTLSConfigCrowdsec:" + prefix + "Scheme https:no") + return tlsConfig, nil } else if insecureVerify { tlsConfig.InsecureSkipVerify = true log.Debug("getTLSConfigCrowdsec:" + prefix + "TLSInsecureVerify tlsInsecure:true") @@ -481,9 +482,7 @@ func GetTLSConfigCrowdsec(config *Config, log *logger.Log, isAppsec bool) (*tls. if isAppsec && config.CrowdsecAppsecScheme != "" { prefix = "CrowdsecAppsec" return getTLSConfig(config, log, prefix, config.CrowdsecAppsecScheme, config.CrowdsecAppsecTLSInsecureVerify) - } else { - prefix = "CrowdsecLapi" - return getTLSConfig(config, log, prefix, config.CrowdsecLapiScheme, config.CrowdsecLapiTLSInsecureVerify) - } - + } + prefix = "CrowdsecLapi" + return getTLSConfig(config, log, prefix, config.CrowdsecLapiScheme, config.CrowdsecLapiTLSInsecureVerify) } From 1ddae9b95142efe3f5a177f3aab52fe82cab90e7 Mon Sep 17 00:00:00 2001 From: maxlerebourg Date: Mon, 8 Dec 2025 09:02:47 +0100 Subject: [PATCH 5/9] :bento: fix lint --- examples/tls-auth/README.md | 2 +- pkg/configuration/configuration.go | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/tls-auth/README.md b/examples/tls-auth/README.md index 96659f97..dac4d031 100644 --- a/examples/tls-auth/README.md +++ b/examples/tls-auth/README.md @@ -89,7 +89,7 @@ make run_tlsauth ``` Note: -> Traefik need to be restart if certificates are regenerated after his launch +> Traefik need to be restarted if certificates are regenerated after his launch ## 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...`. diff --git a/pkg/configuration/configuration.go b/pkg/configuration/configuration.go index 7cff1ae7..ddf975b0 100644 --- a/pkg/configuration/configuration.go +++ b/pkg/configuration/configuration.go @@ -433,11 +433,12 @@ func validateParamsRequired(config *Config) error { func getTLSConfig(config *Config, log *logger.Log, prefix, scheme string, insecureVerify bool) (*tls.Config, error) { tlsConfig := new(tls.Config) tlsConfig.RootCAs = x509.NewCertPool() - //nolint:gocritic if scheme != HTTPS { - log.Debug("getTLSConfigCrowdsec:" + prefix + "Scheme https:no") - return tlsConfig, nil - } else if insecureVerify { + log.Debug("getTLSConfigCrowdsec:" + prefix + "Scheme https:no") + return tlsConfig, nil + } + //nolint:gocritic + if insecureVerify { tlsConfig.InsecureSkipVerify = true log.Debug("getTLSConfigCrowdsec:" + prefix + "TLSInsecureVerify tlsInsecure:true") // If we return here and still want to use client auth this won't work From 85d970c356acc35197461943a13e5b4e1a9149fd Mon Sep 17 00:00:00 2001 From: maxlerebourg Date: Mon, 8 Dec 2025 10:53:16 +0100 Subject: [PATCH 6/9] :bento: fix lint --- pkg/configuration/configuration.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/configuration/configuration.go b/pkg/configuration/configuration.go index ddf975b0..009aae1f 100644 --- a/pkg/configuration/configuration.go +++ b/pkg/configuration/configuration.go @@ -437,7 +437,7 @@ func getTLSConfig(config *Config, log *logger.Log, prefix, scheme string, insecu log.Debug("getTLSConfigCrowdsec:" + prefix + "Scheme https:no") return tlsConfig, nil } - //nolint:gocritic + //nolint:nestif if insecureVerify { tlsConfig.InsecureSkipVerify = true log.Debug("getTLSConfigCrowdsec:" + prefix + "TLSInsecureVerify tlsInsecure:true") From 5080e9bfe8846505b3975280d75f0d0d5c807ee0 Mon Sep 17 00:00:00 2001 From: maxlerebourg Date: Mon, 8 Dec 2025 11:02:04 +0100 Subject: [PATCH 7/9] :bento: fix lint ? --- pkg/configuration/configuration.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/configuration/configuration.go b/pkg/configuration/configuration.go index 009aae1f..6f077dc3 100644 --- a/pkg/configuration/configuration.go +++ b/pkg/configuration/configuration.go @@ -444,7 +444,7 @@ func getTLSConfig(config *Config, log *logger.Log, prefix, scheme string, insecu // If we return here and still want to use client auth this won't work // return tlsConfig, nil } else { - certAuthority, err := GetVariable(config, prefix + "TLSCertificateAuthority") + certAuthority, err := GetVariable(config, prefix+"TLSCertificateAuthority") if err != nil { return nil, err } @@ -457,11 +457,11 @@ func getTLSConfig(config *Config, log *logger.Log, prefix, scheme string, insecu log.Debug("getTLSConfigCrowdsec:" + prefix + "TLSCertificateAuthority CA added successfully") } } - certBouncer, err := GetVariable(config, prefix + "TLSCertificateBouncer") + certBouncer, err := GetVariable(config, prefix+"TLSCertificateBouncer") if err != nil { return nil, err } - certBouncerKey, err := GetVariable(config, prefix + "TLSCertificateBouncerKey") + certBouncerKey, err := GetVariable(config, prefix+"TLSCertificateBouncerKey") if err != nil { return nil, err } From 776daed25747de8d969fcf46312c0a6185085b97 Mon Sep 17 00:00:00 2001 From: maxlerebourg Date: Mon, 8 Dec 2025 11:03:32 +0100 Subject: [PATCH 8/9] :bento: fix lint --- pkg/configuration/configuration.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/configuration/configuration.go b/pkg/configuration/configuration.go index 6f077dc3..6f057719 100644 --- a/pkg/configuration/configuration.go +++ b/pkg/configuration/configuration.go @@ -483,7 +483,7 @@ func GetTLSConfigCrowdsec(config *Config, log *logger.Log, isAppsec bool) (*tls. if isAppsec && config.CrowdsecAppsecScheme != "" { prefix = "CrowdsecAppsec" return getTLSConfig(config, log, prefix, config.CrowdsecAppsecScheme, config.CrowdsecAppsecTLSInsecureVerify) - } + } prefix = "CrowdsecLapi" return getTLSConfig(config, log, prefix, config.CrowdsecLapiScheme, config.CrowdsecLapiTLSInsecureVerify) } From 7115705da045d341d93a48c8b37594c524fe0638 Mon Sep 17 00:00:00 2001 From: maxlerebourg Date: Wed, 10 Dec 2025 09:08:18 +0100 Subject: [PATCH 9/9] :bento: Add default for traceCustomHeaders --- pkg/configuration/configuration.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/configuration/configuration.go b/pkg/configuration/configuration.go index baf132fb..5becb98e 100644 --- a/pkg/configuration/configuration.go +++ b/pkg/configuration/configuration.go @@ -155,6 +155,7 @@ func New() *Config { CaptchaGracePeriodSeconds: 1800, CaptchaHTMLFilePath: "/captcha.html", BanHTMLFilePath: "", + TraceHeadersCustomName: "", RemediationHeadersCustomName: "", ForwardedHeadersCustomName: "X-Forwarded-For", ForwardedHeadersTrustedIPs: []string{},