diff --git a/app/cli/internal/policydevel/eval.go b/app/cli/internal/policydevel/eval.go index 92fbb177e..6148cb69c 100644 --- a/app/cli/internal/policydevel/eval.go +++ b/app/cli/internal/policydevel/eval.go @@ -29,6 +29,10 @@ import ( "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/materials" ) +const ( + enablePrint = true +) + type EvalOptions struct { PolicyPath string MaterialKind string @@ -100,6 +104,7 @@ func verifyMaterial(schema *v1.CraftingSchema, material *v12.Attestation_Materia } opts = append(opts, policies.WithIncludeRawData(debug)) + opts = append(opts, policies.WithEnablePrint(enablePrint)) v := policies.NewPolicyVerifier(schema, nil, logger, opts...) policyEvs, err := v.VerifyMaterial(context.Background(), material, materialPath) diff --git a/pkg/policies/engine/rego/rego.go b/pkg/policies/engine/rego/rego.go index a71e018fa..763cfb404 100644 --- a/pkg/policies/engine/rego/rego.go +++ b/pkg/policies/engine/rego/rego.go @@ -24,6 +24,7 @@ import ( "github.com/chainloop-dev/chainloop/pkg/policies/engine" "github.com/open-policy-agent/opa/ast" "github.com/open-policy-agent/opa/rego" + "github.com/open-policy-agent/opa/v1/topdown/print" "golang.org/x/exp/maps" ) @@ -37,6 +38,8 @@ type Engine struct { allowedNetworkDomains []string // includeRawData determines whether to collect raw evaluation data includeRawData bool + // enablePrint determines whether to enable print statements in rego policies + enablePrint bool } type EngineOption func(*newEngineOptions) @@ -59,10 +62,17 @@ func WithIncludeRawData(include bool) EngineOption { } } +func WithEnablePrint(enable bool) EngineOption { + return func(e *newEngineOptions) { + e.enablePrint = enable + } +} + type newEngineOptions struct { operatingMode EnvironmentMode allowedNetworkDomains []string includeRawData bool + enablePrint bool } // NewEngine creates a new policy engine with the given options @@ -88,6 +98,7 @@ func NewEngine(opts ...EngineOption) *Engine { // append base allowed network domains to the user provided ones allowedNetworkDomains: append(baseAllowedNetworkDomains, options.allowedNetworkDomains...), includeRawData: options.includeRawData, + enablePrint: options.enablePrint, } } @@ -112,6 +123,15 @@ var builtinFuncNotAllowed = []*ast.Builtin{ ast.Trace, } +// Implements the OPA print.Hook interface to capture and output +// print statements from Rego policies during evaluation. +type regoOutputHook struct{} + +func (p *regoOutputHook) Print(_ print.Context, msg string) error { //nolint:forbidigo + fmt.Println(msg) + return nil +} + // Force interface var _ engine.PolicyEngine = (*Engine)(nil) @@ -156,11 +176,21 @@ func (r *Engine) Verify(ctx context.Context, policy *engine.Policy, input []byte var res rego.ResultSet // Function to execute the query with appropriate parameters executeQuery := func(rule string, strict bool) error { + options := []func(r *rego.Rego){regoInput, regoFunc, rego.Capabilities(r.Capabilities())} + + // Add print support if enabled + if r.enablePrint { + options = append(options, + rego.EnablePrintStatements(true), + rego.PrintHook(®oOutputHook{}), + ) + } + if strict { - res, err = queryRego(ctx, rule, regoInput, regoFunc, rego.Capabilities(r.Capabilities()), rego.StrictBuiltinErrors(true)) - } else { - res, err = queryRego(ctx, rule, regoInput, regoFunc, rego.Capabilities(r.Capabilities())) + options = append(options, rego.StrictBuiltinErrors(true)) } + + res, err = queryRego(ctx, rule, options...) return err } diff --git a/pkg/policies/policies.go b/pkg/policies/policies.go index d9c72b148..7b173bbf6 100644 --- a/pkg/policies/policies.go +++ b/pkg/policies/policies.go @@ -66,6 +66,7 @@ type PolicyVerifier struct { client v13.AttestationServiceClient allowedHostnames []string includeRawData bool + enablePrint bool } var _ Verifier = (*PolicyVerifier)(nil) @@ -73,6 +74,7 @@ var _ Verifier = (*PolicyVerifier)(nil) type PolicyVerifierOptions struct { AllowedHostnames []string IncludeRawData bool + EnablePrint bool } type PolicyVerifierOption func(*PolicyVerifierOptions) @@ -89,6 +91,12 @@ func WithIncludeRawData(include bool) PolicyVerifierOption { } } +func WithEnablePrint(enable bool) PolicyVerifierOption { + return func(o *PolicyVerifierOptions) { + o.EnablePrint = enable + } +} + func NewPolicyVerifier(schema *v1.CraftingSchema, client v13.AttestationServiceClient, logger *zerolog.Logger, opts ...PolicyVerifierOption) *PolicyVerifier { options := &PolicyVerifierOptions{} for _, opt := range opts { @@ -101,6 +109,7 @@ func NewPolicyVerifier(schema *v1.CraftingSchema, client v13.AttestationServiceC logger: logger, allowedHostnames: options.AllowedHostnames, includeRawData: options.IncludeRawData, + enablePrint: options.EnablePrint, } } @@ -349,6 +358,10 @@ func (pv *PolicyVerifier) executeScript(ctx context.Context, script *engine.Poli engineOpts = append(engineOpts, rego.WithIncludeRawData(true)) } + if pv.enablePrint { + engineOpts = append(engineOpts, rego.WithEnablePrint(true)) + } + // verify the policy ng := rego.NewEngine(engineOpts...) res, err := ng.Verify(ctx, script, material, getInputArguments(args))