Skip to content
Merged
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/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import (
"github.com/chainloop-dev/chainloop/app/cli/pkg/plugins"
v1 "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1"
"github.com/chainloop-dev/chainloop/pkg/grpcconn"
"github.com/chainloop-dev/chainloop/pkg/policies/engine/rego/builtins"
"github.com/chainloop-dev/chainloop/pkg/policies/engine/builtins"
"github.com/rs/zerolog"
"github.com/spf13/cobra"
"github.com/spf13/viper"
Expand Down
8 changes: 1 addition & 7 deletions app/cli/internal/policydevel/eval.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2025 The Chainloop Authors.
// Copyright 2024-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.
Expand All @@ -18,7 +18,6 @@ import (
"context"
"encoding/json"
"fmt"
"os"

controlplanev1 "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1"
v1 "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
Expand Down Expand Up @@ -203,8 +202,3 @@ func craft(materialPath string, kind v1.CraftingSchema_Material_MaterialType, na
}
return m, nil
}

func fileNotExists(path string) bool {
_, err := os.Stat(path)
return os.IsNotExist(err)
}
2 changes: 1 addition & 1 deletion app/cli/internal/policydevel/init.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2025 The Chainloop Authors.
// Copyright 2024-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.
Expand Down
73 changes: 59 additions & 14 deletions app/cli/internal/policydevel/lint.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2025 The Chainloop Authors.
// Copyright 2024-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.
Expand All @@ -26,7 +26,9 @@ import (

v1 "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
"github.com/chainloop-dev/chainloop/app/controlplane/pkg/unmarshal"
"github.com/chainloop-dev/chainloop/pkg/policies/engine"
"github.com/chainloop-dev/chainloop/pkg/resourceloader"
extism "github.com/extism/go-sdk"
opaAst "github.com/open-policy-agent/opa/v1/ast"
"github.com/open-policy-agent/opa/v1/format"
"github.com/styrainc/regal/pkg/config"
Expand All @@ -43,6 +45,7 @@ type PolicyToLint struct {
Path string
YAMLFiles []*File
RegoFiles []*File
WASMFiles []*File
Format bool
Config string
Errors []ValidationError
Expand Down Expand Up @@ -108,21 +111,21 @@ func Lookup(absPath, config string, format bool) (*PolicyToLint, error) {
return nil, err
}

// Load referenced rego files from all YAML files
if err := policy.loadReferencedRegoFiles(filepath.Dir(resolvedPath)); err != nil {
// Load referenced policy files (rego or wasm) from all YAML files
if err := policy.loadReferencedPolicyFiles(filepath.Dir(resolvedPath)); err != nil {
return nil, err
}

// Verify we found at least one valid file
if len(policy.YAMLFiles) == 0 && len(policy.RegoFiles) == 0 {
return nil, fmt.Errorf("no valid .yaml/.yml or .rego files found")
if len(policy.YAMLFiles) == 0 && len(policy.RegoFiles) == 0 && len(policy.WASMFiles) == 0 {
return nil, fmt.Errorf("no valid .yaml/.yml, .rego, or .wasm files found")
}

return policy, nil
}

// Loads referenced rego files from YAML files in the policy
func (p *PolicyToLint) loadReferencedRegoFiles(baseDir string) error {
// Loads referenced policy files (rego or wasm) from YAML files in the policy
func (p *PolicyToLint) loadReferencedPolicyFiles(baseDir string) error {
seen := make(map[string]struct{})
for _, yamlFile := range p.YAMLFiles {
var parsed v1.Policy
Expand All @@ -131,14 +134,14 @@ func (p *PolicyToLint) loadReferencedRegoFiles(baseDir string) error {
continue
}
for _, spec := range parsed.Spec.Policies {
regoPath := spec.GetPath()
if regoPath != "" {
policyPath := spec.GetPath()
if policyPath != "" {
// If path is relative, make it relative to the YAML file's directory
if !filepath.IsAbs(regoPath) {
regoPath = filepath.Join(baseDir, regoPath)
if !filepath.IsAbs(policyPath) {
policyPath = filepath.Join(baseDir, policyPath)
}

resolvedPath, err := resourceloader.GetPathForResource(regoPath)
resolvedPath, err := resourceloader.GetPathForResource(policyPath)
if err != nil {
return err
}
Expand All @@ -156,13 +159,21 @@ func (p *PolicyToLint) loadReferencedRegoFiles(baseDir string) error {
}

func (p *PolicyToLint) processFile(filePath string) error {
ext := strings.ToLower(filepath.Ext(filePath))

// Read file content once
content, err := os.ReadFile(filePath)
if err != nil {
return err
}

ext := strings.ToLower(filepath.Ext(filePath))
switch ext {
case ".wasm":
// Verify magic bytes
if engine.DetectPolicyType(content) != engine.PolicyTypeWASM {
return fmt.Errorf("file has .wasm extension but is not a valid WASM file")
}
p.WASMFiles = append(p.WASMFiles, &File{Path: filePath, Content: content})
case ".yaml", ".yml":
p.YAMLFiles = append(p.YAMLFiles, &File{
Path: filePath,
Expand All @@ -174,7 +185,7 @@ func (p *PolicyToLint) processFile(filePath string) error {
Content: content,
})
default:
return fmt.Errorf("unsupported file extension %s, must be .yaml/.yml or .rego", ext)
return fmt.Errorf("unsupported file extension %s, must be .yaml/.yml, .rego, or .wasm", ext)
}

return nil
Expand All @@ -185,6 +196,11 @@ func (p *PolicyToLint) Validate() {
for _, regoFile := range p.RegoFiles {
p.validateRegoFile(regoFile)
}

// Validate WASM files
for _, wasmFile := range p.WASMFiles {
p.validateWasmFile(wasmFile)
}
}

func (p *PolicyToLint) validateRegoFile(file *File) {
Expand All @@ -200,6 +216,35 @@ func (p *PolicyToLint) validateRegoFile(file *File) {
}
}

// validateWasmFile validates a WASM policy file by checking that it exports the required Execute function
func (p *PolicyToLint) validateWasmFile(file *File) {
ctx := context.Background()

// Create Extism manifest
manifest := extism.Manifest{
Wasm: []extism.Wasm{
extism.WasmData{Data: file.Content},
},
}

cfg := extism.PluginConfig{
EnableWasi: true,
}

// Create plugin
plugin, err := extism.NewPlugin(ctx, manifest, cfg, []extism.HostFunction{})
if err != nil {
p.AddError(file.Path, fmt.Sprintf("failed to load WASM module: %v", err), 0)
return
}
defer plugin.Close(ctx)

// Check if Execute function is exported
if !plugin.FunctionExists("Execute") {
p.AddError(file.Path, "WASM module missing required 'Execute' function export", 0)
}
}

func (p *PolicyToLint) validateAndFormatRego(content, path string) string {
// 1. Optionally format
if p.Format {
Expand Down
5 changes: 3 additions & 2 deletions app/cli/internal/policydevel/templates/example-policy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ spec:
embedded: |
{{.RegoContent | indent 8}}
{{else -}}
# Path to external Rego policy file
# See docs: https://docs.chainloop.dev/guides/custom-policies#rego-policy-structure
# Path to external policy file (Rego or WASM)
# Rego: https://docs.chainloop.dev/guides/custom-policies#rego-policy-structure
# WASM: https://docs.chainloop.dev/guides/custom-policies#wasm-policy-structure
path: {{.RegoPath}}
{{end -}}
6 changes: 6 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ require (
github.com/bufbuild/protoyaml-go v0.1.11
github.com/casbin/casbin/v2 v2.103.0
github.com/denisbrodbeck/machineid v1.0.1
github.com/extism/go-sdk v1.7.1
github.com/google/go-github/v66 v66.0.0
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3
Expand Down Expand Up @@ -145,6 +146,7 @@ require (
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
Expand All @@ -168,6 +170,7 @@ require (
github.com/gorilla/handlers v1.5.1 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/yamux v0.1.2 // indirect
github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jellydator/ttlcache/v3 v3.3.0 // indirect
Expand Down Expand Up @@ -209,6 +212,8 @@ require (
github.com/stoewer/go-strcase v1.3.0 // indirect
github.com/styrainc/roast v0.15.0 // indirect
github.com/tchap/go-patricia/v2 v2.3.2 // indirect
github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 // indirect
github.com/tetratelabs/wazero v1.9.0 // indirect
github.com/theupdateframework/go-tuf/v2 v2.0.1 // indirect
github.com/tklauser/go-sysconf v0.3.14 // indirect
github.com/tklauser/numcpus v0.9.0 // indirect
Expand All @@ -230,6 +235,7 @@ require (
go.opentelemetry.io/otel/metric v1.36.0 // indirect
go.opentelemetry.io/otel/sdk v1.36.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect
go.opentelemetry.io/proto/otlp v1.6.0 // indirect
go.step.sm/crypto v0.51.2 // indirect
goa.design/goa v2.2.5+incompatible // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
Expand Down
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,8 @@ github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a h1:UwSIFv5g5lIvbGgtf3tVwC7Ky9rmMFBp0RMs+6f6YqE=
github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a/go.mod h1:C8DzXehI4zAbrdlbtOByKX6pfivJTBiV9Jjqv56Yd9Q=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
Expand Down Expand Up @@ -406,6 +408,8 @@ github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJP
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
github.com/extism/go-sdk v1.7.1 h1:lWJos6uY+tRFdlIHR+SJjwFDApY7OypS/2nMhiVQ9Sw=
github.com/extism/go-sdk v1.7.1/go.mod h1:IT+Xdg5AZM9hVtpFUA+uZCJMge/hbvshl8bwzLtFyKA=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
Expand Down Expand Up @@ -724,6 +728,8 @@ github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJ
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca h1:T54Ema1DU8ngI+aef9ZhAhNGQhcRTrWxVeG07F+c/Rw=
github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ=
Expand Down Expand Up @@ -1227,6 +1233,10 @@ github.com/tchap/go-patricia/v2 v2.3.2 h1:xTHFutuitO2zqKAQ5rCROYgUb7Or/+IC3fts9/
github.com/tchap/go-patricia/v2 v2.3.2/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
github.com/testcontainers/testcontainers-go v0.35.0 h1:uADsZpTKFAtp8SLK+hMwSaa+X+JiERHtd4sQAFmXeMo=
github.com/testcontainers/testcontainers-go v0.35.0/go.mod h1:oEVBj5zrfJTrgjwONs1SsRbnBtH9OKl+IGl3UMcr2B4=
github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 h1:ZF+QBjOI+tILZjBaFj3HgFonKXUcwgJ4djLb6i42S3Q=
github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834/go.mod h1:m9ymHTgNSEjuxvw8E7WWe4Pl4hZQHXONY8wE6dMLaRk=
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg=
github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU=
github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI=
Expand Down
46 changes: 46 additions & 0 deletions pkg/policies/engine/builtins/discover.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// 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 builtins

import (
"context"

v1 "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1"
"google.golang.org/grpc"
)

// DiscoverService wraps the gRPC discover functionality to be shared across engines
type DiscoverService struct {
conn *grpc.ClientConn
}

// NewDiscoverService creates a new discover service
func NewDiscoverService(conn *grpc.ClientConn) *DiscoverService {
return &DiscoverService{conn: conn}
}

// Discover calls the DiscoverPrivate gRPC endpoint to get artifact graph data
func (s *DiscoverService) Discover(ctx context.Context, digest, kind string) (*v1.ReferrerServiceDiscoverPrivateResponse, error) {
if s.conn == nil {
return nil, nil
}

client := v1.NewReferrerServiceClient(s.conn)
return client.DiscoverPrivate(ctx, &v1.ReferrerServiceDiscoverPrivateRequest{
Digest: digest,
Kind: kind,
})
}
Loading
Loading