@@ -20,7 +20,6 @@ package config
2020
2121import (
2222 "bytes"
23- "encoding/base64"
2423 "fmt"
2524 "github.com/Masterminds/sprig/v3"
2625 "github.com/rabbitstack/fibratus/pkg/kevent"
@@ -34,6 +33,7 @@ import (
3433 u "net/url"
3534 "os"
3635 "path/filepath"
36+ "strconv"
3737 "strings"
3838 "text/template"
3939)
@@ -43,30 +43,13 @@ type FilterConfig struct {
4343 Name string `json:"name" yaml:"name"`
4444 Description string `json:"description" yaml:"description"`
4545 Condition string `json:"condition" yaml:"condition"`
46- Action string `json:"action" yaml:"action"`
46+ Action []FilterAction `json:"action" yaml:"action"`
47+ Output string `json:"output" yaml:"output"`
48+ Severity string `json:"severity" yaml:"severity"`
4749 Labels map [string ]string `json:"labels" yaml:"labels"`
4850 MinEngineVersion string `json:"min-engine-version" yaml:"min-engine-version"`
4951}
5052
51- // parseTmpl ensures the correctness of the rule
52- // action template by trying to parse the template
53- // string from the base64 payload.
54- func (f FilterConfig ) parseTmpl (resource string ) error {
55- if f .Action == "" {
56- return nil
57- }
58- decoded , err := base64 .StdEncoding .DecodeString (f .Action )
59- if err != nil {
60- return err
61- }
62- tmpl , err := template .New (f .Name ).Funcs (FilterFuncMap ()).Parse (string (decoded ))
63- if err != nil {
64- return cleanupParseError (resource , err )
65- }
66- var bb bytes.Buffer
67- return cleanupParseError (resource , tmpl .Execute (& bb , tmplData ()))
68- }
69-
7053// FilterGroup represents the container for filters.
7154type FilterGroup struct {
7255 Name string `json:"group" yaml:"group"`
@@ -77,18 +60,61 @@ type FilterGroup struct {
7760 Labels map [string ]string `json:"labels" yaml:"labels"`
7861}
7962
80- // IsDisabled determines if this group is disabled.
81- func (g FilterGroup ) IsDisabled () bool { return g .Enabled != nil && ! * g .Enabled }
63+ // FilterAction wraps all possible filter actions.
64+ type FilterAction any
65+
66+ // KillAction defines an action for killing the process
67+ // indicates by the filter field expression.
68+ type KillAction struct {
69+ // Pid indicates the field for which
70+ // the process id is resolved
71+ Pid string `json:"pid" yaml:"pid"`
72+ }
73+
74+ func (a KillAction ) PidToInt (pid string ) uint32 {
75+ n , err := strconv .Atoi (pid )
76+ if err != nil {
77+ return 0
78+ }
79+ return uint32 (n )
80+ }
81+
82+ const (
83+ killActionID = "kill"
84+ )
85+
86+ // DecodeActions converts raw YAML map to
87+ // typed action structures.
88+ func (f FilterConfig ) DecodeActions () ([]any , error ) {
89+ actions := make ([]any , 0 , len (f .Action ))
90+
91+ dec := func (m map [string ]any , o any ) error {
92+ err := decode (m , & o )
93+ if err != nil {
94+ return err
95+ }
96+ actions = append (actions , o )
97+ return nil
98+ }
8299
83- func (g FilterGroup ) validate (resource string ) error {
84- for _ , filter := range g .Rules {
85- if err := filter .parseTmpl (resource ); err != nil {
86- return fmt .Errorf ("invalid %q rule action: %v" , filter .Name , err )
100+ for _ , act := range f .Action {
101+ m , ok := act .(map [string ]any )
102+ if ! ok {
103+ continue
104+ }
105+ if _ , ok := m [killActionID ]; ok {
106+ var kill KillAction
107+ if err := dec (m , kill ); err != nil {
108+ return nil , err
109+ }
87110 }
88111 }
89- return nil
112+ return actions , nil
90113}
91114
115+ // IsDisabled determines if this group is disabled.
116+ func (g FilterGroup ) IsDisabled () bool { return g .Enabled != nil && ! * g .Enabled }
117+
92118// Hash calculates the filter group hash.
93119func (g FilterGroup ) Hash () uint32 {
94120 return hashers .FnvUint32 ([]byte (g .Name ))
@@ -132,6 +158,21 @@ type Macro struct {
132158 List []string `json:"list" yaml:"list"`
133159}
134160
161+ // ActionContext is the convenient structure
162+ // for grouping the event that resulted in
163+ // matched filter along with filter group
164+ // information.
165+ type ActionContext struct {
166+ // Events contains a single element for non-sequence
167+ // group policies or a list of ordered matched events
168+ // for sequence group policies
169+ Events []* kevent.Kevent
170+ // Filter represents the filter that matched the event
171+ Filter * FilterConfig
172+ // Group represents the group where the filter is declared
173+ Group FilterGroup
174+ }
175+
135176const (
136177 rulesFromPaths = "filters.rules.from-paths"
137178 rulesFromURLs = "filters.rules.from-urls"
@@ -307,40 +348,24 @@ func decodeFilterGroups(resource string, b []byte) ([]FilterGroup, error) {
307348 "%v in %s: %v" , rawGroup , resource , multierror .Wrap (errs ... ))
308349 }
309350 }
310- // convert filter action template to
311- // base64 before executing the global
312- // template. The rendered template yields
313- // a yaml payload with template directives
314- // expanded
315- b , err = encodeFilterActions (b )
316- if err != nil {
317- return nil , err
318- }
351+ // render template
319352 b , err = renderTmpl (resource , b )
320353 if err != nil {
321354 return nil , err
322355 }
323-
324356 // now unmarshal into typed group slice
325357 var groups []FilterGroup
326358 if err := yaml .Unmarshal (b , & groups ); err != nil {
327359 return nil , err
328360 }
329- // try to validate filter action template
330- for _ , group := range groups {
331- err := group .validate (resource )
332- if err != nil {
333- return nil , err
334- }
335- }
336361 return groups , nil
337362}
338363
339364// renderTmpl executes templating directives in the
340365// file group yaml file. It returns the byte slice
341366// with yaml content after template expansion.
342367func renderTmpl (filename string , b []byte ) ([]byte , error ) {
343- tmpl , err := template .New (filename ).Funcs (FilterFuncMap ()).Parse (string (b ))
368+ tmpl , err := template .New (filename ).Funcs (sprig . FuncMap ()).Parse (string (b ))
344369 if err != nil {
345370 return nil , cleanupParseError (filename , err )
346371 }
@@ -379,111 +404,3 @@ func cleanupParseError(filename string, err error) error {
379404 }
380405 return fmt .Errorf ("syntax error in (%s) at %s: %s" , location , key , errMsg )
381406}
382-
383- // ActionContext is the convenient structure
384- // for grouping the event that resulted in
385- // matched filter along with filter group
386- // information.
387- type ActionContext struct {
388- Kevt * kevent.Kevent
389- // Kevts contains matched events for sequence group
390- // policies indexed by `k` + the slot number of the
391- // rule that produced a partial match
392- Kevts map [string ]* kevent.Kevent
393- // Events contains a single element for non-sequence
394- // group policies or a list of ordered matched events
395- // for sequence group policies
396- Events []* kevent.Kevent
397- Filter * FilterConfig
398- Group FilterGroup
399- }
400-
401- // FilterFuncMap returns the template func map
402- // populated with some useful template functions
403- // that can be used in rule actions.
404- func FilterFuncMap () template.FuncMap {
405- f := sprig .TxtFuncMap ()
406-
407- extra := template.FuncMap {
408- // This is a placeholder for the functions that might be
409- // late-bound to a template. By declaring them here, we
410- // can still execute the template associated with the
411- // filter action to ensure template syntax is correct
412- "emit" : func (ctx * ActionContext , title string , text string , args ... string ) string { return "" },
413- "kill" : func (pid uint32 ) string { return "" },
414- }
415-
416- for k , v := range extra {
417- f [k ] = v
418- }
419-
420- return f
421- }
422-
423- func tmplData () * ActionContext {
424- return & ActionContext {
425- Filter : & FilterConfig {},
426- Group : FilterGroup {},
427- Kevt : kevent .Empty (),
428- Events : make ([]* kevent.Kevent , 0 ),
429- Kevts : make (map [string ]* kevent.Kevent ),
430- }
431- }
432-
433- const (
434- actionNode = "action"
435- defNode = "def"
436- fromStringsNode = "from-strings"
437- rulesNode = "rules"
438- )
439-
440- // encodeFilterActions convert the filter action template
441- // to base64 payload. Because we only want to execute
442- // the action template when a filter matches in runtime,
443- // encoding the template to base64 prevents the Go templating
444- // engine from expanding the template in parse time, when we
445- // first load all the filter groups.
446- func encodeFilterActions (buf []byte ) ([]byte , error ) {
447- var yn yaml.Node
448- if err := yaml .Unmarshal (buf , & yn ); err != nil {
449- return nil , err
450- }
451-
452- // for each group
453- for _ , n := range yn .Content [0 ].Content {
454- // for each group node
455- for i , gn := range n .Content {
456- // sequence groups action
457- if gn .Value == actionNode && n .Content [i + 1 ].Value != "" {
458- n .Content [i + 1 ].Value =
459- base64 .StdEncoding .EncodeToString ([]byte (n .Content [i + 1 ].Value ))
460- }
461- if gn .Value == fromStringsNode {
462- log .Warnf ("`from-strings` attribute is deprecated and will be " +
463- "removed in future versions. Please consider switching to `rules` attribute" )
464- }
465- if gn .Value == fromStringsNode || gn .Value == rulesNode {
466- content := n .Content [i + 1 ]
467- // for each node in from-strings
468- for _ , s := range content .Content {
469- for j , e := range s .Content {
470- if e .Value == defNode {
471- log .Warnf ("`def` attribute is deprecated and will be " +
472- "removed in future versions. Please consider switching to `condition` attribute" )
473- }
474- if e .Value == actionNode && s .Content [j + 1 ].Value != "" {
475- s .Content [j + 1 ].Value =
476- base64 .StdEncoding .EncodeToString ([]byte (s .Content [j + 1 ].Value ))
477- }
478- }
479- }
480- }
481- }
482- }
483-
484- b , err := yaml .Marshal (& yn )
485- if err != nil {
486- return nil , err
487- }
488- return b , nil
489- }
0 commit comments