From e88762df2692a29b1c305eed369fd9fe20c4237c Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Thu, 22 Jan 2026 11:32:45 -0800 Subject: [PATCH] feat(pull): implement pull command with policy verification Add the pull command to download and extract blob archives from OCI registries with optional policy-based verification. Features: - Pull archives from OCI registries with alias resolution - Policy verification: Sigstore keyless signatures, SLSA provenance - Policy sources: config file, YAML files (--policy), OPA Rego (--policy-rego) - Output formats: text and JSON (--output json) - Quiet mode support - Directory handling: creates destination if needed, skips existing files New files: - internal/policy/loader.go: YAML policy file parsing - internal/policy/builder.go: Convert config policies to registry policies - internal/policy/policy_test.go: Policy package tests - cmd/pull_test.go: Pull command tests Co-Authored-By: Claude Opus 4.5 --- cmd/pull.go | 218 +++++++++++++++++++++++- cmd/pull_test.go | 250 ++++++++++++++++++++++++++++ go.mod | 38 ++++- go.sum | 123 ++++++++++++-- internal/policy/builder.go | 160 ++++++++++++++++++ internal/policy/loader.go | 104 ++++++++++++ internal/policy/policy_test.go | 292 +++++++++++++++++++++++++++++++++ 7 files changed, 1167 insertions(+), 18 deletions(-) create mode 100644 cmd/pull_test.go create mode 100644 internal/policy/builder.go create mode 100644 internal/policy/loader.go create mode 100644 internal/policy/policy_test.go diff --git a/cmd/pull.go b/cmd/pull.go index b95e1ee..f1d0541 100644 --- a/cmd/pull.go +++ b/cmd/pull.go @@ -1,7 +1,19 @@ package cmd import ( + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/meigma/blob" "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/meigma/blob-cli/internal/archive" + internalcfg "github.com/meigma/blob-cli/internal/config" + "github.com/meigma/blob-cli/internal/policy" ) var pullCmd = &cobra.Command{ @@ -19,14 +31,212 @@ attestation requirements before extraction.`, blob pull --policy policy.yaml ghcr.io/acme/configs:v1.0.0 blob pull --no-default-policy foo:v1 ./local # Skip config policies`, Args: cobra.RangeArgs(1, 2), - RunE: func(cmd *cobra.Command, args []string) error { - return nil - }, + RunE: runPull, } func init() { pullCmd.Flags().StringArray("policy", nil, "policy file for verification (repeatable)") pullCmd.Flags().String("policy-rego", "", "OPA Rego policy file") - pullCmd.Flags().String("policy-bundle", "", "OPA bundle for policy evaluation") pullCmd.Flags().Bool("no-default-policy", false, "skip policies from config file") } + +// pullResult contains the result of a pull operation. +type pullResult struct { + Ref string `json:"ref"` + ResolvedRef string `json:"resolved_ref,omitempty"` + Destination string `json:"destination"` + FileCount int `json:"file_count"` + TotalSize uint64 `json:"total_size"` + TotalSizeHuman string `json:"total_size_human,omitempty"` + Verified bool `json:"verified"` + PoliciesCount int `json:"policies_applied,omitempty"` +} + +// pullFlags holds the parsed command flags. +type pullFlags struct { + policyFiles []string + policyRego string + noDefaultPolicy bool +} + +func runPull(cmd *cobra.Command, args []string) error { + // 1. Get config from context + cfg := internalcfg.FromContext(cmd.Context()) + if cfg == nil { + return errors.New("configuration not loaded") + } + + // 2. Parse arguments + inputRef := args[0] + destDir := "." + if len(args) > 1 { + destDir = args[1] + } + + // 3. Parse flags + flags, err := parsePullFlags(cmd) + if err != nil { + return err + } + + // 4. Resolve alias FIRST (before policy matching) + resolvedRef := cfg.ResolveAlias(inputRef) + + // 5. Build policies from config + flags (before creating destination) + policies, err := policy.BuildPolicies( + cfg, + resolvedRef, + flags.policyFiles, + flags.policyRego, + flags.noDefaultPolicy, + ) + if err != nil { + return fmt.Errorf("building policies: %w", err) + } + + // 6. Create client with policies + clientOpts := []blob.Option{blob.WithDockerConfig()} + for _, p := range policies { + clientOpts = append(clientOpts, blob.WithPolicy(p)) + } + client, err := blob.NewClient(clientOpts...) + if err != nil { + return fmt.Errorf("creating client: %w", err) + } + + // 7. Pull archive (policy verification happens here) + ctx := cmd.Context() + blobArchive, err := client.Pull(ctx, resolvedRef) + if err != nil { + if errors.Is(err, blob.ErrPolicyViolation) { + return fmt.Errorf("verification failed: %w", err) + } + return fmt.Errorf("pulling archive: %w", err) + } + + // 8. Prepare destination directory (only after successful pull) + destDir, err = prepareDestination(destDir) + if err != nil { + return err + } + + // 9. Extract files + copyOpts := []blob.CopyOption{ + blob.CopyWithOverwrite(false), + blob.CopyWithPreserveMode(true), + blob.CopyWithPreserveTimes(true), + } + if err := blobArchive.CopyDir(destDir, ".", copyOpts...); err != nil { + return fmt.Errorf("extracting files: %w", err) + } + + // 10. Build result + result := pullResult{ + Ref: inputRef, + Destination: destDir, + FileCount: blobArchive.Len(), + Verified: len(policies) > 0, + } + + if inputRef != resolvedRef { + result.ResolvedRef = resolvedRef + } + + // Compute total size + for entry := range blobArchive.Entries() { + result.TotalSize += entry.OriginalSize() + } + result.TotalSizeHuman = archive.FormatSize(result.TotalSize) + + if len(policies) > 0 { + result.PoliciesCount = len(policies) + } + + // 11. Output result + return outputPullResult(cfg, &result) +} + +// parsePullFlags extracts and validates flags from the command. +func parsePullFlags(cmd *cobra.Command) (pullFlags, error) { + var flags pullFlags + var err error + + flags.policyFiles, err = cmd.Flags().GetStringArray("policy") + if err != nil { + return flags, fmt.Errorf("reading policy flag: %w", err) + } + + flags.policyRego, err = cmd.Flags().GetString("policy-rego") + if err != nil { + return flags, fmt.Errorf("reading policy-rego flag: %w", err) + } + + flags.noDefaultPolicy, err = cmd.Flags().GetBool("no-default-policy") + if err != nil { + return flags, fmt.Errorf("reading no-default-policy flag: %w", err) + } + + return flags, nil +} + +// prepareDestination validates and prepares the destination directory. +func prepareDestination(destDir string) (string, error) { + // Convert to absolute path + absPath, err := filepath.Abs(destDir) + if err != nil { + return "", fmt.Errorf("resolving path: %w", err) + } + + // Check if path exists + info, err := os.Stat(absPath) + if err != nil { + if os.IsNotExist(err) { + // Create directory with restrictive permissions + if mkdirErr := os.MkdirAll(absPath, 0o750); mkdirErr != nil { + return "", fmt.Errorf("creating directory: %w", mkdirErr) + } + return absPath, nil + } + return "", fmt.Errorf("accessing path: %w", err) + } + + // Path exists - must be a directory + if !info.IsDir() { + return "", fmt.Errorf("destination is not a directory: %s", absPath) + } + + return absPath, nil +} + +// outputPullResult formats and outputs the pull result. +func outputPullResult(cfg *internalcfg.Config, result *pullResult) error { + if cfg.Quiet { + return nil + } + if viper.GetString("output") == internalcfg.OutputJSON { + return pullJSON(result) + } + return pullText(result) +} + +func pullJSON(result *pullResult) error { + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + return enc.Encode(result) +} + +func pullText(result *pullResult) error { + fmt.Printf("Pulled %s\n", result.Ref) + if result.ResolvedRef != "" { + fmt.Printf(" Resolved: %s\n", result.ResolvedRef) + } + fmt.Printf(" Destination: %s\n", result.Destination) + fmt.Printf(" Files: %d\n", result.FileCount) + fmt.Printf(" Size: %s\n", result.TotalSizeHuman) + + if result.Verified { + fmt.Printf(" Verified: %d policies applied\n", result.PoliciesCount) + } + + return nil +} diff --git a/cmd/pull_test.go b/cmd/pull_test.go new file mode 100644 index 0000000..e1c4772 --- /dev/null +++ b/cmd/pull_test.go @@ -0,0 +1,250 @@ +package cmd + +import ( + "bytes" + "context" + "encoding/json" + "os" + "path/filepath" + "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + internalcfg "github.com/meigma/blob-cli/internal/config" +) + +func TestPrepareDestination(t *testing.T) { + t.Run("existing directory", func(t *testing.T) { + dir := t.TempDir() + got, err := prepareDestination(dir) + require.NoError(t, err) + assert.Equal(t, dir, got) + }) + + t.Run("nonexistent path creates directory", func(t *testing.T) { + dir := t.TempDir() + newDir := filepath.Join(dir, "subdir", "nested") + + got, err := prepareDestination(newDir) + require.NoError(t, err) + assert.Equal(t, newDir, got) + + // Verify directory was created + info, err := os.Stat(newDir) + require.NoError(t, err) + assert.True(t, info.IsDir()) + }) + + t.Run("path is a file", func(t *testing.T) { + dir := t.TempDir() + file := filepath.Join(dir, "file.txt") + err := os.WriteFile(file, []byte("test"), 0o644) + require.NoError(t, err) + + _, err = prepareDestination(file) + require.Error(t, err) + assert.Contains(t, err.Error(), "not a directory") + }) + + t.Run("relative path converted to absolute", func(t *testing.T) { + // Use current directory which definitely exists + got, err := prepareDestination(".") + require.NoError(t, err) + assert.True(t, filepath.IsAbs(got)) + }) +} + +func TestPullCmd_NilConfig(t *testing.T) { + viper.Reset() + + // Don't set config in context + ctx := context.Background() + + pullCmd.SetContext(ctx) + err := pullCmd.RunE(pullCmd, []string{"ghcr.io/test:v1"}) + + require.Error(t, err) + assert.Contains(t, err.Error(), "configuration not loaded") +} + +func TestPullCmd_InvalidReference(t *testing.T) { + viper.Reset() + + dir := t.TempDir() + cfg := &internalcfg.Config{} + ctx := internalcfg.WithConfig(context.Background(), cfg) + + pullCmd.SetContext(ctx) + err := pullCmd.RunE(pullCmd, []string{"ghcr.io/nonexistent/ref:v1", dir}) + + // Should fail during pull (before destination handling) + require.Error(t, err) + assert.Contains(t, err.Error(), "pulling archive") +} + +func TestPullText(t *testing.T) { + tests := []struct { + name string + result *pullResult + wantOutput string + }{ + { + name: "basic pull", + result: &pullResult{ + Ref: "ghcr.io/test:v1", + Destination: "/tmp/output", + FileCount: 42, + TotalSizeHuman: "1.5M", + Verified: false, + }, + wantOutput: "Pulled ghcr.io/test:v1\n Destination: /tmp/output\n Files: 42\n Size: 1.5M\n", + }, + { + name: "pull with alias resolution", + result: &pullResult{ + Ref: "myalias:v1", + ResolvedRef: "ghcr.io/acme/repo:v1", + Destination: "/tmp/output", + FileCount: 10, + TotalSizeHuman: "512K", + Verified: false, + }, + wantOutput: "Pulled myalias:v1\n Resolved: ghcr.io/acme/repo:v1\n Destination: /tmp/output\n Files: 10\n Size: 512K\n", + }, + { + name: "pull with verification", + result: &pullResult{ + Ref: "ghcr.io/test:v1", + Destination: "/tmp/output", + FileCount: 5, + TotalSizeHuman: "2.3M", + Verified: true, + PoliciesCount: 2, + }, + wantOutput: "Pulled ghcr.io/test:v1\n Destination: /tmp/output\n Files: 5\n Size: 2.3M\n Verified: 2 policies applied\n", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Capture stdout + oldStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + err := pullText(tt.result) + + w.Close() + os.Stdout = oldStdout + + var buf bytes.Buffer + buf.ReadFrom(r) + + require.NoError(t, err) + assert.Equal(t, tt.wantOutput, buf.String()) + }) + } +} + +func TestPullJSON(t *testing.T) { + tests := []struct { + name string + result *pullResult + }{ + { + name: "basic pull", + result: &pullResult{ + Ref: "ghcr.io/test:v1", + Destination: "/tmp/output", + FileCount: 42, + TotalSize: 1572864, + TotalSizeHuman: "1.5M", + Verified: false, + }, + }, + { + name: "pull with alias resolution", + result: &pullResult{ + Ref: "myalias:v1", + ResolvedRef: "ghcr.io/acme/repo:v1", + Destination: "/tmp/output", + FileCount: 10, + TotalSize: 524288, + TotalSizeHuman: "512K", + Verified: false, + }, + }, + { + name: "pull with verification", + result: &pullResult{ + Ref: "ghcr.io/test:v1", + Destination: "/tmp/output", + FileCount: 5, + TotalSize: 2411724, + TotalSizeHuman: "2.3M", + Verified: true, + PoliciesCount: 2, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Capture stdout + oldStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + err := pullJSON(tt.result) + + w.Close() + os.Stdout = oldStdout + + var buf bytes.Buffer + buf.ReadFrom(r) + + require.NoError(t, err) + + // Parse the JSON and verify fields + var got pullResult + err = json.Unmarshal(buf.Bytes(), &got) + require.NoError(t, err) + assert.Equal(t, tt.result.Ref, got.Ref) + assert.Equal(t, tt.result.ResolvedRef, got.ResolvedRef) + assert.Equal(t, tt.result.Destination, got.Destination) + assert.Equal(t, tt.result.FileCount, got.FileCount) + assert.Equal(t, tt.result.TotalSize, got.TotalSize) + assert.Equal(t, tt.result.Verified, got.Verified) + assert.Equal(t, tt.result.PoliciesCount, got.PoliciesCount) + }) + } +} + +func TestOutputPullResult_Quiet(t *testing.T) { + viper.Reset() + + cfg := &internalcfg.Config{Quiet: true} + result := &pullResult{ + Ref: "ghcr.io/test:v1", + Destination: "/tmp/output", + FileCount: 10, + } + + // Capture stdout + oldStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + err := outputPullResult(cfg, result) + + w.Close() + os.Stdout = oldStdout + + var buf bytes.Buffer + buf.ReadFrom(r) + + require.NoError(t, err) + assert.Empty(t, buf.String(), "quiet mode should produce no output") +} diff --git a/go.mod b/go.mod index d3b5458..20a3f99 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,9 @@ go 1.25.5 require ( github.com/meigma/blob v1.0.0 + github.com/meigma/blob/policy/opa v0.0.0-20260121212824-972ce5f91c94 github.com/meigma/blob/policy/sigstore v0.0.0-20260121212824-972ce5f91c94 + github.com/meigma/blob/policy/slsa v0.0.0-20260121212824-972ce5f91c94 github.com/spf13/cobra v1.10.2 github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 @@ -12,16 +14,20 @@ require ( ) require ( + github.com/agnivade/levenshtein v1.2.1 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/coreos/go-oidc/v3 v3.17.0 // indirect github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 // indirect github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/go-ini/ini v1.67.0 // indirect github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -47,6 +53,8 @@ require ( github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/go-openapi/validate v0.25.1 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/goccy/go-json v0.10.5 // indirect github.com/google/certificate-transparency-go v1.3.2 // indirect github.com/google/flatbuffers v25.12.19+incompatible // indirect github.com/google/go-containerregistry v0.20.7 // indirect @@ -59,16 +67,32 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jedisct1/go-minisign v0.0.0-20211028175153-1c139d1cc84b // indirect github.com/klauspost/compress v1.18.3 // indirect + github.com/lestrrat-go/blackmagic v1.0.4 // indirect + github.com/lestrrat-go/dsig v1.0.0 // indirect + github.com/lestrrat-go/dsig-secp256k1 v1.0.0 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/httprc/v3 v3.0.1 // indirect + github.com/lestrrat-go/jwx/v3 v3.0.12 // indirect + github.com/lestrrat-go/option v1.0.1 // indirect + github.com/lestrrat-go/option/v2 v2.0.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/oklog/ulid v1.3.1 // indirect + github.com/open-policy-agent/opa v1.12.3 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/prometheus/procfs v0.17.0 // indirect + github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sassoftware/relic v7.2.1+incompatible // indirect github.com/secure-systems-lab/go-securesystemslib v0.10.0 // indirect + github.com/segmentio/asm v1.2.1 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect github.com/sigstore/protobuf-specs v0.5.0 // indirect github.com/sigstore/rekor v1.4.3 // indirect @@ -76,20 +100,29 @@ require ( github.com/sigstore/sigstore v1.10.3 // indirect github.com/sigstore/sigstore-go v1.1.4 // indirect github.com/sigstore/timestamp-authority/v2 v2.0.3 // indirect + github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/tchap/go-patricia/v2 v2.3.3 // indirect github.com/theupdateframework/go-tuf v0.7.0 // indirect github.com/theupdateframework/go-tuf/v2 v2.3.1 // indirect github.com/transparency-dev/formats v0.0.0-20251017110053-404c0d5b696c // indirect github.com/transparency-dev/merkle v0.0.2 // indirect + github.com/valyala/fastjson v1.6.4 // indirect + github.com/vektah/gqlparser/v2 v2.5.31 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/yashtewari/glob-intersection v0.2.0 // indirect go.mongodb.org/mongo-driver v1.17.6 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel v1.39.0 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/sdk v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.46.0 // indirect golang.org/x/mod v0.30.0 // indirect @@ -99,9 +132,10 @@ require ( golang.org/x/sys v0.39.0 // indirect golang.org/x/term v0.38.0 // indirect golang.org/x/text v0.32.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 // indirect - google.golang.org/grpc v1.76.0 // indirect + google.golang.org/grpc v1.77.0 // indirect google.golang.org/protobuf v1.36.11 // indirect oras.land/oras-go/v2 v2.6.0 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/go.sum b/go.sum index 69d637d..a29aa19 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,14 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgv github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM= +github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE= @@ -68,8 +74,12 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.40.2 h1:HK5ON3KmQV2HcAunnx4sKLB9aPf3 github.com/aws/aws-sdk-go-v2/service/sts v1.40.2/go.mod h1:E19xDjpzPZC7LS2knI9E6BaRFDK43Eul7vd6rSq2HWk= github.com/aws/smithy-go v1.23.2 h1:Crv0eatJUQhaManss33hS5r40CG3ZFH+21XSkqMrIUM= github.com/aws/smithy-go v1.23.2/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/bytecodealliance/wasmtime-go/v39 v39.0.1 h1:RibaT47yiyCRxMOj/l2cvL8cWiWBSqDXHyqsa9sGcCE= +github.com/bytecodealliance/wasmtime-go/v39 v39.0.1/go.mod h1:miR4NYIEBXeDNamZIzpskhJ0z/p8al+lwMWylQ/ZJb4= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= @@ -84,8 +94,8 @@ github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151X github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= -github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/containerd/platforms v1.0.0-rc.2 h1:0SPgaNZPVWGEi4grZdV8VRYQn78y+nm6acgLGv/QzE4= +github.com/containerd/platforms v1.0.0-rc.2/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4= github.com/containerd/stargz-snapshotter/estargz v0.18.1 h1:cy2/lpgBXDA3cDKSyEfNOFMA/c10O1axL69EU7iirO8= github.com/containerd/stargz-snapshotter/estargz v0.18.1/go.mod h1:ALIEqa7B6oVDsrF37GkGN20SuvG/pIMm7FwP7ZmRb0Q= github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc= @@ -97,8 +107,18 @@ github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 h github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw= github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= +github.com/dgraph-io/badger/v4 v4.8.0 h1:JYph1ChBijCw8SLeybvPINizbDKWZ5n/GYbz2yhN/bs= +github.com/dgraph-io/badger/v4 v4.8.0/go.mod h1:U6on6e8k/RTbUWxqKR0MvugJuVmkxSNc79ap4917h4w= +github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM= +github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI= +github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo= +github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/digitorus/pkcs7 v0.0.0-20230713084857-e76b763bdc49/go.mod h1:SKVExuS+vpu2l9IoOc0RwqE7NYnb0JlcFHFnEJkVDzc= github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 h1:ge14PCmCvPjpMQMIAH7uKg0lrtNSOdpYsRXlwk3QbaE= github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352/go.mod h1:SKVExuS+vpu2l9IoOc0RwqE7NYnb0JlcFHFnEJkVDzc= @@ -112,12 +132,18 @@ github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pM github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +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/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= +github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= @@ -125,6 +151,8 @@ github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8 github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= +github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= +github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -188,6 +216,10 @@ github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1 github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= @@ -272,6 +304,22 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA= +github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= +github.com/lestrrat-go/dsig v1.0.0 h1:OE09s2r9Z81kxzJYRn07TFM9XA4akrUdoMwr0L8xj38= +github.com/lestrrat-go/dsig v1.0.0/go.mod h1:dEgoOYYEJvW6XGbLasr8TFcAxoWrKlbQvmJgCR0qkDo= +github.com/lestrrat-go/dsig-secp256k1 v1.0.0 h1:JpDe4Aybfl0soBvoVwjqDbp+9S1Y2OM7gcrVVMFPOzY= +github.com/lestrrat-go/dsig-secp256k1 v1.0.0/go.mod h1:CxUgAhssb8FToqbL8NjSPoGQlnO4w3LG1P0qPWQm/NU= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/httprc/v3 v3.0.1 h1:3n7Es68YYGZb2Jf+k//llA4FTZMl3yCwIjFIk4ubevI= +github.com/lestrrat-go/httprc/v3 v3.0.1/go.mod h1:2uAvmbXE4Xq8kAUjVrZOq1tZVYYYs5iP62Cmtru00xk= +github.com/lestrrat-go/jwx/v3 v3.0.12 h1:p25r68Y4KrbBdYjIsQweYxq794CtGCzcrc5dGzJIRjg= +github.com/lestrrat-go/jwx/v3 v3.0.12/go.mod h1:HiUSaNmMLXgZ08OmGBaPVvoZQgJVOQphSrGr5zMamS8= +github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= +github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss= +github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg= github.com/letsencrypt/boulder v0.20251110.0 h1:J8MnKICeilO91dyQ2n5eBbab24neHzUpYMUIOdOtbjc= github.com/letsencrypt/boulder v0.20251110.0/go.mod h1:ogKCJQwll82m7OVHWyTuf8eeFCjuzdRQlgnZcCl0V+8= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= @@ -284,8 +332,14 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/meigma/blob v1.0.0 h1:LLc6mLB7XdRq5ONSKQddDu7lb3VIqNuAwu5pgStZg8g= github.com/meigma/blob v1.0.0/go.mod h1:/vMKwvjRGVuTzvykLUeWRZ4yHjm02mjp3HrAa7kpLOQ= +github.com/meigma/blob/policy/opa v0.0.0-20260121212824-972ce5f91c94 h1:+f/FSoBWnpjGpV+HvofNBkrROUWwR9EgpB66f0zXJXs= +github.com/meigma/blob/policy/opa v0.0.0-20260121212824-972ce5f91c94/go.mod h1:ZXF4/Rnbo1joGHYNZ2KRJkH7IHv4slkxFTs+ld9+Z04= github.com/meigma/blob/policy/sigstore v0.0.0-20260121212824-972ce5f91c94 h1:tGzZMNZ6w5NOE4gPEYnfCm66PRKyhHI98dpu4BILEeM= github.com/meigma/blob/policy/sigstore v0.0.0-20260121212824-972ce5f91c94/go.mod h1:3G8Wg1USZNPi1/KNHVZsEUZ2wQLGIpRYvXVL/Wo+Phc= +github.com/meigma/blob/policy/slsa v0.0.0-20260121212824-972ce5f91c94 h1:FvaGzDlO8MOc4g6FaUiPv/2VtoxlyQc6zsBy7RJyFSY= +github.com/meigma/blob/policy/slsa v0.0.0-20260121212824-972ce5f91c94/go.mod h1:BKxzXKGu7LD1f/Hh8cScDJbOhor33o5IgZ0TEVtWoDA= +github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= +github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -306,10 +360,14 @@ github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A= github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/open-policy-agent/opa v1.12.3 h1:qe3m/w52baKC/HJtippw+hYBUKCzuBCPjB+D5P9knfc= +github.com/open-policy-agent/opa v1.12.3/go.mod h1:RnDgm04GA1RjEXJvrsG9uNT/+FyBNmozcPvA2qz60M4= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= @@ -320,10 +378,21 @@ github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmd github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= +github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg= +github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -337,8 +406,10 @@ github.com/sassoftware/relic/v7 v7.6.2 h1:rS44Lbv9G9eXsukknS4mSjIAuuX+lMq/FnStgm github.com/sassoftware/relic/v7 v7.6.2/go.mod h1:kjmP0IBVkJZ6gXeAu35/KCEfca//+PKM6vTAsyDPY+k= github.com/secure-systems-lab/go-securesystemslib v0.10.0 h1:l+H5ErcW0PAehBNrBxoGv1jjNpGYdZ9RcheFkB2WI14= github.com/secure-systems-lab/go-securesystemslib v0.10.0/go.mod h1:MRKONWmRoFzPNQ9USRF9i1mc7MvAVvF1LlW8X5VWDvk= -github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= -github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0= +github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= +github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE= github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs= @@ -363,8 +434,8 @@ github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.10.0 h1:dUvPv/MP23Z github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.10.0/go.mod h1:fR/gDdPvJWGWL70/NgBBIL1O0/3Wma6JHs3tSSYg3s4= github.com/sigstore/timestamp-authority/v2 v2.0.3 h1:sRyYNtdED/ttLCMdaYnwpf0zre1A9chvjTnCmWWxN8Y= github.com/sigstore/timestamp-authority/v2 v2.0.3/go.mod h1:mDaHxkt3HmZYoIlwYj4QWo0RUr7VjYU52aVO5f5Qb3I= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af h1:Sp5TG9f7K39yfB+If0vjp97vuT74F72r8hfRpP8jLU0= +github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= @@ -378,10 +449,16 @@ github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/tchap/go-patricia/v2 v2.3.3 h1:xfNEsODumaEcCcY3gI0hYPZ/PcpVv5ju6RMAhgwZDDc= +github.com/tchap/go-patricia/v2 v2.3.3/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= github.com/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU= github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY= github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI= @@ -406,8 +483,18 @@ github.com/transparency-dev/formats v0.0.0-20251017110053-404c0d5b696c h1:5a2XDQ github.com/transparency-dev/formats v0.0.0-20251017110053-404c0d5b696c/go.mod h1:g85IafeFJZLxlzZCDRu4JLpfS7HKzR+Hw9qRh3bVzDI= github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG81+twTK4= github.com/transparency-dev/merkle v0.0.2/go.mod h1:pqSy+OXefQ1EDUVmAJ8MUhHB9TXGuzVAT58PqBoHz1A= +github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ= +github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/vbatts/tar-split v0.12.2 h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4= github.com/vbatts/tar-split v0.12.2/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= +github.com/vektah/gqlparser/v2 v2.5.31 h1:YhWGA1mfTjID7qJhd1+Vxhpk5HTgydrGU9IgkWBTJ7k= +github.com/vektah/gqlparser/v2 v2.5.31/go.mod h1:c1I28gSOVNzlfc4WuDlqU7voQnsqI6OG2amkBAFmgts= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/yashtewari/glob-intersection v0.2.0 h1:8iuHdN88yYuCzCdjt0gDe+6bAhUwBeEWqThExu54RFg= +github.com/yashtewari/glob-intersection v0.2.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok= github.com/ysmood/fetchup v0.2.3 h1:ulX+SonA0Vma5zUFXtv52Kzip/xe7aj4vqT5AJwQ+ZQ= github.com/ysmood/fetchup v0.2.3/go.mod h1:xhibcRKziSvol0H1/pj33dnKrYyI2ebIvz5cOOkYGns= github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ= @@ -432,14 +519,22 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= +go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.step.sm/crypto v0.74.0 h1:/APBEv45yYR4qQFg47HA8w1nesIGcxh44pGyQNw6JRA= go.step.sm/crypto v0.74.0/go.mod h1:UoXqCAJjjRgzPte0Llaqen7O9P7XjPmgjgTHQGkKCDk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -464,6 +559,7 @@ golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= @@ -473,23 +569,26 @@ golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= +golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI= google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964= google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9 h1:LvZVVaPE0JSqL+ZWb6ErZfnEOKIqqFWUJE2D0fObSmc= google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9/go.mod h1:QFOrLhdAe2PsTp3vQY4quuLKTi9j3XG3r6JPPaw7MSc= -google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 h1:8XJ4pajGwOlasW+L13MnEGA8W4115jJySQtVfS2/IBU= -google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4/go.mod h1:NnuHhy+bxcg30o7FnVAZbXsPHUDQ9qKWAQKCD7VxFtk= +google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4= +google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo= google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 h1:tRPGkdGHuewF4UisLzzHHr1spKw92qLM98nIzxbC0wY= google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= -google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= -google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= +google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= +google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= diff --git a/internal/policy/builder.go b/internal/policy/builder.go new file mode 100644 index 0000000..d566df8 --- /dev/null +++ b/internal/policy/builder.go @@ -0,0 +1,160 @@ +package policy + +import ( + "errors" + "fmt" + + "github.com/meigma/blob/policy" + "github.com/meigma/blob/policy/opa" + "github.com/meigma/blob/policy/sigstore" + "github.com/meigma/blob/policy/slsa" + "github.com/meigma/blob/registry" + + "github.com/meigma/blob-cli/internal/config" +) + +// BuildPolicies constructs registry.Policy instances from config and command flags. +// It combines policies from the config file (unless noDefaultPolicy is true) +// with policies from policy files and OPA rego files. +func BuildPolicies( + cfg *config.Config, + ref string, + policyFiles []string, + policyRego string, + noDefaultPolicy bool, +) ([]registry.Policy, error) { + var policies []registry.Policy + + // 1. Config policies (unless skipped) + if !noDefaultPolicy && cfg != nil { + configPolicies := cfg.GetPoliciesForRef(ref) + for i, cfgPolicy := range configPolicies { + regPolicy, err := ConvertConfigPolicy(cfgPolicy) + if err != nil { + return nil, fmt.Errorf("config policy %d: %w", i, err) + } + if regPolicy != nil { + policies = append(policies, regPolicy) + } + } + } + + // 2. YAML policy files + for _, path := range policyFiles { + cfgPolicy, err := LoadFile(path) + if err != nil { + return nil, fmt.Errorf("loading policy %s: %w", path, err) + } + regPolicy, err := ConvertConfigPolicy(*cfgPolicy) + if err != nil { + return nil, fmt.Errorf("policy %s: %w", path, err) + } + if regPolicy != nil { + policies = append(policies, regPolicy) + } + } + + // 3. OPA Rego file + if policyRego != "" { + p, err := opa.NewPolicy(opa.WithPolicyFile(policyRego)) + if err != nil { + return nil, fmt.Errorf("loading rego policy %s: %w", policyRego, err) + } + policies = append(policies, p) + } + + return policies, nil +} + +// ConvertConfigPolicy converts a config.Policy to a registry.Policy. +func ConvertConfigPolicy(cfgPolicy config.Policy) (registry.Policy, error) { + var policies []registry.Policy + + // Handle signature policy + if cfgPolicy.Signature != nil { + sigPolicy, err := buildSignaturePolicy(cfgPolicy.Signature) + if err != nil { + return nil, fmt.Errorf("signature policy: %w", err) + } + if sigPolicy != nil { + policies = append(policies, sigPolicy) + } + } + + // Handle provenance policy + if cfgPolicy.Provenance != nil { + provPolicy, err := buildProvenancePolicy(cfgPolicy.Provenance) + if err != nil { + return nil, fmt.Errorf("provenance policy: %w", err) + } + if provPolicy != nil { + policies = append(policies, provPolicy) + } + } + + if len(policies) == 0 { + return nil, nil //nolint:nilnil // nil policy with no error is valid (no verification required) + } + if len(policies) == 1 { + return policies[0], nil + } + return policy.RequireAll(policies...), nil +} + +// buildSignaturePolicy creates a sigstore policy from config. +func buildSignaturePolicy(sig *config.SignaturePolicy) (registry.Policy, error) { + // Error if both keyless and key are specified to avoid ambiguity + if sig.Keyless != nil && sig.Key != nil { + return nil, errors.New("signature policy cannot specify both keyless and key") + } + + if sig.Keyless != nil { + if sig.Keyless.Issuer == "" { + return nil, errors.New("keyless issuer is required") + } + if sig.Keyless.Identity == "" { + return nil, errors.New("keyless identity is required") + } + return sigstore.NewPolicy( + sigstore.WithIdentity(sig.Keyless.Issuer, sig.Keyless.Identity), + ) + } + if sig.Key != nil { + if sig.Key.Path != "" { + return nil, errors.New("key-based signature verification not yet implemented") + } + if sig.Key.URL != "" { + return nil, errors.New("key URL signature verification not yet implemented") + } + return nil, errors.New("signature key must specify path or url") + } + return nil, errors.New("signature policy must specify keyless or key") +} + +// buildProvenancePolicy creates an SLSA policy from config. +func buildProvenancePolicy(prov *config.ProvenancePolicy) (registry.Policy, error) { + if prov.SLSA == nil { + return nil, errors.New("provenance policy must specify slsa") + } + + // Repository is required for GitHubActionsWorkflow + if prov.SLSA.Repository != "" { + var opts []any + + if prov.SLSA.Branch != "" { + opts = append(opts, slsa.WithWorkflowBranches(prov.SLSA.Branch)) + } + if prov.SLSA.Tag != "" { + opts = append(opts, slsa.WithWorkflowTags(prov.SLSA.Tag)) + } + + return slsa.GitHubActionsWorkflow(prov.SLSA.Repository, opts...) + } + + // Fallback to basic builder requirement + if prov.SLSA.Builder != "" { + return slsa.RequireBuilder(prov.SLSA.Builder), nil + } + + return nil, errors.New("slsa policy must specify repository or builder") +} diff --git a/internal/policy/loader.go b/internal/policy/loader.go new file mode 100644 index 0000000..9f7ad02 --- /dev/null +++ b/internal/policy/loader.go @@ -0,0 +1,104 @@ +// Package policy provides utilities for loading and building verification policies. +package policy + +import ( + "fmt" + "os" + + "gopkg.in/yaml.v3" + + "github.com/meigma/blob-cli/internal/config" +) + +// File represents a YAML policy file structure. +// This matches the format described in DESIGN.md. +type File struct { + Signature *SignatureFile `yaml:"signature"` + Provenance *ProvenanceFile `yaml:"provenance"` +} + +// SignatureFile defines signature verification in a policy file. +type SignatureFile struct { + Keyless *KeylessFile `yaml:"keyless"` + Key *KeyFile `yaml:"key"` +} + +// KeylessFile defines Sigstore keyless verification. +type KeylessFile struct { + Issuer string `yaml:"issuer"` + Identity string `yaml:"identity"` +} + +// KeyFile defines key-based signature verification. +type KeyFile struct { + Path string `yaml:"path"` + URL string `yaml:"url"` +} + +// ProvenanceFile defines provenance verification in a policy file. +type ProvenanceFile struct { + SLSA *SLSAFile `yaml:"slsa"` +} + +// SLSAFile defines SLSA provenance requirements. +type SLSAFile struct { + Builder string `yaml:"builder"` + Repository string `yaml:"repository"` + Branch string `yaml:"branch"` + Tag string `yaml:"tag"` +} + +// LoadFile loads and parses a YAML policy file. +func LoadFile(path string) (*config.Policy, error) { + //nolint:gosec // path is intentionally user-provided for policy loading + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("reading policy file: %w", err) + } + + var pf File + if err := yaml.Unmarshal(data, &pf); err != nil { + return nil, fmt.Errorf("parsing policy file: %w", err) + } + + return convertFileToConfig(&pf), nil +} + +// convertFileToConfig converts a policy file to config.Policy. +func convertFileToConfig(pf *File) *config.Policy { + if pf == nil { + return nil + } + + p := &config.Policy{} + + if pf.Signature != nil { + p.Signature = &config.SignaturePolicy{} + if pf.Signature.Keyless != nil { + p.Signature.Keyless = &config.KeylessConfig{ + Issuer: pf.Signature.Keyless.Issuer, + Identity: pf.Signature.Keyless.Identity, + } + } + if pf.Signature.Key != nil { + p.Signature.Key = &config.KeyConfig{ + Path: pf.Signature.Key.Path, + URL: pf.Signature.Key.URL, + } + } + } + + if pf.Provenance != nil { + p.Provenance = &config.ProvenancePolicy{} + if pf.Provenance.SLSA != nil { + p.Provenance.SLSA = &config.SLSAConfig{ + Builder: pf.Provenance.SLSA.Builder, + Repository: pf.Provenance.SLSA.Repository, + Branch: pf.Provenance.SLSA.Branch, + Tag: pf.Provenance.SLSA.Tag, + } + } + } + + return p +} diff --git a/internal/policy/policy_test.go b/internal/policy/policy_test.go new file mode 100644 index 0000000..99aafe2 --- /dev/null +++ b/internal/policy/policy_test.go @@ -0,0 +1,292 @@ +package policy + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/meigma/blob-cli/internal/config" +) + +func TestLoadFile(t *testing.T) { + t.Run("keyless signature policy", func(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "policy.yaml") + content := ` +signature: + keyless: + issuer: https://token.actions.githubusercontent.com + identity: https://github.com/acme/*/.github/workflows/* +` + err := os.WriteFile(path, []byte(content), 0o644) + require.NoError(t, err) + + policy, err := LoadFile(path) + require.NoError(t, err) + require.NotNil(t, policy) + require.NotNil(t, policy.Signature) + require.NotNil(t, policy.Signature.Keyless) + assert.Equal(t, "https://token.actions.githubusercontent.com", policy.Signature.Keyless.Issuer) + assert.Equal(t, "https://github.com/acme/*/.github/workflows/*", policy.Signature.Keyless.Identity) + }) + + t.Run("SLSA provenance policy", func(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "policy.yaml") + content := ` +provenance: + slsa: + repository: acme/configs + branch: main +` + err := os.WriteFile(path, []byte(content), 0o644) + require.NoError(t, err) + + policy, err := LoadFile(path) + require.NoError(t, err) + require.NotNil(t, policy) + require.NotNil(t, policy.Provenance) + require.NotNil(t, policy.Provenance.SLSA) + assert.Equal(t, "acme/configs", policy.Provenance.SLSA.Repository) + assert.Equal(t, "main", policy.Provenance.SLSA.Branch) + }) + + t.Run("combined signature and provenance", func(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "policy.yaml") + content := ` +signature: + keyless: + issuer: https://token.actions.githubusercontent.com + identity: https://github.com/acme/*/.github/workflows/* +provenance: + slsa: + repository: acme/configs + branch: main + tag: v* +` + err := os.WriteFile(path, []byte(content), 0o644) + require.NoError(t, err) + + policy, err := LoadFile(path) + require.NoError(t, err) + require.NotNil(t, policy) + require.NotNil(t, policy.Signature) + require.NotNil(t, policy.Provenance) + }) + + t.Run("file not found", func(t *testing.T) { + _, err := LoadFile("/nonexistent/policy.yaml") + require.Error(t, err) + assert.Contains(t, err.Error(), "reading policy file") + }) + + t.Run("invalid yaml", func(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "policy.yaml") + err := os.WriteFile(path, []byte("not: valid: yaml: ["), 0o644) + require.NoError(t, err) + + _, err = LoadFile(path) + require.Error(t, err) + assert.Contains(t, err.Error(), "parsing policy file") + }) +} + +func TestConvertConfigPolicy(t *testing.T) { + t.Run("empty policy", func(t *testing.T) { + policy, err := ConvertConfigPolicy(config.Policy{}) + require.NoError(t, err) + assert.Nil(t, policy) + }) + + t.Run("keyless signature only", func(t *testing.T) { + cfgPolicy := config.Policy{ + Signature: &config.SignaturePolicy{ + Keyless: &config.KeylessConfig{ + Issuer: "https://token.actions.githubusercontent.com", + Identity: "https://github.com/acme/*/.github/workflows/*", + }, + }, + } + policy, err := ConvertConfigPolicy(cfgPolicy) + require.NoError(t, err) + assert.NotNil(t, policy) + }) + + t.Run("SLSA provenance with repository", func(t *testing.T) { + cfgPolicy := config.Policy{ + Provenance: &config.ProvenancePolicy{ + SLSA: &config.SLSAConfig{ + Repository: "acme/configs", + Branch: "main", + }, + }, + } + policy, err := ConvertConfigPolicy(cfgPolicy) + require.NoError(t, err) + assert.NotNil(t, policy) + }) + + t.Run("SLSA provenance with builder only", func(t *testing.T) { + cfgPolicy := config.Policy{ + Provenance: &config.ProvenancePolicy{ + SLSA: &config.SLSAConfig{ + Builder: "https://github.com/slsa-framework/slsa-github-generator", + }, + }, + } + policy, err := ConvertConfigPolicy(cfgPolicy) + require.NoError(t, err) + assert.NotNil(t, policy) + }) + + t.Run("missing keyless issuer", func(t *testing.T) { + cfgPolicy := config.Policy{ + Signature: &config.SignaturePolicy{ + Keyless: &config.KeylessConfig{ + Identity: "https://github.com/acme/*/.github/workflows/*", + }, + }, + } + _, err := ConvertConfigPolicy(cfgPolicy) + require.Error(t, err) + assert.Contains(t, err.Error(), "keyless issuer is required") + }) + + t.Run("missing keyless identity", func(t *testing.T) { + cfgPolicy := config.Policy{ + Signature: &config.SignaturePolicy{ + Keyless: &config.KeylessConfig{ + Issuer: "https://token.actions.githubusercontent.com", + }, + }, + } + _, err := ConvertConfigPolicy(cfgPolicy) + require.Error(t, err) + assert.Contains(t, err.Error(), "keyless identity is required") + }) + + t.Run("key path not implemented", func(t *testing.T) { + cfgPolicy := config.Policy{ + Signature: &config.SignaturePolicy{ + Key: &config.KeyConfig{ + Path: "/path/to/key.pub", + }, + }, + } + _, err := ConvertConfigPolicy(cfgPolicy) + require.Error(t, err) + assert.Contains(t, err.Error(), "not yet implemented") + }) + + t.Run("both keyless and key specified", func(t *testing.T) { + cfgPolicy := config.Policy{ + Signature: &config.SignaturePolicy{ + Keyless: &config.KeylessConfig{ + Issuer: "https://token.actions.githubusercontent.com", + Identity: "https://github.com/acme/*/.github/workflows/*", + }, + Key: &config.KeyConfig{ + Path: "/path/to/key.pub", + }, + }, + } + _, err := ConvertConfigPolicy(cfgPolicy) + require.Error(t, err) + assert.Contains(t, err.Error(), "cannot specify both keyless and key") + }) + + t.Run("SLSA missing repository and builder", func(t *testing.T) { + cfgPolicy := config.Policy{ + Provenance: &config.ProvenancePolicy{ + SLSA: &config.SLSAConfig{ + Branch: "main", + }, + }, + } + _, err := ConvertConfigPolicy(cfgPolicy) + require.Error(t, err) + assert.Contains(t, err.Error(), "must specify repository or builder") + }) +} + +func TestBuildPolicies(t *testing.T) { + t.Run("no policies when all disabled", func(t *testing.T) { + cfg := &config.Config{} + policies, err := BuildPolicies(cfg, "ghcr.io/test:v1", nil, "", true) + require.NoError(t, err) + assert.Empty(t, policies) + }) + + t.Run("nil config with no default policy", func(t *testing.T) { + policies, err := BuildPolicies(nil, "ghcr.io/test:v1", nil, "", true) + require.NoError(t, err) + assert.Empty(t, policies) + }) + + t.Run("policy file loading", func(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "policy.yaml") + content := ` +provenance: + slsa: + repository: acme/configs +` + err := os.WriteFile(path, []byte(content), 0o644) + require.NoError(t, err) + + policies, err := BuildPolicies(nil, "ghcr.io/test:v1", []string{path}, "", true) + require.NoError(t, err) + assert.Len(t, policies, 1) + }) + + t.Run("invalid policy file", func(t *testing.T) { + _, err := BuildPolicies(nil, "ghcr.io/test:v1", []string{"/nonexistent.yaml"}, "", true) + require.Error(t, err) + assert.Contains(t, err.Error(), "loading policy") + }) + + t.Run("config policies when not disabled", func(t *testing.T) { + cfg := &config.Config{ + Policies: []config.PolicyRule{ + { + Match: "ghcr\\.io/test/.*", + Policy: config.Policy{ + Provenance: &config.ProvenancePolicy{ + SLSA: &config.SLSAConfig{ + Repository: "test/repo", + }, + }, + }, + }, + }, + } + policies, err := BuildPolicies(cfg, "ghcr.io/test/app:v1", nil, "", false) + require.NoError(t, err) + assert.Len(t, policies, 1) + }) + + t.Run("config policies skipped when disabled", func(t *testing.T) { + cfg := &config.Config{ + Policies: []config.PolicyRule{ + { + Match: "ghcr\\.io/test/.*", + Policy: config.Policy{ + Provenance: &config.ProvenancePolicy{ + SLSA: &config.SLSAConfig{ + Repository: "test/repo", + }, + }, + }, + }, + }, + } + policies, err := BuildPolicies(cfg, "ghcr.io/test/app:v1", nil, "", true) + require.NoError(t, err) + assert.Empty(t, policies) + }) +}