Skip to content

Commit

Permalink
rule: Resolve matcher cache
Browse files Browse the repository at this point in the history
A bug caused the rule matcher to not cache the regular expression result.

Closes #291
  • Loading branch information
aeneasr committed Dec 17, 2019
1 parent a178031 commit 1c3070f
Show file tree
Hide file tree
Showing 11 changed files with 75 additions and 56 deletions.
18 changes: 9 additions & 9 deletions api/decision_test.go
Expand Up @@ -59,14 +59,14 @@ func TestDecisionAPI(t *testing.T) {
defer ts.Close()

ruleNoOpAuthenticator := rule.Rule{
Match: rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-noop/<[0-9]+>"},
Match: &rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-noop/<[0-9]+>"},
Authenticators: []rule.RuleHandler{{Handler: "noop"}},
Authorizer: rule.RuleHandler{Handler: "allow"},
Mutators: []rule.RuleHandler{{Handler: "noop"}},
Upstream: rule.Upstream{URL: ""},
}
ruleNoOpAuthenticatorModifyUpstream := rule.Rule{
Match: rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/strip-path/authn-noop/<[0-9]+>"},
Match: &rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/strip-path/authn-noop/<[0-9]+>"},
Authenticators: []rule.RuleHandler{{Handler: "noop"}},
Authorizer: rule.RuleHandler{Handler: "allow"},
Mutators: []rule.RuleHandler{{Handler: "noop"}},
Expand Down Expand Up @@ -118,7 +118,7 @@ func TestDecisionAPI(t *testing.T) {
d: "should fail because no authorizer was configured",
url: ts.URL + "/decisions" + "/authn-anon/authz-none/cred-none/1234",
rules: []rule.Rule{{
Match: rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-none/cred-none/<[0-9]+>"},
Match: &rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-none/cred-none/<[0-9]+>"},
Authenticators: []rule.RuleHandler{{Handler: "anonymous"}},
Upstream: rule.Upstream{URL: ""},
}},
Expand All @@ -131,7 +131,7 @@ func TestDecisionAPI(t *testing.T) {
d: "should fail because no mutator was configured",
url: ts.URL + "/decisions" + "/authn-anon/authz-allow/cred-none/1234",
rules: []rule.Rule{{
Match: rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-allow/cred-none/<[0-9]+>"},
Match: &rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-allow/cred-none/<[0-9]+>"},
Authenticators: []rule.RuleHandler{{Handler: "anonymous"}},
Authorizer: rule.RuleHandler{Handler: "allow"},
Upstream: rule.Upstream{URL: ""},
Expand All @@ -142,7 +142,7 @@ func TestDecisionAPI(t *testing.T) {
d: "should pass with anonymous and everything else set to noop",
url: ts.URL + "/decisions" + "/authn-anon/authz-allow/cred-noop/1234",
rules: []rule.Rule{{
Match: rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-allow/cred-noop/<[0-9]+>"},
Match: &rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-allow/cred-noop/<[0-9]+>"},
Authenticators: []rule.RuleHandler{{Handler: "anonymous"}},
Authorizer: rule.RuleHandler{Handler: "allow"},
Mutators: []rule.RuleHandler{{Handler: "noop"}},
Expand All @@ -155,7 +155,7 @@ func TestDecisionAPI(t *testing.T) {
d: "should fail when authorizer fails",
url: ts.URL + "/decisions" + "/authn-anon/authz-deny/cred-noop/1234",
rules: []rule.Rule{{
Match: rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-deny/cred-noop/<[0-9]+>"},
Match: &rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-deny/cred-noop/<[0-9]+>"},
Authenticators: []rule.RuleHandler{{Handler: "anonymous"}},
Authorizer: rule.RuleHandler{Handler: "deny"},
Mutators: []rule.RuleHandler{{Handler: "noop"}},
Expand All @@ -167,7 +167,7 @@ func TestDecisionAPI(t *testing.T) {
d: "should fail when authenticator fails",
url: ts.URL + "/decisions" + "/authn-broken/authz-none/cred-none/1234",
rules: []rule.Rule{{
Match: rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-broken/authz-none/cred-none/<[0-9]+>"},
Match: &rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-broken/authz-none/cred-none/<[0-9]+>"},
Authenticators: []rule.RuleHandler{{Handler: "unauthorized"}},
Upstream: rule.Upstream{URL: ""},
}},
Expand All @@ -177,7 +177,7 @@ func TestDecisionAPI(t *testing.T) {
d: "should fail when mutator fails",
url: ts.URL + "/decisions" + "/authn-anonymous/authz-allow/cred-broken/1234",
rules: []rule.Rule{{
Match: rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-anonymous/authz-allow/cred-broken/<[0-9]+>"},
Match: &rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-anonymous/authz-allow/cred-broken/<[0-9]+>"},
Authenticators: []rule.RuleHandler{{Handler: "anonymous"}},
Authorizer: rule.RuleHandler{Handler: "allow"},
Mutators: []rule.RuleHandler{{Handler: "broken"}},
Expand All @@ -189,7 +189,7 @@ func TestDecisionAPI(t *testing.T) {
d: "should fail when one of the mutators fails",
url: ts.URL + "/decisions" + "/authn-anonymous/authz-allow/cred-broken/1234",
rules: []rule.Rule{{
Match: rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-anonymous/authz-allow/cred-broken/<[0-9]+>"},
Match: &rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-anonymous/authz-allow/cred-broken/<[0-9]+>"},
Authenticators: []rule.RuleHandler{{Handler: "anonymous"}},
Authorizer: rule.RuleHandler{Handler: "allow"},
Mutators: []rule.RuleHandler{{Handler: "noop"}, {Handler: "broken"}},
Expand Down
4 changes: 2 additions & 2 deletions api/rule_test.go
Expand Up @@ -59,7 +59,7 @@ func TestHandler(t *testing.T) {
rules := []rule.Rule{
{
ID: "foo1",
Match: rule.RuleMatch{
Match: &rule.RuleMatch{
URL: "https://localhost:1234/<foo|bar>",
Methods: []string{"POST"},
},
Expand All @@ -75,7 +75,7 @@ func TestHandler(t *testing.T) {
},
{
ID: "foo2",
Match: rule.RuleMatch{
Match: &rule.RuleMatch{
URL: "https://localhost:34/<baz|bar>",
Methods: []string{"GET"},
},
Expand Down
12 changes: 6 additions & 6 deletions pipeline/authz/keto_engine_acp_ory_test.go
Expand Up @@ -70,7 +70,7 @@ func TestAuthorizerKetoWarden(t *testing.T) {
{
config: []byte(`{ "required_action": "action", "required_resource": "resource" }`),
rule: &rule.Rule{
Match: rule.RuleMatch{
Match: &rule.RuleMatch{
Methods: []string{"POST"},
URL: "https://localhost/",
},
Expand All @@ -82,7 +82,7 @@ func TestAuthorizerKetoWarden(t *testing.T) {
{
config: []byte(`{ "required_action": "action", "required_resource": "resource", "flavor": "regex" }`),
rule: &rule.Rule{
Match: rule.RuleMatch{
Match: &rule.RuleMatch{
Methods: []string{"POST"},
URL: "https://localhost/",
},
Expand All @@ -99,7 +99,7 @@ func TestAuthorizerKetoWarden(t *testing.T) {
{
config: []byte(`{ "required_action": "action", "required_resource": "resource", "flavor": "exact" }`),
rule: &rule.Rule{
Match: rule.RuleMatch{
Match: &rule.RuleMatch{
Methods: []string{"POST"},
URL: "https://localhost/",
},
Expand All @@ -119,7 +119,7 @@ func TestAuthorizerKetoWarden(t *testing.T) {
{
config: []byte(`{ "required_action": "action:$1:$2", "required_resource": "resource:$1:$2" }`),
rule: &rule.Rule{
Match: rule.RuleMatch{
Match: &rule.RuleMatch{
Methods: []string{"POST"},
URL: "https://localhost/api/users/<[0-9]+>/<[a-z]+>",
},
Expand All @@ -145,7 +145,7 @@ func TestAuthorizerKetoWarden(t *testing.T) {
{
config: []byte(`{ "required_action": "action:$1:$2", "required_resource": "resource:$1:$2", "subject": "{{ .Extra.name }}" }`),
rule: &rule.Rule{
Match: rule.RuleMatch{
Match: &rule.RuleMatch{
Methods: []string{"POST"},
URL: "https://localhost/api/users/<[0-9]+>/<[a-z]+>",
},
Expand All @@ -171,7 +171,7 @@ func TestAuthorizerKetoWarden(t *testing.T) {
{
config: []byte(`{ "required_action": "action:$1:$2", "required_resource": "resource:$1:$2", "subject": "{{ .Extra.name }}" }`),
rule: &rule.Rule{
Match: rule.RuleMatch{
Match: &rule.RuleMatch{
Methods: []string{"POST"},
URL: "https://localhost/api/users/<[0-9]+>/<[a-z]+>",
},
Expand Down
20 changes: 10 additions & 10 deletions proxy/proxy_test.go
Expand Up @@ -93,14 +93,14 @@ func TestProxy(t *testing.T) {
viper.Set(configuration.ViperKeyMutatorNoopIsEnabled, true)

ruleNoOpAuthenticator := rule.Rule{
Match: rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-noop/<[0-9]+>"},
Match: &rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-noop/<[0-9]+>"},
Authenticators: []rule.RuleHandler{{Handler: "noop"}},
Authorizer: rule.RuleHandler{Handler: "allow"},
Mutators: []rule.RuleHandler{{Handler: "noop"}},
Upstream: rule.Upstream{URL: backend.URL},
}
ruleNoOpAuthenticatorModifyUpstream := rule.Rule{
Match: rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/strip-path/authn-noop/<[0-9]+>"},
Match: &rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/strip-path/authn-noop/<[0-9]+>"},
Authenticators: []rule.RuleHandler{{Handler: "noop"}},
Authorizer: rule.RuleHandler{Handler: "allow"},
Mutators: []rule.RuleHandler{{Handler: "noop"}},
Expand Down Expand Up @@ -164,7 +164,7 @@ func TestProxy(t *testing.T) {
d: "should fail because no authorizer was configured",
url: ts.URL + "/authn-anon/authz-none/cred-none/1234",
rules: []rule.Rule{{
Match: rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-none/cred-none/<[0-9]+>"},
Match: &rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-none/cred-none/<[0-9]+>"},
Authenticators: []rule.RuleHandler{{Handler: "anonymous"}},
Upstream: rule.Upstream{URL: backend.URL},
}},
Expand All @@ -177,7 +177,7 @@ func TestProxy(t *testing.T) {
d: "should fail because no credentials issuer was configured",
url: ts.URL + "/authn-anon/authz-allow/cred-none/1234",
rules: []rule.Rule{{
Match: rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-allow/cred-none/<[0-9]+>"},
Match: &rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-allow/cred-none/<[0-9]+>"},
Authenticators: []rule.RuleHandler{{Handler: "anonymous"}},
Authorizer: rule.RuleHandler{Handler: "allow"},
Upstream: rule.Upstream{URL: backend.URL},
Expand All @@ -188,7 +188,7 @@ func TestProxy(t *testing.T) {
d: "should pass with anonymous and everything else set to noop",
url: ts.URL + "/authn-anon/authz-allow/cred-noop/1234",
rules: []rule.Rule{{
Match: rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-allow/cred-noop/<[0-9]+>"},
Match: &rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-allow/cred-noop/<[0-9]+>"},
Authenticators: []rule.RuleHandler{{Handler: "anonymous"}},
Authorizer: rule.RuleHandler{Handler: "allow"},
Mutators: []rule.RuleHandler{{Handler: "noop"}},
Expand All @@ -205,7 +205,7 @@ func TestProxy(t *testing.T) {
d: "should fail when authorizer fails",
url: ts.URL + "/authn-anon/authz-deny/cred-noop/1234",
rules: []rule.Rule{{
Match: rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-deny/cred-noop/<[0-9]+>"},
Match: &rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-deny/cred-noop/<[0-9]+>"},
Authenticators: []rule.RuleHandler{{Handler: "anonymous"}},
Authorizer: rule.RuleHandler{Handler: "deny"},
Mutators: []rule.RuleHandler{{Handler: "noop"}},
Expand All @@ -217,7 +217,7 @@ func TestProxy(t *testing.T) {
d: "should fail when authenticator fails",
url: ts.URL + "/authn-broken/authz-none/cred-none/1234",
rules: []rule.Rule{{
Match: rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-broken/authz-none/cred-none/<[0-9]+>"},
Match: &rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-broken/authz-none/cred-none/<[0-9]+>"},
Authenticators: []rule.RuleHandler{{Handler: "unauthorized"}},
Upstream: rule.Upstream{URL: backend.URL},
}},
Expand All @@ -227,7 +227,7 @@ func TestProxy(t *testing.T) {
d: "should fail because no mutator was configured",
url: ts.URL + "/authn-anon/authz-deny/cred-noop/1234",
rules: []rule.Rule{{
Match: rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-deny/cred-noop/<[0-9]+>"},
Match: &rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-deny/cred-noop/<[0-9]+>"},
Authenticators: []rule.RuleHandler{{Handler: "anonymous"}},
Authorizer: rule.RuleHandler{Handler: "allow"},
Upstream: rule.Upstream{URL: backend.URL},
Expand All @@ -238,7 +238,7 @@ func TestProxy(t *testing.T) {
d: "should fail when one of the mutators fails",
url: ts.URL + "/authn-anon/authz-deny/cred-noop/1234",
rules: []rule.Rule{{
Match: rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-deny/cred-noop/<[0-9]+>"},
Match: &rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-deny/cred-noop/<[0-9]+>"},
Authenticators: []rule.RuleHandler{{Handler: "anonymous"}},
Authorizer: rule.RuleHandler{Handler: "allow"},
Mutators: []rule.RuleHandler{{Handler: "noop"}, {Handler: "broken"}},
Expand All @@ -250,7 +250,7 @@ func TestProxy(t *testing.T) {
d: "should fail when credentials issuer fails",
url: ts.URL + "/authn-anonymous/authz-allow/cred-broken/1234",
rules: []rule.Rule{{
Match: rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-anonymous/authz-allow/cred-broken/<[0-9]+>"},
Match: &rule.RuleMatch{Methods: []string{"GET"}, URL: ts.URL + "/authn-anonymous/authz-allow/cred-broken/<[0-9]+>"},
Authenticators: []rule.RuleHandler{{Handler: "anonymous"}},
Authorizer: rule.RuleHandler{Handler: "allow"},
Mutators: []rule.RuleHandler{{Handler: "broken"}},
Expand Down
15 changes: 12 additions & 3 deletions rule/matcher_test.go
Expand Up @@ -33,7 +33,7 @@ import (
var testRules = []Rule{
{
ID: "foo1",
Match: RuleMatch{URL: "https://localhost:1234/<foo|bar>", Methods: []string{"POST"}},
Match: &RuleMatch{URL: "https://localhost:1234/<foo|bar>", Methods: []string{"POST"}},
Description: "Create users rule",
Authorizer: RuleHandler{Handler: "allow", Config: []byte(`{"type":"any"}`)},
Authenticators: []RuleHandler{{Handler: "anonymous", Config: []byte(`{"name":"anonymous1"}`)}},
Expand All @@ -42,7 +42,7 @@ var testRules = []Rule{
},
{
ID: "foo2",
Match: RuleMatch{URL: "https://localhost:34/<baz|bar>", Methods: []string{"GET"}},
Match: &RuleMatch{URL: "https://localhost:34/<baz|bar>", Methods: []string{"GET"}},
Description: "Get users rule",
Authorizer: RuleHandler{Handler: "deny", Config: []byte(`{"type":"any"}`)},
Authenticators: []RuleHandler{{Handler: "oauth2_introspection", Config: []byte(`{"name":"anonymous1"}`)}},
Expand All @@ -51,7 +51,7 @@ var testRules = []Rule{
},
{
ID: "foo3",
Match: RuleMatch{URL: "https://localhost:343/<baz|bar>", Methods: []string{"GET"}},
Match: &RuleMatch{URL: "https://localhost:343/<baz|bar>", Methods: []string{"GET"}},
Description: "Get users rule",
Authorizer: RuleHandler{Handler: "deny"},
Authenticators: []RuleHandler{{Handler: "oauth2_introspection"}},
Expand Down Expand Up @@ -100,6 +100,15 @@ func TestMatcher(t *testing.T) {
testMatcher(t, matcher, "DELETE", "https://localhost:1234/foo", true, nil)
})

t.Run("case=cache", func(t *testing.T) {
r, err := matcher.Match(context.Background(), "GET", mustParseURL(t, "https://localhost:34/baz"))
require.NoError(t, err)
got, err := matcher.Get(context.Background(), r.ID)
require.NoError(t, err)
assert.NotEmpty(t, got.Match.compiledURL)
assert.NotEmpty(t, got.Match.compiledURLChecksum)
})

require.NoError(t, matcher.Set(context.Background(), testRules[1:]))

t.Run("case=updated", func(t *testing.T) {
Expand Down
12 changes: 7 additions & 5 deletions rule/repository_memory.go
Expand Up @@ -105,14 +105,16 @@ func (m *RepositoryMemory) Set(ctx context.Context, rules []Rule) error {
}

func (m *RepositoryMemory) Match(ctx context.Context, method string, u *url.URL) (*Rule, error) {
m.RLock()
defer m.RUnlock()
m.Lock()
defer m.Unlock()

var rules []Rule
for _, rule := range m.rules {
if err := rule.IsMatching(method, u); err == nil {
rules = append(rules, rule)
for k := range m.rules {
r := &m.rules[k]
if err := r.IsMatching(method, u); err == nil {
rules = append(rules, *r)
}
m.rules[k] = *r
}

if len(rules) == 0 {
Expand Down
10 changes: 5 additions & 5 deletions rule/rule.go
Expand Up @@ -78,7 +78,7 @@ type Rule struct {
Description string `json:"description"`

// Match defines the URL that this rule should match.
Match RuleMatch `json:"match"`
Match *RuleMatch `json:"match"`

// Authenticators is a list of authentication handlers that will try and authenticate the provided credentials.
// Authenticators are checked iteratively from index 0 to n and if the first authenticator to return a positive
Expand Down Expand Up @@ -118,7 +118,7 @@ var _ json.Unmarshaler = new(Rule)

func NewRule() *Rule {
return &Rule{
Match: RuleMatch{},
Match: &RuleMatch{},
Authenticators: []RuleHandler{},
Mutators: []RuleHandler{},
}
Expand All @@ -129,7 +129,7 @@ func (r *Rule) UnmarshalJSON(raw []byte) error {
ID string `json:"id"`
Version string `json:"version"`
Description string `json:"description"`
Match RuleMatch `json:"match"`
Match *RuleMatch `json:"match"`
Authenticators []RuleHandler `json:"authenticators"`
Authorizer RuleHandler `json:"authorizer"`
Mutators []RuleHandler `json:"mutators"`
Expand Down Expand Up @@ -176,11 +176,11 @@ func (r *Rule) CompileURL() (*regexp.Regexp, error) {
m := r.Match
c := crc32.ChecksumIEEE([]byte(m.URL))
if m.compiledURL == nil || c != m.compiledURLChecksum {
r, err := compiler.CompileRegex(m.URL, '<', '>')
regex, err := compiler.CompileRegex(m.URL, '<', '>')
if err != nil {
return nil, errors.Wrap(err, "Unable to compile URL matcher")
}
m.compiledURL = r
m.compiledURL = regex
m.compiledURLChecksum = c
}

Expand Down

0 comments on commit 1c3070f

Please sign in to comment.