diff --git a/pkg/configuration/configuration.go b/pkg/configuration/configuration.go index 08472c0..4b87e18 100644 --- a/pkg/configuration/configuration.go +++ b/pkg/configuration/configuration.go @@ -10,6 +10,7 @@ import ( "os" "path/filepath" "reflect" + "regexp" "strings" ip "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/ip" @@ -144,20 +145,17 @@ func ValidateParams(config *Config) error { } // 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") + return fmt.Errorf("CrowdsecLapiKey || (CrowdsecLapiTLSCertificateBouncer && CrowdsecLapiTLSCertificateBouncerKey): cannot be all empty") } else if lapiKey != "" && (certBouncer == "" || certBouncerKey == "") { - // check LAPIKey - lapiKey = strings.TrimSuffix(lapiKey, "\n") - err = validateParamsAPIKey(lapiKey) - if err != nil { + lapiKey = strings.TrimSpace(lapiKey) + if err = validateParamsAPIKey(lapiKey); err != nil { return err } } // Case https to contact Crowdsec LAPI and certificate must be provided if config.CrowdsecLapiScheme == HTTPS && !config.CrowdsecLapiTLSInsecureVerify { - err = validateParamsTLS(config) - if err != nil { + if err = validateParamsTLS(config); err != nil { return err } } @@ -165,107 +163,16 @@ func ValidateParams(config *Config) error { return nil } -func validateParamsAPIKey(lapiKey string) error { - for i := 0; i < len(lapiKey); i++ { - c := lapiKey[i] - if !validHeaderFieldByte(c) { - return fmt.Errorf("CrowdsecLapiKey contains the following forbidden caracter %q", c) - } - } - return nil -} - // validHeaderFieldByte reports whether b is a valid byte in a header // field name. RFC 7230 says: -// -// header-field = field-name ":" OWS field-value OWS -// field-name = token -// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / -// "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA -// token = 1*tchar -func validHeaderFieldByte(b byte) bool { - // isTokenTable is a copy of net/http/lex.go's isTokenTable. - // See https://httpwg.github.io/specs/rfc7230.html#rule.token.separators - var isTokenTable = [127]bool{ - '!': true, - '#': true, - '$': true, - '%': true, - '&': true, - '\'': true, - '*': true, - '+': true, - '-': true, - '.': true, - '0': true, - '1': true, - '2': true, - '3': true, - '4': true, - '5': true, - '6': true, - '7': true, - '8': true, - '9': true, - 'A': true, - 'B': true, - 'C': true, - 'D': true, - 'E': true, - 'F': true, - 'G': true, - 'H': true, - 'I': true, - 'J': true, - 'K': true, - 'L': true, - 'M': true, - 'N': true, - 'O': true, - 'P': true, - 'Q': true, - 'R': true, - 'S': true, - 'T': true, - 'U': true, - 'W': true, - 'V': true, - 'X': true, - 'Y': true, - 'Z': true, - '^': true, - '_': true, - '`': true, - 'a': true, - 'b': true, - 'c': true, - 'd': true, - 'e': true, - 'f': true, - 'g': true, - 'h': true, - 'i': true, - 'j': true, - 'k': true, - 'l': true, - 'm': true, - 'n': true, - 'o': true, - 'p': true, - 'q': true, - 'r': true, - 's': true, - 't': true, - 'u': true, - 'v': true, - 'w': true, - 'x': true, - 'y': true, - 'z': true, - '|': true, - '~': true, +// valid ! # $ % & ' * + - . ^ _ ` | ~ DIGIT ALPHA +// See https://httpwg.github.io/specs/rfc7230.html#rule.token.separators +func validateParamsAPIKey(lapiKey string) error { + reg := regexp.MustCompile("^[a-zA-Z0-9 !#$%&'*+-.^_`|~]*$") + if !reg.Match([]byte(lapiKey)) { + return fmt.Errorf("CrowdsecLapiKey doesn't valid this regexp: '/%s/'", reg.String()) } - return int(b) < len(isTokenTable) && isTokenTable[b] + return nil } func validateParamsTLS(config *Config) error { diff --git a/pkg/configuration/configuration_test.go b/pkg/configuration/configuration_test.go index f5c00a5..b3cc4db 100644 --- a/pkg/configuration/configuration_test.go +++ b/pkg/configuration/configuration_test.go @@ -70,6 +70,10 @@ func Test_GetVariable(t *testing.T) { } func Test_ValidateParams(t *testing.T) { + cfg1 := New() + cfg1.CrowdsecLapiKey = "test\n\n" + cfg2 := New() + cfg2.CrowdsecLapiKey = "test@" cfg3 := getMinimalConfig() cfg3.CrowdsecMode = "bad" cfg4 := getMinimalConfig() @@ -90,6 +94,8 @@ func Test_ValidateParams(t *testing.T) { 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}, @@ -141,8 +147,9 @@ func Test_validateParamsIPs(t *testing.T) { {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 trimed", 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 a ip list", args: args{listIP: []string{0: "0.0.0.0/0", 1: "1.1.1.1/1"}}, 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) { @@ -185,6 +192,31 @@ func Test_validateParamsRequired(t *testing.T) { } } +func Test_validateParamsAPIKey(t *testing.T) { + type args struct { + lapiKey string + } + tests := []struct { + name string + args args + wantErr bool + }{ + {name: "Validate all the valid characters", args: args{lapiKey: "test!#$%&'*+-.^_`|~"}, wantErr: false}, + {name: "Not validate a @", args: args{lapiKey: "test@"}, wantErr: true}, + {name: "Not validate a (", args: args{lapiKey: "test("}, wantErr: true}, + {name: "Not validate a [", args: args{lapiKey: "test["}, wantErr: true}, + {name: "Not validate a ?", args: args{lapiKey: "test?"}, wantErr: true}, + {name: "Not validate a \\n, (must be trimed before)", args: args{lapiKey: "test\n"}, wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := validateParamsAPIKey(tt.args.lapiKey); (err != nil) != tt.wantErr { + t.Errorf("validateParamsAPIKey() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + func Test_GetTLSConfigCrowdsec(t *testing.T) { type args struct { config *Config diff --git a/pkg/ip/ip.go b/pkg/ip/ip.go index dccde56..357ef57 100644 --- a/pkg/ip/ip.go +++ b/pkg/ip/ip.go @@ -23,9 +23,8 @@ type Checker struct { func NewChecker(trustedIPs []string) (*Checker, error) { checker := &Checker{} - for _, ipMask := range trustedIPs { - // remove leading and trailing spaces - ipMask = strings.TrimSpace(ipMask) + for _, ipMaskRaw := range trustedIPs { + ipMask := strings.TrimSpace(ipMaskRaw) if ipAddr := net.ParseIP(ipMask); ipAddr != nil { checker.authorizedIPs = append(checker.authorizedIPs, &ipAddr) logger.Debug(fmt.Sprintf("IP %v is trusted", ipAddr))