mirror of
https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin.git
synced 2026-07-19 18:52:20 +02:00
* Add parameter to configure Ban Response Content-Type * Add testing for new BanResponseContentType parameter * Ensure there is a fallback to default Content-Type is user provided empty value * Set Content-Type even if banTemplate is nil * Add more edge cases for testing ban response Content-Type * Add CR/LF validation for BanResponseContentType * Add CaptchaResponseContentType to allow separate Content-Type configuration for captcha responses * Add testing for new CaptchaResponseContentType * Update README * Split nil and CR/LF response Content-Type value validation into separate function * Throw error instead of setting the default in case of empty parameter declaration * Update testing accordingly * ✨ remove HTML from var name, add tests and infer content type from filePath * 🍱 fix lint ? * 🍱 fix lint * 🍱 fix lint * 🍱 fix lint + naming * 🍱 fix lint * 🍱 fuck lint --------- Co-authored-by: maxlerebourg <maxlerebourg@gmail.com>
329 lines
12 KiB
Go
329 lines
12 KiB
Go
package configuration
|
|
|
|
import (
|
|
"testing"
|
|
|
|
logger "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/logger"
|
|
)
|
|
|
|
// validPEM is a minimal self-signed certificate accepted by AppendCertsFromPEM,
|
|
// shared by the TLS tests below.
|
|
const validPEM = `-----BEGIN CERTIFICATE-----
|
|
MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw
|
|
DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow
|
|
EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d
|
|
7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B
|
|
5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr
|
|
BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1
|
|
NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l
|
|
Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc
|
|
6MF9+Yw1Yy0t
|
|
-----END CERTIFICATE-----`
|
|
|
|
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) {
|
|
log := logger.New("INFO", "")
|
|
cfg1 := New()
|
|
cfg1.CrowdsecLapiKey = "test\n\n"
|
|
cfg2 := New()
|
|
cfg2.CrowdsecLapiKey = "test@"
|
|
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
|
|
cfg7 := getMinimalConfig()
|
|
cfg7.CrowdsecLapiScheme = HTTPS
|
|
cfg8 := getMinimalConfig()
|
|
cfg8.LogLevel = LogINFO
|
|
cfg9 := getMinimalConfig()
|
|
cfg9.LogLevel = "info"
|
|
cfg10 := getMinimalConfig()
|
|
cfg10.LogLevel = "Warning"
|
|
type args struct {
|
|
config *Config
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
wantErr bool
|
|
}{
|
|
{name: "Validate minimal config", args: args{config: getMinimalConfig()}, wantErr: false},
|
|
{name: "Validate a non trimed crowdsec lapi key", args: args{config: cfg1}, wantErr: false},
|
|
{name: "Not validate unauthorized character in crowdsec lapi key", args: args{config: cfg2}, wantErr: true},
|
|
{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: "Validate https without cert authority (falls back to system trust store)", args: args{config: cfg7}, wantErr: false},
|
|
{name: "Valid log level uppercase INFO", args: args{config: cfg8}, wantErr: false},
|
|
{name: "Valid log level lowercase info", args: args{config: cfg9}, wantErr: false},
|
|
{name: "Invalid log level Warning", args: args{config: cfg10}, wantErr: true},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if err := ValidateParams(tt.args.config, log); (err != nil) != tt.wantErr {
|
|
t.Errorf("validateParams() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_validateParamsTLS(t *testing.T) {
|
|
cfgEmpty := getMinimalConfig()
|
|
cfgValid := getMinimalConfig()
|
|
cfgValid.CrowdsecLapiTLSCertificateAuthority = validPEM
|
|
cfgInvalidCA := getMinimalConfig()
|
|
cfgInvalidCA.CrowdsecLapiTLSCertificateAuthority = "not a pem"
|
|
|
|
tests := []struct {
|
|
name string
|
|
config *Config
|
|
wantErr bool
|
|
}{
|
|
{name: "Empty CA is accepted (system trust store used at runtime)", config: cfgEmpty, wantErr: false},
|
|
{name: "Valid PEM CA is accepted", config: cfgValid, wantErr: false},
|
|
{name: "Invalid CA is rejected", config: cfgInvalidCA, wantErr: true},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if err := validateParamsTLS(tt.config); (err != nil) != tt.wantErr {
|
|
t.Errorf("validateParamsTLS() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_validateParamsIPs(t *testing.T) {
|
|
log := logger.New("INFO", "")
|
|
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 not trimmed", args: args{listIP: []string{0: " 0.0.0.0/0"}}, wantErr: false},
|
|
{name: "Validate an ip", args: args{listIP: []string{0: "0.0.0.0/12"}}, wantErr: false},
|
|
{name: "Validate an 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(log, 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_validateParamsAPIKey(t *testing.T) {
|
|
type args struct {
|
|
lapiKey string
|
|
paramName string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
wantErr bool
|
|
}{
|
|
{name: "Validate all the valid characters", args: args{lapiKey: "test!#$%&'*+-.^_`|~", paramName: "CrowdsecParamName"}, wantErr: false},
|
|
{name: "Not validate a @", args: args{lapiKey: "test@", paramName: "CrowdsecParamName"}, wantErr: true},
|
|
{name: "Not validate a (", args: args{lapiKey: "test(", paramName: "CrowdsecParamName"}, wantErr: true},
|
|
{name: "Not validate a [", args: args{lapiKey: "test[", paramName: "CrowdsecParamName"}, wantErr: true},
|
|
{name: "Not validate a ?", args: args{lapiKey: "test?", paramName: "CrowdsecParamName"}, wantErr: true},
|
|
{name: "Not validate a \\n, (must be trimed before)", args: args{lapiKey: "test\n", paramName: "CrowdsecParamName"}, wantErr: true},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if err := validateParamsAPIKey(tt.args.lapiKey, tt.args.paramName); (err != nil) != tt.wantErr {
|
|
t.Errorf("validateParamsAPIKey() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_GetTLSConfigCrowdsec(t *testing.T) {
|
|
log := logger.New("INFO", "")
|
|
|
|
httpCfg := getMinimalConfig()
|
|
httpCfg.CrowdsecLapiScheme = HTTP
|
|
|
|
httpsSystemCA := getMinimalConfig()
|
|
httpsSystemCA.CrowdsecLapiScheme = HTTPS
|
|
|
|
httpsCustomCA := getMinimalConfig()
|
|
httpsCustomCA.CrowdsecLapiScheme = HTTPS
|
|
httpsCustomCA.CrowdsecLapiTLSCertificateAuthority = validPEM
|
|
|
|
httpsInsecure := getMinimalConfig()
|
|
httpsInsecure.CrowdsecLapiScheme = HTTPS
|
|
httpsInsecure.CrowdsecLapiTLSInsecureVerify = true
|
|
|
|
httpsBadCA := getMinimalConfig()
|
|
httpsBadCA.CrowdsecLapiScheme = HTTPS
|
|
httpsBadCA.CrowdsecLapiTLSCertificateAuthority = "not a pem"
|
|
|
|
tests := []struct {
|
|
name string
|
|
config *Config
|
|
wantErr bool
|
|
wantRootCAsNil bool
|
|
wantInsecureSkip bool
|
|
}{
|
|
{name: "HTTP scheme returns empty tls.Config", config: httpCfg, wantRootCAsNil: true},
|
|
{name: "HTTPS without CA leaves RootCAs nil (system trust store)", config: httpsSystemCA, wantRootCAsNil: true},
|
|
{name: "HTTPS with custom CA populates RootCAs", config: httpsCustomCA, wantRootCAsNil: false},
|
|
{name: "HTTPS with insecure verify sets InsecureSkipVerify", config: httpsInsecure, wantRootCAsNil: true, wantInsecureSkip: true},
|
|
{name: "HTTPS with garbage CA is rejected", config: httpsBadCA, wantErr: true},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := GetTLSConfigCrowdsec(tt.config, log, false)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("GetTLSConfigCrowdsec() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if tt.wantErr {
|
|
return
|
|
}
|
|
if (got.RootCAs == nil) != tt.wantRootCAsNil {
|
|
t.Errorf("GetTLSConfigCrowdsec() RootCAs nil = %v, want nil = %v", got.RootCAs == nil, tt.wantRootCAsNil)
|
|
}
|
|
if got.InsecureSkipVerify != tt.wantInsecureSkip {
|
|
t.Errorf("GetTLSConfigCrowdsec() InsecureSkipVerify = %v, want %v", got.InsecureSkipVerify, tt.wantInsecureSkip)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_getContentTypeFromPath(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
path string
|
|
expected string
|
|
}{
|
|
{name: "HTML file with .html extension", path: "/ban.html", expected: "text/html; charset=utf-8"},
|
|
{name: "JSON file", path: "/ban.json", expected: "application/json"},
|
|
{name: "Text file", path: "/ban.txt", expected: "text/plain"},
|
|
{name: "File with mixed case extension", path: "/ban.HtMl", expected: "text/html; charset=utf-8"},
|
|
{name: "Unknown extension defaults to HTML", path: "/ban.xyz", expected: "text/html; charset=utf-8"},
|
|
{name: "File without extension", path: "/ban", expected: "text/html; charset=utf-8"},
|
|
{name: "Empty path", path: "", expected: ""},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := getContentTypeFromPath(tt.path)
|
|
if got != tt.expected {
|
|
t.Errorf("GetContentTypeFromPath(%q) = %q, want %q", tt.path, got, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|