-
-
Notifications
You must be signed in to change notification settings - Fork 337
[persistent collections] based on PR-866 #1261
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
base: main
Are you sure you want to change the base?
Changes from 11 commits
cb307db
27fd972
2e0507b
12af002
08a1fc5
846cdc3
22c596d
4eb78c3
d4e3d46
3fa1eaf
73d7efe
34be2c4
82b276b
aab978c
7a5729e
240674a
f0c6472
a68bc7a
aa67d52
992f416
09d9c66
4f55210
09261ba
c7b3a59
cf8554a
f5ed9bc
baf203e
a3866e4
16e2144
40fffb0
c400f8a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| // Copyright 2023 Juan Pablo Tosso and the OWASP Coraza contributors | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| package plugins | ||
|
|
||
| import ( | ||
| "github.com/corazawaf/coraza/v3/experimental/plugins/plugintypes" | ||
| "github.com/corazawaf/coraza/v3/internal/persistence" | ||
| ) | ||
|
|
||
| // RegisterPersistenceEngine registers a new persistence engine | ||
| func RegisterPersistenceEngine(name string, engine plugintypes.PersistenceEngine) { | ||
| persistence.Register(name, engine) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| // Copyright 2023 Juan Pablo Tosso and the OWASP Coraza contributors | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| package plugintypes | ||
|
|
||
| type PersistenceEngine interface { | ||
| Open(uri string, ttl int) error | ||
| Close() error | ||
| Sum(collectionName string, collectionKey string, key string, sum int) error | ||
| Get(collectionName string, collectionKey string, key string) (string, error) | ||
|
|
||
| All(collectionName string, collectionKey string) (map[string]string, error) | ||
| Set(collection string, collectionKey string, key string, value string) error | ||
| Remove(collection string, collectionKey string, key string) error | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,9 +4,14 @@ | |
| package actions | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "strings" | ||
|
|
||
| "github.com/corazawaf/coraza/v3/experimental/plugins/macro" | ||
| "github.com/corazawaf/coraza/v3/experimental/plugins/plugintypes" | ||
| "github.com/corazawaf/coraza/v3/internal/collections" | ||
| utils "github.com/corazawaf/coraza/v3/internal/strings" | ||
| "github.com/corazawaf/coraza/v3/types/variables" | ||
| ) | ||
|
|
||
| // Action Group: Non-disruptive | ||
|
|
@@ -23,9 +28,8 @@ import ( | |
| // SecAction "phase:1,id:116,nolog,pass,initcol:ip=%{REMOTE_ADDR}" | ||
| // ``` | ||
| type initcolFn struct { | ||
| collection string | ||
| variable byte | ||
| key string | ||
| collection variables.RuleVariable | ||
| key macro.Macro | ||
| } | ||
|
|
||
| func (a *initcolFn) Init(_ plugintypes.RuleMetadata, data string) error { | ||
|
|
@@ -34,34 +38,34 @@ func (a *initcolFn) Init(_ plugintypes.RuleMetadata, data string) error { | |
| return ErrInvalidKVArguments | ||
| } | ||
|
|
||
| a.collection = col | ||
| a.key = key | ||
| a.variable = 0x0 | ||
| c, err := variables.Parse(col) | ||
| if err != nil { | ||
| return fmt.Errorf("initcol: collection %s is not valid", col) | ||
| } | ||
| // we validate if this is a persistent collection | ||
| persistent := []string{"USER", "SESSION", "IP", "RESOURCE", "GLOBAL"} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we really need to restrict arbitrary collection creation? |
||
| if !utils.InSlice(c.Name(), persistent) { | ||
| return fmt.Errorf("initcol: collection %s is not persistent", c.Name()) | ||
| } | ||
| a.collection = c | ||
| mkey, err := macro.NewMacro(key) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| a.key = mkey | ||
| return nil | ||
| } | ||
|
|
||
| func (a *initcolFn) Evaluate(_ plugintypes.RuleMetadata, _ plugintypes.TransactionState) { | ||
| // tx.DebugLogger().Error().Msg("initcol was used but it's not supported", zap.Int("rule", r.Id)) | ||
| /* | ||
| key := tx.MacroExpansion(a.key) | ||
| data := tx.WAF.Persistence.Get(a.variable, key) | ||
| if data == nil { | ||
| ts := time.Now().UnixNano() | ||
| tss := strconv.FormatInt(ts, 10) | ||
| tsstimeout := strconv.FormatInt(ts+(int64(tx.WAF.CollectionTimeout)*1000), 10) | ||
| data = map[string][]string{ | ||
| "CREATE_TIME": {tss}, | ||
| "IS_NEW": {"1"}, | ||
| "KEY": {key}, | ||
| "LAST_UPDATE_TIME": {tss}, | ||
| "TIMEOUT": {tsstimeout}, | ||
| "UPDATE_COUNTER": {"0"}, | ||
| "UPDATE_RATE": {"0"}, | ||
| } | ||
| } | ||
| tx.GetCollection(a.variable).SetData(data) | ||
| tx.PersistentCollections[a.variable] = key | ||
| */ | ||
| func (a *initcolFn) Evaluate(_ plugintypes.RuleMetadata, txs plugintypes.TransactionState) { | ||
| col := txs.Collection(a.collection) | ||
| key := a.key.Expand(txs) | ||
| txs.DebugLogger().Debug().Str("collection", a.collection.Name()).Str("key", key).Msg("initcol: initializing collection") | ||
| c, ok := col.(*collections.Persistent) | ||
| if !ok { | ||
| txs.DebugLogger().Error().Str("collection", a.collection.Name()).Msg("initcol: collection is not a persistent collection") | ||
| return | ||
| } | ||
| c.Init(key) | ||
| } | ||
|
|
||
| func (a *initcolFn) Type() plugintypes.ActionType { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,24 +1,33 @@ | ||
| // Copyright 2023 Juan Pablo Tosso and the OWASP Coraza contributors | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| package actions | ||
| package actions_test | ||
|
|
||
| import "testing" | ||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/corazawaf/coraza/v3" | ||
| "github.com/corazawaf/coraza/v3/experimental/plugins/plugintypes" | ||
| "github.com/corazawaf/coraza/v3/internal/actions" | ||
| ) | ||
|
|
||
| func TestInitcolInit(t *testing.T) { | ||
| waf, _ := coraza.NewWAF(coraza.NewWAFConfig()) //nolint:errcheck | ||
| initcol, err := actions.Get("initcol") | ||
| if err != nil { | ||
| t.Error(err) | ||
| } | ||
| t.Run("invalid argument", func(t *testing.T) { | ||
| initcol := initcol() | ||
| err := initcol.Init(nil, "foo") | ||
| if err == nil { | ||
| t.Errorf("expected error") | ||
| if err := initcol.Init(&md{}, "test"); err == nil { | ||
| t.Error("expected error") | ||
| } | ||
| }) | ||
|
|
||
| t.Run("passing argument", func(t *testing.T) { | ||
| initcol := initcol() | ||
| err := initcol.Init(nil, "foo=bar") | ||
| if err != nil { | ||
| t.Errorf("unexpected error: %s", err.Error()) | ||
| t.Run("editable variable", func(t *testing.T) { | ||
| if err := initcol.Init(&md{}, "session=abcdef"); err != nil { | ||
| t.Error(err) | ||
| } | ||
| txs := waf.NewTransaction().(plugintypes.TransactionState) | ||
| initcol.Evaluate(&md{}, txs) | ||
| }) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,8 @@ import ( | |
| "strconv" | ||
| "strings" | ||
|
|
||
| utils "github.com/corazawaf/coraza/v3/internal/strings" | ||
|
|
||
| "github.com/corazawaf/coraza/v3/collection" | ||
| "github.com/corazawaf/coraza/v3/experimental/plugins/macro" | ||
| "github.com/corazawaf/coraza/v3/experimental/plugins/plugintypes" | ||
|
|
@@ -76,8 +78,10 @@ func (a *setvarFn) Init(_ plugintypes.RuleMetadata, data string) error { | |
| colKey, colVal, colOk := strings.Cut(key, ".") | ||
| // Right not it only makes sense to allow setting TX | ||
| // key is also required | ||
| if strings.ToUpper(colKey) != "TX" { | ||
| return errors.New("invalid arguments, expected collection TX") | ||
| available := []string{"TX", "USER", "GLOBAL", "RESOURCE", "SESSION", "IP"} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And here?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jptosso Clould you help me here? It's your changes, I believe you have more context here. I personally see the reason of making constraints, just to minimize unpredictable behavior.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hey! setvar can only be used for this set of variables. Others are not mutuable, but IMOwe can use type assertion for this |
||
| // we validate uppercase colKey is one of available | ||
| if !utils.InSlice(strings.ToUpper(colKey), available) { | ||
| return errors.New("setvar: invalid editable collection, available collections are: " + strings.Join(available, ", ")) | ||
| } | ||
| if strings.TrimSpace(colVal) == "" { | ||
| return errors.New("invalid arguments, expected syntax TX.{key}={value}") | ||
|
|
@@ -111,7 +115,7 @@ func (a *setvarFn) Evaluate(r plugintypes.RuleMetadata, tx plugintypes.Transacti | |
| Str("var_key", key). | ||
| Str("var_value", value). | ||
| Int("rule_id", r.ID()). | ||
| Msg("Action evaluated") | ||
| Msg("Action SetVar evaluated") | ||
| a.evaluateTxCollection(r, tx, strings.ToLower(key), value) | ||
| } | ||
|
|
||
|
|
@@ -120,17 +124,14 @@ func (a *setvarFn) Type() plugintypes.ActionType { | |
| } | ||
|
|
||
| func (a *setvarFn) evaluateTxCollection(r plugintypes.RuleMetadata, tx plugintypes.TransactionState, key string, value string) { | ||
| var col collection.Map | ||
| if c, ok := tx.Collection(a.collection).(collection.Map); !ok { | ||
| tx.DebugLogger().Error().Msg("collection in setvar is not a map") | ||
| // TODO for api breaking issues, we have to split this function in Map and Persistent | ||
| var col collection.Editable | ||
| if c, ok := tx.Collection(a.collection).(collection.Editable); !ok { | ||
| tx.DebugLogger().Error().Msg("collection in setvar is not editable") | ||
| return | ||
| } else { | ||
| col = c | ||
| } | ||
| if col == nil { | ||
| tx.DebugLogger().Error().Msg("collection in setvar is nil") | ||
| return | ||
| } | ||
|
|
||
| if a.isRemove { | ||
| col.Remove(key) | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,50 @@ | ||||||
| // Copyright 2023 Juan Pablo Tosso and the OWASP Coraza contributors | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Maybe even 2025.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure. These changes made in 2023 by Juan. |
||||||
| // SPDX-License-Identifier: Apache-2.0 | ||||||
|
|
||||||
| //go:build !tinygo | ||||||
| // +build !tinygo | ||||||
|
|
||||||
| package actions_test | ||||||
|
|
||||||
| import ( | ||||||
| "testing" | ||||||
|
|
||||||
| "github.com/corazawaf/coraza/v3" | ||||||
| "github.com/corazawaf/coraza/v3/experimental/plugins/plugintypes" | ||||||
| "github.com/corazawaf/coraza/v3/internal/actions" | ||||||
| "github.com/corazawaf/coraza/v3/types/variables" | ||||||
| ) | ||||||
|
|
||||||
| type md struct { | ||||||
| } | ||||||
|
|
||||||
| func (md) ID() int { | ||||||
| return 0 | ||||||
| } | ||||||
| func (md) ParentID() int { | ||||||
| return 0 | ||||||
| } | ||||||
| func (md) Status() int { | ||||||
| return 0 | ||||||
| } | ||||||
|
|
||||||
| func TestPersistenceSetvar(t *testing.T) { | ||||||
| a, err := actions.Get("setvar") | ||||||
| if err != nil { | ||||||
| t.Error("failed to get setvar action") | ||||||
| } | ||||||
| waf, err := coraza.NewWAF(coraza.NewWAFConfig().WithDirectives("SecPersistenceEngine default")) | ||||||
| if err != nil { | ||||||
| t.Fatal(err) | ||||||
| } | ||||||
| t.Run("SESSION should be set", func(t *testing.T) { | ||||||
| if err := a.Init(&md{}, "SESSION.test=test"); err != nil { | ||||||
| t.Errorf("unexpected error: %v", err) | ||||||
| } | ||||||
| tx := waf.NewTransaction() | ||||||
| txs := tx.(plugintypes.TransactionState) | ||||||
| a.Evaluate(&md{}, txs) | ||||||
| col := txs.Collection(variables.Session) | ||||||
| col.FindAll() | ||||||
| }) | ||||||
| } | ||||||
Uh oh!
There was an error while loading. Please reload this page.