From 186d34047a5189e379167a8028108c01167a45ea Mon Sep 17 00:00:00 2001 From: Daniel Pacak Date: Sat, 18 Sep 2021 12:42:42 +0200 Subject: [PATCH 1/3] tracee-rules: Allow compiling and evaluating all Rego signatures at once This patch adds new implementation of types.Signature that compiles all Rego signatures into one Rego policy, which is then evaluated with input events. This implementation is more efficient in terms of number of memory allocations and number of objects created to evaluate a Rego policy. The new implementation is activated by specifying the --rego-aio flag. Signed-off-by: Daniel Pacak --- .gitignore | 4 +- Vagrantfile | 22 ++ tracee-rules/main.go | 8 +- tracee-rules/signature.go | 23 +- tracee-rules/signature_test.go | 4 +- tracee-rules/signatures/rego/regosig/aio.go | 253 +++++++++++++++ tracee-rules/signatures/rego/regosig/aio.rego | 24 ++ .../signatures/rego/regosig/aio_test.go | 290 ++++++++++++++++++ .../signatures/rego/regosig/mapper.go | 73 +++++ .../signatures/rego/regosig/mapper_test.go | 1 + .../rego/regosig/traceerego_test.go | 4 +- 11 files changed, 697 insertions(+), 9 deletions(-) create mode 100644 Vagrantfile create mode 100644 tracee-rules/signatures/rego/regosig/aio.go create mode 100644 tracee-rules/signatures/rego/regosig/aio.rego create mode 100644 tracee-rules/signatures/rego/regosig/aio_test.go create mode 100644 tracee-rules/signatures/rego/regosig/mapper.go create mode 100644 tracee-rules/signatures/rego/regosig/mapper_test.go diff --git a/.gitignore b/.gitignore index 17354ebed0ac..29bd091643a3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ .idea /dist -coverage.txt \ No newline at end of file +coverage.txt + +.vagrant/ \ No newline at end of file diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 000000000000..43ba34264247 --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,22 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +Vagrant.configure("2") do |config| + config.vm.box = "ubuntu/focal64" + + config.vm.provider "virtualbox" do |vb| + vb.gui = false + vb.memory = "2048" + end + + config.vm.provision "shell", inline: <<-SHELL + apt-get update + apt-get install --yes build-essential pkgconf libelf-dev llvm-12 clang-12 + + for tool in "clang" "llc" "llvm-strip"; do path=$(which $tool-12) && sudo ln -s $path ${path%-*}; done + + wget --quiet https://golang.org/dl/go1.16.linux-amd64.tar.gz + tar -C /usr/local -xzf go1.16.linux-amd64.tar.gz + echo 'export PATH=$PATH:/usr/local/go/bin' >> /home/vagrant/.profile + SHELL +end diff --git a/tracee-rules/main.go b/tracee-rules/main.go index e85b6e1a41c0..7cbfdf172417 100644 --- a/tracee-rules/main.go +++ b/tracee-rules/main.go @@ -73,7 +73,7 @@ func main() { return errors.New("invalid target specified " + target) } - sigs, err := getSignatures(target, c.Bool("rego-partial-eval"), c.String("rules-dir"), c.StringSlice("rules")) + sigs, err := getSignatures(target, c.Bool("rego-partial-eval"), c.String("rules-dir"), c.StringSlice("rules"), c.Bool("rego-aio")) if err != nil { return err } @@ -87,7 +87,7 @@ func main() { } loadedSigIDs = append(loadedSigIDs, m.ID) } - fmt.Println("Loaded signature(s): ", loadedSigIDs) + fmt.Printf("Loaded %d signature(s): %s\n", len(loadedSigIDs), loadedSigIDs) if c.Bool("list") { return listSigs(os.Stdout, sigs) @@ -168,6 +168,10 @@ func main() { Name: "rego-enable-parsed-events", Usage: "enables pre parsing of input events to rego prior to evaluation", }, + &cli.BoolFlag{ + Name: "rego-aio", + Usage: "compile rego signatures altogether as an aggregate policy. By default each signature is compiled separately.", + }, &cli.StringFlag{ Name: "rego-runtime-target", Usage: "select which runtime target to use for evaluation of rego rules: rego, wasm", diff --git a/tracee-rules/signature.go b/tracee-rules/signature.go index 79f3f4d4a306..056ee67dc4af 100644 --- a/tracee-rules/signature.go +++ b/tracee-rules/signature.go @@ -19,7 +19,7 @@ import ( //go:embed signatures/rego/helpers.rego var regoHelpersCode string -func getSignatures(target string, partialEval bool, rulesDir string, rules []string) ([]types.Signature, error) { +func getSignatures(target string, partialEval bool, rulesDir string, rules []string, aioEnabled bool) ([]types.Signature, error) { if rulesDir == "" { exePath, err := os.Executable() if err != nil { @@ -31,7 +31,7 @@ func getSignatures(target string, partialEval bool, rulesDir string, rules []str if err != nil { return nil, err } - opasigs, err := findRegoSigs(target, partialEval, rulesDir) + opasigs, err := findRegoSigs(target, partialEval, rulesDir, aioEnabled) if err != nil { return nil, err } @@ -79,7 +79,10 @@ func findGoSigs(dir string) ([]types.Signature, error) { return res, nil } -func findRegoSigs(target string, partialEval bool, dir string) ([]types.Signature, error) { +func findRegoSigs(target string, partialEval bool, dir string, aioEnabled bool) ([]types.Signature, error) { + modules := make(map[string]string) + modules["helper.rego"] = regoHelpersCode + regoHelpers := []string{regoHelpersCode} filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { if err != nil { @@ -101,6 +104,7 @@ func findRegoSigs(target string, partialEval bool, dir string) ([]types.Signatur } regoHelpers = append(regoHelpers, string(helperCode)) + modules[path] = string(helperCode) return nil }) @@ -119,6 +123,10 @@ func findRegoSigs(target string, partialEval bool, dir string) ([]types.Signatur log.Printf("error reading file %s: %v", path, err) return nil } + modules[path] = string(regoCode) + if aioEnabled { + return nil + } sig, err := regosig.NewRegoSignature(target, partialEval, append(regoHelpers, string(regoCode))...) if err != nil { newlineOffset := bytes.Index(regoCode, []byte("\n")) @@ -136,6 +144,15 @@ func findRegoSigs(target string, partialEval bool, dir string) ([]types.Signatur res = append(res, sig) return nil }) + if aioEnabled { + aio, err := regosig.NewAIO(modules, + regosig.OPATarget(target), + regosig.OPAPartial(partialEval)) + if err != nil { + return nil, err + } + return []types.Signature{aio}, nil + } return res, nil } diff --git a/tracee-rules/signature_test.go b/tracee-rules/signature_test.go index d96b11215631..ad6c9d3504f0 100644 --- a/tracee-rules/signature_test.go +++ b/tracee-rules/signature_test.go @@ -17,7 +17,7 @@ import ( ) func Test_getSignatures(t *testing.T) { - sigs, err := getSignatures(compile.TargetRego, false, "signatures/rego", []string{"TRC-2"}) + sigs, err := getSignatures(compile.TargetRego, false, "signatures/rego", []string{"TRC-2"}, false) require.NoError(t, err) require.Equal(t, 1, len(sigs)) @@ -79,7 +79,7 @@ func Test_findRegoSigs(t *testing.T) { require.NoError(t, err) // find rego signatures - sigs, err := findRegoSigs(compile.TargetRego, false, testRoot) + sigs, err := findRegoSigs(compile.TargetRego, false, testRoot, false) require.NoError(t, err) assert.Equal(t, len(sigs), 2) diff --git a/tracee-rules/signatures/rego/regosig/aio.go b/tracee-rules/signatures/rego/regosig/aio.go new file mode 100644 index 000000000000..1de5213b23e6 --- /dev/null +++ b/tracee-rules/signatures/rego/regosig/aio.go @@ -0,0 +1,253 @@ +package regosig + +import ( + _ "embed" + + "context" + "fmt" + "sort" + "strings" + + "github.com/aquasecurity/tracee/tracee-ebpf/external" + "github.com/aquasecurity/tracee/tracee-rules/engine" + "github.com/aquasecurity/tracee/tracee-rules/types" + "github.com/open-policy-agent/opa/ast" + "github.com/open-policy-agent/opa/compile" + "github.com/open-policy-agent/opa/rego" +) + +var ( + //go:embed aio.rego + mainRego string +) + +const ( + moduleMain = "main.rego" + queryMetadataAll = "data.main.__rego_metadoc_all__" + querySelectedEventsAll = "data.main.tracee_selected_events_all" + queryMatchAll = "data.main.tracee_match_all" +) + +// Options holds various Option items that can be passed to the NewAIO constructor. +type Options struct { + // OPATarget optionally specifies which OPA target engine to use for + // evaluation. By default, the `rego` engine is used. + OPATarget string + + // OPAPartial optionally specifies whether to use OPA partial evaluation + // or not. By default, partial evaluation is disabled. + // + // NOTE: On average partial evaluation performs better by leveraging + // OPA rules indexing. However, for some rules we noticed that enabling partial + // evaluation significantly degraded performance. + // + // https://blog.openpolicyagent.org/partial-evaluation-162750eaf422 + OPAPartial bool +} + +type Option func(*Options) + +func OPATarget(target string) Option { + return func(o *Options) { + o.OPATarget = target + } +} + +func OPAPartial(partial bool) Option { + return func(o *Options) { + o.OPAPartial = partial + } +} + +func newDefaultOptions() *Options { + return &Options{ + OPATarget: compile.TargetRego, + OPAPartial: false, + } +} + +type aio struct { + cb types.SignatureHandler + metadata types.SignatureMetadata + + preparedQuery rego.PreparedEvalQuery + sigIDToMetadata map[string]types.SignatureMetadata + selectedEvents []types.SignatureEventSelector +} + +// NewAIO constructs a new types.Signature with the specified Rego modules and Option items. +// +// This implementation compiles all modules once and prepares the single, +// aka all in one, query for evaluation. +func NewAIO(modules map[string]string, opts ...Option) (types.Signature, error) { + options := newDefaultOptions() + + for _, opt := range opts { + opt(options) + } + + modules[moduleMain] = mainRego + ctx := context.TODO() + compiler, err := ast.CompileModules(modules) + if err != nil { + return nil, fmt.Errorf("compiling modules: %w", err) + } + + metadataRS, err := rego.New( + rego.Compiler(compiler), + rego.Query(queryMetadataAll), + ).Eval(ctx) + if err != nil { + return nil, fmt.Errorf("evaluating %s query: %w", queryMetadataAll, err) + } + sigIDToMetadata, err := MapRS(metadataRS).ToSignatureMetadataAll() + if err != nil { + return nil, fmt.Errorf("mapping output to metadata: %w", err) + } + + selectedEventsRS, err := rego.New( + rego.Compiler(compiler), + rego.Query(querySelectedEventsAll), + ).Eval(ctx) + if err != nil { + return nil, fmt.Errorf("evaluating %s query: %w", querySelectedEventsAll, err) + } + sigIDToSelectedEvents, err := MapRS(selectedEventsRS).ToSelectedEventsAll() + if err != nil { + return nil, fmt.Errorf("mapping output to selected events: %w", err) + } + + var peq rego.PreparedEvalQuery + + if options.OPAPartial { + pr, err := rego.New( + rego.Compiler(compiler), + rego.Query(queryMatchAll), + ).PartialResult(ctx) + if err != nil { + return nil, fmt.Errorf("partially evaluating %s query: %w", queryMatchAll, err) + } + peq, err = pr.Rego( + rego.Target(options.OPATarget), + ).PrepareForEval(ctx) + if err != nil { + return nil, fmt.Errorf("preparing %s query: %w", queryMatch, err) + } + } else { + peq, err = rego.New( + rego.Target(options.OPATarget), + rego.Compiler(compiler), + rego.Query(queryMatchAll), + ).PrepareForEval(ctx) + if err != nil { + return nil, fmt.Errorf("preparing %s query: %w", queryMetadataAll, err) + } + } + + var sigIDs []string + var selectedEvents []types.SignatureEventSelector + + selectedEventsSet := make(map[types.SignatureEventSelector]bool) + + for sigID, sigEvents := range sigIDToSelectedEvents { + sigIDs = append(sigIDs, sigID) + + for _, sigEvent := range sigEvents { + if _, value := selectedEventsSet[sigEvent]; !value { + selectedEventsSet[sigEvent] = true + selectedEvents = append(selectedEvents, sigEvent) + } + } + } + + sort.Strings(sigIDs) + metadata := types.SignatureMetadata{ + ID: fmt.Sprintf("TRC-AIO (%s)", strings.Join(sigIDs, ",")), + Version: "1.0.0", + Name: "AIO", + } + + return &aio{ + metadata: metadata, + preparedQuery: peq, + sigIDToMetadata: sigIDToMetadata, + selectedEvents: selectedEvents, + }, nil +} + +func (a *aio) Init(cb types.SignatureHandler) error { + a.cb = cb + return nil +} + +func (a *aio) GetMetadata() (types.SignatureMetadata, error) { + return a.metadata, nil +} + +func (a *aio) GetSelectedEvents() ([]types.SignatureEventSelector, error) { + return a.selectedEvents, nil +} + +func (a *aio) OnEvent(ee types.Event) error { + input, event, err := toInputOption(ee) + if err != nil { + return err + } + + ctx := context.TODO() + rs, err := a.preparedQuery.Eval(ctx, input) + if err != nil { + return err + } + data, err := MapRS(rs).ToDataAll() + if err != nil { + return err + } + for sigID, value := range data { + switch v := value.(type) { + case bool: + if v { + a.cb(types.Finding{ + Data: nil, + Context: event, + SigMetadata: a.sigIDToMetadata[sigID], + }) + } + case map[string]interface{}: + a.cb(types.Finding{ + Data: v, + Context: event, + SigMetadata: a.sigIDToMetadata[sigID], + }) + default: + return fmt.Errorf("unrecognized value: %T", v) + } + } + return nil +} + +func toInputOption(ee types.Event) (rego.EvalOption, external.Event, error) { + var input rego.EvalOption + var event external.Event + + switch ee.(type) { + case external.Event: + event = ee.(external.Event) + input = rego.EvalInput(ee) + case engine.ParsedEvent: + pe := ee.(engine.ParsedEvent) + event = pe.Event + input = rego.EvalParsedInput(pe.Value) + default: + return nil, external.Event{}, fmt.Errorf("unrecognized event type: %T", ee) + } + return input, event, nil +} + +func (a *aio) Close() { + // noop +} + +func (a aio) OnSignal(signal types.Signal) error { + return fmt.Errorf("unsupported operation") +} diff --git a/tracee-rules/signatures/rego/regosig/aio.rego b/tracee-rules/signatures/rego/regosig/aio.rego new file mode 100644 index 000000000000..0c0bca8bf485 --- /dev/null +++ b/tracee-rules/signatures/rego/regosig/aio.rego @@ -0,0 +1,24 @@ +package main + +# Returns the map of signature identifiers to signature metadata. +__rego_metadoc_all__[id] = resp { + some i + resp := data.tracee[i].__rego_metadoc__ + id := resp.id +} + +# Returns the map of signature identifiers to signature selected events. +tracee_selected_events_all[id] = resp { + some i + resp := data.tracee[i].tracee_selected_events + metadata := data.tracee[i].__rego_metadoc__ + id := metadata.id +} + +# Returns the map of signature identifiers to values matching the input event. +tracee_match_all[id] = resp { + some i + resp := data.tracee[i].tracee_match + metadata := data.tracee[i].__rego_metadoc__ + id := metadata.id +} \ No newline at end of file diff --git a/tracee-rules/signatures/rego/regosig/aio_test.go b/tracee-rules/signatures/rego/regosig/aio_test.go new file mode 100644 index 000000000000..096778d022d7 --- /dev/null +++ b/tracee-rules/signatures/rego/regosig/aio_test.go @@ -0,0 +1,290 @@ +package regosig_test + +import ( + "encoding/json" + "fmt" + "os" + "testing" + + tracee "github.com/aquasecurity/tracee/tracee-ebpf/external" + "github.com/aquasecurity/tracee/tracee-rules/signatures/rego/regosig" + "github.com/aquasecurity/tracee/tracee-rules/types" + "github.com/open-policy-agent/opa/compile" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// findingsHolder is a utility struct that defines types.SignatureHandler callback method +// and holds the types.Finding value received as the callback's argument. +type findingsHolder struct { + values []types.Finding +} + +func (h *findingsHolder) OnFinding(f types.Finding) { + h.values = append(h.values, f) +} + +func (h *findingsHolder) GroupBySigID() map[string]types.Finding { + r := make(map[string]types.Finding) + for _, v := range h.values { + r[v.SigMetadata.ID] = v + } + return r +} + +func TestAio_GetMetadata(t *testing.T) { + sig, err := regosig.NewAIO(map[string]string{ + "test_boolean.rego": testRegoCodeBoolean, + "test_object.rego": testRegoCodeObject, + }) + require.NoError(t, err) + + metadata, err := sig.GetMetadata() + require.NoError(t, err) + assert.Equal(t, types.SignatureMetadata{ + ID: "TRC-AIO (TRC-BOOL,TRC-OBJECT)", + Version: "1.0.0", + Name: "AIO", + }, metadata) +} + +func TestAio_GetSelectedEvents(t *testing.T) { + sig, err := regosig.NewAIO(map[string]string{ + "test_boolean.rego": testRegoCodeBoolean, + "test_object.rego": testRegoCodeObject, + }) + require.NoError(t, err) + events, err := sig.GetSelectedEvents() + require.NoError(t, err) + + eventsSet := make(map[types.SignatureEventSelector]bool) + for _, event := range events { + if _, value := eventsSet[event]; !value { + eventsSet[event] = true + } + } + + assert.Equal(t, map[types.SignatureEventSelector]bool{ + types.SignatureEventSelector{ + Source: "tracee", + Name: "ptrace", + }: true, + types.SignatureEventSelector{ + Source: "tracee", + Name: "execve", + }: true, + }, eventsSet) +} + +func TestAio_OnEvent(t *testing.T) { + options := []struct { + target string + partial bool + }{ + { + target: compile.TargetRego, + partial: false, + }, + { + target: compile.TargetRego, + partial: true, + }, + { + target: compile.TargetWasm, + partial: false, + }, + { + target: compile.TargetWasm, + partial: true, + }, + } + + for _, tc := range options { + t.Run(fmt.Sprintf("target=%s,partial=%t", tc.target, tc.partial), func(t *testing.T) { + AioOnEventSpec(t, tc.target, tc.partial) + }) + } + +} + +// AioOnEventSpec describes the behavior of aio.OnEvent. +func AioOnEventSpec(t *testing.T, target string, partial bool) { + testCases := []struct { + name string + modules map[string]string + event tracee.Event + // findings are grouped by signature identifier for comparison + findings map[string]types.Finding + }{ + { + name: "Should return finding when single rule matches", + modules: map[string]string{ + "test_boolean.rego": testRegoCodeBoolean, + "test_object.rego": testRegoCodeObject, + }, + event: tracee.Event{ + Args: []tracee.Argument{ + { + ArgMeta: tracee.ArgMeta{ + Name: "doesn't matter", + }, + Value: "ends with yo", + }, + }, + }, + findings: map[string]types.Finding{ + "TRC-BOOL": { + Data: nil, + Context: tracee.Event{ + Args: []tracee.Argument{ + { + ArgMeta: tracee.ArgMeta{ + Name: "doesn't matter", + }, + Value: "ends with yo", + }, + }, + }, + SigMetadata: types.SignatureMetadata{ + ID: "TRC-BOOL", + Version: "0.1.0", + Name: "test name", + Description: "test description", + Tags: []string{ + "tag1", + "tag2", + }, + Properties: map[string]interface{}{ + "p1": "test", + "p2": json.Number("1"), + "p3": true, + }, + }, + }, + }, + }, + { + name: "Should return multiple findings when multiple rules match", + modules: map[string]string{ + "test_boolean.rego": testRegoCodeBoolean, + "test_object.rego": testRegoCodeObject, + }, + event: tracee.Event{ + Args: []tracee.Argument{ + { + ArgMeta: tracee.ArgMeta{ + Name: "doesn't matter", + }, + Value: "ends with yo", + }, + { + ArgMeta: tracee.ArgMeta{ + Name: "doesn't matter", + }, + Value: 1337, + }, + }, + }, + findings: map[string]types.Finding{ + "TRC-OBJECT": { + Data: map[string]interface{}{ + "p1": "test", + "p2": json.Number("1"), + "p3": true, + }, + Context: tracee.Event{ + Args: []tracee.Argument{ + { + ArgMeta: tracee.ArgMeta{ + Name: "doesn't matter", + }, + Value: "ends with yo", + }, + { + ArgMeta: tracee.ArgMeta{ + Name: "doesn't matter", + }, + Value: 1337, + }, + }, + }, + SigMetadata: types.SignatureMetadata{ + ID: "TRC-OBJECT", + Version: "0.3.0", + Name: "test name", + Description: "test description", + Tags: []string{ + "tag1", + "tag2", + }, + Properties: map[string]interface{}{ + "p1": "test", + "p2": json.Number("1"), + "p3": true, + }, + }, + }, + "TRC-BOOL": { + Data: nil, + Context: tracee.Event{ + Args: []tracee.Argument{ + { + ArgMeta: tracee.ArgMeta{ + Name: "doesn't matter", + }, + Value: "ends with yo", + }, + { + ArgMeta: tracee.ArgMeta{ + Name: "doesn't matter", + }, + Value: 1337, + }, + }, + }, + SigMetadata: types.SignatureMetadata{ + ID: "TRC-BOOL", + Version: "0.1.0", + Name: "test name", + Description: "test description", + Tags: []string{ + "tag1", + "tag2", + }, + Properties: map[string]interface{}{ + "p1": "test", + "p2": json.Number("1"), + "p3": true, + }, + }, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + sig, err := regosig.NewAIO(tc.modules, + regosig.OPATarget(target), + regosig.OPAPartial(partial), + ) + require.NoError(t, err) + + holder := &findingsHolder{} + err = sig.Init(holder.OnFinding) + require.NoError(t, err) + + err = sig.OnEvent(tc.event) + require.NoError(t, err) + + assert.Equal(t, tc.findings, holder.GroupBySigID()) + }) + } +} + +func TestAio_OnSignal(t *testing.T) { + sig, err := regosig.NewAIO(map[string]string{}) + require.NoError(t, err) + err = sig.OnSignal(os.Kill) + assert.EqualError(t, err, "unsupported operation") +} diff --git a/tracee-rules/signatures/rego/regosig/mapper.go b/tracee-rules/signatures/rego/regosig/mapper.go new file mode 100644 index 000000000000..a8871bd5c0ee --- /dev/null +++ b/tracee-rules/signatures/rego/regosig/mapper.go @@ -0,0 +1,73 @@ +package regosig + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + + "github.com/aquasecurity/tracee/tracee-rules/types" + "github.com/open-policy-agent/opa/rego" +) + +type Mapper struct { + rego.ResultSet +} + +func MapRS(rs rego.ResultSet) *Mapper { + return &Mapper{ + ResultSet: rs, + } +} + +func (m Mapper) ToSignatureMetadataAll() (map[string]types.SignatureMetadata, error) { + if m.isEmpty() { + return nil, errors.New("empty result set") + } + resJSON, err := json.Marshal(m.ResultSet[0].Expressions[0].Value) + if err != nil { + return nil, err + } + dec := json.NewDecoder(bytes.NewBuffer(resJSON)) + dec.UseNumber() + var res map[string]types.SignatureMetadata + err = dec.Decode(&res) + if err != nil { + return nil, err + } + return res, nil +} + +func (m Mapper) ToSelectedEventsAll() (map[string][]types.SignatureEventSelector, error) { + if m.isEmpty() { + return nil, errors.New("empty result set") + } + resJSON, err := json.Marshal(m.ResultSet[0].Expressions[0].Value) + if err != nil { + return nil, err + } + dec := json.NewDecoder(bytes.NewBuffer(resJSON)) + dec.UseNumber() + var res map[string][]types.SignatureEventSelector + err = dec.Decode(&res) + if err != nil { + return nil, err + } + return res, nil +} + +func (m Mapper) ToDataAll() (map[string]interface{}, error) { + if m.isEmpty() { + return nil, errors.New("empty result set") + } + values, ok := m.ResultSet[0].Expressions[0].Value.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("unrecognized value: %T", m.ResultSet[0].Expressions[0].Value) + } + return values, nil +} + +func (m Mapper) isEmpty() bool { + rs := m.ResultSet + return len(rs) == 0 || len(rs[0].Expressions) == 0 || rs[0].Expressions[0].Value == nil +} diff --git a/tracee-rules/signatures/rego/regosig/mapper_test.go b/tracee-rules/signatures/rego/regosig/mapper_test.go new file mode 100644 index 000000000000..0dcb3476ee9d --- /dev/null +++ b/tracee-rules/signatures/rego/regosig/mapper_test.go @@ -0,0 +1 @@ +package regosig_test diff --git a/tracee-rules/signatures/rego/regosig/traceerego_test.go b/tracee-rules/signatures/rego/regosig/traceerego_test.go index 8fb99fb7bc3c..e6163e50d931 100644 --- a/tracee-rules/signatures/rego/regosig/traceerego_test.go +++ b/tracee-rules/signatures/rego/regosig/traceerego_test.go @@ -60,7 +60,7 @@ __rego_metadoc__ := { tracee_selected_events[eventSelector] { eventSelector := { "source": "tracee", - "name": "execve" + "name": "ptrace" } } @@ -78,6 +78,8 @@ tracee_match = res { // findingHolder is a utility struct that defines types.SignatureHandler callback method // and holds the types.Finding value received as the callback's argument. +// +// Deprecated use findingsHolder instead. type findingHolder struct { value *types.Finding } From 3fa7e4ccf8b64e0e6eb7f52a0a5d9fb4bf1cebf7 Mon Sep 17 00:00:00 2001 From: Simar Date: Mon, 20 Sep 2021 12:23:14 -0700 Subject: [PATCH 2/3] feat: Add a test for invalid rego return value Signed-off-by: Simar --- .../signatures/rego/regosig/aio_test.go | 29 ++++++++++++++++--- .../rego/regosig/traceerego_test.go | 26 +++++++++++++++++ 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/tracee-rules/signatures/rego/regosig/aio_test.go b/tracee-rules/signatures/rego/regosig/aio_test.go index 096778d022d7..a1c0a9b093af 100644 --- a/tracee-rules/signatures/rego/regosig/aio_test.go +++ b/tracee-rules/signatures/rego/regosig/aio_test.go @@ -114,7 +114,8 @@ func AioOnEventSpec(t *testing.T, target string, partial bool) { modules map[string]string event tracee.Event // findings are grouped by signature identifier for comparison - findings map[string]types.Finding + findings map[string]types.Finding + wantError string }{ { name: "Should return finding when single rule matches", @@ -260,6 +261,23 @@ func AioOnEventSpec(t *testing.T, target string, partial bool) { }, }, }, + { + name: "Should return error when invalid value received", + modules: map[string]string{ + "test_invalid.rego": testRegoCodeInvalidObject, + }, + event: tracee.Event{ + Args: []tracee.Argument{ + { + ArgMeta: tracee.ArgMeta{ + Name: "doesn't matter", + }, + Value: "ends with invalid", + }, + }, + }, + wantError: "unrecognized value: string", + }, } for _, tc := range testCases { @@ -275,9 +293,12 @@ func AioOnEventSpec(t *testing.T, target string, partial bool) { require.NoError(t, err) err = sig.OnEvent(tc.event) - require.NoError(t, err) - - assert.Equal(t, tc.findings, holder.GroupBySigID()) + if tc.wantError != "" { + require.EqualError(t, err, tc.wantError, tc.name) + } else { + require.NoError(t, err, tc.name) + assert.Equal(t, tc.findings, holder.GroupBySigID()) + } }) } } diff --git a/tracee-rules/signatures/rego/regosig/traceerego_test.go b/tracee-rules/signatures/rego/regosig/traceerego_test.go index e6163e50d931..743068cbc3e7 100644 --- a/tracee-rules/signatures/rego/regosig/traceerego_test.go +++ b/tracee-rules/signatures/rego/regosig/traceerego_test.go @@ -73,6 +73,32 @@ tracee_match = res { "p3": true } } +` + testRegoCodeInvalidObject = `package tracee.TRC_INVALID +__rego_metadoc__ := { + "id": "TRC-INVALID", + "version": "0.3.0", + "name": "test name", + "description": "test description", + "tags": [ "tag1", "tag2" ], + "properties": { + "p1": "test", + "p2": 1, + "p3": true + } +} + +tracee_selected_events[eventSelector] { + eventSelector := { + "source": "tracee", + "name": "ptrace" + } +} + +tracee_match = res { + endswith(input.args[0].value, "invalid") + res := "foo bar string" +} ` ) From 03f87bb75c3b8b31503854c8216fbb639017bde0 Mon Sep 17 00:00:00 2001 From: Daniel Pacak Date: Mon, 20 Sep 2021 22:46:58 +0200 Subject: [PATCH 3/3] Remove Vagrantfile Signed-off-by: Daniel Pacak --- .gitignore | 2 -- Vagrantfile | 22 ---------------------- 2 files changed, 24 deletions(-) delete mode 100644 Vagrantfile diff --git a/.gitignore b/.gitignore index 29bd091643a3..e74362031a1d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,3 @@ /dist coverage.txt - -.vagrant/ \ No newline at end of file diff --git a/Vagrantfile b/Vagrantfile deleted file mode 100644 index 43ba34264247..000000000000 --- a/Vagrantfile +++ /dev/null @@ -1,22 +0,0 @@ -# -*- mode: ruby -*- -# vi: set ft=ruby : - -Vagrant.configure("2") do |config| - config.vm.box = "ubuntu/focal64" - - config.vm.provider "virtualbox" do |vb| - vb.gui = false - vb.memory = "2048" - end - - config.vm.provision "shell", inline: <<-SHELL - apt-get update - apt-get install --yes build-essential pkgconf libelf-dev llvm-12 clang-12 - - for tool in "clang" "llc" "llvm-strip"; do path=$(which $tool-12) && sudo ln -s $path ${path%-*}; done - - wget --quiet https://golang.org/dl/go1.16.linux-amd64.tar.gz - tar -C /usr/local -xzf go1.16.linux-amd64.tar.gz - echo 'export PATH=$PATH:/usr/local/go/bin' >> /home/vagrant/.profile - SHELL -end