From 4117a0b3917a3ea1da9afb8c9bc61f23bed1b2a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Thu, 26 Mar 2020 16:12:23 +0000 Subject: [PATCH] fix(backend): anchor all regexes in the config --- cmd/karma/acl.go | 5 +-- cmd/karma/main.go | 6 ++-- docs/ACLs.md | 2 ++ docs/CONFIGURATION.md | 11 +++--- internal/config/config.go | 6 ++-- internal/regex/regex.go | 26 ++++++++++++++ internal/regex/regex_test.go | 66 ++++++++++++++++++++++++++++++++++++ 7 files changed, 110 insertions(+), 12 deletions(-) create mode 100644 internal/regex/regex.go create mode 100644 internal/regex/regex_test.go diff --git a/cmd/karma/acl.go b/cmd/karma/acl.go index 10ddb2454..2e3717c25 100644 --- a/cmd/karma/acl.go +++ b/cmd/karma/acl.go @@ -7,6 +7,7 @@ import ( "github.com/prymitive/karma/internal/alertmanager" "github.com/prymitive/karma/internal/config" "github.com/prymitive/karma/internal/models" + "github.com/prymitive/karma/internal/regex" "github.com/prymitive/karma/internal/slices" ) @@ -188,7 +189,7 @@ func newSilenceACLFromConfig(cfg config.SilenceACLRule) (*silenceACL, error) { } if filter.NameRegex != "" { - re, err := regexp.Compile(filter.NameRegex) + re, err := regex.CompileAnchored(filter.NameRegex) if err != nil { return nil, fmt.Errorf("invalid ACL rule, failed to parse name_re %q: %s", filter.NameRegex, err) } @@ -196,7 +197,7 @@ func newSilenceACLFromConfig(cfg config.SilenceACLRule) (*silenceACL, error) { } if filter.ValueRegex != "" { - re, err := regexp.Compile(filter.ValueRegex) + re, err := regex.CompileAnchored(filter.ValueRegex) if err != nil { return nil, fmt.Errorf("invalid ACL rule, failed to parse value_re %q: %s", filter.ValueRegex, err) } diff --git a/cmd/karma/main.go b/cmd/karma/main.go index 34c8f7bc6..12e4a8252 100644 --- a/cmd/karma/main.go +++ b/cmd/karma/main.go @@ -9,7 +9,6 @@ import ( "os" "os/signal" "path" - "regexp" "strings" "syscall" "time" @@ -17,6 +16,7 @@ import ( "github.com/prymitive/karma/internal/alertmanager" "github.com/prymitive/karma/internal/config" "github.com/prymitive/karma/internal/models" + "github.com/prymitive/karma/internal/regex" "github.com/prymitive/karma/internal/transform" "github.com/prymitive/karma/internal/uri" @@ -80,7 +80,7 @@ func headerAuth(name, valueRegex string) gin.HandlerFunc { return } - r := regexp.MustCompile("^" + valueRegex + "$") + r := regex.MustCompileAnchored(valueRegex) matches := r.FindAllStringSubmatch(user, 1) if len(matches) > 0 && len(matches[0]) > 1 { c.Set(gin.AuthUserKey, matches[0][1]) @@ -274,7 +274,7 @@ func mainSetup(errorHandling pflag.ErrorHandling) (*gin.Engine, error) { if rule.Regex == "" || rule.URITemplate == "" { return nil, fmt.Errorf("Invalid link detect rule, regex '%s' uriTemplate '%s'", rule.Regex, rule.URITemplate) } - re, err := regexp.Compile(rule.Regex) + re, err := regex.CompileAnchored(rule.Regex) if err != nil { return nil, fmt.Errorf("Invalid link detect rule '%s': %s", rule.Regex, err) } diff --git a/docs/ACLs.md b/docs/ACLs.md index 5bbd2d412..566016775 100644 --- a/docs/ACLs.md +++ b/docs/ACLs.md @@ -203,9 +203,11 @@ matchers: Filter works by comparing `name` and `name_re` with silence matcher `name`, `value` and `value_re` with silence matcher `value` and `isRegex` on the filter with `isRegex` on silence matcher. See examples below. + All regexes will be automatically anchored. - `matchers:required` - list of additional matchers that must be part of the silence if it matches groups, alertmanagers and filters. This is only used if `action` is set to `requireMatcher`. + All regexes will be automatically anchored. Syntax for each matcher: ```YAML diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 334407645..403192122 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -44,7 +44,7 @@ Syntax: authentication: header: name: string - value_re: string + value_re: regex basicAuth: users: - username: string @@ -59,6 +59,7 @@ authentication: request header value (when `authentication:users:header:name` is set). It must include one numbered capturing group, whatever is matched by that group will be used as the silence form author field. + All regexes are anchored. This option must be set when `authentication:users:header:name` is set. - `authentication:users` - list of users (username & password) allowed to login. Passwords are stored plain without any encryption. @@ -654,7 +655,7 @@ labels: custom: foo: - value: string - value_re: string + value_re: regex color: string keep: list of strings strip: list of strings @@ -674,7 +675,8 @@ labels: - `value` - the exact value of the label to match against - `value_re` - Go compatible - [regular expression](https://golang.org/pkg/regexp/) to match against + [regular expression](https://golang.org/pkg/regexp/) to match against. + All regexes will be automatically anchored. - `color`: color to apply if either `value` or `value_re` matches Either `value` or `value_re` is required, both can be set in which case @@ -854,7 +856,8 @@ silences: turn them into links. Each rule must specify: - `regex` - regular expression that matches ticket system IDs. Each regex must - contain at least one capture group `(regex)`. + contain at least one capture group `(regex)`. All regexes will be + automatically anchored. - `uriTemplate` - template string that will be used to generate a link. Each template must include `$1` which will be replaced with text matched by the `regex`. diff --git a/internal/config/config.go b/internal/config/config.go index 7d7eb23e8..1461dd86a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -4,10 +4,10 @@ import ( "bufio" "bytes" "os" - "regexp" "strings" "time" + "github.com/prymitive/karma/internal/regex" "github.com/prymitive/karma/internal/slices" "github.com/prymitive/karma/internal/uri" @@ -243,7 +243,7 @@ func (config *configSchema) Read(flags *pflag.FlagSet) string { } if config.Authentication.Header.ValueRegex != "" { - _, err = regexp.Compile(config.Authentication.Header.ValueRegex) + _, err = regex.CompileAnchored(config.Authentication.Header.ValueRegex) if err != nil { log.Fatalf("Invalid regex for authentication.header.value_re: %s", err.Error()) } @@ -295,7 +295,7 @@ func (config *configSchema) Read(flags *pflag.FlagSet) string { log.Fatalf("Custom label color for '%s' is missing 'value' or 'value_re'", labelName) } if customColor.ValueRegex != "" { - config.Labels.Color.Custom[labelName][i].CompiledRegex, err = regexp.Compile(customColor.ValueRegex) + config.Labels.Color.Custom[labelName][i].CompiledRegex, err = regex.CompileAnchored(customColor.ValueRegex) if err != nil { log.Fatalf("Failed to parse custom color regex rule '%s' for '%s' label: %s", customColor.ValueRegex, labelName, err) } diff --git a/internal/regex/regex.go b/internal/regex/regex.go new file mode 100644 index 000000000..a4d66ab59 --- /dev/null +++ b/internal/regex/regex.go @@ -0,0 +1,26 @@ +package regex + +import ( + "regexp" + "strings" +) + +func wrapRegex(r string) string { + var prefix, suffix string + if !strings.HasPrefix(r, "^") { + prefix = "^" + } + if !strings.HasSuffix(r, "$") { + suffix = "$" + } + return prefix + r + suffix +} + +func MustCompileAnchored(r string) *regexp.Regexp { + + return regexp.MustCompile(wrapRegex(r)) +} + +func CompileAnchored(r string) (*regexp.Regexp, error) { + return regexp.Compile(wrapRegex(r)) +} diff --git a/internal/regex/regex_test.go b/internal/regex/regex_test.go new file mode 100644 index 000000000..a2e66d3c5 --- /dev/null +++ b/internal/regex/regex_test.go @@ -0,0 +1,66 @@ +package regex_test + +import ( + "fmt" + "testing" + + "github.com/prymitive/karma/internal/regex" +) + +func TestMustCompileAnchored(t *testing.T) { + type testCaseT struct { + in string + out string + } + + testCases := []testCaseT{ + {in: "foo", out: "^foo$"}, + {in: "^foo", out: "^foo$"}, + {in: "foo$", out: "^foo$"}, + {in: "^foo$", out: "^foo$"}, + {in: "^^foo$", out: "^^foo$"}, + {in: "foo$$", out: "^foo$$"}, + } + + for _, testCase := range testCases { + t.Run(fmt.Sprintf("%q => %q", testCase.in, testCase.out), func(t *testing.T) { + r := regex.MustCompileAnchored(testCase.in) + if r.String() != testCase.out { + t.Errorf("Regex mismatch, expected %q got %q", testCase.out, r.String()) + } + }) + } +} + +func TestCompileAnchored(t *testing.T) { + type testCaseT struct { + in string + out string + error bool + } + + testCases := []testCaseT{ + {in: "foo", out: "^foo$"}, + {in: "^foo", out: "^foo$"}, + {in: "foo$", out: "^foo$"}, + {in: "^foo$", out: "^foo$"}, + {in: "^^foo$", out: "^^foo$"}, + {in: "foo$$", out: "^foo$$"}, + {in: ".******", out: "", error: true}, + } + + for _, testCase := range testCases { + t.Run(fmt.Sprintf("%q => %q", testCase.in, testCase.out), func(t *testing.T) { + r, err := regex.CompileAnchored(testCase.in) + hadError := err != nil + if testCase.error != hadError { + t.Errorf("CompileAnchored err=%v, expected error=%v", err, testCase.error) + } + if err == nil { + if r.String() != testCase.out { + t.Errorf("Regex mismatch, expected %q got %q", testCase.out, r.String()) + } + } + }) + } +}