From 4a64f1e70c6413f991f1760a5eb1d353026927c0 Mon Sep 17 00:00:00 2001 From: chenk Date: Tue, 30 Aug 2022 11:19:11 +0300 Subject: [PATCH] feat: support for dynamic metadata fields (#902) * feat: support for dynamic metadata fields Signed-off-by: chenk * test: update dynamic test Signed-off-by: chenk * fix: cyclomatic complexity Signed-off-by: chenk Signed-off-by: chenk Co-authored-by: Liam Galvin --- pkg/rego/metadata.go | 21 ++++++++++--- pkg/rego/scanner.go | 2 +- pkg/rego/scanner_test.go | 66 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 5 deletions(-) diff --git a/pkg/rego/metadata.go b/pkg/rego/metadata.go index 08edcbbf5..8f4d1cc50 100644 --- a/pkg/rego/metadata.go +++ b/pkg/rego/metadata.go @@ -76,7 +76,7 @@ func NewMetadataRetriever(compiler *ast.Compiler) *MetadataRetriever { } } -func (m *MetadataRetriever) RetrieveMetadata(ctx context.Context, module *ast.Module) (*StaticMetadata, error) { +func (m *MetadataRetriever) RetrieveMetadata(ctx context.Context, module *ast.Module, inputs ...Input) (*StaticMetadata, error) { namespace := getModuleNamespace(module) metadataQuery := fmt.Sprintf("data.%s.__rego_metadata__", namespace) @@ -96,6 +96,11 @@ func (m *MetadataRetriever) RetrieveMetadata(ctx context.Context, module *ast.Mo rego.Query(metadataQuery), rego.Compiler(m.compiler), } + // support dynamic metadata fields + for _, in := range inputs { + options = append(options, rego.Input(in.Contents)) + } + instance := rego.New(options...) set, err := instance.Eval(ctx) if err != nil { @@ -119,6 +124,15 @@ func (m *MetadataRetriever) RetrieveMetadata(ctx context.Context, module *ast.Mo return nil, fmt.Errorf("failed to parse metadata: not an object") } + err = m.updateMetadata(meta, &metadata) + if err != nil { + return nil, err + } + + return &metadata, nil +} + +func (m *MetadataRetriever) updateMetadata(meta map[string]interface{}, metadata *StaticMetadata) error { if raw, ok := meta["id"]; ok { metadata.ID = fmt.Sprintf("%s", raw) } @@ -149,14 +163,13 @@ func (m *MetadataRetriever) RetrieveMetadata(ctx context.Context, module *ast.Mo if raw, ok := meta["frameworks"]; ok { frameworks, ok := raw.(map[string][]string) if !ok { - return nil, fmt.Errorf("failed to parse framework metadata: not an object") + return fmt.Errorf("failed to parse framework metadata: not an object") } for fw, sections := range frameworks { metadata.Frameworks[framework.Framework(fw)] = sections } } - - return &metadata, nil + return nil } func (m *MetadataRetriever) queryInputOptions(ctx context.Context, module *ast.Module) InputOptions { diff --git a/pkg/rego/scanner.go b/pkg/rego/scanner.go index fd887a3dd..9f412f410 100644 --- a/pkg/rego/scanner.go +++ b/pkg/rego/scanner.go @@ -192,7 +192,7 @@ func (s *Scanner) ScanInput(ctx context.Context, inputs ...Input) (scan.Results, continue } - staticMeta, err := s.retriever.RetrieveMetadata(ctx, module) + staticMeta, err := s.retriever.RetrieveMetadata(ctx, module, inputs...) if err != nil { return nil, err } diff --git a/pkg/rego/scanner_test.go b/pkg/rego/scanner_test.go index e76d148c3..85c2db4da 100644 --- a/pkg/rego/scanner_test.go +++ b/pkg/rego/scanner_test.go @@ -678,3 +678,69 @@ deny { assert.Greater(t, len(results.GetFailed()[0].Traces()), 0) } +func Test_dynamicMetadata(t *testing.T) { + + srcFS := testutil.CreateFS(t, map[string]string{ + "policies/test.rego": ` +package defsec.test + +__rego_metadata__ := { + "title" : sprintf("i am %s",[input.text]) +} + +deny { + input.text +} + +`, + }) + + scanner := NewScanner() + require.NoError( + t, + scanner.LoadPolicies(false, srcFS, []string{"policies"}, nil), + ) + + results, err := scanner.ScanInput(context.TODO(), Input{ + Path: "/evil.lol", + Contents: map[string]interface{}{ + "text": "dynamic", + }, + Type: "???", + }) + require.NoError(t, err) + assert.Equal(t, results[0].Rule().Summary, "i am dynamic") +} +func Test_staticMetadata(t *testing.T) { + + srcFS := testutil.CreateFS(t, map[string]string{ + "policies/test.rego": ` +package defsec.test + +__rego_metadata__ := { + "title" : "i am static" +} + +deny { + input.text +} + +`, + }) + + scanner := NewScanner() + require.NoError( + t, + scanner.LoadPolicies(false, srcFS, []string{"policies"}, nil), + ) + + results, err := scanner.ScanInput(context.TODO(), Input{ + Path: "/evil.lol", + Contents: map[string]interface{}{ + "text": "test", + }, + Type: "???", + }) + require.NoError(t, err) + assert.Equal(t, results[0].Rule().Summary, "i am static") +}