From 3a22d373f58e3d332e9703c6c73662287fddcb57 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Mon, 22 Jan 2024 18:56:05 +0700 Subject: [PATCH] refactor: migrate REGO functions to Go --- go.mod | 2 + go.sum | 4 +- pkg/rego/custom.go | 109 ---------------------- pkg/rego/result.go | 198 +++++++++++++++++++++++++--------------- pkg/rego/result_test.go | 54 ++++++++--- pkg/rego/scanner.go | 30 +++++- 6 files changed, 199 insertions(+), 198 deletions(-) delete mode 100644 pkg/rego/custom.go diff --git a/go.mod b/go.mod index 6ef9dd2ee..0093216ba 100644 --- a/go.mod +++ b/go.mod @@ -85,3 +85,5 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) + +replace github.com/aquasecurity/trivy-policies => github.com/nikpivkin/trivy-policies v0.0.0-20240122112512-4cefa920d85c diff --git a/go.sum b/go.sum index f5cfbe50b..c8e6aabab 100644 --- a/go.sum +++ b/go.sum @@ -22,8 +22,6 @@ github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6 github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= -github.com/aquasecurity/trivy-policies v0.8.0 h1:LvmIdw/DfTF72Lc8L+CKLYzfb5BFYzLBGFFR95PKC74= -github.com/aquasecurity/trivy-policies v0.8.0/go.mod h1:qF/t59pgK/0JTV6tXaeA3Iw3opzoMgzGCDcTDBmqb30= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= @@ -122,6 +120,8 @@ github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQ github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/nikpivkin/trivy-policies v0.0.0-20240122112512-4cefa920d85c h1:haHvIlIYDbyg3FcFrsPWcaEwBGBAkuPwPQ6GegoWXUE= +github.com/nikpivkin/trivy-policies v0.0.0-20240122112512-4cefa920d85c/go.mod h1:I8cjIJ/PzMkhIY+2DVT2Vco0USiOqXjw04HzSEZXp9c= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/open-policy-agent/opa v0.60.0 h1:ZPoPt4yeNs5UXCpd/P/btpSyR8CR0wfhVoh9BOwgJNs= github.com/open-policy-agent/opa v0.60.0/go.mod h1:aD5IK6AiLNYBjNXn7E02++yC8l4Z+bRDvgM6Ss0bBzA= diff --git a/pkg/rego/custom.go b/pkg/rego/custom.go deleted file mode 100644 index c15b05a45..000000000 --- a/pkg/rego/custom.go +++ /dev/null @@ -1,109 +0,0 @@ -package rego - -import ( - "github.com/open-policy-agent/opa/ast" - "github.com/open-policy-agent/opa/rego" - "github.com/open-policy-agent/opa/types" -) - -func init() { - rego.RegisterBuiltin2(®o.Function{ - Name: "result.new", - Decl: types.NewFunction(types.Args(types.S, types.A), types.A), - }, - createResult, - ) - - rego.RegisterBuiltin1(®o.Function{ - Name: "isManaged", - Decl: types.NewFunction(types.Args(types.A), types.B), - }, - func(c rego.BuiltinContext, resource *ast.Term) (*ast.Term, error) { - metadata, err := createResult(c, ast.StringTerm(""), resource) - if err != nil { - return nil, err - } - return metadata.Get(ast.StringTerm("managed")), nil - }, - ) -} - -func createResult(ctx rego.BuiltinContext, msg, cause *ast.Term) (*ast.Term, error) { - - metadata := map[string]*ast.Term{ - "startline": ast.IntNumberTerm(0), - "endline": ast.IntNumberTerm(0), - "sourceprefix": ast.StringTerm(""), - "filepath": ast.StringTerm(""), - "explicit": ast.BooleanTerm(false), - "managed": ast.BooleanTerm(true), - "fskey": ast.StringTerm(""), - "resource": ast.StringTerm(""), - "parent": ast.NullTerm(), - } - if msg != nil { - metadata["msg"] = msg - } - - // universal - input := cause.Get(ast.StringTerm("__defsec_metadata")) - if input == nil { - // docker - input = cause - } - metadata = updateMetadata(metadata, input) - - if term := input.Get(ast.StringTerm("parent")); term != nil { - var err error - metadata["parent"], err = createResult(ctx, nil, term) - if err != nil { - return nil, err - } - } - - var values [][2]*ast.Term - for key, val := range metadata { - values = append(values, [2]*ast.Term{ - ast.StringTerm(key), - val, - }) - } - return ast.ObjectTerm(values...), nil -} - -func updateMetadata(metadata map[string]*ast.Term, input *ast.Term) map[string]*ast.Term { - if term := input.Get(ast.StringTerm("startline")); term != nil { - metadata["startline"] = term - } - if term := input.Get(ast.StringTerm("StartLine")); term != nil { - metadata["startline"] = term - } - if term := input.Get(ast.StringTerm("endline")); term != nil { - metadata["endline"] = term - } - if term := input.Get(ast.StringTerm("EndLine")); term != nil { - metadata["endline"] = term - } - if term := input.Get(ast.StringTerm("filepath")); term != nil { - metadata["filepath"] = term - } - if term := input.Get(ast.StringTerm("sourceprefix")); term != nil { - metadata["sourceprefix"] = term - } - if term := input.Get(ast.StringTerm("Path")); term != nil { - metadata["filepath"] = term - } - if term := input.Get(ast.StringTerm("explicit")); term != nil { - metadata["explicit"] = term - } - if term := input.Get(ast.StringTerm("managed")); term != nil { - metadata["managed"] = term - } - if term := input.Get(ast.StringTerm("fskey")); term != nil { - metadata["fskey"] = term - } - if term := input.Get(ast.StringTerm("resource")); term != nil { - metadata["resource"] = term - } - return metadata -} diff --git a/pkg/rego/result.go b/pkg/rego/result.go index 94319eee4..e5a2f5b0f 100644 --- a/pkg/rego/result.go +++ b/pkg/rego/result.go @@ -10,6 +10,8 @@ import ( "github.com/open-policy-agent/opa/rego" ) +const denyMessage = "Rego policy resulted in DENY" + type regoResult struct { Filepath string Resource string @@ -24,6 +26,13 @@ type regoResult struct { Parent *regoResult } +func messageResult(msg string) *regoResult { + return ®oResult{ + Managed: true, + Message: msg, + } +} + func (r regoResult) GetMetadata() defsecTypes.Metadata { var m defsecTypes.Metadata if !r.Managed { @@ -46,102 +55,147 @@ func (r regoResult) GetRawValue() interface{} { return nil } -func parseResult(raw interface{}) *regoResult { - var result regoResult - result.Managed = true - switch val := raw.(type) { - case []interface{}: - var msg string - for _, item := range val { - switch raw := item.(type) { - case map[string]interface{}: - result = parseCause(raw) - case string: - msg = raw - } +func (r *regoResult) applyOffset(offset int) { + r.StartLine += offset + r.EndLine += offset +} + +func (r *regoResult) updateMeta(raw map[string]any) { + for k, v := range raw { + switch k { + case "startline", "StartLine": + r.StartLine = parseLineNumber(v) + case "endline", "EndLine": + r.EndLine = parseLineNumber(v) + case "filepath", "Path": + r.Filepath = getString(v) + case "sourceprefix": + r.SourcePrefix = getString(v) + case "explicit": + r.Explicit = getBool(v) + case "managed": + r.Managed = getBool(v) + case "fskey": + r.FSKey = getString(v) + case "resource": + r.Resource = getString(v) } - result.Message = msg - case string: - result.Message = val - case map[string]interface{}: - result = parseCause(val) - default: - result.Message = "Rego policy resulted in DENY" } - return &result } -func parseCause(cause map[string]interface{}) regoResult { - var result regoResult - result.Managed = true - if msg, ok := cause["msg"]; ok { - result.Message = fmt.Sprintf("%s", msg) +func getString(raw any) string { + if str, ok := raw.(string); ok { + return str } - if filepath, ok := cause["filepath"]; ok { - result.Filepath = fmt.Sprintf("%s", filepath) + return "" +} + +func getBool(raw any) bool { + if b, ok := raw.(bool); ok { + return b } - if msg, ok := cause["fskey"]; ok { - result.FSKey = fmt.Sprintf("%s", msg) + return false +} + +func newRegoResult(rawInput any) *regoResult { + result := ®oResult{ + Managed: true, } - if msg, ok := cause["resource"]; ok { - result.Resource = fmt.Sprintf("%s", msg) + + input, ok := rawInput.(map[string]any) + if !ok { + return result } - if start, ok := cause["startline"]; ok { - result.StartLine = parseLineNumber(start) + + if rawMsg, exists := input["msg"]; exists { + if msg, ok := rawMsg.(string); ok { + result.Message = msg + } } - if end, ok := cause["endline"]; ok { - result.EndLine = parseLineNumber(end) + + meta := parseMetadata(input) + result.updateMeta(meta) + + if parent, ok := meta["parent"]; ok { + result.Parent = newRegoResult(map[string]any{"metadata": parent}) } - if prefix, ok := cause["sourceprefix"]; ok { - result.SourcePrefix = fmt.Sprintf("%s", prefix) + + return result +} + +func parseMetadata(input map[string]any) map[string]any { + res := make(map[string]any) + rawMetadata, exists := input["metadata"] + if !exists { + // for backward compatibility + rawMetadata = input } - if explicit, ok := cause["explicit"]; ok { - if set, ok := explicit.(bool); ok { - result.Explicit = set - } + + cause, ok := rawMetadata.(map[string]any) + if !ok { + return res } - if managed, ok := cause["managed"]; ok { - if set, ok := managed.(bool); ok { - result.Managed = set + + rawDefsecMeta, exists := cause["__defsec_metadata"] + if !exists { + res = cause + } else { + defsecMeta, ok := rawDefsecMeta.(map[string]any) + if !ok { + return res } + res = defsecMeta } - if parent, ok := cause["parent"]; ok { - if m, ok := parent.(map[string]interface{}); ok { - parentResult := parseCause(m) - result.Parent = &parentResult + + return res +} + +func parseResult(raw any) *regoResult { + + switch val := raw.(type) { + case []any: + var msg string + var result *regoResult + for _, item := range val { + switch raw := item.(type) { + case map[string]any: + if res := newRegoResult(raw); res != nil { + result = res + } + case string: + msg = raw + } + } + if result != nil { + result.Message = msg + return result } + return messageResult(msg) + case string: + return messageResult(val) + case map[string]any: + return newRegoResult(val) + default: + return messageResult(denyMessage) } - return result } -func parseLineNumber(raw interface{}) int { - str := fmt.Sprintf("%s", raw) - n, _ := strconv.Atoi(str) +func parseLineNumber(raw any) int { + n, _ := strconv.Atoi(fmt.Sprintf("%s", raw)) return n } func (s *Scanner) convertResults(set rego.ResultSet, input Input, namespace string, rule string, traces []string) scan.Results { var results scan.Results - offset := 0 - if input.Contents != nil { - if xx, ok := input.Contents.(map[string]interface{}); ok { - if md, ok := xx["__defsec_metadata"]; ok { - if md2, ok := md.(map[string]interface{}); ok { - if sl, ok := md2["offset"]; ok { - offset, _ = sl.(int) - } - } - } - } - } + offset := input.GetOffset() + for _, result := range set { for _, expression := range result.Expressions { - values, ok := expression.Value.([]interface{}) + values, ok := expression.Value.([]any) if !ok { - values = []interface{}{expression.Value} + values = []any{expression.Value} } - for _, value := range values { regoResult := parseResult(value) regoResult.FS = input.FS @@ -151,16 +205,10 @@ func (s *Scanner) convertResults(set rego.ResultSet, input Input, namespace stri if regoResult.Message == "" { regoResult.Message = fmt.Sprintf("Rego policy rule: %s.%s", namespace, rule) } - regoResult.StartLine += offset - regoResult.EndLine += offset + regoResult.applyOffset(offset) results.AddRego(regoResult.Message, namespace, rule, traces, regoResult) } } } return results } - -func (s *Scanner) embellishResultsWithRuleMetadata(results scan.Results, metadata StaticMetadata) scan.Results { - results.SetRule(metadata.ToRule()) - return results -} diff --git a/pkg/rego/result_test.go b/pkg/rego/result_test.go index d958f7962..b39b6682c 100644 --- a/pkg/rego/result_test.go +++ b/pkg/rego/result_test.go @@ -37,7 +37,7 @@ func Test_parseResult(t *testing.T) { }, }, { - name: "maps", + name: "slice", input: []interface{}{ "message", map[string]interface{}{ @@ -51,7 +51,7 @@ func Test_parseResult(t *testing.T) { }, }, { - name: "map", + name: "legacy", input: map[string]interface{}{ "msg": "message", "filepath": "a.out", @@ -76,19 +76,51 @@ func Test_parseResult(t *testing.T) { }, }, { - name: "parent", - input: map[string]interface{}{ - "msg": "child", - "parent": map[string]interface{}{ - "msg": "parent", + name: "with parent", + input: map[string]any{ + "msg": "message", + "metadata": map[string]any{ + "filepath": "a.out", + "fskey": "abcd", + "resource": "resource", + "startline": "123", + "endline": "456", + "sourceprefix": "git", + "explicit": true, + "managed": true, + "parent": map[string]any{ + "__defsec_metadata": map[string]any{ + "filepath": "parent-a.out", + "fskey": "parent-abcd", + "resource": "parent-resource", + "startline": "234", + "endline": "345", + "sourceprefix": "parent-git", + "explicit": true, + "managed": true, + }, + }, }, }, want: regoResult{ - Message: "child", - Managed: true, + Filepath: "a.out", + FSKey: "abcd", + Resource: "resource", + StartLine: 123, + EndLine: 456, + SourcePrefix: "git", + Explicit: true, + Managed: true, + Message: "message", Parent: ®oResult{ - Message: "parent", - Managed: true, + Filepath: "parent-a.out", + FSKey: "parent-abcd", + Resource: "parent-resource", + StartLine: 234, + EndLine: 345, + SourcePrefix: "parent-git", + Explicit: true, + Managed: true, }, }, }, diff --git a/pkg/rego/scanner.go b/pkg/rego/scanner.go index 53b33bb9c..6af367637 100644 --- a/pkg/rego/scanner.go +++ b/pkg/rego/scanner.go @@ -207,6 +207,33 @@ type Input struct { Contents interface{} `json:"contents"` } +func (i Input) GetOffset() int { + if i.Contents == nil { + return 0 + } + + contents, ok := i.Contents.(map[string]any) + if !ok { + return 0 + } + rawMetadata, ok := contents["__defsec_metadata"] + if !ok { + return 0 + } + metadata, ok := rawMetadata.(map[string]any) + if !ok { + return 0 + } + rawOffset, ok := metadata["offset"] + if !ok { + return 0 + } + if offset, ok := rawOffset.(int); ok { + return offset + } + return 0 +} + func GetInputsContents(inputs []Input) []any { results := make([]any, len(inputs)) for i, c := range inputs { @@ -265,7 +292,8 @@ func (s *Scanner) ScanInput(ctx context.Context, inputs ...Input) (scan.Results, if err != nil { return nil, err } - results = append(results, s.embellishResultsWithRuleMetadata(ruleResults, *staticMeta)...) + ruleResults.SetRule(staticMeta.ToRule()) + results = append(results, ruleResults...) } }