Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v13] Okta Import Rules use Teleport style regexes. #27126

Merged
merged 1 commit into from
May 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions lib/services/local/okta.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package local

import (
"context"
"regexp"
"time"

"github.com/gravitational/trace"
Expand All @@ -29,6 +28,7 @@ import (
"github.com/gravitational/teleport/lib/backend"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/services/local/generic"
"github.com/gravitational/teleport/lib/utils"
)

const (
Expand Down Expand Up @@ -123,15 +123,15 @@ func validateOktaImportRuleRegexes(importRule types.OktaImportRule) error {
for _, match := range mapping.GetMatches() {
if ok, regexes := match.GetAppNameRegexes(); ok {
for _, regex := range regexes {
if _, err := regexp.Compile(regex); err != nil {
if _, err := utils.CompileExpression(regex); err != nil {
errs = append(errs, err)
}
}
}

if ok, regexes := match.GetGroupNameRegexes(); ok {
for _, regex := range regexes {
if _, err := regexp.Compile(regex); err != nil {
if _, err := utils.CompileExpression(regex); err != nil {
errs = append(errs, err)
}
}
Expand Down
4 changes: 2 additions & 2 deletions lib/services/local/okta_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ func TestValidateOktaImportRuleRegexes(t *testing.T) {
spec: types.OktaImportRuleSpecV1{
Mappings: []*types.OktaImportRuleMappingV1{
{
Match: []*types.OktaImportRuleMatchV1{createRegexMatch("(bad", ".*")},
Match: []*types.OktaImportRuleMatchV1{createRegexMatch("^(bad$", ".*")},
AddLabels: map[string]string{"label1": "value1"},
},
{
Expand All @@ -274,7 +274,7 @@ func TestValidateOktaImportRuleRegexes(t *testing.T) {
AddLabels: map[string]string{"label1": "value1"},
},
{
Match: []*types.OktaImportRuleMatchV1{createRegexMatch(".*", "(bad")},
Match: []*types.OktaImportRuleMatchV1{createRegexMatch(".*", "^(bad$")},
AddLabels: map[string]string{"label1": "value1"},
},
},
Expand Down
49 changes: 34 additions & 15 deletions lib/utils/replace.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ type RegexpConfig struct {

// KubeResourceMatchesRegex checks whether the input matches any of the given
// expressions.
// This function returns as soon as it finds the first match or when matchString
// This function returns as soon as it finds the first match or when MatchString
// returns an error.
// This function supports regex expressions in the Name and Namespace fields,
// but not for the Kind field.
Expand All @@ -101,13 +101,13 @@ func KubeResourceMatchesRegex(input types.KubernetesResource, resources []types.
if input.Kind != resource.Kind {
continue
}
switch ok, err := matchString(input.Name, resource.Name); {
switch ok, err := MatchString(input.Name, resource.Name); {
case err != nil:
return false, trace.Wrap(err)
case !ok:
continue
}
if ok, err := matchString(input.Namespace, resource.Namespace); err != nil || ok {
if ok, err := MatchString(input.Namespace, resource.Namespace); err != nil || ok {
return ok, trace.Wrap(err)
}
}
Expand All @@ -119,7 +119,7 @@ func KubeResourceMatchesRegex(input types.KubernetesResource, resources []types.
// match is always evaluated as a regex either an exact match or regexp.
func SliceMatchesRegex(input string, expressions []string) (bool, error) {
for _, expression := range expressions {
result, err := matchString(input, expression)
result, err := MatchString(input, expression)
if err != nil || result {
return result, trace.Wrap(err)
}
Expand All @@ -139,16 +139,26 @@ func mustCache[K comparable, V any](size int) *lru.Cache[K, V] {
return cache
}

// exprCache interns compiled regular expressions created in matchString
// exprCache interns compiled regular expressions created in MatchString
// to improve performance.
var exprCache = mustCache[string, *regexp.Regexp](1000)

func matchString(input, expression string) (bool, error) {
if expr, ok := exprCache.Get(expression); ok {
return expr.MatchString(input), nil
// MatchString will match an input against the given expression. The expression is cached for later use.
func MatchString(input, expression string) (bool, error) {
expr, err := compileRegexCached(expression)
if err != nil {
return false, trace.BadParameter(err.Error())
}

original := expression
// Since the expression is always surrounded by ^ and $ this is an exact
// match for either a plain string (for example ^hello$) or for a regexp
// (for example ^hel*o$).
return expr.MatchString(input), nil
}

// CompileExpression compiles the given regex expression with Teleport's custom globbing
// and quoting logic.
func CompileExpression(expression string) (*regexp.Regexp, error) {
if !strings.HasPrefix(expression, "^") || !strings.HasSuffix(expression, "$") {
// replace glob-style wildcards with regexp wildcards
// for plain strings, and quote all characters that could
Expand All @@ -158,15 +168,24 @@ func matchString(input, expression string) (bool, error) {

expr, err := regexp.Compile(expression)
if err != nil {
return false, trace.BadParameter(err.Error())
return nil, trace.BadParameter(err.Error())
}

return expr, nil
}

func compileRegexCached(expression string) (*regexp.Regexp, error) {
if expr, ok := exprCache.Get(expression); ok {
return expr, nil
}

exprCache.Add(original, expr)
expr, err := CompileExpression(expression)
if err != nil {
return nil, trace.Wrap(err)
}

// Since the expression is always surrounded by ^ and $ this is an exact
// match for either a plain string (for example ^hello$) or for a regexp
// (for example ^hel*o$).
return expr.MatchString(input), nil
exprCache.Add(expression, expr)
return expr, nil
}

var (
Expand Down