Skip to content

Commit

Permalink
feat: allow to check params in artifact rules (#661)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
yrobla authored Aug 14, 2023
1 parent 8107824 commit 4f14459
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 8 deletions.
7 changes: 4 additions & 3 deletions cmd/cli/app/rule_type/rtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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]

Expand Down Expand Up @@ -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)
Expand All @@ -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":
Expand Down
2 changes: 2 additions & 0 deletions examples/github/policies/policy_artifact.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ artifact:
- context: github
rules:
- type: artifact_signature
params:
tag: main
def:
is_signed: true
is_verified: true
Expand Down
46 changes: 42 additions & 4 deletions internal/engine/rule_data_ingester.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -200,15 +202,51 @@ 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)
method := value.MethodByName(idi.method)

// 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),
Expand All @@ -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)
}
Expand Down
3 changes: 2 additions & 1 deletion internal/engine/rule_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}

Expand Down

0 comments on commit 4f14459

Please sign in to comment.