From 4f14459e45dd5dd4d6c34c237e49cb6452f3f682 Mon Sep 17 00:00:00 2001 From: Yolanda Robla Mota Date: Mon, 14 Aug 2023 10:32:03 +0200 Subject: [PATCH] feat: allow to check params in artifact rules (#661) * feat: allow to check params in artifact rules When checking policies for artifacts, we may not be interested in applying to all artifacts, but to specific artifactName and/or tag. Expose those parameters and compare with entity, to only compare the ones matching the parmams. Closes: #658 * change ent type to protoreflect.ProtoMessage everywhere --- cmd/cli/app/rule_type/rtest.go | 7 +-- examples/github/policies/policy_artifact.yaml | 2 + internal/engine/rule_data_ingester.go | 46 +++++++++++++++++-- internal/engine/rule_types.go | 3 +- 4 files changed, 50 insertions(+), 8 deletions(-) diff --git a/cmd/cli/app/rule_type/rtest.go b/cmd/cli/app/rule_type/rtest.go index 7081bb5650..4382993175 100644 --- a/cmd/cli/app/rule_type/rtest.go +++ b/cmd/cli/app/rule_type/rtest.go @@ -26,6 +26,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" + "google.golang.org/protobuf/reflect/protoreflect" "github.com/stacklok/mediator/internal/engine" "github.com/stacklok/mediator/internal/util" @@ -110,7 +111,7 @@ func testCmdRun(cmd *cobra.Command, _ []string) error { return runEvaluationForRules(eng, ent, rules) } -func runEvaluationForRules(eng *engine.RuleTypeEngine, ent any, frags []*pb.PipelinePolicy_Rule) error { +func runEvaluationForRules(eng *engine.RuleTypeEngine, ent protoreflect.ProtoMessage, frags []*pb.PipelinePolicy_Rule) error { for idx := range frags { frag := frags[idx] @@ -159,7 +160,7 @@ func readRuleTypeFromFile(fpath string) (*pb.RuleType, error) { // golang structure. // TODO: We probably want to move this code to a utility once we land the server // side code. -func readEntityFromFile(fpath string, entType string) (any, error) { +func readEntityFromFile(fpath string, entType string) (protoreflect.ProtoMessage, error) { f, err := os.Open(filepath.Clean(fpath)) if err != nil { return nil, fmt.Errorf("error opening file: %w", err) @@ -171,7 +172,7 @@ func readEntityFromFile(fpath string, entType string) (any, error) { return nil, fmt.Errorf("error converting yaml to json: %w", err) } - var out any + var out protoreflect.ProtoMessage switch entType { case "repository": diff --git a/examples/github/policies/policy_artifact.yaml b/examples/github/policies/policy_artifact.yaml index ae397bb377..a2c9a7389a 100644 --- a/examples/github/policies/policy_artifact.yaml +++ b/examples/github/policies/policy_artifact.yaml @@ -10,6 +10,8 @@ artifact: - context: github rules: - type: artifact_signature + params: + tag: main def: is_signed: true is_verified: true diff --git a/internal/engine/rule_data_ingester.go b/internal/engine/rule_data_ingester.go index 315e82c4e4..0c2afd1a96 100644 --- a/internal/engine/rule_data_ingester.go +++ b/internal/engine/rule_data_ingester.go @@ -28,7 +28,9 @@ import ( "text/template" "github.com/itchyny/gojq" + "google.golang.org/protobuf/reflect/protoreflect" + "github.com/stacklok/mediator/internal/util" pb "github.com/stacklok/mediator/pkg/generated/protobuf/go/mediator/v1" ghclient "github.com/stacklok/mediator/pkg/providers/github" "github.com/stacklok/mediator/pkg/rule_methods" @@ -38,7 +40,7 @@ import ( // It allows for different mechanisms for ingesting data // in order to evaluate a rule. type RuleDataIngest interface { - Eval(ctx context.Context, ent any, pol, params map[string]any) error + Eval(ctx context.Context, ent protoreflect.ProtoMessage, pol, params map[string]any) error } // ErrEvaluationFailed is an error that occurs during evaluation of a rule. @@ -142,7 +144,7 @@ type RestEndpointTemplateParams struct { } // Eval evaluates the rule type against the given entity and policy -func (rdi *RestRuleDataIngest) Eval(ctx context.Context, ent any, pol, params map[string]any) error { +func (rdi *RestRuleDataIngest) Eval(ctx context.Context, ent protoreflect.ProtoMessage, pol, params map[string]any) error { endpoint := new(bytes.Buffer) retp := &RestEndpointTemplateParams{ Entity: ent, @@ -200,8 +202,36 @@ func (rdi *RestRuleDataIngest) Eval(ctx context.Context, ent any, pol, params ma return nil } +func entityMatchesParams(ctx context.Context, ent protoreflect.ProtoMessage, params map[string]any) (bool, error) { + // first convert to json string + jsonStr, err := util.GetJsonFromProto(ent) + if err != nil { + return false, fmt.Errorf("cannot convert entity to json: %w", err) + } + var jsonData map[string]interface{} + err = json.Unmarshal([]byte(jsonStr), &jsonData) + if err != nil { + return false, fmt.Errorf("cannot unmarshal json: %w", err) + } + for key, val := range params { + // if key does not start with dot add it + if !strings.HasPrefix(key, ".") { + key = "." + key + } + expectedVal, err := JQGetValuesFromAccessor(ctx, key, jsonData) + if err != nil { + return false, fmt.Errorf("cannot get values from data accessor: %w", err) + } + if !reflect.DeepEqual(expectedVal, val) { + // just continue, this entity is not matching our parameters + return false, nil + } + } + return true, nil +} + // Eval evaluates the rule type against the given entity and policy -func (idi *BuiltinRuleDataIngest) Eval(ctx context.Context, ent any, pol, _ map[string]any) error { +func (idi *BuiltinRuleDataIngest) Eval(ctx context.Context, ent protoreflect.ProtoMessage, pol, params map[string]any) error { // call internal method stored in pkg and method rm := rule_methods.RuleMethods{} value := reflect.ValueOf(rm) @@ -209,6 +239,14 @@ func (idi *BuiltinRuleDataIngest) Eval(ctx context.Context, ent any, pol, _ map[ // Check if the method exists if method.IsValid() { + matches, err := entityMatchesParams(ctx, ent, params) + if err != nil { + return fmt.Errorf("cannot check if entity matches params: %w", err) + } + if !matches { + log.Printf("entity not matching parameters, skipping") + return nil + } // call method // Call the method (empty parameter list) result := method.Call([]reflect.Value{reflect.ValueOf(ctx), @@ -224,7 +262,7 @@ func (idi *BuiltinRuleDataIngest) Eval(ctx context.Context, ent any, pol, _ map[ } methodResult := result[0].Interface().(json.RawMessage) var resultObj interface{} - err := json.Unmarshal(methodResult, &resultObj) + err = json.Unmarshal(methodResult, &resultObj) if err != nil { return fmt.Errorf("cannot unmarshal json: %w", err) } diff --git a/internal/engine/rule_types.go b/internal/engine/rule_types.go index a86c26d1c1..91d44e54ed 100644 --- a/internal/engine/rule_types.go +++ b/internal/engine/rule_types.go @@ -24,6 +24,7 @@ import ( "github.com/xeipuuv/gojsonschema" "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/reflect/protoreflect" "github.com/stacklok/mediator/pkg/db" pb "github.com/stacklok/mediator/pkg/generated/protobuf/go/mediator/v1" @@ -163,7 +164,7 @@ func (r *RuleTypeEngine) ValidateAgainstSchema(contextualPolicy any) error { } // Eval runs the rule type engine against the given entity -func (r *RuleTypeEngine) Eval(ctx context.Context, ent any, pol, params map[string]any) error { +func (r *RuleTypeEngine) Eval(ctx context.Context, ent protoreflect.ProtoMessage, pol, params map[string]any) error { return r.rdi.Eval(ctx, ent, pol, params) }