From 66bde047ba604ff163d07318bce686371146bc4b Mon Sep 17 00:00:00 2001 From: Sylwester Piskozub Date: Fri, 7 Nov 2025 16:24:01 +0100 Subject: [PATCH 1/2] feat(policy-devel): allow external policy references Signed-off-by: Sylwester Piskozub --- app/cli/cmd/policy_develop_eval.go | 4 +-- app/cli/documentation/cli-reference.mdx | 4 +-- app/cli/internal/policydevel/eval.go | 32 +++++++++++++++-------- app/cli/pkg/action/policy_develop_eval.go | 22 +++++++++++----- pkg/policies/loader.go | 6 ++--- pkg/policies/policies.go | 2 +- pkg/policies/policy_groups.go | 2 +- 7 files changed, 45 insertions(+), 27 deletions(-) diff --git a/app/cli/cmd/policy_develop_eval.go b/app/cli/cmd/policy_develop_eval.go index 027ae5907..0cccd4052 100644 --- a/app/cli/cmd/policy_develop_eval.go +++ b/app/cli/cmd/policy_develop_eval.go @@ -44,7 +44,7 @@ The command checks if there is a path in the policy for the specified kind and evaluates the policy against the provided material or attestation.`, Example: ` # Evaluate policy against a material file - chainloop policy eval --policy policy.yaml --material sbom.json --kind SBOM_CYCLONEDX_JSON --annotation key1=value1,key2=value2 --input key3=value3`, + chainloop policy develop eval --policy policy.yaml --material sbom.json --kind SBOM_CYCLONEDX_JSON --annotation key1=value1,key2=value2 --input key3=value3`, RunE: func(_ *cobra.Command, _ []string) error { opts := &action.PolicyEvalOpts{ MaterialPath: materialPath, @@ -74,7 +74,7 @@ evaluates the policy against the provided material or attestation.`, cobra.CheckErr(cmd.MarkFlagRequired("material")) cmd.Flags().StringVar(&kind, "kind", "", fmt.Sprintf("Kind of the material: %q", schemaapi.ListAvailableMaterialKind())) cmd.Flags().StringSliceVar(&annotations, "annotation", []string{}, "Key-value pairs of material annotations (key=value)") - cmd.Flags().StringVarP(&policyPath, "policy", "p", "policy.yaml", "Path to custom policy file") + cmd.Flags().StringVarP(&policyPath, "policy", "p", "policy.yaml", "Policy reference (file://, http://, https://, chainloop://)") cmd.Flags().StringArrayVar(&inputs, "input", []string{}, "Key-value pairs of policy inputs (key=value)") cmd.Flags().StringSliceVar(&allowedHostnames, "allowed-hostnames", []string{}, "Additional hostnames allowed for http.send requests in policies") cmd.Flags().BoolVarP(&debug, "debug", "", false, "Include detailed evaluation inputs/outputs in JSON output and enable verbose logging") diff --git a/app/cli/documentation/cli-reference.mdx b/app/cli/documentation/cli-reference.mdx index 4cdef395f..863b8f845 100755 --- a/app/cli/documentation/cli-reference.mdx +++ b/app/cli/documentation/cli-reference.mdx @@ -2858,7 +2858,7 @@ Examples ``` Evaluate policy against a material file -chainloop policy eval --policy policy.yaml --material sbom.json --kind SBOM_CYCLONEDX_JSON --annotation key1=value1,key2=value2 --input key3=value3 +chainloop policy develop eval --policy policy.yaml --material sbom.json --kind SBOM_CYCLONEDX_JSON --annotation key1=value1,key2=value2 --input key3=value3 ``` Options @@ -2871,7 +2871,7 @@ Options --input stringArray Key-value pairs of policy inputs (key=value) --kind string Kind of the material: ["ARTIFACT" "ATTESTATION" "BLACKDUCK_SCA_JSON" "CHAINLOOP_RUNNER_CONTEXT" "CONTAINER_IMAGE" "CSAF_INFORMATIONAL_ADVISORY" "CSAF_SECURITY_ADVISORY" "CSAF_SECURITY_INCIDENT_RESPONSE" "CSAF_VEX" "EVIDENCE" "GHAS_CODE_SCAN" "GHAS_DEPENDENCY_SCAN" "GHAS_SECRET_SCAN" "GITLAB_SECURITY_REPORT" "HELM_CHART" "JACOCO_XML" "JUNIT_XML" "OPENVEX" "SARIF" "SBOM_CYCLONEDX_JSON" "SBOM_SPDX_JSON" "SLSA_PROVENANCE" "STRING" "TWISTCLI_SCAN_JSON" "ZAP_DAST_ZIP"] --material string Path to material or attestation file --p, --policy string Path to custom policy file (default "policy.yaml") +-p, --policy string Policy reference (file://, http://, https://, chainloop://) (default "policy.yaml") ``` Options inherited from parent commands diff --git a/app/cli/internal/policydevel/eval.go b/app/cli/internal/policydevel/eval.go index f5a2c80bb..48c5dee89 100644 --- a/app/cli/internal/policydevel/eval.go +++ b/app/cli/internal/policydevel/eval.go @@ -20,6 +20,7 @@ import ( "fmt" "os" + controlplanev1 "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1" v1 "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" "github.com/chainloop-dev/chainloop/pkg/casclient" "github.com/chainloop-dev/chainloop/pkg/policies" @@ -34,13 +35,14 @@ const ( ) type EvalOptions struct { - PolicyPath string - MaterialKind string - Annotations map[string]string - MaterialPath string - Inputs map[string]string - AllowedHostnames []string - Debug bool + PolicyPath string + MaterialKind string + Annotations map[string]string + MaterialPath string + Inputs map[string]string + AllowedHostnames []string + Debug bool + AttestationClient controlplanev1.AttestationServiceClient } type EvalResult struct { @@ -74,7 +76,7 @@ func Evaluate(opts *EvalOptions, logger zerolog.Logger) (*EvalSummary, error) { material.Annotations = opts.Annotations // 3. Verify material against policy - summary, err := verifyMaterial(policies, material, opts.MaterialPath, opts.Debug, opts.AllowedHostnames, &logger) + summary, err := verifyMaterial(policies, material, opts.MaterialPath, opts.Debug, opts.AllowedHostnames, opts.AttestationClient, &logger) if err != nil { return nil, err } @@ -83,10 +85,18 @@ func Evaluate(opts *EvalOptions, logger zerolog.Logger) (*EvalSummary, error) { } func createPolicies(policyPath string, inputs map[string]string) (*v1.Policies, error) { + // Check if the policy path already has a scheme (chainloop://, http://, https://, file://) + ref := policyPath + scheme, _ := policies.RefParts(policyPath) + if scheme == "" { + // Default to file:// + ref = fmt.Sprintf("file://%s", policyPath) + } + return &v1.Policies{ Materials: []*v1.PolicyAttachment{ { - Policy: &v1.PolicyAttachment_Ref{Ref: fmt.Sprintf("file://%s", policyPath)}, + Policy: &v1.PolicyAttachment_Ref{Ref: ref}, With: inputs, }, }, @@ -94,7 +104,7 @@ func createPolicies(policyPath string, inputs map[string]string) (*v1.Policies, }, nil } -func verifyMaterial(pol *v1.Policies, material *v12.Attestation_Material, materialPath string, debug bool, allowedHostnames []string, logger *zerolog.Logger) (*EvalSummary, error) { +func verifyMaterial(pol *v1.Policies, material *v12.Attestation_Material, materialPath string, debug bool, allowedHostnames []string, attestationClient controlplanev1.AttestationServiceClient, logger *zerolog.Logger) (*EvalSummary, error) { var opts []policies.PolicyVerifierOption if len(allowedHostnames) > 0 { opts = append(opts, policies.WithAllowedHostnames(allowedHostnames...)) @@ -103,7 +113,7 @@ func verifyMaterial(pol *v1.Policies, material *v12.Attestation_Material, materi opts = append(opts, policies.WithIncludeRawData(debug)) opts = append(opts, policies.WithEnablePrint(enablePrint)) - v := policies.NewPolicyVerifier(pol, nil, logger, opts...) + v := policies.NewPolicyVerifier(pol, attestationClient, logger, opts...) policyEvs, err := v.VerifyMaterial(context.Background(), material, materialPath) if err != nil { return nil, err diff --git a/app/cli/pkg/action/policy_develop_eval.go b/app/cli/pkg/action/policy_develop_eval.go index 708e28446..1a89d416d 100644 --- a/app/cli/pkg/action/policy_develop_eval.go +++ b/app/cli/pkg/action/policy_develop_eval.go @@ -16,6 +16,8 @@ package action import ( + pb "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1" + "github.com/chainloop-dev/chainloop/app/cli/internal/policydevel" ) @@ -42,14 +44,20 @@ func NewPolicyEval(opts *PolicyEvalOpts, actionOpts *ActionsOpts) (*PolicyEval, } func (action *PolicyEval) Run() (*policydevel.EvalSummary, error) { + var attClient pb.AttestationServiceClient + if action.CPConnection != nil { + attClient = pb.NewAttestationServiceClient(action.CPConnection) + } + evalOpts := &policydevel.EvalOptions{ - PolicyPath: action.opts.PolicyPath, - MaterialKind: action.opts.Kind, - Annotations: action.opts.Annotations, - MaterialPath: action.opts.MaterialPath, - Inputs: action.opts.Inputs, - AllowedHostnames: action.opts.AllowedHostnames, - Debug: action.opts.Debug, + PolicyPath: action.opts.PolicyPath, + MaterialKind: action.opts.Kind, + Annotations: action.opts.Annotations, + MaterialPath: action.opts.MaterialPath, + Inputs: action.opts.Inputs, + AllowedHostnames: action.opts.AllowedHostnames, + Debug: action.opts.Debug, + AttestationClient: attClient, } // Evaluate policy diff --git a/pkg/policies/loader.go b/pkg/policies/loader.go index 040661dc9..995bd5461 100644 --- a/pkg/policies/loader.go +++ b/pkg/policies/loader.go @@ -244,7 +244,7 @@ func unmarshallResource(raw []byte, ref string, digest string, dest proto.Messag // IsProviderScheme takes a policy reference and returns whether it's referencing to an external provider or not func IsProviderScheme(ref string) bool { - scheme, _ := refParts(ref) + scheme, _ := RefParts(ref) return scheme == chainloopScheme || scheme == "" } @@ -306,7 +306,7 @@ func ProviderParts(reference string) *ProviderRef { } func ensureScheme(ref string, expected ...string) (string, error) { - scheme, id := refParts(ref) + scheme, id := RefParts(ref) for _, ex := range expected { if scheme == ex { return id, nil @@ -316,7 +316,7 @@ func ensureScheme(ref string, expected ...string) (string, error) { return "", fmt.Errorf("unexpected policy reference scheme: %q", scheme) } -func refParts(ref string) (string, string) { +func RefParts(ref string) (string, string) { parts := strings.SplitN(ref, "://", 2) if len(parts) == 2 { return parts[0], parts[1] diff --git a/pkg/policies/policies.go b/pkg/policies/policies.go index c92beeb45..e9f08c94c 100644 --- a/pkg/policies/policies.go +++ b/pkg/policies/policies.go @@ -406,7 +406,7 @@ func (pv *PolicyVerifier) getLoader(attachment *v1.PolicyAttachment) (Loader, er } var loader Loader - scheme, _ := refParts(ref) + scheme, _ := RefParts(ref) switch scheme { // No scheme means chainloop loader case chainloopScheme, "": diff --git a/pkg/policies/policy_groups.go b/pkg/policies/policy_groups.go index c39857529..5d7c241d6 100644 --- a/pkg/policies/policy_groups.go +++ b/pkg/policies/policy_groups.go @@ -192,7 +192,7 @@ func getGroupLoader(attachment *v1.PolicyGroupAttachment, opts *LoadPolicyGroupO } var loader GroupLoader - scheme, _ := refParts(ref) + scheme, _ := RefParts(ref) switch scheme { // No scheme means chainloop loader case chainloopScheme, "": From a529157467ec8aa63bd65826c669d22b7ece4532 Mon Sep 17 00:00:00 2001 From: Sylwester Piskozub Date: Mon, 10 Nov 2025 16:37:34 +0100 Subject: [PATCH 2/2] update examples Signed-off-by: Sylwester Piskozub --- app/cli/cmd/policy_develop_eval.go | 2 +- app/cli/documentation/cli-reference.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/cli/cmd/policy_develop_eval.go b/app/cli/cmd/policy_develop_eval.go index 0cccd4052..747709e29 100644 --- a/app/cli/cmd/policy_develop_eval.go +++ b/app/cli/cmd/policy_develop_eval.go @@ -74,7 +74,7 @@ evaluates the policy against the provided material or attestation.`, cobra.CheckErr(cmd.MarkFlagRequired("material")) cmd.Flags().StringVar(&kind, "kind", "", fmt.Sprintf("Kind of the material: %q", schemaapi.ListAvailableMaterialKind())) cmd.Flags().StringSliceVar(&annotations, "annotation", []string{}, "Key-value pairs of material annotations (key=value)") - cmd.Flags().StringVarP(&policyPath, "policy", "p", "policy.yaml", "Policy reference (file://, http://, https://, chainloop://)") + cmd.Flags().StringVarP(&policyPath, "policy", "p", "policy.yaml", "Policy reference (./my-policy.yaml, https://my-domain.com/my-policy.yaml, chainloop://my-stored-policy)") cmd.Flags().StringArrayVar(&inputs, "input", []string{}, "Key-value pairs of policy inputs (key=value)") cmd.Flags().StringSliceVar(&allowedHostnames, "allowed-hostnames", []string{}, "Additional hostnames allowed for http.send requests in policies") cmd.Flags().BoolVarP(&debug, "debug", "", false, "Include detailed evaluation inputs/outputs in JSON output and enable verbose logging") diff --git a/app/cli/documentation/cli-reference.mdx b/app/cli/documentation/cli-reference.mdx index 863b8f845..b7d4638a1 100755 --- a/app/cli/documentation/cli-reference.mdx +++ b/app/cli/documentation/cli-reference.mdx @@ -2871,7 +2871,7 @@ Options --input stringArray Key-value pairs of policy inputs (key=value) --kind string Kind of the material: ["ARTIFACT" "ATTESTATION" "BLACKDUCK_SCA_JSON" "CHAINLOOP_RUNNER_CONTEXT" "CONTAINER_IMAGE" "CSAF_INFORMATIONAL_ADVISORY" "CSAF_SECURITY_ADVISORY" "CSAF_SECURITY_INCIDENT_RESPONSE" "CSAF_VEX" "EVIDENCE" "GHAS_CODE_SCAN" "GHAS_DEPENDENCY_SCAN" "GHAS_SECRET_SCAN" "GITLAB_SECURITY_REPORT" "HELM_CHART" "JACOCO_XML" "JUNIT_XML" "OPENVEX" "SARIF" "SBOM_CYCLONEDX_JSON" "SBOM_SPDX_JSON" "SLSA_PROVENANCE" "STRING" "TWISTCLI_SCAN_JSON" "ZAP_DAST_ZIP"] --material string Path to material or attestation file --p, --policy string Policy reference (file://, http://, https://, chainloop://) (default "policy.yaml") +-p, --policy string Policy reference (./my-policy.yaml, https://my-domain.com/my-policy.yaml, chainloop://my-stored-policy) (default "policy.yaml") ``` Options inherited from parent commands