mirror of
https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin.git
synced 2026-07-21 11:38:59 +02:00
Merge pull request #57 from maxlerebourg/46-feature-add-automated-tests-to-the-plugin
✅ Add test structure for bouncer pkg
This commit is contained in:
+12
-246
@@ -5,30 +5,22 @@ package crowdsec_bouncer_traefik_plugin //nolint:revive,stylecheck
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
cache "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/cache"
|
||||
configuration "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/configuration"
|
||||
ip "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/ip"
|
||||
logger "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/logger"
|
||||
simpleredis "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/simpleredis"
|
||||
)
|
||||
|
||||
const (
|
||||
streamMode = "stream"
|
||||
liveMode = "live"
|
||||
noneMode = "none"
|
||||
crowdsecLapiHeader = "X-Api-Key"
|
||||
crowdsecLapiRoute = "v1/decisions"
|
||||
crowdsecLapiStreamRoute = "v1/decisions/stream"
|
||||
@@ -41,49 +33,9 @@ var (
|
||||
ticker chan bool
|
||||
)
|
||||
|
||||
// Config the plugin configuration.
|
||||
type Config struct {
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
LogLevel string `json:"logLevel,omitempty"`
|
||||
CrowdsecMode string `json:"crowdsecMode,omitempty"`
|
||||
CrowdsecLapiScheme string `json:"crowdsecLapiScheme,omitempty"`
|
||||
CrowdsecLapiHost string `json:"crowdsecLapiHost,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"`
|
||||
UpdateIntervalSeconds int64 `json:"updateIntervalSeconds,omitempty"`
|
||||
DefaultDecisionSeconds int64 `json:"defaultDecisionSeconds,omitempty"`
|
||||
ForwardedHeadersCustomName string `json:"forwardedheaderscustomheader,omitempty"`
|
||||
ForwardedHeadersTrustedIPs []string `json:"forwardedHeadersTrustedIps,omitempty"`
|
||||
ClientTrustedIPs []string `json:"clientTrustedIps,omitempty"`
|
||||
RedisCacheEnabled bool `json:"redisCacheEnabled,omitempty"`
|
||||
RedisCacheHost string `json:"redisCacheHost,omitempty"`
|
||||
}
|
||||
|
||||
// CreateConfig creates the default plugin configuration.
|
||||
func CreateConfig() *Config {
|
||||
return &Config{
|
||||
Enabled: false,
|
||||
LogLevel: "INFO",
|
||||
CrowdsecMode: liveMode,
|
||||
CrowdsecLapiScheme: "http",
|
||||
CrowdsecLapiHost: "crowdsec:8080",
|
||||
CrowdsecLapiKey: "",
|
||||
CrowdsecLapiTLSInsecureVerify: false,
|
||||
UpdateIntervalSeconds: 60,
|
||||
DefaultDecisionSeconds: 60,
|
||||
ForwardedHeadersCustomName: "X-Forwarded-For",
|
||||
ForwardedHeadersTrustedIPs: []string{},
|
||||
ClientTrustedIPs: []string{},
|
||||
RedisCacheEnabled: false,
|
||||
RedisCacheHost: "redis:6379",
|
||||
}
|
||||
func CreateConfig() *configuration.Config {
|
||||
return configuration.New()
|
||||
}
|
||||
|
||||
// Bouncer a Bouncer struct.
|
||||
@@ -106,9 +58,9 @@ type Bouncer struct {
|
||||
}
|
||||
|
||||
// New creates the crowdsec bouncer plugin.
|
||||
func New(ctx context.Context, next http.Handler, config *Config, name string) (http.Handler, error) {
|
||||
func New(ctx context.Context, next http.Handler, config *configuration.Config, name string) (http.Handler, error) {
|
||||
logger.Init(config.LogLevel)
|
||||
err := validateParams(config)
|
||||
err := configuration.ValidateParams(config)
|
||||
if err != nil {
|
||||
logger.Info(fmt.Sprintf("New:validateParams %s", err.Error()))
|
||||
return nil, err
|
||||
@@ -117,12 +69,12 @@ func New(ctx context.Context, next http.Handler, config *Config, name string) (h
|
||||
serverChecker, _ := ip.NewChecker(config.ForwardedHeadersTrustedIPs)
|
||||
clientChecker, _ := ip.NewChecker(config.ClientTrustedIPs)
|
||||
|
||||
tlsConfig, err := getTLSConfigCrowdsec(config)
|
||||
tlsConfig, err := configuration.GetTLSConfigCrowdsec(config)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("New:getTLSConfigCrowdsec fail to get tlsConfig %s", err.Error()))
|
||||
return nil, err
|
||||
}
|
||||
apiKey, err := getVariable(config, "CrowdsecLapiKey")
|
||||
apiKey, err := configuration.GetVariable(config, "CrowdsecLapiKey")
|
||||
if err != nil && len(tlsConfig.Certificates) == 0 {
|
||||
logger.Error(fmt.Sprintf("New:crowdsecLapiKey fail to get CrowdsecLapiKey and no client certificate setup %s", err.Error()))
|
||||
return nil, err
|
||||
@@ -159,7 +111,7 @@ func New(ctx context.Context, next http.Handler, config *Config, name string) (h
|
||||
if config.RedisCacheEnabled {
|
||||
cache.InitRedisClient(config.RedisCacheHost)
|
||||
}
|
||||
if config.CrowdsecMode == streamMode && ticker == nil {
|
||||
if config.CrowdsecMode == configuration.StreamMode && ticker == nil {
|
||||
ticker = startTicker(config, func() {
|
||||
handleStreamCache(bouncer)
|
||||
})
|
||||
@@ -199,7 +151,7 @@ func (bouncer *Bouncer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
|
||||
// TODO This should be simplified
|
||||
if bouncer.crowdsecMode != noneMode {
|
||||
if bouncer.crowdsecMode != configuration.NoneMode {
|
||||
isBanned, erro := cache.GetDecision(remoteIP)
|
||||
if erro != nil {
|
||||
logger.Debug(fmt.Sprintf("ServeHTTP:getDecision ip:%s %s", remoteIP, erro.Error()))
|
||||
@@ -219,7 +171,7 @@ 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.
|
||||
if bouncer.crowdsecMode == streamMode {
|
||||
if bouncer.crowdsecMode == configuration.StreamMode {
|
||||
if isCrowdsecStreamHealthy {
|
||||
bouncer.next.ServeHTTP(rw, req)
|
||||
} else {
|
||||
@@ -258,16 +210,7 @@ type Stream struct {
|
||||
New []Decision `json:"new"`
|
||||
}
|
||||
|
||||
func contains(source []string, target string) bool {
|
||||
for _, item := range source {
|
||||
if item == target {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func startTicker(config *Config, work func()) chan bool {
|
||||
func startTicker(config *configuration.Config, work func()) chan bool {
|
||||
ticker := time.NewTicker(time.Duration(config.UpdateIntervalSeconds) * time.Second)
|
||||
stop := make(chan bool, 1)
|
||||
go func() {
|
||||
@@ -286,7 +229,7 @@ func startTicker(config *Config, work func()) chan bool {
|
||||
|
||||
// We are now in none or live mode.
|
||||
func handleNoStreamCache(bouncer *Bouncer, remoteIP string) error {
|
||||
isLiveMode := bouncer.crowdsecMode == liveMode
|
||||
isLiveMode := bouncer.crowdsecMode == configuration.LiveMode
|
||||
routeURL := url.URL{
|
||||
Scheme: bouncer.crowdsecScheme,
|
||||
Host: bouncer.crowdsecHost,
|
||||
@@ -391,180 +334,3 @@ func crowdsecQuery(bouncer *Bouncer, stringURL string) ([]byte, error) {
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
func getTLSConfigCrowdsec(config *Config) (*tls.Config, error) {
|
||||
tlsConfig := new(tls.Config)
|
||||
tlsConfig.RootCAs = x509.NewCertPool()
|
||||
//nolint:gocritic
|
||||
if config.CrowdsecLapiScheme != "https" {
|
||||
logger.Debug("getTLSConfigCrowdsec:CrowdsecLapiScheme not https")
|
||||
return tlsConfig, nil
|
||||
} else if config.CrowdsecLapiTLSInsecureVerify {
|
||||
logger.Debug("getTLSConfigCrowdsec:CrowdsecLapiTLSInsecureVerify is true")
|
||||
tlsConfig.InsecureSkipVerify = 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")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert := []byte(certAuthority)
|
||||
if !tlsConfig.RootCAs.AppendCertsFromPEM(cert) {
|
||||
logger.Debug("getTLSConfigCrowdsec:CrowdsecLapiTLSCertificateAuthority read cert failed")
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
|
||||
certBouncer, err := getVariable(config, "CrowdsecLapiTLSCertificateBouncer")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certBouncerKey, err := getVariable(config, "CrowdsecLapiTLSCertificateBouncerKey")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if certBouncer == "" || certBouncerKey == "" {
|
||||
return tlsConfig, nil
|
||||
}
|
||||
clientCert, err := tls.X509KeyPair([]byte(certBouncer), []byte(certBouncerKey))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getTLSClientConfigCrowdsec impossible to generate ClientCert %w", err)
|
||||
}
|
||||
tlsConfig.Certificates = append(tlsConfig.Certificates, clientCert)
|
||||
|
||||
return tlsConfig, nil
|
||||
}
|
||||
|
||||
func getVariable(config *Config, key string) (string, error) {
|
||||
value := ""
|
||||
object := reflect.Indirect(reflect.ValueOf(config))
|
||||
field := object.FieldByName(fmt.Sprintf("%sFile", key))
|
||||
// Here linter say you should simplify this code, but lets not, performance is important not clarity and complexity
|
||||
fp := field.String()
|
||||
if fp != "" {
|
||||
file, err := os.Stat(fp)
|
||||
if err != nil {
|
||||
return value, fmt.Errorf("%s:%s invalid path %w", key, fp, err)
|
||||
}
|
||||
if file.IsDir() {
|
||||
return value, fmt.Errorf("%s:%s path must be a file", key, fp)
|
||||
}
|
||||
fileValue, err := os.ReadFile(filepath.Clean(fp))
|
||||
if err != nil {
|
||||
return value, fmt.Errorf("%s:%s read file path failed %w", key, fp, err)
|
||||
}
|
||||
value = string(fileValue)
|
||||
return value, nil
|
||||
}
|
||||
field = object.FieldByName(key)
|
||||
value = field.String()
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func validateParams(config *Config) error {
|
||||
if err := validateParamsRequired(config); err != nil {
|
||||
return err
|
||||
}
|
||||
testURL := url.URL{
|
||||
Scheme: config.CrowdsecLapiScheme,
|
||||
Host: config.CrowdsecLapiHost,
|
||||
}
|
||||
// This only check that the format of the URL scheme:// is correct and do not make requests
|
||||
|
||||
if _, err := http.NewRequest(http.MethodGet, testURL.String(), nil); err != nil {
|
||||
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")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
certBouncer, err := getVariable(config, "CrowdsecLapiTLSCertificateBouncer")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
certBouncerKey, err := getVariable(config, "CrowdsecLapiTLSCertificateBouncerKey")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// We need to either have crowdsecLapiKey defined or the BouncerCert and Bouncerkey
|
||||
if lapiKey == "" && (certBouncer == "" || certBouncerKey == "") {
|
||||
return fmt.Errorf("CrowdsecLapiKey || (CrowdsecLapiTLSCertificateBouncer && CrowdsecLapiTLSCertificateBouncerKey): cannot be both empty")
|
||||
}
|
||||
|
||||
// Case https to contact Crowdsec LAPI and certificate must be provided
|
||||
if config.CrowdsecLapiScheme == "https" && !config.CrowdsecLapiTLSInsecureVerify {
|
||||
err = validateParamsTLS(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateParamsTLS(config *Config) error {
|
||||
certAuth, err := getVariable(config, "CrowdsecLapiTLSCertificateAuthority")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if certAuth == "" {
|
||||
return fmt.Errorf("CrowdsecLapiTLSCertificateAuthority must be specified when CrowdsecLapiScheme='https' and CrowdsecLapiTLSInsecureVerify=false")
|
||||
}
|
||||
tlsConfig := new(tls.Config)
|
||||
tlsConfig.RootCAs = x509.NewCertPool()
|
||||
if !tlsConfig.RootCAs.AppendCertsFromPEM([]byte(certAuth)) {
|
||||
return fmt.Errorf("failed parsing pem file")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateParamsIPs(listIP []string, key string) error {
|
||||
if len(listIP) > 0 {
|
||||
if _, err := ip.NewChecker(listIP); err != nil {
|
||||
return fmt.Errorf("%s must be a list of IP/CIDR :%w", key, err)
|
||||
}
|
||||
} else {
|
||||
logger.Debug(fmt.Sprintf("No IP provided for %s", key))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateParamsRequired(config *Config) error {
|
||||
requiredStrings := map[string]string{
|
||||
"CrowdsecLapiScheme": config.CrowdsecLapiScheme,
|
||||
"CrowdsecLapiHost": config.CrowdsecLapiHost,
|
||||
"CrowdsecMode": config.CrowdsecMode,
|
||||
}
|
||||
requiredInt := map[string]int64{
|
||||
"UpdateIntervalSeconds": config.UpdateIntervalSeconds,
|
||||
"DefaultDecisionSeconds": config.DefaultDecisionSeconds,
|
||||
}
|
||||
for key, val := range requiredInt {
|
||||
if val < 1 {
|
||||
return fmt.Errorf("%v: cannot be less than 1", key)
|
||||
}
|
||||
}
|
||||
for key, val := range requiredStrings {
|
||||
if len(val) == 0 {
|
||||
return fmt.Errorf("%v: cannot be empty", key)
|
||||
}
|
||||
}
|
||||
if !contains([]string{noneMode, liveMode, streamMode}, config.CrowdsecMode) {
|
||||
return fmt.Errorf("CrowdsecMode: must be one of 'none', 'live' or 'stream'")
|
||||
}
|
||||
if !contains([]string{"http", "https"}, config.CrowdsecLapiScheme) {
|
||||
return fmt.Errorf("CrowdsecLapiScheme: must be one of 'http' or 'https'")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+150
-63
@@ -4,72 +4,14 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
"text/template"
|
||||
|
||||
configuration "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/configuration"
|
||||
ip "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/ip"
|
||||
)
|
||||
|
||||
func TestCreation(t *testing.T) {
|
||||
cfg := CreateConfig()
|
||||
cfg.CrowdsecLapiKey = "test"
|
||||
|
||||
ctx := context.Background()
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {})
|
||||
|
||||
handler, err := New(ctx, next, cfg, "demo-plugin")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://localhost", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(recorder, req)
|
||||
}
|
||||
|
||||
// func TestValidateParamsCrowdsecLapiKey(t *testing.T) {
|
||||
// cfg := CreateConfig()
|
||||
// err := validateParams(cfg)
|
||||
// fmt.Println(err.Error())
|
||||
// if err == nil {
|
||||
// t.Errorf("Need error here %s", err.Error())
|
||||
// }
|
||||
// }
|
||||
|
||||
// func TestValidateParamsCrowdsecLapiScheme(t *testing.T) {
|
||||
// cfg := CreateConfig()
|
||||
// cfg.CrowdsecLapiKey = "test"
|
||||
// cfg.CrowdsecLapiScheme = "bad"
|
||||
// err := validateParams(cfg)
|
||||
// fmt.Println(err.Error())
|
||||
// if err == nil {
|
||||
// t.Errorf("Need error here %s", err.Error())
|
||||
// }
|
||||
// }
|
||||
|
||||
// func TestValidateParamsCrowdsecMode(t *testing.T) {
|
||||
// cfg := CreateConfig()
|
||||
// cfg.CrowdsecLapiKey = "test"
|
||||
// cfg.CrowdsecMode = "bad"
|
||||
// err := validateParams(cfg)
|
||||
// fmt.Println(err.Error())
|
||||
// if err == nil {
|
||||
// t.Errorf("Need error here %s", err.Error())
|
||||
// }
|
||||
// }
|
||||
|
||||
// func TestValidateParamsUpdateIntervalSeconds(t *testing.T) {
|
||||
// cfg := CreateConfig()
|
||||
// cfg.CrowdsecLapiKey = "test"
|
||||
// cfg.UpdateIntervalSeconds = 0
|
||||
// err := validateParams(cfg)
|
||||
// fmt.Println(err.Error())
|
||||
// if err == nil {
|
||||
// t.Errorf("Need error here %s", err.Error())
|
||||
// }
|
||||
// }
|
||||
|
||||
func TestServeHTTP(t *testing.T) {
|
||||
cfg := CreateConfig()
|
||||
cfg.CrowdsecLapiKey = "test"
|
||||
@@ -90,3 +32,148 @@ func TestServeHTTP(t *testing.T) {
|
||||
|
||||
handler.ServeHTTP(recorder, req)
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context //nolint:containedctx
|
||||
next http.Handler
|
||||
config *configuration.Config
|
||||
name string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want http.Handler
|
||||
wantErr bool
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := New(tt.args.ctx, tt.args.next, tt.args.config, tt.args.name)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("New() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBouncer_ServeHTTP(t *testing.T) {
|
||||
type fields struct {
|
||||
next http.Handler
|
||||
name string
|
||||
template *template.Template
|
||||
enabled bool
|
||||
crowdsecScheme string
|
||||
crowdsecHost string
|
||||
crowdsecKey string
|
||||
crowdsecMode string
|
||||
updateInterval int64
|
||||
defaultDecisionTimeout int64
|
||||
customHeader string
|
||||
clientPoolStrategy *ip.PoolStrategy
|
||||
serverPoolStrategy *ip.PoolStrategy
|
||||
client *http.Client
|
||||
}
|
||||
type args struct {
|
||||
rw http.ResponseWriter
|
||||
req *http.Request
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
bouncer := &Bouncer{
|
||||
next: tt.fields.next,
|
||||
name: tt.fields.name,
|
||||
template: tt.fields.template,
|
||||
enabled: tt.fields.enabled,
|
||||
crowdsecScheme: tt.fields.crowdsecScheme,
|
||||
crowdsecHost: tt.fields.crowdsecHost,
|
||||
crowdsecKey: tt.fields.crowdsecKey,
|
||||
crowdsecMode: tt.fields.crowdsecMode,
|
||||
updateInterval: tt.fields.updateInterval,
|
||||
defaultDecisionTimeout: tt.fields.defaultDecisionTimeout,
|
||||
customHeader: tt.fields.customHeader,
|
||||
clientPoolStrategy: tt.fields.clientPoolStrategy,
|
||||
serverPoolStrategy: tt.fields.serverPoolStrategy,
|
||||
client: tt.fields.client,
|
||||
}
|
||||
bouncer.ServeHTTP(tt.args.rw, tt.args.req)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_handleNoStreamCache(t *testing.T) {
|
||||
type args struct {
|
||||
bouncer *Bouncer
|
||||
remoteIP string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := handleNoStreamCache(tt.args.bouncer, tt.args.remoteIP); (err != nil) != tt.wantErr {
|
||||
t.Errorf("handleNoStreamCache() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_handleStreamCache(t *testing.T) {
|
||||
type args struct {
|
||||
bouncer *Bouncer
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
handleStreamCache(tt.args.bouncer)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_crowdsecQuery(t *testing.T) {
|
||||
type args struct {
|
||||
bouncer *Bouncer
|
||||
stringURL string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []byte
|
||||
wantErr bool
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := crowdsecQuery(tt.args.bouncer, tt.args.stringURL)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("crowdsecQuery() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("crowdsecQuery() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+226
@@ -0,0 +1,226 @@
|
||||
// Package cache implements utility routines for manipulating cache.
|
||||
// It supports currently local file and redis cache.
|
||||
package cache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_getDecisionLocalCache(t *testing.T) {
|
||||
IPInCache := "10.0.0.10"
|
||||
IPNotInCache := "10.0.0.20"
|
||||
setDecisionLocalCache(IPInCache, "t", 10)
|
||||
type args struct {
|
||||
clientIP string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
wantErr bool
|
||||
valueErr string
|
||||
}{
|
||||
{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 invalid value", args: args{clientIP: "zaeaea"}, 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 {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := getDecisionLocalCache(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 {
|
||||
t.Errorf("GetDecision() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("GetDecision() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDecision(t *testing.T) {
|
||||
type args struct {
|
||||
clientIP string
|
||||
isBanned bool
|
||||
duration int64
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
SetDecision(tt.args.clientIP, tt.args.isBanned, tt.args.duration)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitRedisClient(t *testing.T) {
|
||||
type args struct {
|
||||
host string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
InitRedisClient(tt.args.host)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
// Package configuration implements plugin Config, default Config values and validation param functions.
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
|
||||
ip "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/ip"
|
||||
logger "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/logger"
|
||||
)
|
||||
|
||||
// Enums for crowdsec mode.
|
||||
const (
|
||||
StreamMode = "stream"
|
||||
LiveMode = "live"
|
||||
NoneMode = "none"
|
||||
HTTPS = "https"
|
||||
HTTP = "http"
|
||||
)
|
||||
|
||||
// Config the plugin configuration.
|
||||
type Config struct {
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
LogLevel string `json:"logLevel,omitempty"`
|
||||
CrowdsecMode string `json:"crowdsecMode,omitempty"`
|
||||
CrowdsecLapiScheme string `json:"crowdsecLapiScheme,omitempty"`
|
||||
CrowdsecLapiHost string `json:"crowdsecLapiHost,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"`
|
||||
UpdateIntervalSeconds int64 `json:"updateIntervalSeconds,omitempty"`
|
||||
DefaultDecisionSeconds int64 `json:"defaultDecisionSeconds,omitempty"`
|
||||
ForwardedHeadersCustomName string `json:"forwardedheaderscustomheader,omitempty"`
|
||||
ForwardedHeadersTrustedIPs []string `json:"forwardedHeadersTrustedIps,omitempty"`
|
||||
ClientTrustedIPs []string `json:"clientTrustedIps,omitempty"`
|
||||
RedisCacheEnabled bool `json:"redisCacheEnabled,omitempty"`
|
||||
RedisCacheHost string `json:"redisCacheHost,omitempty"`
|
||||
}
|
||||
|
||||
func contains(source []string, target string) bool {
|
||||
for _, item := range source {
|
||||
if item == target {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// New creates the default plugin configuration.
|
||||
func New() *Config {
|
||||
return &Config{
|
||||
Enabled: false,
|
||||
LogLevel: "INFO",
|
||||
CrowdsecMode: LiveMode,
|
||||
CrowdsecLapiScheme: HTTP,
|
||||
CrowdsecLapiHost: "crowdsec:8080",
|
||||
CrowdsecLapiKey: "",
|
||||
CrowdsecLapiTLSInsecureVerify: false,
|
||||
UpdateIntervalSeconds: 60,
|
||||
DefaultDecisionSeconds: 60,
|
||||
ForwardedHeadersCustomName: "X-Forwarded-For",
|
||||
ForwardedHeadersTrustedIPs: []string{},
|
||||
ClientTrustedIPs: []string{},
|
||||
RedisCacheEnabled: false,
|
||||
RedisCacheHost: "redis:6379",
|
||||
}
|
||||
}
|
||||
|
||||
// GetVariable get variable from file and after in the variables gave by user.
|
||||
func GetVariable(config *Config, key string) (string, error) {
|
||||
value := ""
|
||||
object := reflect.Indirect(reflect.ValueOf(config))
|
||||
field := object.FieldByName(fmt.Sprintf("%sFile", key))
|
||||
// Here linter say you should simplify this code, but lets not, performance is important not clarity and complexity
|
||||
fp := field.String()
|
||||
if fp != "" {
|
||||
file, err := os.Stat(fp)
|
||||
if err != nil {
|
||||
return value, fmt.Errorf("%s:%s invalid path %w", key, fp, err)
|
||||
}
|
||||
if file.IsDir() {
|
||||
return value, fmt.Errorf("%s:%s path must be a file", key, fp)
|
||||
}
|
||||
fileValue, err := os.ReadFile(filepath.Clean(fp))
|
||||
if err != nil {
|
||||
return value, fmt.Errorf("%s:%s read file path failed %w", key, fp, err)
|
||||
}
|
||||
value = string(fileValue)
|
||||
return value, nil
|
||||
}
|
||||
field = object.FieldByName(key)
|
||||
value = field.String()
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// ValidateParams validate all the param gave by user.
|
||||
func ValidateParams(config *Config) error {
|
||||
if err := validateParamsRequired(config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// This only check that the format of the URL scheme:// is correct and do not make requests
|
||||
testURL := url.URL{
|
||||
Scheme: config.CrowdsecLapiScheme,
|
||||
Host: config.CrowdsecLapiHost,
|
||||
}
|
||||
if _, err := http.NewRequest(http.MethodGet, testURL.String(), nil); err != nil {
|
||||
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")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
certBouncer, err := GetVariable(config, "CrowdsecLapiTLSCertificateBouncer")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
certBouncerKey, err := GetVariable(config, "CrowdsecLapiTLSCertificateBouncerKey")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// We need to either have crowdsecLapiKey defined or the BouncerCert and Bouncerkey
|
||||
if lapiKey == "" && (certBouncer == "" || certBouncerKey == "") {
|
||||
return fmt.Errorf("CrowdsecLapiKey || (CrowdsecLapiTLSCertificateBouncer && CrowdsecLapiTLSCertificateBouncerKey): cannot be both empty")
|
||||
}
|
||||
|
||||
// Case https to contact Crowdsec LAPI and certificate must be provided
|
||||
if config.CrowdsecLapiScheme == HTTPS && !config.CrowdsecLapiTLSInsecureVerify {
|
||||
err = validateParamsTLS(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateParamsTLS(config *Config) error {
|
||||
certAuth, err := GetVariable(config, "CrowdsecLapiTLSCertificateAuthority")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if certAuth == "" {
|
||||
return fmt.Errorf("CrowdsecLapiTLSCertificateAuthority must be specified when CrowdsecLapiScheme='https' and CrowdsecLapiTLSInsecureVerify=false")
|
||||
}
|
||||
tlsConfig := new(tls.Config)
|
||||
tlsConfig.RootCAs = x509.NewCertPool()
|
||||
if !tlsConfig.RootCAs.AppendCertsFromPEM([]byte(certAuth)) {
|
||||
return fmt.Errorf("failed parsing pem file")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateParamsIPs(listIP []string, key string) error {
|
||||
if len(listIP) > 0 {
|
||||
if _, err := ip.NewChecker(listIP); err != nil {
|
||||
return fmt.Errorf("%s must be a list of IP/CIDR :%w", key, err)
|
||||
}
|
||||
} else {
|
||||
logger.Debug(fmt.Sprintf("No IP provided for %s", key))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateParamsRequired(config *Config) error {
|
||||
requiredStrings := map[string]string{
|
||||
"CrowdsecLapiScheme": config.CrowdsecLapiScheme,
|
||||
"CrowdsecLapiHost": config.CrowdsecLapiHost,
|
||||
"CrowdsecMode": config.CrowdsecMode,
|
||||
}
|
||||
for key, val := range requiredStrings {
|
||||
if len(val) == 0 {
|
||||
return fmt.Errorf("%v: cannot be empty", key)
|
||||
}
|
||||
}
|
||||
requiredInt := map[string]int64{
|
||||
"UpdateIntervalSeconds": config.UpdateIntervalSeconds,
|
||||
"DefaultDecisionSeconds": config.DefaultDecisionSeconds,
|
||||
}
|
||||
for key, val := range requiredInt {
|
||||
if val < 1 {
|
||||
return fmt.Errorf("%v: cannot be less than 1", key)
|
||||
}
|
||||
}
|
||||
if !contains([]string{NoneMode, LiveMode, StreamMode}, config.CrowdsecMode) {
|
||||
return fmt.Errorf("CrowdsecMode: must be one of 'none', 'live' or 'stream'")
|
||||
}
|
||||
if !contains([]string{HTTP, HTTPS}, config.CrowdsecLapiScheme) {
|
||||
return fmt.Errorf("CrowdsecLapiScheme: must be one of 'http' or 'https'")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTLSConfigCrowdsec get TLS config from Config.
|
||||
func GetTLSConfigCrowdsec(config *Config) (*tls.Config, error) {
|
||||
tlsConfig := new(tls.Config)
|
||||
tlsConfig.RootCAs = x509.NewCertPool()
|
||||
//nolint:gocritic
|
||||
if config.CrowdsecLapiScheme != HTTPS {
|
||||
logger.Debug("getTLSConfigCrowdsec:CrowdsecLapiScheme not https")
|
||||
return tlsConfig, nil
|
||||
} else if config.CrowdsecLapiTLSInsecureVerify {
|
||||
logger.Debug("getTLSConfigCrowdsec:CrowdsecLapiTLSInsecureVerify is true")
|
||||
tlsConfig.InsecureSkipVerify = 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")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert := []byte(certAuthority)
|
||||
if !tlsConfig.RootCAs.AppendCertsFromPEM(cert) {
|
||||
logger.Debug("getTLSConfigCrowdsec:CrowdsecLapiTLSCertificateAuthority read cert failed")
|
||||
// here we return because if CrowdsecLapiTLSInsecureVerify is false
|
||||
// and CA not load, we can't communicate with https
|
||||
return nil, fmt.Errorf("getTLSConfigCrowdsec:cannot load CA and verify cert is enabled")
|
||||
}
|
||||
}
|
||||
|
||||
certBouncer, err := GetVariable(config, "CrowdsecLapiTLSCertificateBouncer")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certBouncerKey, err := GetVariable(config, "CrowdsecLapiTLSCertificateBouncerKey")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if certBouncer == "" || certBouncerKey == "" {
|
||||
return tlsConfig, nil
|
||||
}
|
||||
clientCert, err := tls.X509KeyPair([]byte(certBouncer), []byte(certBouncerKey))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getTLSClientConfigCrowdsec impossible to generate ClientCert %w", err)
|
||||
}
|
||||
tlsConfig.Certificates = append(tlsConfig.Certificates, clientCert)
|
||||
|
||||
return tlsConfig, nil
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func getMinimalConfig() *Config {
|
||||
cfg := New()
|
||||
cfg.CrowdsecLapiKey = "test"
|
||||
return cfg
|
||||
}
|
||||
|
||||
func Test_contains(t *testing.T) {
|
||||
type args struct {
|
||||
source []string
|
||||
target string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{name: "Contain in the list", args: args{source: []string{"a", "b"}, target: "a"}, want: true},
|
||||
{name: "Contain not in the list", args: args{source: []string{"a", "b"}, target: "c"}, want: false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := contains(tt.args.source, tt.args.target); got != tt.want {
|
||||
t.Errorf("contains() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_GetVariable(t *testing.T) {
|
||||
cfg1 := New()
|
||||
cfg1.CrowdsecLapiKey = "test"
|
||||
cfg2 := New()
|
||||
cfg2.CrowdsecLapiKeyFile = "../../tests/.keytest"
|
||||
cfg3 := New()
|
||||
cfg3.CrowdsecLapiKeyFile = "../../tests/.bad"
|
||||
type args struct {
|
||||
config *Config
|
||||
key string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "Validate a key string", args: args{config: cfg1, key: "CrowdsecLapiKey"}, want: "test", wantErr: false},
|
||||
{name: "Validate a key file", args: args{config: cfg2, key: "CrowdsecLapiKey"}, want: "test", wantErr: false},
|
||||
{name: "Not validate an invalid file", args: args{config: cfg3, key: "CrowdsecLapiKey"}, want: "", wantErr: true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := GetVariable(tt.args.config, tt.args.key)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("getVariable() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("getVariable() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ValidateParams(t *testing.T) {
|
||||
cfg3 := getMinimalConfig()
|
||||
cfg3.CrowdsecMode = "bad"
|
||||
cfg4 := getMinimalConfig()
|
||||
cfg4.UpdateIntervalSeconds = 0
|
||||
cfg5 := getMinimalConfig()
|
||||
cfg5.ClientTrustedIPs = []string{0: "bad"}
|
||||
cfg6 := getMinimalConfig()
|
||||
cfg6.CrowdsecLapiScheme = HTTPS
|
||||
cfg6.CrowdsecLapiTLSInsecureVerify = true
|
||||
cfg8 := getMinimalConfig()
|
||||
cfg8.CrowdsecLapiScheme = HTTPS
|
||||
type args struct {
|
||||
config *Config
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "Validate minimal config", args: args{config: getMinimalConfig()}, wantErr: false},
|
||||
{name: "Not validate an absent crowdsec lapi key", args: args{config: New()}, wantErr: true},
|
||||
{name: "Not validate a not listed item", args: args{config: cfg3}, wantErr: true},
|
||||
{name: "Not validate a bad number", args: args{config: cfg4}, wantErr: true},
|
||||
{name: "Not validate a bad clients ips", args: args{config: cfg5}, wantErr: true},
|
||||
// HTTPS enabled
|
||||
{name: "Validate https config with insecure verify", args: args{config: cfg6}, wantErr: false},
|
||||
{name: "Not validate https without cert authority", args: args{config: cfg8}, wantErr: true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := ValidateParams(tt.args.config); (err != nil) != tt.wantErr {
|
||||
t.Errorf("validateParams() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_validateParamsTLS(t *testing.T) {
|
||||
type args struct {
|
||||
config *Config
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := validateParamsTLS(tt.args.config); (err != nil) != tt.wantErr {
|
||||
t.Errorf("validateParamsTLS() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_validateParamsIPs(t *testing.T) {
|
||||
type args struct {
|
||||
listIP []string
|
||||
key string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "Not validate a non ip", args: args{listIP: []string{0: "bad"}}, wantErr: true},
|
||||
{name: "Not validate localhost", args: args{listIP: []string{0: "localhost"}}, wantErr: true},
|
||||
{name: "Not validate a weird ip", args: args{listIP: []string{0: "0.0.0.0/89"}}, wantErr: true},
|
||||
{name: "Not validate a weird ip 2", args: args{listIP: []string{0: "0.0.0.256/12"}}, wantErr: true},
|
||||
{name: "Validate an ip", args: args{listIP: []string{0: "0.0.0.0/12"}}, wantErr: false},
|
||||
{name: "Validate a ip list", args: args{listIP: []string{0: "0.0.0.0/0", 1: "1.1.1.1/1"}}, wantErr: false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := validateParamsIPs(tt.args.listIP, tt.args.key); (err != nil) != tt.wantErr {
|
||||
t.Errorf("validateParamsIPs() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_validateParamsRequired(t *testing.T) {
|
||||
cfg2 := getMinimalConfig()
|
||||
cfg2.CrowdsecLapiScheme = "bad"
|
||||
cfg3 := getMinimalConfig()
|
||||
cfg3.CrowdsecMode = "bad"
|
||||
cfg4 := getMinimalConfig()
|
||||
cfg4.UpdateIntervalSeconds = 0
|
||||
cfg5 := getMinimalConfig()
|
||||
cfg5.DefaultDecisionSeconds = 0
|
||||
type args struct {
|
||||
config *Config
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "Validate minimal config", args: args{config: getMinimalConfig()}, wantErr: false},
|
||||
{name: "Not validate a bad crowdsec scheme", args: args{config: cfg2}, wantErr: true},
|
||||
{name: "Not validate a bad crowdsec mode", args: args{config: cfg3}, wantErr: true},
|
||||
{name: "Not validate a bad update interval seconds", args: args{config: cfg4}, wantErr: true},
|
||||
{name: "Not validate a bad default decision seconds", args: args{config: cfg5}, wantErr: true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := validateParamsRequired(tt.args.config); (err != nil) != tt.wantErr {
|
||||
t.Errorf("validateParamsRequired() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_GetTLSConfigCrowdsec(t *testing.T) {
|
||||
type args struct {
|
||||
config *Config
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *tls.Config
|
||||
wantErr bool
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := GetTLSConfigCrowdsec(tt.args.config)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("getTLSConfigCrowdsec() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("getTLSConfigCrowdsec() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
test
|
||||
Reference in New Issue
Block a user