Standalone mode come back (#74)

*  Standalone mode come back

* 🍱 fix lint

* 🐛 fix tests cache

* 🐛 fix tests

* 🐛 fix tests

* 🚨 fix lint

* 🚨 fix lint

* 🍱 add logging

* :rotating-light: fix lint

* 🍱 fix comments

* 🐛 fix tests

* 🚨 Fix lint

Co-authored-by: Mathieu HANOTAUX <mathieu@hanotaux.fr>
This commit is contained in:
maxlerebourg
2023-01-02 11:53:57 +01:00
committed by GitHub
co-authored by Mathieu HANOTAUX
parent 87a839f22d
commit 1b310b2c15
9 changed files with 291 additions and 268 deletions
+26 -11
View File
@@ -24,6 +24,7 @@ There are 3 operating modes (CrowdsecMode) for this plugin:
| none | If the client IP is on ban list, it will get a http code 403 response. Otherwise, request will continue as usual. All request call the Crowdsec LAPI | | none | If the client IP is on ban list, it will get a http code 403 response. Otherwise, request will continue as usual. All request call the Crowdsec LAPI |
| live | If the client IP is on ban list, it will get a http code 403 response. Otherwise, request will continue as usual. The bouncer can leverage use of a local cache in order to reduce the number of requests made to the Crowdsec LAPI. It will keep in cache the status for each IP that makes queries. | | live | If the client IP is on ban list, it will get a http code 403 response. Otherwise, request will continue as usual. The bouncer can leverage use of a local cache in order to reduce the number of requests made to the Crowdsec LAPI. It will keep in cache the status for each IP that makes queries. |
| stream | Stream Streaming mode allows you to keep in the local cache only the Banned IPs, every requests that does not hit the cache is authorized. Every minute, the cache is updated with news from the Crowdsec LAPI. | | stream | Stream Streaming mode allows you to keep in the local cache only the Banned IPs, every requests that does not hit the cache is authorized. Every minute, the cache is updated with news from the Crowdsec LAPI. |
| alone | Standalone mode, similar to the streaming mode but the blacklisted IPs are fetched on the CAPI. Every 2 hours, the cache is updated with news from the Crowdsec CAPI. It does not include any localy banned IP, but can work without a crowdsec service. |
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 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.
@@ -55,7 +56,7 @@ make run
- default: `INFO`, expected values are: `INFO`, `DEBUG` - default: `INFO`, expected values are: `INFO`, `DEBUG`
- CrowdsecMode - CrowdsecMode
- string - string
- default: `live`, expected values are: `none`, `live`, `stream` - default: `live`, expected values are: `none`, `live`, `stream`, `alone`
- CrowdsecLapiScheme - CrowdsecLapiScheme
- string - string
- default: `http`, expected values are: `http`, `https` - default: `http`, expected values are: `http`, `https`
@@ -83,14 +84,6 @@ make run
- string - string
- default: "" - default: ""
- PEM-encoded client private key of the Bouncer - PEM-encoded client private key of the Bouncer
- UpdateIntervalSeconds
- int64
- default: 60
- Used only in `stream` mode, the interval between requests to fetch blacklisted IPs from LAPI
- DefaultDecisionSeconds
- int64
- default: 60
- Used only in `live` mode, decision duration of accepted IPs
- ClientTrustedIPs - ClientTrustedIPs
- string - string
- default: [] - default: []
@@ -111,6 +104,23 @@ make run
- string - string
- default: "redis:6379" - default: "redis:6379"
- hostname and port for the redis service - hostname and port for the redis service
- UpdateIntervalSeconds
- int64
- default: 60
- Used only in `stream` mode, the interval between requests to fetch blacklisted IPs from LAPI
- DefaultDecisionSeconds
- int64
- default: 60
- Used only in `live` mode, decision duration of accepted IPs
- CrowdsecCapiMachineId
- string
- Used only in `alone` mode, login for Crowdsec CAPI
- CrowdsecCapiPassword
- string
- Used only in `alone` mode, password for Crowdsec CAPI
- CrowdsecCapiScenarios
- []string
- Used only in `alone` mode, scenarios for Crowdsec CAPI
### Configuration ### Configuration
@@ -160,6 +170,12 @@ http:
crowdsecLapiHost: crowdsec:8080 crowdsecLapiHost: crowdsec:8080
crowdsecLapiScheme: http crowdsecLapiScheme: http
crowdsecLapiTLSInsecureVerify: false crowdsecLapiTLSInsecureVerify: false
crowdsecCapiMachineId: login
crowdsecCapiPassword: password
crowdsecCapiScenarios:
- crowdsecurity/http-path-traversal-probing
- crowdsecurity/http-xss-probing
- crowdsecurity/http-generic-bf
forwardedHeadersTrustedIPs: forwardedHeadersTrustedIPs:
- 10.0.10.23/32 - 10.0.10.23/32
- 10.0.20.0/24 - 10.0.20.0/24
@@ -190,12 +206,11 @@ http:
ic5cDRo6/VD3CS3MYzyBcibaGaV34nr0G/pI+KEqkYChzk/PZRA= ic5cDRo6/VD3CS3MYzyBcibaGaV34nr0G/pI+KEqkYChzk/PZRA=
-----END RSA PRIVATE KEY----- -----END RSA PRIVATE KEY-----
crowdsecLapiTLSCertificateBouncerKeyFile: /etc/traefik/crowdsec-certs/bouncer-key.pem crowdsecLapiTLSCertificateBouncerKeyFile: /etc/traefik/crowdsec-certs/bouncer-key.pem
``` ```
#### Fill variable with value of file #### Fill variable with value of file
`CrowdsecLapiTlsCertificateBouncerKey`, `CrowdsecLapiTlsCertificateBouncer`, `CrowdsecLapiTlsCertificateAuthority` and `CrowdsecLapiKey` can be provided with the content as raw or through a file path that Traefik can read. `CrowdsecLapiTlsCertificateBouncerKey`, `CrowdsecLapiTlsCertificateBouncer`, `CrowdsecLapiTlsCertificateAuthority`, `CrowdsecCapiMachineId`, `CrowdsecCapiPassword` and `CrowdsecLapiKey` 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. The file variable will be used as preference if both content and file are provided for the same variable.
Format is: Format is:
+118 -31
View File
@@ -5,6 +5,7 @@ package crowdsec_bouncer_traefik_plugin //nolint:revive,stylecheck
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/tls"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
@@ -24,14 +25,17 @@ import (
const ( const (
crowdsecLapiHeader = "X-Api-Key" crowdsecLapiHeader = "X-Api-Key"
crowdsecCapiHeader = "Authorization"
crowdsecLapiRoute = "v1/decisions" crowdsecLapiRoute = "v1/decisions"
crowdsecLapiStreamRoute = "v1/decisions/stream" crowdsecLapiStreamRoute = "v1/decisions/stream"
crowdsecCapiLogin = "v2/watchers/login"
crowdsecCapiStreamRoute = "v2/decisions/stream"
cacheTimeoutKey = "updated" cacheTimeoutKey = "updated"
) )
//nolint:gochecknoglobals //nolint:gochecknoglobals
var ( var (
isCrowdsecStreamHealthy = false isCrowdsecStreamHealthy = true
ticker chan bool ticker chan bool
) )
@@ -51,12 +55,18 @@ type Bouncer struct {
crowdsecHost string crowdsecHost string
crowdsecKey string crowdsecKey string
crowdsecMode string crowdsecMode string
crowdsecMachineID string
crowdsecPassword string
crowdsecScenarios []string
updateInterval int64 updateInterval int64
defaultDecisionTimeout int64 defaultDecisionTimeout int64
customHeader string customHeader string
crowdsecStreamRoute string
crowdsecHeader string
clientPoolStrategy *ip.PoolStrategy clientPoolStrategy *ip.PoolStrategy
serverPoolStrategy *ip.PoolStrategy serverPoolStrategy *ip.PoolStrategy
client *http.Client httpClient *http.Client
cacheClient *cache.Client
} }
// New creates the crowdsec bouncer plugin. // New creates the crowdsec bouncer plugin.
@@ -64,24 +74,39 @@ func New(ctx context.Context, next http.Handler, config *configuration.Config, n
logger.Init(config.LogLevel) logger.Init(config.LogLevel)
err := configuration.ValidateParams(config) err := configuration.ValidateParams(config)
if err != nil { if err != nil {
logger.Info(fmt.Sprintf("New:validateParams %s", err.Error())) logger.Error(fmt.Sprintf("New:validateParams %s", err.Error()))
return nil, err return nil, err
} }
serverChecker, _ := ip.NewChecker(config.ForwardedHeadersTrustedIPs) serverChecker, _ := ip.NewChecker(config.ForwardedHeadersTrustedIPs)
clientChecker, _ := ip.NewChecker(config.ClientTrustedIPs) clientChecker, _ := ip.NewChecker(config.ClientTrustedIPs)
tlsConfig, err := configuration.GetTLSConfigCrowdsec(config) var tlsConfig *tls.Config
crowdsecStreamRoute := ""
crowdsecHeader := ""
if config.CrowdsecMode == configuration.AloneMode {
config.CrowdsecCapiMachineID, _ = configuration.GetVariable(config, "CrowdsecCapiMachineId")
config.CrowdsecCapiPassword, _ = configuration.GetVariable(config, "CrowdsecCapiPassword")
config.CrowdsecLapiHost = "api.crowdsec.net"
config.CrowdsecLapiScheme = "https"
config.UpdateIntervalSeconds = 7200
crowdsecStreamRoute = crowdsecCapiStreamRoute
crowdsecHeader = crowdsecCapiHeader
} else {
crowdsecStreamRoute = crowdsecLapiStreamRoute
crowdsecHeader = crowdsecLapiHeader
tlsConfig, err = configuration.GetTLSConfigCrowdsec(config)
if err != nil { if err != nil {
logger.Error(fmt.Sprintf("New:getTLSConfigCrowdsec fail to get tlsConfig %s", err.Error())) logger.Error(fmt.Sprintf("New:getTLSConfigCrowdsec fail to get tlsConfig %s", err.Error()))
return nil, err return nil, err
} }
apiKey, err := configuration.GetVariable(config, "CrowdsecLapiKey") apiKey, errAPIKey := configuration.GetVariable(config, "CrowdsecLapiKey")
if err != nil && len(tlsConfig.Certificates) == 0 { if errAPIKey != nil && len(tlsConfig.Certificates) == 0 {
logger.Error(fmt.Sprintf("New:crowdsecLapiKey fail to get CrowdsecLapiKey and no client certificate setup %s", err.Error())) logger.Error(fmt.Sprintf("New:crowdsecLapiKey fail to get CrowdsecLapiKey and no client certificate setup %s", errAPIKey.Error()))
return nil, err return nil, err
} }
apiKey = strings.TrimSuffix(apiKey, "\n") config.CrowdsecLapiKey = apiKey
}
bouncer := &Bouncer{ bouncer := &Bouncer{
next: next, next: next,
@@ -92,17 +117,22 @@ func New(ctx context.Context, next http.Handler, config *configuration.Config, n
crowdsecMode: config.CrowdsecMode, crowdsecMode: config.CrowdsecMode,
crowdsecScheme: config.CrowdsecLapiScheme, crowdsecScheme: config.CrowdsecLapiScheme,
crowdsecHost: config.CrowdsecLapiHost, crowdsecHost: config.CrowdsecLapiHost,
crowdsecKey: apiKey, crowdsecKey: config.CrowdsecLapiKey,
crowdsecMachineID: config.CrowdsecCapiMachineID,
crowdsecPassword: config.CrowdsecCapiPassword,
crowdsecScenarios: config.CrowdsecCapiScenarios,
updateInterval: config.UpdateIntervalSeconds, updateInterval: config.UpdateIntervalSeconds,
customHeader: config.ForwardedHeadersCustomName, customHeader: config.ForwardedHeadersCustomName,
defaultDecisionTimeout: config.DefaultDecisionSeconds, defaultDecisionTimeout: config.DefaultDecisionSeconds,
crowdsecStreamRoute: crowdsecStreamRoute,
crowdsecHeader: crowdsecHeader,
serverPoolStrategy: &ip.PoolStrategy{ serverPoolStrategy: &ip.PoolStrategy{
Checker: serverChecker, Checker: serverChecker,
}, },
clientPoolStrategy: &ip.PoolStrategy{ clientPoolStrategy: &ip.PoolStrategy{
Checker: clientChecker, Checker: clientChecker,
}, },
client: &http.Client{ httpClient: &http.Client{
Transport: &http.Transport{ Transport: &http.Transport{
MaxIdleConns: 10, MaxIdleConns: 10,
IdleConnTimeout: 30 * time.Second, IdleConnTimeout: 30 * time.Second,
@@ -110,16 +140,24 @@ func New(ctx context.Context, next http.Handler, config *configuration.Config, n
}, },
Timeout: 10 * time.Second, Timeout: 10 * time.Second,
}, },
cacheClient: &cache.Client{},
}
bouncer.cacheClient.New(config.RedisCacheEnabled, config.RedisCacheHost)
if (config.CrowdsecMode == configuration.StreamMode || config.CrowdsecMode == configuration.AloneMode) && ticker == nil {
if config.CrowdsecMode == configuration.AloneMode {
err = getToken(bouncer)
if err != nil {
logger.Error(fmt.Sprintf("New:getToken %s", err.Error()))
return nil, err
} }
if config.RedisCacheEnabled {
cache.InitRedisClient(config.RedisCacheHost)
} }
if config.CrowdsecMode == configuration.StreamMode && ticker == nil {
ticker = startTicker(config, func() { ticker = startTicker(config, func() {
handleStreamCache(bouncer) handleStreamCache(bouncer)
}) })
go handleStreamCache(bouncer) go handleStreamCache(bouncer)
} }
logger.Debug(fmt.Sprintf("New initialized mode:%s", config.CrowdsecMode))
return bouncer, nil return bouncer, nil
} }
@@ -155,9 +193,9 @@ func (bouncer *Bouncer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
// TODO This should be simplified // TODO This should be simplified
if bouncer.crowdsecMode != configuration.NoneMode { if bouncer.crowdsecMode != configuration.NoneMode {
isBanned, erro := cache.GetDecision(remoteIP) isBanned, erro := bouncer.cacheClient.GetDecision(remoteIP)
if erro != nil { if erro != nil {
logger.Debug(fmt.Sprintf("ServeHTTP:getDecision ip:%s %s", remoteIP, erro.Error())) logger.Debug(fmt.Sprintf("ServeHTTP:getDecision ip:%s isBanned:true %s", remoteIP, erro.Error()))
if erro.Error() == simpleredis.RedisUnreachable { if erro.Error() == simpleredis.RedisUnreachable {
rw.WriteHeader(http.StatusForbidden) rw.WriteHeader(http.StatusForbidden)
return return
@@ -174,19 +212,20 @@ func (bouncer *Bouncer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
} }
// Right here if we cannot join the stream we forbid the request to go on. // Right here if we cannot join the stream we forbid the request to go on.
if bouncer.crowdsecMode == configuration.StreamMode { if bouncer.crowdsecMode == configuration.StreamMode || bouncer.crowdsecMode == configuration.AloneMode {
if isCrowdsecStreamHealthy { if isCrowdsecStreamHealthy {
bouncer.next.ServeHTTP(rw, req) bouncer.next.ServeHTTP(rw, req)
} else { } else {
logger.Error(fmt.Sprintf("ServeHTTP:isCrowdsecStreamHealthy ip:%s", remoteIP)) logger.Error(fmt.Sprintf("ServeHTTP isCrowdsecStreamHealthy:false ip:%s", remoteIP))
rw.WriteHeader(http.StatusForbidden) rw.WriteHeader(http.StatusForbidden)
} }
} else { } else {
err = handleNoStreamCache(bouncer, remoteIP) err = handleNoStreamCache(bouncer, remoteIP)
if err != nil { if err != nil {
logger.Debug(fmt.Sprintf("ServeHTTP:handleNoStreamCache ip:%s %s", remoteIP, err.Error())) logger.Debug(fmt.Sprintf("ServeHTTP:handleNoStreamCache ip:%s isBanned:true %s", remoteIP, err.Error()))
rw.WriteHeader(http.StatusForbidden) rw.WriteHeader(http.StatusForbidden)
} else { } else {
logger.Debug(fmt.Sprintf("ServeHTTP:handleNoStreamCache ip:%s isBanned:false", remoteIP))
bouncer.next.ServeHTTP(rw, req) bouncer.next.ServeHTTP(rw, req)
} }
} }
@@ -213,6 +252,13 @@ type Stream struct {
New []Decision `json:"new"` New []Decision `json:"new"`
} }
// Login Body returned from Crowdsec Login CAPI.
type Login struct {
Code int `json:"code"`
Token string `json:"token"`
Expire string `json:"expire"`
}
func startTicker(config *configuration.Config, work func()) chan bool { func startTicker(config *configuration.Config, work func()) chan bool {
ticker := time.NewTicker(time.Duration(config.UpdateIntervalSeconds) * time.Second) ticker := time.NewTicker(time.Duration(config.UpdateIntervalSeconds) * time.Second)
stop := make(chan bool, 1) stop := make(chan bool, 1)
@@ -239,14 +285,14 @@ func handleNoStreamCache(bouncer *Bouncer, remoteIP string) error {
Path: crowdsecLapiRoute, Path: crowdsecLapiRoute,
RawQuery: fmt.Sprintf("ip=%v&banned=true", remoteIP), RawQuery: fmt.Sprintf("ip=%v&banned=true", remoteIP),
} }
body, err := crowdsecQuery(bouncer, routeURL.String()) body, err := crowdsecQuery(bouncer, routeURL.String(), false)
if err != nil { if err != nil {
return err return err
} }
if bytes.Equal(body, []byte("null")) { if bytes.Equal(body, []byte("null")) {
if isLiveMode { if isLiveMode {
cache.SetDecision(remoteIP, false, bouncer.defaultDecisionTimeout) bouncer.cacheClient.SetDecision(remoteIP, false, bouncer.defaultDecisionTimeout)
} }
return nil return nil
} }
@@ -258,7 +304,7 @@ func handleNoStreamCache(bouncer *Bouncer, remoteIP string) error {
} }
if len(decisions) == 0 { if len(decisions) == 0 {
if isLiveMode { if isLiveMode {
cache.SetDecision(remoteIP, false, bouncer.defaultDecisionTimeout) bouncer.cacheClient.SetDecision(remoteIP, false, bouncer.defaultDecisionTimeout)
} }
return nil return nil
} }
@@ -267,29 +313,53 @@ func handleNoStreamCache(bouncer *Bouncer, remoteIP string) error {
return fmt.Errorf("handleNoStreamCache:parseDuration %w", err) return fmt.Errorf("handleNoStreamCache:parseDuration %w", err)
} }
if isLiveMode { if isLiveMode {
cache.SetDecision(remoteIP, true, int64(duration.Seconds())) bouncer.cacheClient.SetDecision(remoteIP, true, int64(duration.Seconds()))
} }
return fmt.Errorf("handleNoStreamCache:banned") return fmt.Errorf("handleNoStreamCache:banned")
} }
func getToken(bouncer *Bouncer) error {
loginURL := url.URL{
Scheme: bouncer.crowdsecScheme,
Host: bouncer.crowdsecHost,
Path: crowdsecCapiLogin,
}
body, err := crowdsecQuery(bouncer, loginURL.String(), true)
if err != nil {
return err
}
var login Login
err = json.Unmarshal(body, &login)
if err != nil {
isCrowdsecStreamHealthy = false
return fmt.Errorf("getToken:parsingBody %w", err)
}
if login.Code == 200 && len(login.Token) > 0 {
bouncer.crowdsecKey = login.Token
logger.Debug(fmt.Sprintf("getToken statusCode:%d", login.Code))
return nil
}
return fmt.Errorf("getToken statusCode:%d", login.Code)
}
func handleStreamCache(bouncer *Bouncer) { func handleStreamCache(bouncer *Bouncer) {
// TODO clean properly on exit. // TODO clean properly on exit.
// Instead of blocking the goroutine interval for all the secondary node, // Instead of blocking the goroutine interval for all the secondary node,
// if the master service is shut down, other goroutine can take the lead // if the master service is shut down, other goroutine can take the lead
// because updated routine information is in the cache // because updated routine information is in the cache
_, err := cache.GetDecision(cacheTimeoutKey) _, err := bouncer.cacheClient.GetDecision(cacheTimeoutKey)
if err == nil { if err == nil {
logger.Debug("handleStreamCache:alreadyUpdated") logger.Debug("handleStreamCache:alreadyUpdated")
return return
} }
cache.SetDecision(cacheTimeoutKey, false, bouncer.updateInterval-1) bouncer.cacheClient.SetDecision(cacheTimeoutKey, false, bouncer.updateInterval-1)
streamRouteURL := url.URL{ streamRouteURL := url.URL{
Scheme: bouncer.crowdsecScheme, Scheme: bouncer.crowdsecScheme,
Host: bouncer.crowdsecHost, Host: bouncer.crowdsecHost,
Path: crowdsecLapiStreamRoute, Path: bouncer.crowdsecStreamRoute,
RawQuery: fmt.Sprintf("startup=%t", !isCrowdsecStreamHealthy), RawQuery: fmt.Sprintf("startup=%t", !isCrowdsecStreamHealthy),
} }
body, err := crowdsecQuery(bouncer, streamRouteURL.String()) body, err := crowdsecQuery(bouncer, streamRouteURL.String(), false)
if err != nil { if err != nil {
logger.Error(err.Error()) logger.Error(err.Error())
isCrowdsecStreamHealthy = false isCrowdsecStreamHealthy = false
@@ -305,23 +375,40 @@ func handleStreamCache(bouncer *Bouncer) {
for _, decision := range stream.New { for _, decision := range stream.New {
duration, err := time.ParseDuration(decision.Duration) duration, err := time.ParseDuration(decision.Duration)
if err == nil { if err == nil {
cache.SetDecision(decision.Value, true, int64(duration.Seconds())) bouncer.cacheClient.SetDecision(decision.Value, true, int64(duration.Seconds()))
} }
} }
for _, decision := range stream.Deleted { for _, decision := range stream.Deleted {
cache.DeleteDecision(decision.Value) bouncer.cacheClient.DeleteDecision(decision.Value)
} }
logger.Debug("handleStreamCache:updated")
isCrowdsecStreamHealthy = true isCrowdsecStreamHealthy = true
} }
func crowdsecQuery(bouncer *Bouncer, stringURL string) ([]byte, error) { func crowdsecQuery(bouncer *Bouncer, stringURL string, isPost bool) ([]byte, error) {
var req *http.Request var req *http.Request
if isPost {
data := []byte(fmt.Sprintf(
`{"machine_id": "%v","password": "%v","scenarios": ["%v"]}`,
bouncer.crowdsecMachineID,
bouncer.crowdsecPassword,
strings.Join(bouncer.crowdsecScenarios, `","`),
))
req, _ = http.NewRequest(http.MethodPost, stringURL, bytes.NewBuffer(data))
} else {
req, _ = http.NewRequest(http.MethodGet, stringURL, nil) req, _ = http.NewRequest(http.MethodGet, stringURL, nil)
req.Header.Add(crowdsecLapiHeader, bouncer.crowdsecKey) }
res, err := bouncer.client.Do(req) req.Header.Add(bouncer.crowdsecHeader, bouncer.crowdsecKey)
res, err := bouncer.httpClient.Do(req)
if err != nil { if err != nil {
return nil, fmt.Errorf("crowdsecQuery url:%s %w", stringURL, err) return nil, fmt.Errorf("crowdsecQuery url:%s %w", stringURL, err)
} }
if res.StatusCode == http.StatusUnauthorized && bouncer.crowdsecMode == configuration.AloneMode {
if errToken := getToken(bouncer); errToken != nil {
return nil, fmt.Errorf("crowdsecQuery:renewToken url:%s %w", stringURL, errToken)
}
return crowdsecQuery(bouncer, stringURL, false)
}
if res.StatusCode != http.StatusOK { if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("crowdsecQuery url:%s, statusCode:%d", stringURL, res.StatusCode) return nil, fmt.Errorf("crowdsecQuery url:%s, statusCode:%d", stringURL, res.StatusCode)
} }
+7 -3
View File
@@ -8,6 +8,7 @@ import (
"testing" "testing"
"text/template" "text/template"
cache "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/cache"
configuration "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/configuration" configuration "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/configuration"
ip "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/ip" ip "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/ip"
) )
@@ -77,7 +78,8 @@ func TestBouncer_ServeHTTP(t *testing.T) {
customHeader string customHeader string
clientPoolStrategy *ip.PoolStrategy clientPoolStrategy *ip.PoolStrategy
serverPoolStrategy *ip.PoolStrategy serverPoolStrategy *ip.PoolStrategy
client *http.Client httpClient *http.Client
cacheClient *cache.Client
} }
type args struct { type args struct {
rw http.ResponseWriter rw http.ResponseWriter
@@ -106,7 +108,8 @@ func TestBouncer_ServeHTTP(t *testing.T) {
customHeader: tt.fields.customHeader, customHeader: tt.fields.customHeader,
clientPoolStrategy: tt.fields.clientPoolStrategy, clientPoolStrategy: tt.fields.clientPoolStrategy,
serverPoolStrategy: tt.fields.serverPoolStrategy, serverPoolStrategy: tt.fields.serverPoolStrategy,
client: tt.fields.client, httpClient: tt.fields.httpClient,
cacheClient: tt.fields.cacheClient,
} }
bouncer.ServeHTTP(tt.args.rw, tt.args.req) bouncer.ServeHTTP(tt.args.rw, tt.args.req)
}) })
@@ -155,6 +158,7 @@ func Test_crowdsecQuery(t *testing.T) {
type args struct { type args struct {
bouncer *Bouncer bouncer *Bouncer
stringURL string stringURL string
isPost bool
} }
tests := []struct { tests := []struct {
name string name string
@@ -166,7 +170,7 @@ func Test_crowdsecQuery(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
got, err := crowdsecQuery(tt.args.bouncer, tt.args.stringURL) got, err := crowdsecQuery(tt.args.bouncer, tt.args.stringURL, tt.args.isPost)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("crowdsecQuery() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("crowdsecQuery() error = %v, wantErr %v", err, tt.wantErr)
return return
+1 -1
View File
@@ -2,7 +2,7 @@ version: "3.8"
services: services:
traefik: traefik:
image: "traefik:v2.9.4" image: "traefik:v2.9.6"
container_name: "traefik" container_name: "traefik"
restart: unless-stopped restart: unless-stopped
command: command:
+2 -2
View File
@@ -2,7 +2,7 @@ version: "3.8"
services: services:
traefik: traefik:
image: "traefik:v2.9.4" image: "traefik:v2.9.6"
container_name: "traefik" container_name: "traefik"
restart: unless-stopped restart: unless-stopped
command: command:
@@ -14,7 +14,7 @@ services:
- "--entrypoints.web.address=:80" - "--entrypoints.web.address=:80"
- "--experimental.plugins.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.1.3" - "--experimental.plugins.bouncer.version=v1.1.8"
volumes: volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro" - "/var/run/docker.sock:/var/run/docker.sock:ro"
- "logs:/var/log/traefik" - "logs:/var/log/traefik"
@@ -0,0 +1,23 @@
#### Generate CAPI credentials (only for `alone` mode)
You need to create a crowdsec API credentials for the CAPI.
You can follow the documentation here: https://docs.crowdsec.net/docs/central_api/intro
```bash
curl -X POST "https://api.crowdsec.net/v2/watchers" -H "accept: application/json" -H "Content-Type: application/json" -d "{ \"password\": \"PASSWORD\", \"machine_id\": \"LOGIN\"}"
```
These CAPI credentials must be set in your docker-compose.yml or in your config files
```yaml
...
whoami:
labels:
- "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdsecCapiMachineId=LOGIN"
- "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdsecCapiPassword=PASSWORD"
- "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdseccapiscenarios=crowdsecurity/http-generic-bf,crowdsecurity/http-xss-probing,..."
- "traefik.http.middlewares.crowdsec.plugin.bouncer.enabled=true"
```
You can then run all the containers:
```bash
docker-compose up -d
```
+39 -37
View File
@@ -18,14 +18,13 @@ const (
//nolint:gochecknoglobals //nolint:gochecknoglobals
var ( var (
cache = ttl_map.New()
redis simpleredis.SimpleRedis redis simpleredis.SimpleRedis
redisEnabled = false cache = ttl_map.New()
) )
// FileSystem Cache type localCache struct{}
func getDecisionLocalCache(clientIP string) (bool, error) { func (localCache) getDecision(clientIP string) (bool, error) {
banned, isCached := cache.Get(clientIP) banned, isCached := cache.Get(clientIP)
bannedString, isValid := banned.(string) bannedString, isValid := banned.(string)
if isCached && isValid && len(bannedString) > 0 { if isCached && isValid && len(bannedString) > 0 {
@@ -34,17 +33,17 @@ func getDecisionLocalCache(clientIP string) (bool, error) {
return false, fmt.Errorf("cache:miss") return false, fmt.Errorf("cache:miss")
} }
func setDecisionLocalCache(clientIP string, value string, duration int64) { func (localCache) setDecision(clientIP string, value string, duration int64) {
cache.Set(clientIP, value, duration) cache.Set(clientIP, value, duration)
} }
func deleteDecisionLocalCache(clientIP string) { func (localCache) deleteDecision(clientIP string) {
cache.Del(clientIP) cache.Del(clientIP)
} }
// Redis Cache type redisCache struct{}
func getDecisionRedisCache(clientIP string) (bool, error) { func (redisCache) getDecision(clientIP string) (bool, error) {
banned, err := redis.Get(clientIP) banned, err := redis.Get(clientIP)
bannedString := string(banned) bannedString := string(banned)
if err == nil && len(bannedString) > 0 { if err == nil && len(bannedString) > 0 {
@@ -53,58 +52,61 @@ func getDecisionRedisCache(clientIP string) (bool, error) {
return false, err return false, err
} }
func setDecisionRedisCache(clientIP string, value string, duration int64) { func (redisCache) setDecision(clientIP string, value string, duration int64) {
if err := redis.Set(clientIP, []byte(value), duration); err != nil { if err := redis.Set(clientIP, []byte(value), duration); err != nil {
logger.Error(fmt.Sprintf("cache:setDecisionRedisCache %s", err.Error())) logger.Error(fmt.Sprintf("cache:setDecisionRedisCache %s", err.Error()))
} }
} }
func deleteDecisionRedisCache(clientIP string) { func (redisCache) deleteDecision(clientIP string) {
if err := redis.Del(clientIP); err != nil { if err := redis.Del(clientIP); err != nil {
logger.Error(fmt.Sprintf("cache:deleteDecisionRedisCache %s", err.Error())) logger.Error(fmt.Sprintf("cache:deleteDecisionRedisCache %s", err.Error()))
} }
} }
// DeleteDecision delete decision in cache. type cacheInterface interface {
func DeleteDecision(clientIP string) { setDecision(clientIP string, value string, duration int64)
logger.Debug("cache:DeleteDecision") getDecision(clientIP string) (bool, error)
if redisEnabled { deleteDecision(clientIP string)
deleteDecisionRedisCache(clientIP) }
// Client Cache client.
type Client struct {
cache cacheInterface
}
// New Initialize cache client.
func (client *Client) New(isRedis bool, host string) {
if isRedis {
redis.Init(host)
client.cache = &redisCache{}
} else { } else {
deleteDecisionLocalCache(clientIP) client.cache = &localCache{}
} }
logger.Debug(fmt.Sprintf("cache:New initialized isRedis:%v", isRedis))
}
// DeleteDecision delete decision in cache.
func (client *Client) DeleteDecision(clientIP string) {
logger.Debug(fmt.Sprintf("cache:DeleteDecision ip:%v", clientIP))
client.cache.deleteDecision(clientIP)
} }
// GetDecision check in the cache if the IP has the banned / not banned value. // GetDecision check in the cache if the IP has the banned / not banned value.
// Otherwise return with an error to add the IP in cache if we are on. // Otherwise return with an error to add the IP in cache if we are on.
func GetDecision(clientIP string) (bool, error) { func (client *Client) GetDecision(clientIP string) (bool, error) {
logger.Debug("cache:GetDecision") logger.Debug(fmt.Sprintf("cache:GetDecision ip:%v", clientIP))
if redisEnabled { return client.cache.getDecision(clientIP)
return getDecisionRedisCache(clientIP)
}
return getDecisionLocalCache(clientIP)
} }
// SetDecision update the cache with the IP as key and the value banned / not banned. // SetDecision update the cache with the IP as key and the value banned / not banned.
func SetDecision(clientIP string, isBanned bool, duration int64) { func (client *Client) SetDecision(clientIP string, isBanned bool, duration int64) {
logger.Debug(fmt.Sprintf("cache:SetDecision ip:%v isBanned:%v", clientIP, isBanned))
var value string var value string
if isBanned { if isBanned {
logger.Debug(fmt.Sprintf("cache:SetDecision ip:%v banned", clientIP))
value = cacheBannedValue value = cacheBannedValue
} else { } else {
value = cacheNoBannedValue value = cacheNoBannedValue
} }
logger.Debug("cache:SetDecision") client.cache.setDecision(clientIP, value, duration)
if redisEnabled {
setDecisionRedisCache(clientIP, value, duration)
} else {
setDecisionLocalCache(clientIP, value, duration)
}
}
// InitRedisClient loads variables.
func InitRedisClient(host string) {
redisEnabled = true
redis.Init(host)
logger.Debug("cache:InitRedisClient redis:initialized")
} }
+38 -162
View File
@@ -6,10 +6,11 @@ import (
"testing" "testing"
) )
func Test_getDecisionLocalCache(t *testing.T) { func Test_GetDecision(t *testing.T) {
IPInCache := "10.0.0.10" IPInCache := "10.0.0.10"
IPNotInCache := "10.0.0.20" IPNotInCache := "10.0.0.20"
setDecisionLocalCache(IPInCache, "t", 10) client := &Client{cache: &localCache{}}
client.SetDecision(IPInCache, true, 10)
type args struct { type args struct {
clientIP string clientIP string
} }
@@ -22,205 +23,80 @@ func Test_getDecisionLocalCache(t *testing.T) {
}{ }{
{name: "Fetch Known valid IP", args: args{clientIP: IPInCache}, want: true, wantErr: false, valueErr: ""}, {name: "Fetch Known valid IP", args: args{clientIP: IPInCache}, want: true, wantErr: false, valueErr: ""},
{name: "Fetch Unknown valid IP", args: args{clientIP: IPNotInCache}, want: false, wantErr: true, valueErr: "cache:miss"}, {name: "Fetch Unknown valid IP", args: args{clientIP: IPNotInCache}, want: false, wantErr: true, valueErr: "cache:miss"},
{name: "Fetch invalid value", args: args{clientIP: "zaeaea"}, want: false, wantErr: true, valueErr: "cache:miss"}, {name: "Fetch invalid value", args: args{clientIP: "test"}, want: false, wantErr: true, valueErr: "cache:miss"},
{name: "Fetch empty value", args: args{clientIP: ""}, want: false, wantErr: true, valueErr: "cache:miss"}, {name: "Fetch empty value", args: args{clientIP: ""}, want: false, wantErr: true, valueErr: "cache:miss"},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
got, err := getDecisionLocalCache(tt.args.clientIP) got, err := client.GetDecision(tt.args.clientIP)
if (err != nil) != tt.wantErr {
t.Errorf("getDecisionLocalCache() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("getDecisionLocalCache() = %v, want %v", got, tt.want)
return
}
if tt.valueErr != "" && tt.valueErr != err.Error() {
t.Errorf("getDecisionLocalCache() err = %v, want %v", err.Error(), tt.valueErr)
}
})
}
}
func Test_setDecisionLocalCache(t *testing.T) {
IPInCache := "10.0.0.10"
type args struct {
clientIP string
value string
duration int64
}
tests := []struct {
name string
args args
}{
{name: "Set valid IP in local cache as t", args: args{clientIP: IPInCache, value: "t", duration: 0}},
{name: "Set valid IP in local cache as f", args: args{clientIP: IPInCache, value: "f", duration: 0}},
{name: "Set valid IP in local cache as empty str", args: args{clientIP: IPInCache, value: "", duration: 0}},
{name: "Set valid IP in local cache as f for -1 sec", args: args{clientIP: IPInCache, value: "f", duration: -1}},
{name: "Set valid IP in local cache as f for 10 sec", args: args{clientIP: IPInCache, value: "f", duration: 10}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
setDecisionLocalCache(tt.args.clientIP, tt.args.value, tt.args.duration)
})
}
}
func Test_deleteDecisionLocalCache(t *testing.T) {
type args struct {
clientIP string
}
tests := []struct {
name string
args args
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
deleteDecisionLocalCache(tt.args.clientIP)
})
}
}
func Test_getDecisionRedisCache(t *testing.T) {
type args struct {
clientIP string
}
tests := []struct {
name string
args args
want bool
wantErr bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := getDecisionRedisCache(tt.args.clientIP)
if (err != nil) != tt.wantErr {
t.Errorf("getDecisionRedisCache() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("getDecisionRedisCache() = %v, want %v", got, tt.want)
}
})
}
}
func Test_setDecisionRedisCache(t *testing.T) {
type args struct {
clientIP string
value string
duration int64
}
tests := []struct {
name string
args args
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
setDecisionRedisCache(tt.args.clientIP, tt.args.value, tt.args.duration)
})
}
}
func Test_deleteDecisionRedisCache(t *testing.T) {
type args struct {
clientIP string
}
tests := []struct {
name string
args args
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
deleteDecisionRedisCache(tt.args.clientIP)
})
}
}
func TestDeleteDecision(t *testing.T) {
type args struct {
clientIP string
}
tests := []struct {
name string
args args
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
DeleteDecision(tt.args.clientIP)
})
}
}
func TestGetDecision(t *testing.T) {
type args struct {
clientIP string
}
tests := []struct {
name string
args args
want bool
wantErr bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GetDecision(tt.args.clientIP)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("GetDecision() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("GetDecision() error = %v, wantErr %v", err, tt.wantErr)
return return
} }
if got != tt.want { if got != tt.want {
t.Errorf("GetDecision() = %v, want %v", got, tt.want) t.Errorf("GetDecision() = %v, want %v", got, tt.want)
return
}
if tt.valueErr != "" && tt.valueErr != err.Error() {
t.Errorf("GetDecision() err = %v, want %v", err.Error(), tt.valueErr)
} }
}) })
} }
} }
func TestSetDecision(t *testing.T) { func Test_SetDecision(t *testing.T) {
client := &Client{cache: &localCache{}}
IPInCache := "10.0.0.11"
type args struct { type args struct {
clientIP string clientIP string
isBanned bool value bool
duration int64 duration int64
} }
tests := []struct { tests := []struct {
name string name string
args args args args
want bool
}{ }{
// TODO: Add test cases. {name: "Set valid IP in local cache for 0 sec", args: args{clientIP: IPInCache, value: true, duration: 0}, want: false},
{name: "Set valid IP in local cache for 10 sec", args: args{clientIP: IPInCache, value: true, duration: 10}, want: true},
{name: "Set valid IP in local cache for 10 sec", args: args{clientIP: IPInCache, value: false, duration: 10}, want: false},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
SetDecision(tt.args.clientIP, tt.args.isBanned, tt.args.duration) client.SetDecision(tt.args.clientIP, tt.args.value, tt.args.duration)
got, _ := client.GetDecision(tt.args.clientIP)
if got != tt.want {
t.Errorf("SetDecision() = %v, want %v", got, tt.want)
return
}
}) })
} }
} }
func TestInitRedisClient(t *testing.T) { func Test_DeleteDecision(t *testing.T) {
IPInCache := "10.0.0.12"
IPNotInCache := "10.0.0.22"
client := &Client{cache: &localCache{}}
client.SetDecision(IPInCache, true, 10)
type args struct { type args struct {
host string clientIP string
} }
tests := []struct { tests := []struct {
name string name string
args args args args
want bool
}{ }{
// TODO: Add test cases. {name: "Delete Known valid IP", args: args{clientIP: IPInCache}, want: false},
{name: "Delete Unknown valid IP", args: args{clientIP: IPNotInCache}, want: false},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
InitRedisClient(tt.args.host) client.DeleteDecision(tt.args.clientIP)
got, _ := client.GetDecision(tt.args.clientIP)
if got != tt.want {
t.Errorf("DeleteDecision() = %v, want %v", got, tt.want)
return
}
}) })
} }
} }
+29 -13
View File
@@ -19,6 +19,7 @@ import (
// Enums for crowdsec mode. // Enums for crowdsec mode.
const ( const (
AloneMode = "alone"
StreamMode = "stream" StreamMode = "stream"
LiveMode = "live" LiveMode = "live"
NoneMode = "none" NoneMode = "none"
@@ -42,6 +43,11 @@ type Config struct {
CrowdsecLapiTLSCertificateBouncerFile string `json:"crowdsecLapiTlsCertificateBouncerFile,omitempty"` CrowdsecLapiTLSCertificateBouncerFile string `json:"crowdsecLapiTlsCertificateBouncerFile,omitempty"`
CrowdsecLapiTLSCertificateBouncerKey string `json:"crowdsecLapiTlsCertificateBouncerKey,omitempty"` CrowdsecLapiTLSCertificateBouncerKey string `json:"crowdsecLapiTlsCertificateBouncerKey,omitempty"`
CrowdsecLapiTLSCertificateBouncerKeyFile string `json:"crowdsecLapiTlsCertificateBouncerKeyFile,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"` UpdateIntervalSeconds int64 `json:"updateIntervalSeconds,omitempty"`
DefaultDecisionSeconds int64 `json:"defaultDecisionSeconds,omitempty"` DefaultDecisionSeconds int64 `json:"defaultDecisionSeconds,omitempty"`
ForwardedHeadersCustomName string `json:"forwardedheaderscustomheader,omitempty"` ForwardedHeadersCustomName string `json:"forwardedheaderscustomheader,omitempty"`
@@ -100,11 +106,11 @@ func GetVariable(config *Config, key string) (string, error) {
return value, fmt.Errorf("%s:%s read file path failed %w", key, fp, err) return value, fmt.Errorf("%s:%s read file path failed %w", key, fp, err)
} }
value = string(fileValue) value = string(fileValue)
return value, nil return strings.TrimSpace(value), nil
} }
field = object.FieldByName(key) field = object.FieldByName(key)
value = field.String() value = field.String()
return value, nil return strings.TrimSpace(value), nil
} }
// ValidateParams validate all the param gave by user. // ValidateParams validate all the param gave by user.
@@ -115,6 +121,23 @@ func ValidateParams(config *Config) error {
return err return err
} }
if err := validateParamsIPs(config.ForwardedHeadersTrustedIPs, "ForwardedHeadersTrustedIPs"); err != nil {
return err
}
if err := validateParamsIPs(config.ClientTrustedIPs, "ClientTrustedIPs"); err != nil {
return err
}
if config.CrowdsecMode == AloneMode {
if _, err := GetVariable(config, "CrowdsecCapiMachineId"); err != nil {
return err
}
if _, err := GetVariable(config, "CrowdsecCapiPassword"); err != nil {
return err
}
return nil
}
// This only check that the format of the URL scheme:// is correct and do not make requests // This only check that the format of the URL scheme:// is correct and do not make requests
testURL := url.URL{ testURL := url.URL{
Scheme: config.CrowdsecLapiScheme, Scheme: config.CrowdsecLapiScheme,
@@ -124,13 +147,6 @@ func ValidateParams(config *Config) error {
return fmt.Errorf("CrowdsecLapiScheme://CrowdsecLapiHost: '%v://%v' must be an URL", config.CrowdsecLapiScheme, config.CrowdsecLapiHost) return fmt.Errorf("CrowdsecLapiScheme://CrowdsecLapiHost: '%v://%v' must be an URL", config.CrowdsecLapiScheme, config.CrowdsecLapiHost)
} }
if err := validateParamsIPs(config.ForwardedHeadersTrustedIPs, "ForwardedHeadersTrustedIPs"); err != nil {
return err
}
if err := validateParamsIPs(config.ClientTrustedIPs, "ClientTrustedIPs"); err != nil {
return err
}
lapiKey, err := GetVariable(config, "CrowdsecLapiKey") lapiKey, err := GetVariable(config, "CrowdsecLapiKey")
if err != nil { if err != nil {
return err return err
@@ -222,8 +238,8 @@ func validateParamsRequired(config *Config) error {
return fmt.Errorf("%v: cannot be less than 1", key) return fmt.Errorf("%v: cannot be less than 1", key)
} }
} }
if !contains([]string{NoneMode, LiveMode, StreamMode}, config.CrowdsecMode) { if !contains([]string{NoneMode, LiveMode, StreamMode, AloneMode}, config.CrowdsecMode) {
return fmt.Errorf("CrowdsecMode: must be one of 'none', 'live' or 'stream'") return fmt.Errorf("CrowdsecMode: must be one of 'none', 'live', 'stream' or 'alone'")
} }
if !contains([]string{HTTP, HTTPS}, config.CrowdsecLapiScheme) { if !contains([]string{HTTP, HTTPS}, config.CrowdsecLapiScheme) {
return fmt.Errorf("CrowdsecLapiScheme: must be one of 'http' or 'https'") return fmt.Errorf("CrowdsecLapiScheme: must be one of 'http' or 'https'")
@@ -237,10 +253,10 @@ func GetTLSConfigCrowdsec(config *Config) (*tls.Config, error) {
tlsConfig.RootCAs = x509.NewCertPool() tlsConfig.RootCAs = x509.NewCertPool()
//nolint:gocritic //nolint:gocritic
if config.CrowdsecLapiScheme != HTTPS { if config.CrowdsecLapiScheme != HTTPS {
logger.Debug("getTLSConfigCrowdsec:CrowdsecLapiScheme not https") logger.Debug("getTLSConfigCrowdsec:CrowdsecLapiScheme https:no")
return tlsConfig, nil return tlsConfig, nil
} else if config.CrowdsecLapiTLSInsecureVerify { } else if config.CrowdsecLapiTLSInsecureVerify {
logger.Debug("getTLSConfigCrowdsec:CrowdsecLapiTLSInsecureVerify is true") logger.Debug("getTLSConfigCrowdsec:CrowdsecLapiTLSInsecureVerify tlsInsecure:true")
tlsConfig.InsecureSkipVerify = true tlsConfig.InsecureSkipVerify = true
// If we return here and still want to use client auth this won't work // If we return here and still want to use client auth this won't work
// return tlsConfig, nil // return tlsConfig, nil