forked from thanos-io/thanos
-
Notifications
You must be signed in to change notification settings - Fork 14
[ENGMP-365] Add ProtectionEngine and sample Noop protection implementation #316
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
Open
yishi3-db
wants to merge
12
commits into
db_main
Choose a base branch
from
yi-shi_data/yishi3-db/query-protection-engine
base: db_main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
ec24cf9
add protection log in logger
yishi3-db 20cd0b1
basic datastructures
yishi3-db 89fa137
change ProtectionResult Action from string to RuleAction
yishi3-db 89a5c0a
simplified ProtectionAction string
yishi3-db f91a3ff
add protection engine and sample protection impls
yishi3-db e6ddf82
add protection engine and impls unit tests
yishi3-db 3f389ac
lint: remove current unused fields
yishi3-db bfd7d5e
use sync.Mutex instead of sync.RWMutex
yishi3-db 73d76f5
rename NoopProtection to AlwaysMatchProtection for clarity
yishi3-db 8ae6173
Merge branch 'db_main' into yi-shi_data/yishi3-db/query-protection-en…
yishi3-db 9ff1c51
rename noop protection to always-match
yishi3-db dd71bad
deduplicate alwaysMatchProtection in engine test
yishi3-db File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| // Copyright (c) The Thanos Authors. | ||
| // Licensed under the Apache License 2.0. | ||
|
|
||
| package queryfrontend | ||
|
|
||
| import ( | ||
| "context" | ||
| "sync" | ||
| ) | ||
|
|
||
| // ProtectionEngine evaluates a list of rules against a query request. | ||
| type ProtectionEngine struct { | ||
| mu sync.Mutex | ||
| rules []*Rule | ||
| } | ||
|
|
||
| // NewProtectionEngine creates a new ProtectionEngine with the given rules. | ||
| func NewProtectionEngine(rules []*Rule) *ProtectionEngine { | ||
| return &ProtectionEngine{rules: rules} | ||
| } | ||
|
|
||
| // UpdateRules replaces the current rule set. Safe for concurrent use. | ||
| func (e *ProtectionEngine) UpdateRules(rules []*Rule) { | ||
| e.mu.Lock() | ||
| defer e.mu.Unlock() | ||
| e.rules = rules | ||
| } | ||
|
|
||
| // Evaluate runs all applicable rules against the request. | ||
| // Rules are evaluated in order; the first matching rule wins. | ||
| // Returns the updated context (with ProtectionResult if a rule triggered), | ||
| // the action to take, and any error. | ||
| func (e *ProtectionEngine) Evaluate(ctx context.Context, req thanosQueryReq) (*ProtectionResult, error) { | ||
| e.mu.Lock() | ||
| rules := e.rules | ||
| e.mu.Unlock() | ||
|
|
||
| for _, rule := range rules { | ||
| // Skip disabled rules. | ||
| if !rule.enabled { | ||
| continue | ||
| } | ||
|
|
||
| // Check actor filter. | ||
| if rule.actorRegex != nil && !rule.actorRegex.MatchString(req.actor) { | ||
| continue | ||
| } | ||
|
|
||
| matched, err := rule.protection.Run(ctx, req) | ||
| if !matched { | ||
| continue | ||
| } | ||
|
|
||
| return &ProtectionResult{ | ||
| Triggered: true, | ||
| RuleName: rule.name, | ||
| Action: rule.action, | ||
| }, err | ||
| } | ||
| return nil, nil | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| // Copyright (c) The Thanos Authors. | ||
| // Licensed under the Apache License 2.0. | ||
|
|
||
| // Tests for ProtectionEngine.Evaluate: verifies that rules are correctly | ||
| // evaluated, filtered, and prioritized against query requests. | ||
| package queryfrontend | ||
|
|
||
| import ( | ||
| "context" | ||
| "regexp" | ||
| "testing" | ||
|
|
||
| "github.com/pkg/errors" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| // neverMatchProtection never matches (returns false). | ||
| type neverMatchProtection struct{} | ||
|
|
||
| func (p *neverMatchProtection) Name() string { return "never" } | ||
| func (p *neverMatchProtection) Run(_ context.Context, _ thanosQueryReq) (bool, error) { | ||
| return false, nil | ||
| } | ||
|
|
||
| // errorProtection always matches and returns an error. | ||
| type errorProtection struct{} | ||
|
|
||
| func (p *errorProtection) Name() string { return "error" } | ||
| func (p *errorProtection) Run(_ context.Context, _ thanosQueryReq) (bool, error) { | ||
| return true, errors.New("protection error") | ||
| } | ||
|
|
||
| func TestProtectionEngine_NoRules(t *testing.T) { | ||
| engine := NewProtectionEngine(nil) | ||
| protectionResult, err := engine.Evaluate(context.Background(), thanosQueryReq{}) | ||
| require.NoError(t, err) | ||
| require.Nil(t, protectionResult) | ||
| } | ||
|
|
||
| func TestProtectionEngine_DisabledRuleSkipped(t *testing.T) { | ||
| engine := NewProtectionEngine([]*Rule{ | ||
| NewRule("disabled", &AlwaysMatchProtection{}, RuleActionBlock, nil, false), | ||
| }) | ||
| protectionResult, err := engine.Evaluate(context.Background(), thanosQueryReq{}) | ||
| require.NoError(t, err) | ||
| require.Nil(t, protectionResult) | ||
| } | ||
|
|
||
| func TestProtectionEngine_ActorRegexNoMatch(t *testing.T) { | ||
| engine := NewProtectionEngine([]*Rule{ | ||
| NewRule("filtered", &AlwaysMatchProtection{}, RuleActionBlock, regexp.MustCompile("^admin$"), true), | ||
| }) | ||
| protectionResult, err := engine.Evaluate(context.Background(), thanosQueryReq{actor: "user"}) | ||
| require.NoError(t, err) | ||
| require.Nil(t, protectionResult) | ||
| } | ||
|
|
||
| func TestProtectionEngine_ActorRegexMatch(t *testing.T) { | ||
| engine := NewProtectionEngine([]*Rule{ | ||
| NewRule("filtered", &AlwaysMatchProtection{}, RuleActionBlock, regexp.MustCompile("^admin$"), true), | ||
| }) | ||
| protectionResult, err := engine.Evaluate(context.Background(), thanosQueryReq{actor: "admin"}) | ||
| require.NoError(t, err) | ||
| require.Equal(t, RuleActionBlock, protectionResult.Action) | ||
| require.True(t, protectionResult.Triggered) | ||
| require.Equal(t, "filtered", protectionResult.RuleName) | ||
| } | ||
|
|
||
| func TestProtectionEngine_FirstMatchingRuleWins(t *testing.T) { | ||
| engine := NewProtectionEngine([]*Rule{ | ||
| NewRule("first", &neverMatchProtection{}, RuleActionBlock, nil, true), | ||
| NewRule("second", &AlwaysMatchProtection{}, RuleActionLog, nil, true), | ||
| NewRule("third", &AlwaysMatchProtection{}, RuleActionBlock, nil, true), | ||
| }) | ||
| protectionResult, err := engine.Evaluate(context.Background(), thanosQueryReq{}) | ||
| require.NoError(t, err) | ||
| require.Equal(t, RuleActionLog, protectionResult.Action) | ||
| require.Equal(t, true, protectionResult.Triggered) | ||
| require.Equal(t, "second", protectionResult.RuleName) | ||
| } | ||
|
|
||
| func TestProtectionEngine_RunError(t *testing.T) { | ||
| engine := NewProtectionEngine([]*Rule{ | ||
| NewRule("error-rule", &errorProtection{}, RuleActionLog, nil, true), | ||
| }) | ||
| _, err := engine.Evaluate(context.Background(), thanosQueryReq{}) | ||
| require.Error(t, err) | ||
| require.Contains(t, err.Error(), "protection error") | ||
| } | ||
|
|
||
| func TestProtectionEngine_UpdateRules(t *testing.T) { | ||
| engine := NewProtectionEngine([]*Rule{ | ||
| NewRule("block-all", &AlwaysMatchProtection{}, RuleActionBlock, nil, true), | ||
| }) | ||
|
|
||
| protectionResult, err := engine.Evaluate(context.Background(), thanosQueryReq{}) | ||
| require.NoError(t, err) | ||
| require.Equal(t, RuleActionBlock, protectionResult.Action) | ||
| require.Equal(t, true, protectionResult.Triggered) | ||
|
|
||
| engine.UpdateRules(nil) | ||
|
|
||
| protectionResult, err = engine.Evaluate(context.Background(), thanosQueryReq{}) | ||
| require.NoError(t, err) | ||
| require.Nil(t, protectionResult) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| // Copyright (c) The Thanos Authors. | ||
| // Licensed under the Apache License 2.0. | ||
|
|
||
| package queryfrontend | ||
|
|
||
| import ( | ||
| "context" | ||
|
|
||
| "github.com/pkg/errors" | ||
| ) | ||
|
|
||
| // This file contains all protection rule implementations. | ||
| // Each protection implements the Protection interface defined in protection.go. | ||
|
|
||
| // AlwaysMatchProtection is a protection that always matches every query. | ||
| type AlwaysMatchProtection struct{} | ||
|
|
||
| func (n *AlwaysMatchProtection) Name() string { return "always-match" } | ||
|
|
||
| func (n *AlwaysMatchProtection) Run(_ context.Context, _ thanosQueryReq) (bool, error) { | ||
| return true, nil | ||
| } | ||
|
|
||
| // protectionRegistry maps protection names (as used in config) to their factory functions. | ||
| var protectionRegistry = map[string]ProtectionFactory{ | ||
| "always-match": func(_ map[string]string) (Protection, error) { | ||
| return &AlwaysMatchProtection{}, nil | ||
| }, | ||
| } | ||
|
|
||
| // LookupProtection returns the factory for the given protection name. | ||
| func LookupProtection(name string) (ProtectionFactory, error) { | ||
| factory, ok := protectionRegistry[name] | ||
| if !ok { | ||
| return nil, errors.Errorf("unknown protection %q", name) | ||
| } | ||
| return factory, nil | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| // Copyright (c) The Thanos Authors. | ||
| // Licensed under the Apache License 2.0. | ||
|
|
||
| // Tests for protection implementations in protection_impls.go. | ||
| package queryfrontend | ||
|
|
||
| import ( | ||
| "context" | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| func TestAlwaysMatchProtection_AlwaysMatches(t *testing.T) { | ||
| p := &AlwaysMatchProtection{} | ||
| matched, err := p.Run(context.Background(), thanosQueryReq{}) | ||
| require.NoError(t, err) | ||
| require.True(t, matched) | ||
| } | ||
|
|
||
| func TestLookupProtection_AlwaysMatch(t *testing.T) { | ||
| factory, err := LookupProtection("always-match") | ||
| require.NoError(t, err) | ||
| require.NotNil(t, factory) | ||
|
|
||
| p, err := factory(nil) | ||
| require.NoError(t, err) | ||
| require.Equal(t, "always-match", p.Name()) | ||
| } | ||
|
|
||
| func TestLookupProtection_Unknown(t *testing.T) { | ||
| _, err := LookupProtection("unknown") | ||
| require.Error(t, err) | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.