WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/cli/cmd/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ func newPolicyCmd() *cobra.Command {
Short: "Craft chainloop policies",
}

cmd.AddCommand(newPolicyDevelopCmd())
cmd.AddCommand(newPolicyEvalCmd(), newPolicyDevelopCmd())
return cmd
}
80 changes: 80 additions & 0 deletions app/cli/cmd/policy_eval.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//
// Copyright 2025 The Chainloop Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
"fmt"

"github.com/chainloop-dev/chainloop/app/cli/cmd/output"
"github.com/chainloop-dev/chainloop/app/cli/pkg/action"
schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
"github.com/spf13/cobra"
)

func newPolicyEvalCmd() *cobra.Command {
var (
materialPath string
kind string
annotations []string
policyPath string
inputs []string
)

cmd := &cobra.Command{
Use: "eval",
Short: "Evaluate a policy",
Long: `Evaluate a policy with organization settings.

This command uses organization context to evaluate policies.

For offline development and testing with debug capabilities, use 'chainloop policy develop eval' instead.`,
Example: `
chainloop policy eval --policy policy.yaml --input digest=sha256:80058e45a56daa50ae2a130bd1bd13b1fb9aff13a55b2d98615fff6eb3b0fffb`,
Annotations: map[string]string{
useAPIToken: trueString,
},
RunE: func(cmd *cobra.Command, _ []string) error {
opts := &action.PolicyEvaluateOpts{
MaterialPath: materialPath,
Kind: kind,
Annotations: parseKeyValue(annotations),
PolicyPath: policyPath,
Inputs: parseKeyValue(inputs),
}

policyEval, err := action.NewPolicyEvaluate(opts, ActionOpts)
if err != nil {
return err
}

result, err := policyEval.Run(cmd.Context())
if err != nil {
return err
}

return output.EncodeJSON(result)
},
}

cmd.Flags().StringVar(&materialPath, "material", "", "Path to material or attestation file")
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 reference (./my-policy.yaml, https://my-domain.com/my-policy.yaml)")
cobra.CheckErr(cmd.MarkFlagRequired("policy"))
cmd.Flags().StringArrayVar(&inputs, "input", []string{}, "Key-value pairs of policy inputs (key=value)")

return cmd
}
50 changes: 50 additions & 0 deletions app/cli/documentation/cli-reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3017,6 +3017,56 @@ Options inherited from parent commands
-y, --yes Skip confirmation
```

### chainloop policy eval

Evaluate a policy

Synopsis

Evaluate a policy with organization settings.

This command uses organization context to evaluate policies.

For offline development and testing with debug capabilities, use 'chainloop policy develop eval' instead.

```
chainloop policy eval [flags]
```

Examples

```

chainloop policy eval --policy policy.yaml --input digest=sha256:80058e45a56daa50ae2a130bd1bd13b1fb9aff13a55b2d98615fff6eb3b0fffb
```

Options

```
--annotation strings Key-value pairs of material annotations (key=value)
-h, --help help for eval
--input stringArray Key-value pairs of policy inputs (key=value)
--kind string Kind of the material: ["ARTIFACT" "ATTESTATION" "BLACKDUCK_SCA_JSON" "CHAINLOOP_PR_INFO" "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 (./my-policy.yaml, https://my-domain.com/my-policy.yaml)
```

Options inherited from parent commands

```
--artifact-cas string URL for the Artifacts Content Addressable Storage API ($CHAINLOOP_ARTIFACT_CAS_API) (default "api.cas.chainloop.dev:443")
--artifact-cas-ca string CUSTOM CA file for the Artifacts CAS API (optional) ($CHAINLOOP_ARTIFACT_CAS_API_CA)
-c, --config string Path to an existing config file (default is $HOME/.config/chainloop/config.toml)
--control-plane string URL for the Control Plane API ($CHAINLOOP_CONTROL_PLANE_API) (default "api.cp.chainloop.dev:443")
--control-plane-ca string CUSTOM CA file for the Control Plane API (optional) ($CHAINLOOP_CONTROL_PLANE_API_CA)
--debug Enable debug/verbose logging mode
-i, --insecure Skip TLS transport during connection to the control plane ($CHAINLOOP_API_INSECURE)
-n, --org string organization name
-o, --output string Output format, valid options are json and table (default "table")
-t, --token string API token. NOTE: Alternatively use the env variable CHAINLOOP_TOKEN
-y, --yes Skip confirmation
```

### chainloop policy help

Help about any command
Expand Down
6 changes: 4 additions & 2 deletions app/cli/internal/policydevel/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func Evaluate(opts *EvalOptions, logger zerolog.Logger) (*EvalSummary, error) {
}

// 2. Craft material with annotations
material, err := craftMaterial(opts.MaterialPath, opts.MaterialKind, &logger)
material, err := CraftMaterial(opts.MaterialPath, opts.MaterialKind, &logger)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -166,7 +166,9 @@ func verifyMaterial(pol *v1.Policies, material *v12.Attestation_Material, materi
return summary, nil
}

func craftMaterial(materialPath, materialKind string, logger *zerolog.Logger) (*v12.Attestation_Material, error) {
// CraftMaterial creates an attestation material from a file path, with optional explicit kind or auto-detection.
// This is a shared utility function used by both policy eval and policy devel eval commands.
func CraftMaterial(materialPath, materialKind string, logger *zerolog.Logger) (*v12.Attestation_Material, error) {
backend := &casclient.CASBackend{
Name: "backend",
MaxSize: 0,
Expand Down
138 changes: 138 additions & 0 deletions app/cli/pkg/action/policy_eval.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
//
// Copyright 2025 The Chainloop Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package action

import (
"context"
"fmt"

"github.com/chainloop-dev/chainloop/app/cli/internal/policydevel"
pb "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1"
schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
attestationapi "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1"
"github.com/chainloop-dev/chainloop/pkg/policies"
)

type PolicyEvaluateOpts struct {
MaterialPath string
Kind string
Annotations map[string]string
PolicyPath string
Inputs map[string]string
}

type PolicyEvaluate struct {
*ActionsOpts
opts *PolicyEvaluateOpts
}

func NewPolicyEvaluate(opts *PolicyEvaluateOpts, actionOpts *ActionsOpts) (*PolicyEvaluate, error) {
if actionOpts.CPConnection == nil {
return nil, fmt.Errorf("control plane connection is required")
}

return &PolicyEvaluate{
ActionsOpts: actionOpts,
opts: opts,
}, nil
}

func (action *PolicyEvaluate) Run(ctx context.Context) (*attestationapi.PolicyEvaluation, error) {
// 1. Get organization settings
contextClient := pb.NewContextServiceClient(action.CPConnection)
contextResp, err := contextClient.Current(ctx, &pb.ContextServiceCurrentRequest{})
if err != nil {
return nil, fmt.Errorf("fetching organization settings: %w", err)
}

if contextResp.Result == nil || contextResp.Result.CurrentMembership == nil || contextResp.Result.CurrentMembership.Org == nil {
return nil, fmt.Errorf("no organization context found")
}

org := contextResp.Result.CurrentMembership.Org
allowedHostnames := org.PolicyAllowedHostnames

// 2. Create policy attachment
ref := action.opts.PolicyPath
scheme, _ := policies.RefParts(action.opts.PolicyPath)
if scheme == "" {
// If no scheme, assume it's a file path and add file:// prefix
ref = fmt.Sprintf("file://%s", action.opts.PolicyPath)
}

policyAttachment := &schemaapi.PolicyAttachment{
Policy: &schemaapi.PolicyAttachment_Ref{Ref: ref},
With: action.opts.Inputs,
}

// 3. Create policies structure based on whether we have a material
var pol *schemaapi.Policies
if action.opts.MaterialPath != "" {
// Material-based evaluation
pol = &schemaapi.Policies{
Materials: []*schemaapi.PolicyAttachment{policyAttachment},
}
} else {
// Generic evaluation
pol = &schemaapi.Policies{}
}

// 4. Create policy verifier with organization's allowed hostnames
verifierOpts := []policies.PolicyVerifierOption{
policies.WithIncludeRawData(false),
policies.WithEnablePrint(false),
policies.WithGRPCConn(action.CPConnection),
}
if len(allowedHostnames) > 0 {
verifierOpts = append(verifierOpts, policies.WithAllowedHostnames(allowedHostnames...))
}

attClient := pb.NewAttestationServiceClient(action.CPConnection)
verifier := policies.NewPolicyVerifier(pol, attClient, &action.Logger, verifierOpts...)

// 5. Evaluate: either material-based or generic
if action.opts.MaterialPath != "" {
// Material-based evaluation
material, err := policydevel.CraftMaterial(action.opts.MaterialPath, action.opts.Kind, &action.Logger)
if err != nil {
return nil, fmt.Errorf("crafting material: %w", err)
}
material.Annotations = action.opts.Annotations

policyEvs, err := verifier.VerifyMaterial(ctx, material, action.opts.MaterialPath)
if err != nil {
return nil, fmt.Errorf("evaluating policy against material: %w", err)
}

if len(policyEvs) == 0 || policyEvs[0] == nil {
return nil, fmt.Errorf("no execution branch matched, or all of them were ignored, for kind %s", material.MaterialType.String())
}

return policyEvs[0], nil
}

// Generic evaluation
policyEv, err := verifier.EvaluateGeneric(ctx, policyAttachment)
if err != nil {
return nil, fmt.Errorf("evaluating policy: %w", err)
}

if policyEv == nil {
return nil, fmt.Errorf("no execution branch matched, or all of them were ignored")
}

return policyEv, nil
}
19 changes: 19 additions & 0 deletions pkg/policies/policies.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,25 @@ func (pv *PolicyVerifier) VerifyMaterial(ctx context.Context, material *v12.Atte
return result, nil
}

// EvaluateGeneric evaluates a single policy attachment.
func (pv *PolicyVerifier) EvaluateGeneric(ctx context.Context, attachment *v1.PolicyAttachment) (*v12.PolicyEvaluation, error) {
// Use empty JSON as material input
input := []byte("{}")

// Evaluate without material context
ev, err := pv.evaluatePolicyAttachment(ctx, attachment, input,
&evalOpts{
kind: v1.CraftingSchema_Material_MATERIAL_TYPE_UNSPECIFIED,
name: "",
},
)
if err != nil {
return nil, NewPolicyError(err)
}

return ev, nil
}

type evalOpts struct {
name string
kind v1.CraftingSchema_Material_MaterialType
Expand Down
Loading