From 014f8d1bbe4ece9f8b96d044d1f8dc0dbe5f8482 Mon Sep 17 00:00:00 2001 From: Tanmay Jain Date: Tue, 21 Jul 2026 13:40:42 +0530 Subject: [PATCH] FEAT(provenance): Add SLSA provenance attestation and verification (#13667) * feat(provenance): add SLSA provenance and attestation signing libraries Add internal/provenance for deriving in-toto subjects from Packer artifacts, building SLSA Provenance v1 predicates, wrapping in-toto statements, and best-effort git/CI source detection. Add internal/attestation for DSSE envelope handling and a pluggable Signer/Verifier backend supporting key (local PEM), kms (aws/gcp/ azure/hashivault), and keyless (Sigstore Fulcio) modes, plus Sigstore bundle handling and DSSE/policy verification. Add the supporting module dependencies in go.mod/go.sum. * feat(provenance): add provenance post-processor Add the opt-in "provenance" post-processor that runs after a build, derives subjects from the artifact, emits DSSE-wrapped SLSA provenance (and optional SBOM) attestations, signs them via the configured signing backend, and writes sidecar files (including *.sigstore.json bundles in keyless mode). Register it in the core post-processor set. The provenance enable flag is a tri-state so an unset value stays enabled through HCL2 decoding instead of being silently disabled. * feat(provenance): add verify-attestation command Add "packer verify-attestation" to verify signed DSSE attestations against key, KMS, and keyless policy inputs, including optional Sigstore bundle checks for Rekor and timestamp evidence. Register the command in the CLI. * docs(provenance): add reference CI workflows and changelog Add reference GitHub Actions workflows under examples/ci for SLSA L2 keyless signing and L3-compatible delegated signing * fix: lint and tests * Added docs for Provenance PostProcessor --- .go-version | 2 +- command/execute.go | 2 + command/verify_attestation.go | 155 ++++ command/verify_attestation_test.go | 244 +++++ commands.go | 4 + examples/ci/README.md | 34 + examples/ci/github-actions-l2-keyless.yml | 93 ++ examples/ci/github-actions-l3-delegated.yml | 64 ++ go.mod | 246 ++--- go.sum | 595 ++++++++----- hcl2template/types.build.post-processor.go | 1 + internal/attestation/bundle.go | 22 + internal/attestation/dsse.go | 67 ++ internal/attestation/sign_key.go | 238 +++++ internal/attestation/sign_key_test.go | 110 +++ internal/attestation/sign_keyless.go | 360 ++++++++ internal/attestation/sign_kms.go | 140 +++ internal/attestation/sign_kms_keyless_test.go | 762 ++++++++++++++++ internal/attestation/sign_kms_provider_aws.go | 8 + .../attestation/sign_kms_provider_azure.go | 8 + internal/attestation/sign_kms_provider_gcp.go | 8 + .../sign_kms_provider_hashivault.go | 8 + .../attestation/sign_kms_providers_default.go | 18 + .../attestation/sign_kms_providers_test.go | 37 + internal/attestation/signer.go | 94 ++ internal/attestation/verify.go | 412 +++++++++ .../attestation/verify_autodiscover_test.go | 175 ++++ internal/attestation/verify_test.go | 95 ++ internal/provenance/gitinfo.go | 175 ++++ internal/provenance/gitinfo_test.go | 85 ++ internal/provenance/predicate.go | 115 +++ internal/provenance/predicate_test.go | 49 + internal/provenance/statement.go | 22 + internal/provenance/statement_test.go | 26 + internal/provenance/subject.go | 130 +++ internal/provenance/subject_test.go | 128 +++ post-processor/provenance/README.md | 115 +++ .../provenance/hcl2_configure_test.go | 57 ++ post-processor/provenance/post-processor.go | 747 ++++++++++++++++ .../provenance/post-processor.hcl2spec.go | 89 ++ .../provenance/post-processor_test.go | 840 ++++++++++++++++++ 41 files changed, 6246 insertions(+), 334 deletions(-) create mode 100644 command/verify_attestation.go create mode 100644 command/verify_attestation_test.go create mode 100644 examples/ci/README.md create mode 100644 examples/ci/github-actions-l2-keyless.yml create mode 100644 examples/ci/github-actions-l3-delegated.yml create mode 100644 internal/attestation/bundle.go create mode 100644 internal/attestation/dsse.go create mode 100644 internal/attestation/sign_key.go create mode 100644 internal/attestation/sign_key_test.go create mode 100644 internal/attestation/sign_keyless.go create mode 100644 internal/attestation/sign_kms.go create mode 100644 internal/attestation/sign_kms_keyless_test.go create mode 100644 internal/attestation/sign_kms_provider_aws.go create mode 100644 internal/attestation/sign_kms_provider_azure.go create mode 100644 internal/attestation/sign_kms_provider_gcp.go create mode 100644 internal/attestation/sign_kms_provider_hashivault.go create mode 100644 internal/attestation/sign_kms_providers_default.go create mode 100644 internal/attestation/sign_kms_providers_test.go create mode 100644 internal/attestation/signer.go create mode 100644 internal/attestation/verify.go create mode 100644 internal/attestation/verify_autodiscover_test.go create mode 100644 internal/attestation/verify_test.go create mode 100644 internal/provenance/gitinfo.go create mode 100644 internal/provenance/gitinfo_test.go create mode 100644 internal/provenance/predicate.go create mode 100644 internal/provenance/predicate_test.go create mode 100644 internal/provenance/statement.go create mode 100644 internal/provenance/statement_test.go create mode 100644 internal/provenance/subject.go create mode 100644 internal/provenance/subject_test.go create mode 100644 post-processor/provenance/README.md create mode 100644 post-processor/provenance/hcl2_configure_test.go create mode 100644 post-processor/provenance/post-processor.go create mode 100644 post-processor/provenance/post-processor.hcl2spec.go create mode 100644 post-processor/provenance/post-processor_test.go diff --git a/.go-version b/.go-version index 4fd162530..f8f738140 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.25.11 +1.26.3 diff --git a/command/execute.go b/command/execute.go index e7c87b936..3a4a374aa 100644 --- a/command/execute.go +++ b/command/execute.go @@ -25,6 +25,7 @@ import ( checksumpostprocessor "github.com/hashicorp/packer/post-processor/checksum" compresspostprocessor "github.com/hashicorp/packer/post-processor/compress" manifestpostprocessor "github.com/hashicorp/packer/post-processor/manifest" + provenancepostprocessor "github.com/hashicorp/packer/post-processor/provenance" shelllocalpostprocessor "github.com/hashicorp/packer/post-processor/shell-local" breakpointprovisioner "github.com/hashicorp/packer/provisioner/breakpoint" fileprovisioner "github.com/hashicorp/packer/provisioner/file" @@ -63,6 +64,7 @@ var PostProcessors = map[string]packersdk.PostProcessor{ "checksum": new(checksumpostprocessor.PostProcessor), "compress": new(compresspostprocessor.PostProcessor), "manifest": new(manifestpostprocessor.PostProcessor), + "provenance": new(provenancepostprocessor.PostProcessor), "shell-local": new(shelllocalpostprocessor.PostProcessor), } diff --git a/command/verify_attestation.go b/command/verify_attestation.go new file mode 100644 index 000000000..b76f4f647 --- /dev/null +++ b/command/verify_attestation.go @@ -0,0 +1,155 @@ +// Copyright IBM Corp. 2024, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +package command + +import ( + "context" + "strings" + + internalattestation "github.com/hashicorp/packer/internal/attestation" + "github.com/posener/complete" +) + +type VerifyAttestationCommand struct { + Meta +} + +type VerifyAttestationArgs struct { + AttestationPath string + SigningMode string + Key string + Verifier string + PredicateType string + BuilderID string + SourceURI string + ArtifactPath string + TrustedRootPath string + KeylessIdentity string + KeylessOIDCIssuer string + SigstoreBundlePath string + RequireTransparencyLog bool + RequireObserverTimestamp bool +} + +func (c *VerifyAttestationCommand) Run(args []string) int { + ctx, cleanup := handleTermInterrupt(c.Ui) + defer cleanup() + + cfg, ret := c.ParseArgs(args) + if ret != 0 { + return ret + } + + return c.RunContext(ctx, cfg) +} + +func (c *VerifyAttestationCommand) ParseArgs(args []string) (*VerifyAttestationArgs, int) { + var cfg VerifyAttestationArgs + + flags := c.FlagSet("verify-attestation") + flags.Usage = func() { c.Ui.Say(c.Help()) } + flags.StringVar(&cfg.SigningMode, "signing-mode", "", "Signing mode used for the attestation") + flags.StringVar(&cfg.Key, "key", "", "PEM path or KMS/Vault URI") + flags.StringVar(&cfg.Verifier, "verifier", "", "PEM verifier path") + flags.StringVar(&cfg.PredicateType, "predicate-type", "", "Expected attestation predicate type") + flags.StringVar(&cfg.BuilderID, "builder-id", "", "Expected SLSA builder ID") + flags.StringVar(&cfg.SourceURI, "source-uri", "", "Expected resolved source URI") + flags.StringVar(&cfg.ArtifactPath, "artifact", "", "Artifact path to match against attestation subjects") + flags.StringVar(&cfg.TrustedRootPath, "trusted-root-path", "", "Optional Sigstore trusted-root JSON file") + flags.StringVar(&cfg.KeylessIdentity, "keyless-identity", "", "Expected keyless signing identity") + flags.StringVar(&cfg.KeylessOIDCIssuer, "keyless-oidc-issuer", "", "Expected keyless OIDC issuer") + flags.StringVar(&cfg.SigstoreBundlePath, "bundle", "", "Optional Sigstore bundle JSON file for Rekor or timestamp verification") + flags.BoolVar(&cfg.RequireTransparencyLog, "require-rekor", false, "Require Rekor transparency log verification using the Sigstore bundle") + flags.BoolVar(&cfg.RequireObserverTimestamp, "require-timestamp", false, "Require a trusted observer timestamp from Rekor integrated time or RFC3161 timestamp evidence") + if err := flags.Parse(args); err != nil { + return &cfg, 1 + } + + args = flags.Args() + if len(args) != 1 { + flags.Usage() + return &cfg, 1 + } + cfg.AttestationPath = args[0] + return &cfg, 0 +} + +func (c *VerifyAttestationCommand) RunContext(ctx context.Context, cfg *VerifyAttestationArgs) int { + _, err := internalattestation.VerifyAttestationFile(ctx, cfg.AttestationPath, internalattestation.BackendConfig{ + Mode: cfg.SigningMode, + SignerRef: cfg.Key, + VerifierRef: cfg.Verifier, + TrustedRootPath: cfg.TrustedRootPath, + KeylessIdentity: cfg.KeylessIdentity, + KeylessOIDCIssuer: cfg.KeylessOIDCIssuer, + }, internalattestation.VerificationPolicy{ + PredicateType: cfg.PredicateType, + BuilderID: cfg.BuilderID, + SourceURI: cfg.SourceURI, + ArtifactPath: cfg.ArtifactPath, + SigstoreBundlePath: cfg.SigstoreBundlePath, + RequireTransparencyLog: cfg.RequireTransparencyLog, + RequireObserverTimestamp: cfg.RequireObserverTimestamp, + }) + if err != nil { + c.Ui.Error(err.Error()) + return 1 + } + + c.Ui.Say("Attestation verified.") + return 0 +} + +func (*VerifyAttestationCommand) Help() string { + helpText := ` +Usage: packer verify-attestation [options] ATTESTATION + + Verifies a signed DSSE attestation and optionally enforces policy checks + such as predicate type, builder identity, source URI, and subject digest. + +Options: + + -signing-mode=MODE Signing mode: key, kms, keyless. Auto-detected when possible. + -key=PATH_OR_URI PEM key path or KMS/Vault URI used for verification when no verifier is supplied. + -verifier=PATH PEM verifier path. + -predicate-type=TYPE Expected attestation predicate type. + -builder-id=ID Expected SLSA builder ID. + -source-uri=URI Expected resolved source URI. + -artifact=PATH Artifact path to match against attestation subjects. + -trusted-root-path=PATH Optional Sigstore trusted-root JSON for keyless verification. + -keyless-identity=IDENTITY Expected keyless signing identity. + -keyless-oidc-issuer=ISSUER Expected keyless OIDC issuer. + -bundle=PATH Optional Sigstore bundle JSON for Rekor or timestamp verification. + -require-rekor Require Rekor transparency log verification from the bundle. + -require-timestamp Require a trusted observer timestamp from Rekor integrated time or RFC3161 evidence. +` + + return strings.TrimSpace(helpText) +} + +func (*VerifyAttestationCommand) Synopsis() string { + return "verify a signed attestation against policy" +} + +func (*VerifyAttestationCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictNothing +} + +func (*VerifyAttestationCommand) AutocompleteFlags() complete.Flags { + return complete.Flags{ + "-signing-mode": complete.PredictNothing, + "-key": complete.PredictNothing, + "-verifier": complete.PredictNothing, + "-predicate-type": complete.PredictNothing, + "-builder-id": complete.PredictNothing, + "-source-uri": complete.PredictNothing, + "-artifact": complete.PredictNothing, + "-trusted-root-path": complete.PredictNothing, + "-keyless-identity": complete.PredictNothing, + "-keyless-oidc-issuer": complete.PredictNothing, + "-bundle": complete.PredictNothing, + "-require-rekor": complete.PredictNothing, + "-require-timestamp": complete.PredictNothing, + } +} diff --git a/command/verify_attestation_test.go b/command/verify_attestation_test.go new file mode 100644 index 000000000..45a9f3750 --- /dev/null +++ b/command/verify_attestation_test.go @@ -0,0 +1,244 @@ +// Copyright IBM Corp. 2024, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +package command + +import ( + "bytes" + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "encoding/hex" + "encoding/json" + "encoding/pem" + "os" + "path/filepath" + "strings" + "testing" + + packersdk "github.com/hashicorp/packer-plugin-sdk/packer" + internalattestation "github.com/hashicorp/packer/internal/attestation" + internalprovenance "github.com/hashicorp/packer/internal/provenance" +) + +func TestVerifyAttestationCommandRun(t *testing.T) { + artifactPath := filepath.Join(t.TempDir(), "artifact.txt") + if err := os.WriteFile(artifactPath, []byte("hello verify"), 0600); err != nil { + t.Fatalf("write artifact: %v", err) + } + + privateKeyPath, publicKeyPath := writeVerifierKeypair(t) + attestationPath := writeSignedAttestation(t, privateKeyPath, artifactPath, "https://example.com/builder", "git+https://github.com/hashicorp/packer@refs/heads/main") + + meta := testMeta(t) + command := &VerifyAttestationCommand{Meta: meta} + ret := command.Run([]string{ + "-verifier=" + publicKeyPath, + "-predicate-type=" + internalprovenance.SLSAProvenanceV1PredicateType, + "-builder-id=https://example.com/builder", + "-source-uri=git+https://github.com/hashicorp/packer@refs/heads/main", + "-artifact=" + artifactPath, + attestationPath, + }) + if ret != 0 { + fatalCommand(t, meta) + } + + ui := meta.Ui.(*packersdk.BasicUi) + if got := ui.Writer.(*bytes.Buffer).String(); !strings.Contains(got, "Attestation verified.") { + t.Fatalf("expected success output, got %q", got) + } +} + +func TestVerifyAttestationCommandRejectsMismatchedBuilderID(t *testing.T) { + artifactPath := filepath.Join(t.TempDir(), "artifact.txt") + if err := os.WriteFile(artifactPath, []byte("hello verify"), 0600); err != nil { + t.Fatalf("write artifact: %v", err) + } + + privateKeyPath, publicKeyPath := writeVerifierKeypair(t) + attestationPath := writeSignedAttestation(t, privateKeyPath, artifactPath, "https://example.com/builder", "git+https://github.com/hashicorp/packer@refs/heads/main") + + meta := testMeta(t) + command := &VerifyAttestationCommand{Meta: meta} + ret := command.Run([]string{ + "-verifier=" + publicKeyPath, + "-builder-id=https://example.com/other-builder", + attestationPath, + }) + if ret == 0 { + t.Fatalf("expected builder mismatch to fail") + } +} + +func TestVerifyAttestationCommandRequiresBundleForRekorChecks(t *testing.T) { + artifactPath := filepath.Join(t.TempDir(), "artifact.txt") + if err := os.WriteFile(artifactPath, []byte("hello verify"), 0600); err != nil { + t.Fatalf("write artifact: %v", err) + } + + privateKeyPath, publicKeyPath := writeVerifierKeypair(t) + attestationPath := writeSignedAttestation(t, privateKeyPath, artifactPath, "https://example.com/builder", "git+https://github.com/hashicorp/packer@refs/heads/main") + + meta := testMeta(t) + command := &VerifyAttestationCommand{Meta: meta} + ret := command.Run([]string{ + "-verifier=" + publicKeyPath, + "-require-rekor", + attestationPath, + }) + if ret == 0 { + t.Fatalf("expected missing bundle to fail") + } + + ui := meta.Ui.(*packersdk.BasicUi) + if got := ui.ErrorWriter.(*bytes.Buffer).String(); !strings.Contains(got, "requires -bundle") { + t.Fatalf("expected missing bundle error, got %q", got) + } +} + +func TestVerifyAttestationCommandRejectsTamperedPayload(t *testing.T) { + artifactPath := filepath.Join(t.TempDir(), "artifact.txt") + if err := os.WriteFile(artifactPath, []byte("hello verify"), 0600); err != nil { + t.Fatalf("write artifact: %v", err) + } + + privateKeyPath, publicKeyPath := writeVerifierKeypair(t) + attestationPath := writeSignedAttestation(t, privateKeyPath, artifactPath, "https://example.com/builder", "git+https://github.com/hashicorp/packer@refs/heads/main") + + tamperAttestationPayload(t, attestationPath) + + meta := testMeta(t) + command := &VerifyAttestationCommand{Meta: meta} + ret := command.Run([]string{ + "-verifier=" + publicKeyPath, + attestationPath, + }) + if ret == 0 { + t.Fatalf("expected tampered payload to fail verification") + } +} + +func tamperAttestationPayload(t *testing.T, attestationPath string) { + t.Helper() + + contents, err := os.ReadFile(attestationPath) + if err != nil { + t.Fatalf("read attestation: %v", err) + } + + var envelope internalattestation.Envelope + if err := json.Unmarshal(contents, &envelope); err != nil { + t.Fatalf("unmarshal envelope: %v", err) + } + + payload, err := internalattestation.DecodeEnvelopePayload(envelope) + if err != nil { + t.Fatalf("decode payload: %v", err) + } + + tampered := bytes.Replace(payload, []byte("https://example.com/builder"), []byte("https://example.com/evilbuilder"), 1) + if bytes.Equal(tampered, payload) { + t.Fatalf("expected payload to be modified") + } + envelope.Payload = base64.StdEncoding.EncodeToString(tampered) + + tamperedEnvelope, err := json.Marshal(envelope) + if err != nil { + t.Fatalf("marshal tampered envelope: %v", err) + } + if err := os.WriteFile(attestationPath, tamperedEnvelope, 0600); err != nil { + t.Fatalf("write tampered attestation: %v", err) + } +} + +func writeSignedAttestation(t *testing.T, privateKeyPath, artifactPath, builderID, sourceURI string) string { + t.Helper() + + artifactDigest := sha256.Sum256([]byte(readFileString(t, artifactPath))) + statement := internalprovenance.WrapInToto( + []internalprovenance.Subject{{ + Name: filepath.Base(artifactPath), + Digest: internalprovenance.DigestSet{ + "sha256": hex.EncodeToString(artifactDigest[:]), + }, + }}, + internalprovenance.SLSAProvenanceV1PredicateType, + internalprovenance.BuildSLSAPredicate(internalprovenance.PredicateInput{ + BuilderID: builderID, + ResolvedDependencies: []internalprovenance.ResolvedDependency{{URI: sourceURI}}, + }), + ) + payload, err := internalattestation.MarshalPayload(statement) + if err != nil { + t.Fatalf("marshal statement: %v", err) + } + + signer, err := internalattestation.NewSigner(context.Background(), internalattestation.BackendConfig{Mode: internalattestation.SigningModeKey, SignerRef: privateKeyPath}) + if err != nil { + t.Fatalf("create signer: %v", err) + } + + signature, err := signer.Sign(context.Background(), internalattestation.InTotoPayloadType, payload) + if err != nil { + t.Fatalf("sign statement: %v", err) + } + + attestationPath := filepath.Join(t.TempDir(), "attestation.json") + envelope, err := json.Marshal(internalattestation.NewEnvelope(internalattestation.InTotoPayloadType, payload, signature)) + if err != nil { + t.Fatalf("marshal envelope: %v", err) + } + if err := os.WriteFile(attestationPath, envelope, 0600); err != nil { + t.Fatalf("write attestation: %v", err) + } + + return attestationPath +} + +func writeVerifierKeypair(t *testing.T) (string, string) { + t.Helper() + + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("generate private key: %v", err) + } + + privateKeyDER, err := x509.MarshalECPrivateKey(privateKey) + if err != nil { + t.Fatalf("marshal private key: %v", err) + } + + publicKeyDER, err := x509.MarshalPKIXPublicKey(privateKey.Public()) + if err != nil { + t.Fatalf("marshal public key: %v", err) + } + + dir := t.TempDir() + privateKeyPath := filepath.Join(dir, "signer.pem") + publicKeyPath := filepath.Join(dir, "verifier.pem") + + if err := os.WriteFile(privateKeyPath, pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: privateKeyDER}), 0600); err != nil { + t.Fatalf("write private key: %v", err) + } + if err := os.WriteFile(publicKeyPath, pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: publicKeyDER}), 0600); err != nil { + t.Fatalf("write public key: %v", err) + } + + return privateKeyPath, publicKeyPath +} + +func readFileString(t *testing.T, path string) string { + t.Helper() + + contents, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read file %q: %v", path, err) + } + + return string(contents) +} diff --git a/commands.go b/commands.go index 5097902af..ff2ad4272 100644 --- a/commands.go +++ b/commands.go @@ -112,6 +112,10 @@ func init() { return &command.SBOMGenerateCommand{Meta: *CommandMeta}, nil }, + "verify-attestation": func() (cli.Command, error) { + return &command.VerifyAttestationCommand{Meta: *CommandMeta}, nil + }, + // plugin is essentially an alias to the plugins command // // It is not meant to be documented or used outside of simple diff --git a/examples/ci/README.md b/examples/ci/README.md new file mode 100644 index 000000000..87d472372 --- /dev/null +++ b/examples/ci/README.md @@ -0,0 +1,34 @@ +# Provenance CI reference workflows + +These are copy-paste reference workflows for producing SLSA provenance with the +Packer `provenance` post-processor. They are **not** wired into this +repository's own CI — copy them into your project's `.github/workflows/` +directory and adapt the template and artifact paths. + +## SLSA levels and Packer + +SLSA Build levels are mostly properties of the build **platform**, not the +build **tool**. Packer is a tool, so its reach is: + +| SLSA Build level | Requirement | Packer's role | What Packer provides | +|---|---|---|---| +| **L1** | Provenance exists and is distributed | Fully in Packer | Provenance generation | +| **L2** | Provenance signed by a hosted platform | Packer signs via CI OIDC identity | Keyless signing in CI | +| **L3** | Hardened platform; build steps cannot reach the signing key | Platform property; Packer is compatible | Delegated-signing pattern | +| **L4** | — | Not defined in SLSA v1.0 | — | + +Packer generates SLSA Provenance v1 and reaches Build L1, and L2 when run on a +hosted CI with keyless signing. L3 is a property of the build platform: it +requires the signing key to be unreachable by the build steps. Packer does not +confer L3 on its own, but the delegated-signing pattern below is compatible with +an L3 platform. + +## Workflows + +- [`github-actions-l2-keyless.yml`](github-actions-l2-keyless.yml) — L2: Packer + signs provenance keyless using the workflow's OIDC identity, uploads to Rekor, + and verifies the signed attestation with `packer verify-attestation`. +- [`github-actions-l3-delegated.yml`](github-actions-l3-delegated.yml) — + L3-compatible: the build job only builds and publishes a digest; provenance + generation and signing are delegated to an isolated reusable workflow so the + build steps cannot reach the signing material. diff --git a/examples/ci/github-actions-l2-keyless.yml b/examples/ci/github-actions-l2-keyless.yml new file mode 100644 index 000000000..df58b3cef --- /dev/null +++ b/examples/ci/github-actions-l2-keyless.yml @@ -0,0 +1,93 @@ +# Reference workflow — NOT wired into this repository's CI. +# +# Copy this into your own project's .github/workflows/ directory. +# +# SLSA Build L2 pattern: Packer signs the provenance attestation keyless, using +# the GitHub Actions workflow's own OIDC identity (Fulcio) and records the +# signature in the Rekor transparency log. This yields signed, transparently +# logged provenance produced by a hosted platform. +# +# This does NOT confer SLSA L3 on its own: the build job can still reach the +# (ephemeral) signing material. See github-actions-l3-delegated.yml for the +# L3-compatible delegated-signing pattern. + +name: build-and-sign-provenance + +on: + push: + tags: + - "v*" + +permissions: + contents: read + # Required so Packer's keyless signing can request an OIDC token from GitHub + # and exchange it with Fulcio for a short-lived signing certificate. + id-token: write + +jobs: + build: + runs-on: ubuntu-latest + env: + # Must match the workflow's OIDC identity so verification can pin it. + KEYLESS_IDENTITY: "https://github.com/${{ github.repository }}/.github/workflows/build-and-sign-provenance.yml@${{ github.ref }}" + KEYLESS_OIDC_ISSUER: "https://token.actions.githubusercontent.com" + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Packer + uses: hashicorp/setup-packer@main + with: + version: latest + + - name: Initialize plugins + run: packer init . + + # The template below is expected to declare two input variables and a + # keyless provenance post-processor. NOTE: Packer's `env()` function may + # only appear in a variable `default`, never inline in a block, so the + # workflow passes the values with `-var` instead. + # + # variable "keyless_identity" { type = string } + # variable "keyless_oidc_issuer" { type = string } + # + # post-processor "provenance" { + # signing_mode = "keyless" + # upload_tlog = true + # keyless_identity = var.keyless_identity + # keyless_oidc_issuer = var.keyless_oidc_issuer + # } + # + # Packer picks up the ambient GitHub OIDC token automatically from the + # ACTIONS_ID_TOKEN_REQUEST_URL / ACTIONS_ID_TOKEN_REQUEST_TOKEN variables + # that are available when `id-token: write` is granted above. + - name: Build and sign + run: | + packer build \ + -var "keyless_identity=${KEYLESS_IDENTITY}" \ + -var "keyless_oidc_issuer=${KEYLESS_OIDC_ISSUER}" \ + . + + # Verify the signed attestation with Rekor-backed transparency evidence + # using the generated Sigstore bundle sidecar (*.sigstore.json). + - name: Verify attestation + run: | + for att in *.provenance.json; do + packer verify-attestation \ + -signing-mode=keyless \ + -bundle="${att%.json}.sigstore.json" \ + -require-rekor \ + -require-timestamp \ + -keyless-identity="${KEYLESS_IDENTITY}" \ + -keyless-oidc-issuer="${KEYLESS_OIDC_ISSUER}" \ + -predicate-type="https://slsa.dev/provenance/v1" \ + "${att}" + done + + - name: Upload provenance artifacts + uses: actions/upload-artifact@v4 + with: + name: provenance + path: | + *.provenance.json + *.sigstore.json diff --git a/examples/ci/github-actions-l3-delegated.yml b/examples/ci/github-actions-l3-delegated.yml new file mode 100644 index 000000000..efcf1c8d3 --- /dev/null +++ b/examples/ci/github-actions-l3-delegated.yml @@ -0,0 +1,64 @@ +# Reference workflow — NOT wired into this repository's CI. +# +# Copy this into your own project's .github/workflows/ directory. +# +# SLSA Build L3-compatible pattern: the build job ONLY builds the artifact and +# publishes its digest. Provenance generation and signing are delegated to an +# isolated, reusable workflow (the SLSA GitHub generator) that runs in a +# separate job the build steps cannot influence or reach. This keeps the +# signing material unreachable from the build, which is what L3 requires. +# +# Packer itself does not confer L3. It participates by producing the artifact; +# the platform (isolated signer) provides the L3 property. + +name: build-and-delegate-provenance + +on: + push: + tags: + - "v*" + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + outputs: + digest: ${{ steps.hash.outputs.digest }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Packer + uses: hashicorp/setup-packer@main + with: + version: latest + + - name: Initialize plugins + run: packer init . + + # Build only. Do NOT sign here: signing in the build job would defeat the + # isolation that the delegated signer provides. + - name: Build artifact + run: packer build . + + # Emit a base64-encoded subject digest for the delegated generator. + - name: Compute artifact digest + id: hash + run: | + # Adjust the artifact path to match your build output. + echo "digest=$(sha256sum output/image.qcow2 | base64 -w0)" >> "${GITHUB_OUTPUT}" + + # Isolated, reusable workflow that generates and signs SLSA provenance in a + # job the build cannot reach. This is the component that provides the L3 + # property; pin it to a released tag in real usage. + provenance: + needs: [build] + permissions: + actions: read + id-token: write + contents: write + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0 + with: + base64-subjects: ${{ needs.build.outputs.digest }} diff --git a/go.mod b/go.mod index 82a2ef47f..1dc22cedf 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/chzyer/readline v1.5.1 github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 github.com/go-git/go-git/v5 v5.19.1 - github.com/go-openapi/runtime v0.28.0 + github.com/go-openapi/runtime v0.32.3 github.com/gobwas/glob v0.2.3 github.com/gofrs/flock v0.8.1 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect @@ -24,7 +24,7 @@ require ( github.com/hashicorp/hcp-sdk-go v0.174.0 github.com/hashicorp/packer-plugin-sdk v0.6.10 github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 - github.com/klauspost/compress v1.18.5 + github.com/klauspost/compress v1.18.6 github.com/klauspost/pgzip v1.2.6 github.com/masterzen/winrm v0.0.0-20250927112105-5f8e6c707321 github.com/mattn/go-runewidth v0.0.19 // indirect @@ -57,37 +57,51 @@ require ( github.com/CycloneDX/cyclonedx-go v0.11.0 github.com/Masterminds/semver/v3 v3.4.0 github.com/anchore/syft v1.42.3 - github.com/go-openapi/strfmt v0.23.0 + github.com/go-openapi/strfmt v0.26.3 github.com/google/go-github/v75 v75.0.0 github.com/oklog/ulid v1.3.1 github.com/pierrec/lz4/v4 v4.1.22 github.com/shirou/gopsutil/v3 v3.23.4 + github.com/sigstore/protobuf-specs v0.5.1 + github.com/sigstore/sigstore v1.10.8 + github.com/sigstore/sigstore-go v1.2.1 + github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.8 + github.com/sigstore/sigstore/pkg/signature/kms/azure v1.10.8 + github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.10.8 + github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.10.8 github.com/spdx/tools-golang v0.5.7 - google.golang.org/grpc v1.79.3 + google.golang.org/grpc v1.81.1 modernc.org/sqlite v1.46.1 ) require ( cel.dev/expr v0.25.1 // indirect - cloud.google.com/go/auth v0.18.2 // indirect + cloud.google.com/go/auth v0.20.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect - cloud.google.com/go/iam v1.5.3 // indirect - cloud.google.com/go/monitoring v1.24.3 // indirect - cloud.google.com/go/storage v1.61.3 // indirect - cyphar.com/go-pathrs v0.2.1 // indirect + cloud.google.com/go/iam v1.11.0 // indirect + cloud.google.com/go/kms v1.31.0 // indirect + cloud.google.com/go/longrunning v1.0.0 // indirect + cloud.google.com/go/monitoring v1.25.0 // indirect + cloud.google.com/go/storage v1.62.2 // indirect dario.cat/mergo v1.0.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.5.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect github.com/Azure/go-ntlmssp v0.1.1 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.7.0 // indirect github.com/BurntSushi/toml v1.6.0 // indirect github.com/ChrisTrenkamp/goxpath v0.0.0-20210404020558-97928f7e12b6 // indirect github.com/DataDog/zstd v1.5.5 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/sprig/v3 v3.3.0 // indirect - github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/Microsoft/hcsshim v0.14.1 // indirect + github.com/Microsoft/go-winio v0.6.3-0.20251027160822-ad3df93bed29 // indirect + github.com/Microsoft/hcsshim v0.15.0-rc.1 // indirect github.com/OneOfOne/xxhash v1.2.8 // indirect github.com/ProtonMail/go-crypto v1.4.1 // indirect github.com/STARRY-S/zip v0.2.3 // indirect @@ -116,40 +130,42 @@ require ( github.com/armon/go-metrics v0.4.1 // indirect github.com/armon/go-radix v1.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/aws/aws-sdk-go v1.45.6 // indirect - github.com/aws/aws-sdk-go-v2 v1.41.5 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect - github.com/aws/aws-sdk-go-v2/config v1.32.12 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.19.12 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 // indirect - github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 // indirect + github.com/aws/aws-sdk-go v1.55.5 // indirect + github.com/aws/aws-sdk-go-v2 v1.41.7 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10 // indirect + github.com/aws/aws-sdk-go-v2/config v1.32.17 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.19.16 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23 // indirect + github.com/aws/aws-sdk-go-v2/service/kms v1.52.0 // indirect + github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0 // indirect github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.37.0 // indirect - github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 // indirect - github.com/aws/smithy-go v1.24.2 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.11 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.17 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.42.1 // indirect + github.com/aws/smithy-go v1.25.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/becheran/wildmatch-go v1.0.0 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/bgentry/speakeasy v0.2.0 // indirect github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef // indirect github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect + github.com/blang/semver v3.5.1+incompatible // indirect github.com/bmatcuk/doublestar v1.3.4 // indirect github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect github.com/bodgit/ntlmssp v0.0.0-20240506230425-31973bb52d9b // indirect github.com/bodgit/plumbing v1.3.0 // indirect github.com/bodgit/sevenzip v1.6.1 // indirect github.com/bodgit/windows v1.0.1 // indirect - github.com/cenkalti/backoff/v3 v3.2.2 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charmbracelet/colorprofile v0.4.1 // indirect github.com/charmbracelet/lipgloss v1.1.0 // indirect @@ -159,36 +175,38 @@ require ( github.com/clipperhouse/displaywidth v0.10.0 // indirect github.com/clipperhouse/uax29/v2 v2.6.0 // indirect github.com/cloudflare/circl v1.6.3 // indirect - github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect - github.com/containerd/cgroups/v3 v3.1.2 // indirect - github.com/containerd/containerd/api v1.10.0 // indirect - github.com/containerd/containerd/v2 v2.2.5 // indirect - github.com/containerd/continuity v0.4.5 // indirect + github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect + github.com/containerd/cgroups/v3 v3.1.3 // indirect + github.com/containerd/containerd/api v1.11.1 // indirect + github.com/containerd/containerd/v2 v2.3.3 // indirect + github.com/containerd/continuity v0.5.0 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/fifo v1.1.0 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/containerd/platforms v1.0.0-rc.2 // indirect - github.com/containerd/plugin v1.0.0 // indirect - github.com/containerd/stargz-snapshotter/estargz v0.18.2 // indirect - github.com/containerd/ttrpc v1.2.7 // indirect + github.com/containerd/platforms v1.0.0-rc.4 // indirect + github.com/containerd/plugin v1.1.0 // indirect + github.com/containerd/ttrpc v1.2.8 // indirect github.com/containerd/typeurl/v2 v2.2.3 // indirect + github.com/coreos/go-oidc/v3 v3.17.0 // indirect + github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 // indirect github.com/cyphar/filepath-securejoin v0.6.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da // indirect + github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 // indirect + github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect github.com/diskfs/go-diskfs v1.7.0 // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/cli v29.3.0+incompatible // indirect - github.com/docker/distribution v2.8.3+incompatible // indirect + github.com/docker/cli v29.4.3+incompatible // indirect github.com/docker/docker-credential-helpers v0.9.5 // indirect - github.com/docker/go-connections v0.6.0 // indirect + github.com/docker/go-connections v0.7.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dylanmei/iso8601 v0.1.0 // indirect github.com/elliotchance/phpserialize v1.4.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect - github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect - github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect + github.com/envoyproxy/go-control-plane/envoy v1.37.0 // indirect + github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect github.com/facebookincubator/nvdtools v0.1.5 // indirect github.com/fatih/color v1.18.0 // indirect github.com/felixge/fgprof v0.9.5 // indirect @@ -202,40 +220,44 @@ require ( github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect - github.com/go-openapi/analysis v0.23.0 // indirect - github.com/go-openapi/errors v0.22.2 // indirect - github.com/go-openapi/jsonpointer v0.21.0 // indirect - github.com/go-openapi/jsonreference v0.21.0 // indirect - github.com/go-openapi/loads v0.22.0 // indirect - github.com/go-openapi/spec v0.21.0 // indirect - github.com/go-openapi/swag v0.24.1 // indirect - github.com/go-openapi/swag/cmdutils v0.24.0 // indirect - github.com/go-openapi/swag/conv v0.24.0 // indirect - github.com/go-openapi/swag/fileutils v0.24.0 // indirect - github.com/go-openapi/swag/jsonname v0.24.0 // indirect - github.com/go-openapi/swag/jsonutils v0.24.0 // indirect - github.com/go-openapi/swag/loading v0.24.0 // indirect - github.com/go-openapi/swag/mangling v0.24.0 // indirect - github.com/go-openapi/swag/netutils v0.24.0 // indirect - github.com/go-openapi/swag/stringutils v0.24.0 // indirect - github.com/go-openapi/swag/typeutils v0.24.0 // indirect - github.com/go-openapi/swag/yamlutils v0.24.0 // indirect - github.com/go-openapi/validate v0.24.0 // indirect + github.com/go-openapi/analysis v0.25.2 // indirect + github.com/go-openapi/errors v0.22.7 // indirect + github.com/go-openapi/jsonpointer v0.23.1 // indirect + github.com/go-openapi/jsonreference v0.21.6 // indirect + github.com/go-openapi/loads v0.23.3 // indirect + github.com/go-openapi/runtime/server-middleware v0.30.0 // indirect + github.com/go-openapi/spec v0.22.5 // indirect + github.com/go-openapi/swag v0.26.0 // indirect + github.com/go-openapi/swag/cmdutils v0.26.0 // indirect + github.com/go-openapi/swag/conv v0.26.0 // indirect + github.com/go-openapi/swag/fileutils v0.26.0 // indirect + github.com/go-openapi/swag/jsonname v0.26.0 // indirect + github.com/go-openapi/swag/jsonutils v0.26.0 // indirect + github.com/go-openapi/swag/loading v0.26.0 // indirect + github.com/go-openapi/swag/mangling v0.26.0 // indirect + github.com/go-openapi/swag/netutils v0.26.0 // indirect + github.com/go-openapi/swag/stringutils v0.26.0 // indirect + github.com/go-openapi/swag/typeutils v0.26.0 // indirect + github.com/go-openapi/swag/yamlutils v0.26.0 // indirect + github.com/go-openapi/validate v0.25.3 // indirect github.com/go-restruct/restruct v1.2.0-alpha // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/goccy/go-yaml v1.19.2 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/gohugoio/hashstructure v0.6.0 // indirect - github.com/google/go-containerregistry v0.21.2 // indirect + github.com/golang-jwt/jwt/v5 v5.3.1 // indirect + github.com/google/certificate-transparency-go v1.3.3 // indirect + github.com/google/go-containerregistry v0.21.6 // indirect github.com/google/licensecheck v0.3.1 // indirect - github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect + github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect - github.com/googleapis/gax-go/v2 v2.17.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.15 // indirect + github.com/googleapis/gax-go/v2 v2.22.0 // indirect github.com/gookit/color v1.6.0 // indirect github.com/gpustack/gguf-parser-go v0.24.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.72 // indirect github.com/hashicorp/consul/api v1.25.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect @@ -245,21 +267,23 @@ require ( github.com/hashicorp/go-getter/s3/v2 v2.2.2 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect - github.com/hashicorp/go-retryablehttp v0.7.7 // indirect + github.com/hashicorp/go-retryablehttp v0.7.8 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect - github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect + github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect github.com/hashicorp/go-sockaddr v1.0.7 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect + github.com/hashicorp/hcl v1.0.1-vault-7 // indirect github.com/hashicorp/serf v0.10.1 // indirect - github.com/hashicorp/vault/api v1.14.0 // indirect + github.com/hashicorp/vault/api v1.22.0 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/henvic/httpretty v0.1.4 // indirect github.com/huandu/xstrings v1.5.0 // indirect github.com/iancoleman/strcase v0.3.0 // indirect + github.com/in-toto/attestation v1.2.0 // indirect + github.com/in-toto/in-toto-golang v0.11.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jcmturner/aescts/v2 v2.0.0 // indirect @@ -268,17 +292,18 @@ require ( github.com/jcmturner/goidentity/v6 v6.0.1 // indirect github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect github.com/jcmturner/rpc/v2 v2.0.3 // indirect + github.com/jedisct1/go-minisign v0.0.0-20211028175153-1c139d1cc84b // indirect + github.com/jellydator/ttlcache/v3 v3.4.0 // indirect github.com/jinzhu/copier v0.4.0 // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/josharian/intern v1.0.0 // indirect + github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953 // indirect github.com/kevinburke/ssh_config v1.6.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/kr/fs v0.1.0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect github.com/lucasb-eyer/go-colorful v1.3.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect - github.com/mailru/easyjson v0.9.0 // indirect github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -294,8 +319,8 @@ require ( github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/locker v1.0.1 // indirect - github.com/moby/moby/api v1.54.0 // indirect - github.com/moby/moby/client v0.3.0 // indirect + github.com/moby/moby/api v1.54.2 // indirect + github.com/moby/moby/client v0.4.1 // indirect github.com/moby/sys/mountinfo v0.7.2 // indirect github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/sys/signal v0.7.1 // indirect @@ -304,10 +329,12 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/muesli/termenv v0.16.0 // indirect + github.com/natefinch/atomic v1.0.1 // indirect github.com/ncruces/go-strftime v1.0.0 // indirect github.com/nix-community/go-nix v0.0.0-20250101154619-4bdde671e0a1 // indirect github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect github.com/nwaples/rardecode/v2 v2.2.0 // indirect + github.com/oklog/ulid/v2 v2.1.1 // indirect github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect github.com/olekukonko/errors v1.2.0 // indirect github.com/olekukonko/ll v0.1.6 // indirect @@ -315,12 +342,11 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/opencontainers/runtime-spec v1.3.0 // indirect - github.com/opencontainers/selinux v1.13.1 // indirect - github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pborman/indent v1.2.1 // indirect github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/pelletier/go-toml/v2 v2.3.0 // indirect github.com/pjbgf/sha1cd v0.6.0 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pkg/profile v1.7.0 // indirect github.com/pkg/xattr v0.4.9 // indirect @@ -331,36 +357,45 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect github.com/rust-secure-code/go-rustaudit v0.0.0-20250226111315-e20ec32e963c // indirect github.com/ryanuber/go-glob v1.0.0 // indirect - github.com/sagikazarmark/locafero v0.7.0 // indirect + github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect github.com/sassoftware/go-rpmutils v0.4.0 // indirect + github.com/sassoftware/relic v7.2.1+incompatible // indirect github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e // indirect + github.com/secure-systems-lab/go-securesystemslib v0.11.0 // indirect github.com/sergi/go-diff v1.4.0 // indirect + github.com/shibumi/go-pathspec v1.3.0 // indirect github.com/shoenig/go-m1cpu v0.1.5 // indirect github.com/shopspring/decimal v1.4.0 // indirect + github.com/sigstore/rekor v1.5.2 // indirect + github.com/sigstore/rekor-tiles/v2 v2.2.2-0.20260601073857-5d098a2b6443 // indirect + github.com/sigstore/timestamp-authority/v2 v2.1.2 // indirect github.com/sirupsen/logrus v1.9.4 // indirect github.com/skeema/knownhosts v1.3.2 // indirect github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/smallnest/ringbuffer v0.0.0-20241116012123-461381446e3d // indirect github.com/sorairolake/lzip-go v0.3.8 // indirect - github.com/sourcegraph/conc v0.3.0 // indirect + github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb // indirect github.com/spf13/afero v1.15.0 // indirect - github.com/spf13/cast v1.7.1 // indirect + github.com/spf13/cast v1.10.0 // indirect github.com/spf13/cobra v1.10.2 // indirect github.com/spf13/pflag v1.0.10 // indirect - github.com/spf13/viper v1.20.0 // indirect + github.com/spf13/viper v1.21.0 // indirect github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/sylabs/sif/v2 v2.24.0 // indirect github.com/sylabs/squashfs v1.0.6 // indirect github.com/therootcompany/xz v1.0.1 // indirect + github.com/theupdateframework/go-tuf v0.7.0 // indirect + github.com/theupdateframework/go-tuf/v2 v2.4.2-0.20260407074541-7e8f69f906ef // indirect github.com/tidwall/transform v0.0.0-20201103190739-32f242e2dbde // indirect github.com/tklauser/go-sysconf v0.3.11 // indirect github.com/tklauser/numcpus v0.6.0 // indirect + github.com/transparency-dev/formats v0.1.1 // indirect + github.com/transparency-dev/merkle v0.0.2 // indirect github.com/ugorji/go/codec v1.2.6 // indirect github.com/vbatts/go-mtree v0.7.0 // indirect - github.com/vbatts/tar-split v0.12.2 // indirect github.com/vifraa/gopom v1.0.0 // indirect github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect @@ -370,39 +405,38 @@ require ( github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect - go.mongodb.org/mongo-driver v1.17.9 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect - go.opentelemetry.io/otel v1.43.0 // indirect - go.opentelemetry.io/otel/metric v1.43.0 // indirect - go.opentelemetry.io/otel/sdk v1.43.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect - go.opentelemetry.io/otel/trace v1.43.0 // indirect - go.uber.org/atomic v1.9.0 // indirect - go.uber.org/multierr v1.9.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.42.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect + go.opentelemetry.io/otel v1.44.0 // indirect + go.opentelemetry.io/otel/metric v1.44.0 // indirect + go.opentelemetry.io/otel/sdk v1.44.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect + go.opentelemetry.io/otel/trace v1.44.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect go4.org v0.0.0-20230225012048-214862532bf5 // indirect golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect golang.org/x/time v0.15.0 // indirect - golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect - gonum.org/v1/gonum v0.16.0 // indirect - google.golang.org/api v0.271.0 // indirect - google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect - google.golang.org/protobuf v1.36.11 // indirect + golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect + gonum.org/v1/gonum v0.17.0 // indirect + google.golang.org/api v0.280.0 // indirect + google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68 // indirect + google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/klog/v2 v2.140.0 // indirect modernc.org/libc v1.67.6 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect ) -go 1.25.11 +go 1.26.3 replace github.com/zclconf/go-cty => github.com/nywilken/go-cty v1.13.3 // added by packer-sdc fix as noted in github.com/hashicorp/packer-plugin-sdk/issues/187 diff --git a/go.sum b/go.sum index a6a17a11e..d9ca5218e 100644 --- a/go.sum +++ b/go.sum @@ -31,8 +31,8 @@ cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0c cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= -cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM= -cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M= +cloud.google.com/go/auth v0.20.0 h1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA= +cloud.google.com/go/auth v0.20.0/go.mod h1:942/yi/itH1SsmpyrbnTMDgGfdy2BUqIKyd0cyYLc5Q= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= @@ -46,14 +46,16 @@ cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCB cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= -cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= -cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= -cloud.google.com/go/logging v1.13.1 h1:O7LvmO0kGLaHY/gq8cV7T0dyp6zJhYAOtZPX4TF3QtY= -cloud.google.com/go/logging v1.13.1/go.mod h1:XAQkfkMBxQRjQek96WLPNze7vsOmay9H5PqfsNYDqvw= -cloud.google.com/go/longrunning v0.8.0 h1:LiKK77J3bx5gDLi4SMViHixjD2ohlkwBi+mKA7EhfW8= -cloud.google.com/go/longrunning v0.8.0/go.mod h1:UmErU2Onzi+fKDg2gR7dusz11Pe26aknR4kHmJJqIfk= -cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE= -cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI= +cloud.google.com/go/iam v1.11.0 h1:KieQ9Pb+LLPak1O3Rv3GgCxhnmkYf7Xyh0P5HfF1jFM= +cloud.google.com/go/iam v1.11.0/go.mod h1:KP+nKGugNJW4LcLx1uEZcq1ok5sQHFaQehQNl4QDgV4= +cloud.google.com/go/kms v1.31.0 h1:LS8N92OxFDgOLg5NCo3OmbvjtQAIVT5gUHVLKIDHaFE= +cloud.google.com/go/kms v1.31.0/go.mod h1:YIyXZym11R5uovJJt4oN5eUL3oPmirF3yKeIh6QAf4U= +cloud.google.com/go/logging v1.13.2 h1:qqlHCBvieJT9Cdq4QqYx1KPadCQ2noD4FK02eNqHAjA= +cloud.google.com/go/logging v1.13.2/go.mod h1:zaybliM3yun1J8mU2dVQ1/qDzjbOqEijZCn6hSBtKak= +cloud.google.com/go/longrunning v1.0.0 h1:lwzWEYD8+NkYV7dhexOz6kmlvajZA70+bW/xMhRVVdY= +cloud.google.com/go/longrunning v1.0.0/go.mod h1:8nqFBPOO1U/XkhWl0I19AMZEphrHi73VNABIpKYaTwM= +cloud.google.com/go/monitoring v1.25.0 h1:HnsTIOxTN6BCSkt1P/Im23r1m7MHTTpmSYCzPkW7NK4= +cloud.google.com/go/monitoring v1.25.0/go.mod h1:wlj6rX+JGyusw/8+2duW4cJ6kmDHGmde3zMTJuG3Jpc= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -63,19 +65,39 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.61.3 h1:VS//ZfBuPGDvakfD9xyPW1RGF1Vy3BWUoVZXgW1KMOg= -cloud.google.com/go/storage v1.61.3/go.mod h1:JtqK8BBB7TWv0HVGHubtUdzYYrakOQIsMLffZ2Z/HWk= +cloud.google.com/go/storage v1.62.2 h1:WgR4U9n7bIzXkkVnwPKKE8bkaKUNsHG+0MAAlh9DGU4= +cloud.google.com/go/storage v1.62.2/go.mod h1:cpYz/kRVZ+UQAF1uHeea10/9ewcRbxGoGNKsS9daSXA= cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U= cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s= -cyphar.com/go-pathrs v0.2.1 h1:9nx1vOgwVvX1mNBWDu93+vaceedpbsDqo+XuBGL40b8= -cyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= +filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= +filippo.io/mldsa v0.0.0-20260215214346-43d0283efc3e h1:VsUbObBMxXlc23Eb9VeeJYE4jvTs87qa5RqSN2U5FJU= +filippo.io/mldsa v0.0.0-20260215214346-43d0283efc3e/go.mod h1:32qQ5yj3R24Eu03iWFWchdC3OB653wPvoepWejkefbY= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d h1:zjqpY4C7H15HjRPEenkS4SAn3Jy2eRRjkjZbGR30TOg= +github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d/go.mod h1:XNqJ7hv2kY++g8XEHREpi+JqZo3+0l+CH2egBVN4yqM= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1 h1:jHb/wfvRikGdxMXYV3QG/SzUOPYN9KEUUuC0Yd0/vC0= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1/go.mod h1:pzBXCYn05zvYIrwLgtK8Ap8QcjRg+0i76tMQdWN6wOk= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.5.0 h1:MaKvxE6D0KkjOg6Wd9M00iqP5PR0kUxCfiezes4JweM= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.5.0/go.mod h1:i2h9fsTFKZorh8RdV2IcSUf/Qj98GlTkrTvUbX/s8as= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA= github.com/Azure/go-ntlmssp v0.1.1 h1:l+FM/EEMb0U9QZE7mKNEDw5Mu3mFiaa2GKOoTSsNDPw= github.com/Azure/go-ntlmssp v0.1.1/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= +github.com/AzureAD/microsoft-authentication-library-for-go v1.7.0 h1:4iB+IesclUXdP0ICgAabvq2FYLXrJWKx1fJQ+GxSo3Y= +github.com/AzureAD/microsoft-authentication-library-for-go v1.7.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= @@ -88,8 +110,8 @@ github.com/CycloneDX/cyclonedx-go v0.11.0/go.mod h1:vUvbCXQsEm48OI6oOlanxstwNByX github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 h1:DHa2U07rk8syqvCge0QIGMCE1WxGj9njT44GH7zNJLQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 h1:UnDZ/zFfG1JhH/DqxIZYU/1CUAlTUScoXD/LcM2Ykk8= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0/go.mod h1:IA1C1U7jO/ENqm/vhi7V9YYpBsp+IMyqNrEN94N7tVc= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.55.0 h1:7t/qx5Ost0s0wbA/VDrByOooURhp+ikYwv20i9Y07TQ= @@ -105,10 +127,10 @@ github.com/Masterminds/sprig/v3 v3.2.1/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFP github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Microsoft/hcsshim v0.14.1 h1:CMuB3fqQVfPdhyXhUqYdUmPUIOhJkmghCx3dJet8Cqs= -github.com/Microsoft/hcsshim v0.14.1/go.mod h1:VnzvPLyWUhxiPVsJ31P6XadxCcTogTguBFDy/1GR/OM= +github.com/Microsoft/go-winio v0.6.3-0.20251027160822-ad3df93bed29 h1:0kQAzHq8vLs7Pptv+7TxjdETLf/nIqJpIB4oC6Ba4vY= +github.com/Microsoft/go-winio v0.6.3-0.20251027160822-ad3df93bed29/go.mod h1:ZWa7ssZJT30CCDGJ7fk/2SBTq9BIQrrVjrcss0UW2s0= +github.com/Microsoft/hcsshim v0.15.0-rc.1 h1:FbbwtQmiD+BVHynGkx5S65JkLyhkEiiTP8nrpmg2SZw= +github.com/Microsoft/hcsshim v0.15.0-rc.1/go.mod h1:HWvvUPIy9HF6LotILj1G4VyS065rcLQ6tqj6tMUdOfI= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= @@ -126,6 +148,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= +github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/anchore/clio v0.0.0-20250319180342-2cfe4b0cb716 h1:2sIdYJlQESEnyk3Y0WD2vXWW5eD2iMz9Ev8fj1Z8LNA= github.com/anchore/clio v0.0.0-20250319180342-2cfe4b0cb716/go.mod h1:Utb9i4kwiCWvqAIxZaJeMIXFO9uOgQXlvH2BfbfO/zI= github.com/anchore/fangs v0.0.0-20250319222917-446a1e748ec2 h1:GC2QaO0YsmjpsZ4rtVKv9DnproIxqqn+qkskpc+i8MA= @@ -188,48 +212,48 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= -github.com/aws/aws-sdk-go v1.45.6 h1:Y2isQQBZsnO15dzUQo9YQRThtHgrV200XCH05BRHVJI= -github.com/aws/aws-sdk-go v1.45.6/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= -github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY= -github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI= -github.com/aws/aws-sdk-go-v2/config v1.32.12 h1:O3csC7HUGn2895eNrLytOJQdoL2xyJy0iYXhoZ1OmP0= -github.com/aws/aws-sdk-go-v2/config v1.32.12/go.mod h1:96zTvoOFR4FURjI+/5wY1vc1ABceROO4lWgWJuxgy0g= -github.com/aws/aws-sdk-go-v2/credentials v1.19.12 h1:oqtA6v+y5fZg//tcTWahyN9PEn5eDU/Wpvc2+kJ4aY8= -github.com/aws/aws-sdk-go-v2/credentials v1.19.12/go.mod h1:U3R1RtSHx6NB0DvEQFGyf/0sbrpJrluENHdPy1j/3TE= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 h1:zOgq3uezl5nznfoK3ODuqbhVg1JzAGDUhXOsU0IDCAo= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20/go.mod h1:z/MVwUARehy6GAg/yQ1GO2IMl0k++cu1ohP9zo887wE= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 h1:qYQ4pzQ2Oz6WpQ8T3HvGHnZydA72MnLuFK9tJwmrbHw= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 h1:rWyie/PxDRIdhNf4DzRk0lvjVOqFJuNnO8WwaIRVxzQ= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22/go.mod h1:zd/JsJ4P7oGfUhXn1VyLqaRZwPmZwg44Jf2dS84Dm3Y= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 h1:JRaIgADQS/U6uXDqlPiefP32yXTda7Kqfx+LgspooZM= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13/go.mod h1:CEuVn5WqOMilYl+tbccq8+N2ieCy0gVn3OtRb0vBNNM= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 h1:c31//R3xgIJMSC8S6hEVq+38DcvUlgFY0FM6mSI5oto= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 h1:ZlvrNcHSFFWURB8avufQq9gFsheUgjVD9536obIknfM= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21/go.mod h1:cv3TNhVrssKR0O/xxLJVRfd2oazSnZnkUeTf6ctUwfQ= -github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 h1:HwxWTbTrIHm5qY+CAEur0s/figc3qwvLWsNkF4RPToo= -github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3/go.mod h1:uoA43SdFwacedBfSgfFSjjCvYe8aYBS7EnU5GZ/YKMM= +github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= +github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go-v2 v1.41.7 h1:DWpAJt66FmnnaRIOT/8ASTucrvuDPZASqhhLey6tLY8= +github.com/aws/aws-sdk-go-v2 v1.41.7/go.mod h1:4LAfZOPHNVNQEckOACQx60Y8pSRjIkNZQz1w92xpMJc= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10 h1:gx1AwW1Iyk9Z9dD9F4akX5gnN3QZwUB20GGKH/I+Rho= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10/go.mod h1:qqY157uZoqm5OXq/amuaBJyC9hgBCBQnsaWnPe905GY= +github.com/aws/aws-sdk-go-v2/config v1.32.17 h1:FpL4/758/diKwqbytU0prpuiu60fgXKUWCpDJtApclU= +github.com/aws/aws-sdk-go-v2/config v1.32.17/go.mod h1:OXqUMzgXytfoF9JaKkhrOYsyh72t9G+MJH8mMRaexOE= +github.com/aws/aws-sdk-go-v2/credentials v1.19.16 h1:r3RJBuU7X9ibt8RHbMjWE6y60QbKBiII6wSrXnapxSU= +github.com/aws/aws-sdk-go-v2/credentials v1.19.16/go.mod h1:6cx7zqDENJDbBIIWX6P8s0h6hqHC8Avbjh9Dseo27ug= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23 h1:UuSfcORqNSz/ey3VPRS8TcVH2Ikf0/sC+Hdj400QI6U= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23/go.mod h1:+G/OSGiOFnSOkYloKj/9M35s74LgVAdJBSD5lsFfqKg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 h1:GpT/TrnBYuE5gan2cZbTtvP+JlHsutdmlV2YfEyNde0= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23/go.mod h1:xYWD6BS9ywC5bS3sz9Xh04whO/hzK2plt2Zkyrp4JuA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 h1:bpd8vxhlQi2r1hiueOw02f/duEPTMK59Q4QMAoTTtTo= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23/go.mod h1:15DfR2nw+CRHIk0tqNyifu3G1YdAOy68RftkhMDDwYk= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 h1:OQqn11BtaYv1WLUowvcA30MpzIu8Ti4pcLPIIyoKZrA= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24/go.mod h1:X5ZJyfwVrWA96GzPmUCWFQaEARPR7gCrpq2E92PJwAE= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 h1:FLudkZLt5ci0ozzgkVo8BJGwvqNaZbTWb3UcucAateA= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9/go.mod h1:w7wZ/s9qK7c8g4al+UyoF1Sp/Z45UwMGcqIzLWVQHWk= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15 h1:ieLCO1JxUWuxTZ1cRd0GAaeX7O6cIxnwk7tc1LsQhC4= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15/go.mod h1:e3IzZvQ3kAWNykvE0Tr0RDZCMFInMvhku3qNpcIQXhM= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 h1:pbrxO/kuIwgEsOPLkaHu0O+m4fNgLU8B3vxQ+72jTPw= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23/go.mod h1:/CMNUqoj46HpS3MNRDEDIwcgEnrtZlKRaHNaHxIFpNA= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23 h1:03xatSQO4+AM1lTAbnRg5OK528EUg744nW7F73U8DKw= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23/go.mod h1:M8l3mwgx5ToK7wot2sBBce/ojzgnPzZXUV445gTSyE8= +github.com/aws/aws-sdk-go-v2/service/kms v1.52.0 h1:QNtg+Mtj1zmepk568+UKBD5DFfqh+ESTUUqQT27JkQc= +github.com/aws/aws-sdk-go-v2/service/kms v1.52.0/go.mod h1:Y0+uxvxz6ib4KktRdK0V4X45Vcs/JyYoz8H71pO8xeI= +github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0 h1:etqBTKY581iwLL/H/S2sVgk3C9lAsTJFeXWFDsDcWOU= +github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0/go.mod h1:L2dcoOgS2VSgbPLvpak2NyUPsO1TBN7M45Z4H7DlRc4= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.37.0 h1:fC0s79wxfsbz/4WCvosbHLk2mb9ICjPyB+lWs6a0TGM= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.37.0/go.mod h1:6HxvKCop1trgfFlQGQmlq+WbMM5yPazMN9ClWFWGtDM= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 h1:0GFOLzEbOyZABS3PhYfBIx2rNBACYcKty+XGkTgw1ow= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.8/go.mod h1:LXypKvk85AROkKhOG6/YEcHFPoX+prKTowKnVdcaIxE= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 h1:kiIDLZ005EcKomYYITtfsjn7dtOwHDOFy7IbPXKek2o= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.13/go.mod h1:2h/xGEowcW/g38g06g3KpRWDlT+OTfxxI0o1KqayAB8= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 h1:jzKAXIlhZhJbnYwHbvUQZEB8KfgAEuG0dc08Bkda7NU= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17/go.mod h1:Al9fFsXjv4KfbzQHGe6V4NZSZQXecFcvaIF4e70FoRA= -github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 h1:Cng+OOwCHmFljXIxpEVXAGMnBia8MSU6Ch5i9PgBkcU= -github.com/aws/aws-sdk-go-v2/service/sts v1.41.9/go.mod h1:LrlIndBDdjA/EeXeyNBle+gyCwTlizzW5ycgWnvIxkk= -github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng= -github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.11 h1:TdJ+HdzOBhU8+iVAOGUTU63VXopcumCOF1paFulHWZc= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.11/go.mod h1:R82ZRExE/nheo0N+T8zHPcLRTcH8MGsnR3BiVGX0TwI= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.17 h1:7byT8HUWrgoRp6sXjxtZwgOKfhss5fW6SkLBtqzgRoE= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.17/go.mod h1:xNWknVi4Ezm1vg1QsB/5EWpAJURq22uqd38U8qKvOJc= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21 h1:+1Kl1zx6bWi4X7cKi3VYh29h8BvsCoHQEQ6ST9X8w7w= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21/go.mod h1:4vIRDq+CJB2xFAXZ+YgGUTiEft7oAQlhIs71xcSeuVg= +github.com/aws/aws-sdk-go-v2/service/sts v1.42.1 h1:F/M5Y9I3nwr2IEpshZgh1GeHpOItExNM9L1euNuh/fk= +github.com/aws/aws-sdk-go-v2/service/sts v1.42.1/go.mod h1:mTNxImtovCOEEuD65mKW7DCsL+2gjEH+RPEAexAzAio= +github.com/aws/smithy-go v1.25.1 h1:J8ERsGSU7d+aCmdQur5Txg6bVoYelvQJgtZehD12GkI= +github.com/aws/smithy-go v1.25.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/becheran/wildmatch-go v1.0.0 h1:mE3dGGkTmpKtT4Z+88t8RStG40yN9T+kFEGj2PZFSzA= @@ -249,6 +273,8 @@ github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef h1:TSFnfbbu2oAO github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef/go.mod h1:9iglf1GG4oNRJ39bZ5AZrjgAFD2RwQbXw6Qf7Cs47wo= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= +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/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0= github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= @@ -263,8 +289,10 @@ github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4= github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= -github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= -github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= +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= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -319,16 +347,18 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w= -github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= -github.com/containerd/cgroups/v3 v3.1.2 h1:OSosXMtkhI6Qove637tg1XgK4q+DhR0mX8Wi8EhrHa4= -github.com/containerd/cgroups/v3 v3.1.2/go.mod h1:PKZ2AcWmSBsY/tJUVhtS/rluX0b1uq1GmPO1ElCmbOw= -github.com/containerd/containerd/api v1.10.0 h1:5n0oHYVBwN4VhoX9fFykCV9dF1/BvAXeg2F8W6UYq1o= -github.com/containerd/containerd/api v1.10.0/go.mod h1:NBm1OAk8ZL+LG8R0ceObGxT5hbUYj7CzTmR3xh0DlMM= -github.com/containerd/containerd/v2 v2.2.5 h1:KTFzB02LviYmmfRmz8r9UFd+n6YlddVFK+5lbgQXUTU= -github.com/containerd/containerd/v2 v2.2.5/go.mod h1:5t2+xFv2dGd/iDYp9Z8DXB4cmWrWQi1XqxGJPS2gBzU= -github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= -github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= +github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik= +github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= +github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= +github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= +github.com/containerd/cgroups/v3 v3.1.3 h1:eUNflyMddm18+yrDmZPn3jI7C5hJ9ahABE5q6dyLYXQ= +github.com/containerd/cgroups/v3 v3.1.3/go.mod h1:PKZ2AcWmSBsY/tJUVhtS/rluX0b1uq1GmPO1ElCmbOw= +github.com/containerd/containerd/api v1.11.1 h1:h8nfoDW9+fNsC/9TwiAHj8B1GzXKtR4eFtkhi/X5RLU= +github.com/containerd/containerd/api v1.11.1/go.mod h1:CaQFRu+N1MtbgL6JDOJLUB1hCKESU1lD6MuTJhgtdlw= +github.com/containerd/containerd/v2 v2.3.3 h1:MUNBVVBTBpPll7KPh5GTvkC3cfG03PQLAHVdsUoue9k= +github.com/containerd/containerd/v2 v2.3.3/go.mod h1:rHKGm3VW6wNrINb3x8mNT+w7qYXFVElTt/8HTuxVhD4= +github.com/containerd/continuity v0.5.0 h1:7a85HZpCSs+1Zps0Ee3DPSuAWY+0SJM1JNM51nlEVDg= +github.com/containerd/continuity v0.5.0/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= @@ -337,22 +367,26 @@ github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o= 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 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/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y= -github.com/containerd/plugin v1.0.0/go.mod h1:hQfJe5nmWfImiqT1q8Si3jLv3ynMUIBB47bQ+KexvO8= -github.com/containerd/stargz-snapshotter/estargz v0.18.2 h1:yXkZFYIzz3eoLwlTUZKz2iQ4MrckBxJjkmD16ynUTrw= -github.com/containerd/stargz-snapshotter/estargz v0.18.2/go.mod h1:XyVU5tcJ3PRpkA9XS2T5us6Eg35yM0214Y+wvrZTBrY= -github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ= -github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= +github.com/containerd/platforms v1.0.0-rc.4 h1:M42JrUT4zfZTqtkUwkr0GzmUWbfyO5VO0Q5b3op97T4= +github.com/containerd/platforms v1.0.0-rc.4/go.mod h1:lKlMXyLybmBedS/JJm11uDofzI8L2v0J2ZbYvNsbq1A= +github.com/containerd/plugin v1.1.0 h1:O+7lczNJVMy8rz0YNx3xGB8tTf5qY4i5abF041Ew19U= +github.com/containerd/plugin v1.1.0/go.mod h1:qBTum+A8lJ6lO44A19Eo7y1OlcLj4OWFH1DA/vnHmcc= +github.com/containerd/ttrpc v1.2.8 h1:xbVu6D4qF2jihdh9rDVOKqUMiFBQk6YctTdo1zk087Y= +github.com/containerd/ttrpc v1.2.8/go.mod h1:wyZW2K79t4Hfcxl+GUvkZqRBzJlqFFvgEeeWXa42tyE= github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= +github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc= +github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 h1:uX1JmpONuD549D73r6cgnxyUu18Zb7yHAy5AYU0Pm4Q= +github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw= github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= +github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ= +github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs= 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= @@ -360,20 +394,23 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da h1:ZOjWpVsFZ06eIhnh4mkaceTiVoktdU67+M7KDHJ268M= github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da/go.mod h1:B3tI9iGHi4imdLi4Asdha1Sc6feLMTfPLXh9IUYmysk= github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4= +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= +github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 h1:lxmTCgmHE1GUYL7P0MlNa00M67axePTq+9nBSGddR8I= +github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7/go.mod h1:GvWntX9qiTlOud0WkQ6ewFm0LPy5JUR1Xo0Ngbd1w6Y= github.com/diskfs/go-diskfs v1.7.0 h1:vonWmt5CMowXwUc79jWyGrf2DIMeoOjkLlMnQYGVOs8= github.com/diskfs/go-diskfs v1.7.0/go.mod h1:LhQyXqOugWFRahYUSw47NyZJPezFzB9UELwhpszLP/k= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c= github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0= -github.com/docker/cli v29.3.0+incompatible h1:z3iWveU7h19Pqx7alZES8j+IeFQZ1lhTwb2F+V9SVvk= -github.com/docker/cli v29.3.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= -github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/cli v29.4.3+incompatible h1:u+UliYm2J/rYrIh2FqHQg32neRG8GjbvNuwQRTzGspU= +github.com/docker/cli v29.4.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY= github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c= -github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= -github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= +github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c= +github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= 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/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4= @@ -404,16 +441,18 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go. github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= -github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g= -github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= +github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ= +github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= -github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4= -github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA= +github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds= +github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= +github.com/erofs/go-erofs v0.3.0 h1:o/W5ABAA3sHYl97WL93dacKEfeDpJhdFf3c2snAti7I= +github.com/erofs/go-erofs v0.3.0/go.mod h1:XkSeN9MHszGd4+3gcEjadJLYHCQpWzJ7/8yznzMuzJs= github.com/facebookincubator/flog v0.0.0-20190930132826-d2511d0ce33c/go.mod h1:QGzNH9ujQ2ZUr/CjDGZGWeDAVStrWNjHeEcjJL96Nuk= github.com/facebookincubator/nvdtools v0.1.5 h1:jbmDT1nd6+k+rlvKhnkgMokrCAzHoASWE5LtHbX2qFQ= github.com/facebookincubator/nvdtools v0.1.5/go.mod h1:Kh55SAWnjckS96TBSrXI99KrEKH4iB0OJby3N8GRJO4= @@ -447,6 +486,8 @@ github.com/glebarez/go-sqlite v1.20.3 h1:89BkqGOXR9oRmG58ZrzgoY/Fhy5x0M+/WV48U5z github.com/glebarez/go-sqlite v1.20.3/go.mod h1:u3N6D/wftiAzIOJtZl6BmedqxmmkDfH3q+ihjqxC9u0= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-chi/chi/v5 v5.3.0 h1:halUjDxhshgXHMrao5bB8eNBXo/rnzwr8m5m36glehM= +github.com/go-chi/chi/v5 v5.3.0/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmmBPA= @@ -471,50 +512,60 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU= -github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo= -github.com/go-openapi/errors v0.22.2 h1:rdxhzcBUazEcGccKqbY1Y7NS8FDcMyIRr0934jrYnZg= -github.com/go-openapi/errors v0.22.2/go.mod h1:+n/5UdIqdVnLIJ6Q9Se8HNGUXYaY6CN8ImWzfi/Gzp0= -github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= -github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= -github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= -github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco= -github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs= -github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ= -github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc= -github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= -github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= -github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c= -github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4= -github.com/go-openapi/swag v0.24.1 h1:DPdYTZKo6AQCRqzwr/kGkxJzHhpKxZ9i/oX0zag+MF8= -github.com/go-openapi/swag v0.24.1/go.mod h1:sm8I3lCPlspsBBwUm1t5oZeWZS0s7m/A+Psg0ooRU0A= -github.com/go-openapi/swag/cmdutils v0.24.0 h1:KlRCffHwXFI6E5MV9n8o8zBRElpY4uK4yWyAMWETo9I= -github.com/go-openapi/swag/cmdutils v0.24.0/go.mod h1:uxib2FAeQMByyHomTlsP8h1TtPd54Msu2ZDU/H5Vuf8= -github.com/go-openapi/swag/conv v0.24.0 h1:ejB9+7yogkWly6pnruRX45D1/6J+ZxRu92YFivx54ik= -github.com/go-openapi/swag/conv v0.24.0/go.mod h1:jbn140mZd7EW2g8a8Y5bwm8/Wy1slLySQQ0ND6DPc2c= -github.com/go-openapi/swag/fileutils v0.24.0 h1:U9pCpqp4RUytnD689Ek/N1d2N/a//XCeqoH508H5oak= -github.com/go-openapi/swag/fileutils v0.24.0/go.mod h1:3SCrCSBHyP1/N+3oErQ1gP+OX1GV2QYFSnrTbzwli90= -github.com/go-openapi/swag/jsonname v0.24.0 h1:2wKS9bgRV/xB8c62Qg16w4AUiIrqqiniJFtZGi3dg5k= -github.com/go-openapi/swag/jsonname v0.24.0/go.mod h1:GXqrPzGJe611P7LG4QB9JKPtUZ7flE4DOVechNaDd7Q= -github.com/go-openapi/swag/jsonutils v0.24.0 h1:F1vE1q4pg1xtO3HTyJYRmEuJ4jmIp2iZ30bzW5XgZts= -github.com/go-openapi/swag/jsonutils v0.24.0/go.mod h1:vBowZtF5Z4DDApIoxcIVfR8v0l9oq5PpYRUuteVu6f0= -github.com/go-openapi/swag/loading v0.24.0 h1:ln/fWTwJp2Zkj5DdaX4JPiddFC5CHQpvaBKycOlceYc= -github.com/go-openapi/swag/loading v0.24.0/go.mod h1:gShCN4woKZYIxPxbfbyHgjXAhO61m88tmjy0lp/LkJk= -github.com/go-openapi/swag/mangling v0.24.0 h1:PGOQpViCOUroIeak/Uj/sjGAq9LADS3mOyjznmHy2pk= -github.com/go-openapi/swag/mangling v0.24.0/go.mod h1:Jm5Go9LHkycsz0wfoaBDkdc4CkpuSnIEf62brzyCbhc= -github.com/go-openapi/swag/netutils v0.24.0 h1:Bz02HRjYv8046Ycg/w80q3g9QCWeIqTvlyOjQPDjD8w= -github.com/go-openapi/swag/netutils v0.24.0/go.mod h1:WRgiHcYTnx+IqfMCtu0hy9oOaPR0HnPbmArSRN1SkZM= -github.com/go-openapi/swag/stringutils v0.24.0 h1:i4Z/Jawf9EvXOLUbT97O0HbPUja18VdBxeadyAqS1FM= -github.com/go-openapi/swag/stringutils v0.24.0/go.mod h1:5nUXB4xA0kw2df5PRipZDslPJgJut+NjL7D25zPZ/4w= -github.com/go-openapi/swag/typeutils v0.24.0 h1:d3szEGzGDf4L2y1gYOSSLeK6h46F+zibnEas2Jm/wIw= -github.com/go-openapi/swag/typeutils v0.24.0/go.mod h1:q8C3Kmk/vh2VhpCLaoR2MVWOGP8y7Jc8l82qCTd1DYI= -github.com/go-openapi/swag/yamlutils v0.24.0 h1:bhw4894A7Iw6ne+639hsBNRHg9iZg/ISrOVr+sJGp4c= -github.com/go-openapi/swag/yamlutils v0.24.0/go.mod h1:DpKv5aYuaGm/sULePoeiG8uwMpZSfReo1HR3Ik0yaG8= -github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58= -github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= +github.com/go-openapi/analysis v0.25.2 h1:I0vy4n3alz+DHTiN1PRhCb7QZxkK6g5YmswZKv2TKuw= +github.com/go-openapi/analysis v0.25.2/go.mod h1:Uhs1t/2XR10EnwONYILGEzw8gcfGIG5Xk5K2AxnhqDo= +github.com/go-openapi/errors v0.22.7 h1:JLFBGC0Apwdzw3484MmBqspjPbwa2SHvpDm0u5aGhUA= +github.com/go-openapi/errors v0.22.7/go.mod h1://QW6SD9OsWtH6gHllUCddOXDL0tk0ZGNYHwsw4sW3w= +github.com/go-openapi/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4= +github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY= +github.com/go-openapi/jsonreference v0.21.6 h1:NZ5nGfnaM1n4I43Xjm1e5/M2GjOwQwndQz22uhxwD+Y= +github.com/go-openapi/jsonreference v0.21.6/go.mod h1:xzbgtQ3ZbWxvET3AxdzCJlJt6vkovbf+IfSPJjD0tUY= +github.com/go-openapi/loads v0.23.3 h1:g5Xap1JfwKkUnZdn+S0L3SzBDpcTIYzZ5Qaag0YDkKQ= +github.com/go-openapi/loads v0.23.3/go.mod h1:NOH07zLajXo8y55hom0omlHWDVVvCwBM/S+csCK8LqA= +github.com/go-openapi/runtime v0.32.3 h1:J7Ycy5DJmhhP1By3NifhRUjnkXTrk21qbeqSULjwX8U= +github.com/go-openapi/runtime v0.32.3/go.mod h1:/WTQi0fa5DiGnnCXQKsTkSm15OzJp8Uz3H2t+67TBr4= +github.com/go-openapi/runtime/server-middleware v0.30.0 h1:8rPoJ/xv7JL8BsovaqboKETlpWBArVh8n+0L/GyePog= +github.com/go-openapi/runtime/server-middleware v0.30.0/go.mod h1:OYNT/TxNvB/VK5oe4htM2jDTwlEXuejVJmu0DVZfAMs= +github.com/go-openapi/spec v0.22.5 h1:KhO7RBlKQfonUWX2WzQCoLIXVA6AcNqDGZ3a1Dutdlo= +github.com/go-openapi/spec v0.22.5/go.mod h1:vxpOtMya5TXtENXKE5bKqv5NjocVhyhxHrlZfvKnZ74= +github.com/go-openapi/strfmt v0.26.3 h1:rzmslHarJgBbf2qfGge+X3htclQfmXqBZMm0Too0HhU= +github.com/go-openapi/strfmt v0.26.3/go.mod h1:a5nsUw0oRpQzZeOwx8bi6cKbzFZslpbCKt1LEot+KnQ= +github.com/go-openapi/swag v0.26.0 h1:GVDXCmfvhfu1BxiHo8/FA+BbKmhecHnG3varjON5/RI= +github.com/go-openapi/swag v0.26.0/go.mod h1:82g3193sZJRbocs7bNCqGfIgq8pkuwVwCfhKIRlEQF0= +github.com/go-openapi/swag/cmdutils v0.26.0 h1:iowihOcvq7y4egO8cOq0dmfohz6wfeQ63U1EnuhO2TU= +github.com/go-openapi/swag/cmdutils v0.26.0/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= +github.com/go-openapi/swag/conv v0.26.0 h1:5yGGsPYI1ZCva93U0AoKi/iZrNhaJEjr324YVsiD89I= +github.com/go-openapi/swag/conv v0.26.0/go.mod h1:tpAmIL7X58VPnHHiSO4uE3jBeRamGsFsfdDeDtb5ECE= +github.com/go-openapi/swag/fileutils v0.26.0 h1:WJoPRvsA7QRiiWluowkLJa9jaYR7FCuxmDvnCgaRRxU= +github.com/go-openapi/swag/fileutils v0.26.0/go.mod h1:0WDJ7lp67eNjPMO50wAWYlKvhOb6CQ37rzR7wrgI8Tc= +github.com/go-openapi/swag/jsonname v0.26.0 h1:gV1NFX9M8avo0YSpmWogqfQISigCmpaiNci8cGECU5w= +github.com/go-openapi/swag/jsonname v0.26.0/go.mod h1:urBBR8bZNoDYGr653ynhIx+gTeIz0ARZxHkAPktJK2M= +github.com/go-openapi/swag/jsonutils v0.26.0 h1:FawFML2iAXsPqmERscuMPIHmFsoP1tOqWkxBaKNMsnA= +github.com/go-openapi/swag/jsonutils v0.26.0/go.mod h1:2VmA0CJlyFqgawOaPI9psnjFDqzyivIqLYN34t9p91E= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0 h1:apqeINu/ICHouqiRZbyFvuDge5jCmmLTqGQ9V95EaOM= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0/go.mod h1:AyM6QT8uz5IdKxk5akv0y6u4QvcL9GWERt0Jx/F/R8Y= +github.com/go-openapi/swag/loading v0.26.0 h1:Apg6zaKhCJurpJer0DCxq99qwmhFddBhaMX7kilDcko= +github.com/go-openapi/swag/loading v0.26.0/go.mod h1:dBxQ/6V2uBaAQdevN18VELE6xSpJWZxLX4txe12JwDg= +github.com/go-openapi/swag/mangling v0.26.0 h1:Du2YC4YLA/Y5m/YKQd7AnY5qq0wRKSFZTTt8ktFaXcQ= +github.com/go-openapi/swag/mangling v0.26.0/go.mod h1:jifS7W9vbg+pw63bT+GI53otluMQL3CeemuyCHKwVx0= +github.com/go-openapi/swag/netutils v0.26.0 h1:CmZp+ZT7HrmFwrC3GdGsXBq2+42T1bjKBapcqVpIs3c= +github.com/go-openapi/swag/netutils v0.26.0/go.mod h1:5iK+Ok3ZohWWex1C50BFTPexi03UaPwjW4Oj8kgrpwo= +github.com/go-openapi/swag/stringutils v0.26.0 h1:qZQngLxs5s7SLijc3N2ZO+fUq2o8LjuWAASSrJuh+xg= +github.com/go-openapi/swag/stringutils v0.26.0/go.mod h1:sWn5uY+QIIspwPhvgnqJsH8xqFT2ZbYcvbcFanRyhFE= +github.com/go-openapi/swag/typeutils v0.26.0 h1:2kdEwdiNWy+JJdOvu5MA2IIg2SylWAFuuyQIKYybfq4= +github.com/go-openapi/swag/typeutils v0.26.0/go.mod h1:oovDuIUvTrEHVMqWilQzKzV4YlSKgyZmFh7AlfABNVE= +github.com/go-openapi/swag/yamlutils v0.26.0 h1:H7O8l/8NJJQ/oiReEN+oMpnGMyt8G0hl460nRZxhLMQ= +github.com/go-openapi/swag/yamlutils v0.26.0/go.mod h1:1evKEGAtP37Pkwcc7EWMF0hedX0/x3Rkvei2wtG/TbU= +github.com/go-openapi/testify/enable/yaml/v2 v2.5.1 h1:q9NtHwK4qHF7yZziBPvZyv7zWAIk8ok88Gh2mR6Jpc8= +github.com/go-openapi/testify/enable/yaml/v2 v2.5.1/go.mod h1:JW0MXIotCYps/XsgJnG3a8Q7rE5xAiBwoOD5OfaIQBk= +github.com/go-openapi/testify/v2 v2.5.1 h1:TMdhCaw8fUNraVSf3Omoob1dO/AzBfhtFAPW0an6sBo= +github.com/go-openapi/testify/v2 v2.5.1/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= +github.com/go-openapi/validate v0.25.3 h1:4nzAIavcJ7WveHK2+V1UAkZK3kWcjzxZCzjfZAfavKs= +github.com/go-openapi/validate v0.25.3/go.mod h1:GemfuGMyYpIaBoKpX3z8sLywrmxpzWVOoJ7R0VeAVuk= github.com/go-restruct/restruct v1.2.0-alpha h1:2Lp474S/9660+SJjpVxoKuWX09JsXHSrdV7Nv3/gkvc= github.com/go-restruct/restruct v1.2.0-alpha/go.mod h1:KqrpKpn4M8OLznErihXTGLlsXFGeLxHUrLRRI/1YjGk= +github.com/go-rod/rod v0.116.2 h1:A5t2Ky2A+5eD/ZJQr1EfsQSe5rms5Xof/qj296e+ZqA= +github.com/go-rod/rod v0.116.2/go.mod h1:H+CMO9SCNc2TJ2WfrG+pKhITz57uGNYU43qYHh438Mg= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= @@ -529,6 +580,8 @@ github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/K github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +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/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= @@ -538,6 +591,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gohugoio/hashstructure v0.6.0 h1:7wMB/2CfXoThFYhdWRGv3u3rUM761Cq29CxUW+NltUg= github.com/gohugoio/hashstructure v0.6.0/go.mod h1:lapVLk9XidheHG1IQ4ZSbyYrXcaILU1ZEP/+vno5rBQ= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -576,8 +631,10 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6 github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/certificate-transparency-go v1.3.3 h1:hq/rSxztSkXN2tx/3jQqF6Xc0O565UQPdHrOWvZwybo= +github.com/google/certificate-transparency-go v1.3.3/go.mod h1:iR17ZgSaXRzSa5qvjFl8TnVD5h8ky2JMVio+dzoKMgA= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -593,13 +650,15 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-containerregistry v0.21.2 h1:vYaMU4nU55JJGFC9JR/s8NZcTjbE9DBBbvusTW9NeS0= -github.com/google/go-containerregistry v0.21.2/go.mod h1:ctO5aCaewH4AK1AumSF5DPW+0+R+d2FmylMJdp5G7p0= +github.com/google/go-containerregistry v0.21.6 h1:T+yqQIlJXKrM98Om4DlW3GoWQAmhZuLMwoDOvVrtiUM= +github.com/google/go-containerregistry v0.21.6/go.mod h1:U7MMSBIJynke2MVQrQk19NP9k/uQsGz/h0amIFSHMbo= github.com/google/go-github/v75 v75.0.0 h1:k7q8Bvg+W5KxRl9Tjq16a9XEgVY1pwuiG5sIL7435Ic= github.com/google/go-github/v75 v75.0.0/go.mod h1:H3LUJEA1TCrzuUqtdAQniBNwuKiQIqdGKgBo1/M/uqI= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/licensecheck v0.3.1 h1:QoxgoDkaeC4nFrtGN1jV7IPmDCHFNIVh54e5hSt6sPs= github.com/google/licensecheck v0.3.1/go.mod h1:ORkR35t/JjW+emNKtfJDII0zlciG9JgbT7SmsohlHmY= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= @@ -625,23 +684,25 @@ github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= -github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= -github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 h1:EwtI+Al+DeppwYX2oXJCETMO23COyaKGP6fHVpkpWpg= +github.com/google/pprof v0.0.0-20260402051712-545e8a4df936/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= +github.com/google/trillian v1.7.3 h1:hziW+vo4czis48tzx2GK5xRBl/ZxBA9B0/UR5avXOro= +github.com/google/trillian v1.7.3/go.mod h1:qh8iy4x/GvnVXUBd5pK4oncuT1Y9vVYfibQVsR/WpKg= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8= -github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg= +github.com/googleapis/enterprise-certificate-proxy v0.3.15 h1:xolVQTEXusUcAA5UgtyRLjelpFFHWlPQ4XfWGc7MBas= +github.com/googleapis/enterprise-certificate-proxy v0.3.15/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc= -github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY= +github.com/googleapis/gax-go/v2 v2.22.0 h1:PjIWBpgGIVKGoCXuiCoP64altEJCj3/Ei+kSU5vlZD4= +github.com/googleapis/gax-go/v2 v2.22.0/go.mod h1:irWBbALSr0Sk3qlqb9SyJ1h68WjgeFuiOzI4Rqw5+aY= github.com/gookit/assert v0.1.1 h1:lh3GcawXe/p+cU7ESTZ5Ui3Sm/x8JWpIis4/1aF0mY0= github.com/gookit/assert v0.1.1/go.mod h1:jS5bmIVQZTIwk42uXl4lyj4iaaxx32tqH16CFj0VX2E= github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= @@ -653,7 +714,11 @@ github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7Fsg github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gpustack/gguf-parser-go v0.24.0 h1:tdJceXYp9e5RhE9RwVYIuUpir72Jz2D68NEtDXkKCKc= github.com/gpustack/gguf-parser-go v0.24.0/go.mod h1:y4TwTtDqFWTK+xvprOjRUh+dowgU2TKCX37vRKvGiZ0= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs= github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026 h1:BpJ2o0OR5FV7vrkDYfXYVJQeMNWa8RhklZOpW2ITAIQ= github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026/go.mod h1:5Scbynm8dF1XAPwIwkGPqzkM/shndPm79Jd1003hTjE= github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.72 h1:vTCWu1wbdYo7PEZFem/rlr01+Un+wwVmI7wiegFdRLk= @@ -698,19 +763,17 @@ github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= -github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= +github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= +github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 h1:om4Al8Oy7kCm/B86rLCLah4Dt5Aa0Fr5rYBG60OzwHQ= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= -github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= +github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM= +github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw= github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= @@ -727,8 +790,9 @@ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+l github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I= +github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE= github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM= github.com/hashicorp/hcp-sdk-go v0.174.0 h1:BVUBgq4ZX5U5LeSb3OZilLUOFOlDqm1lfdFQDui8iPI= @@ -746,12 +810,14 @@ github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKEN github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= -github.com/hashicorp/vault/api v1.14.0 h1:Ah3CFLixD5jmjusOgm8grfN9M0d+Y8fVR2SW0K6pJLU= -github.com/hashicorp/vault/api v1.14.0/go.mod h1:pV9YLxBGSz+cItFDd8Ii4G17waWOQ32zVjMWHe/cOqk= +github.com/hashicorp/vault/api v1.22.0 h1:+HYFquE35/B74fHoIeXlZIP2YADVboaPjaSicHEZiH0= +github.com/hashicorp/vault/api v1.22.0/go.mod h1:IUZA2cDvr4Ok3+NtK2Oq/r+lJeXkeCrHRmqdyWfpmGM= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/henvic/httpretty v0.1.4 h1:Jo7uwIRWVFxkqOnErcoYfH90o3ddQyVrSANeS4cxYmU= github.com/henvic/httpretty v0.1.4/go.mod h1:Dn60sQTZfbt2dYsdUSNsCljyF4AfdqnuJFDLJA1I4AM= +github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb28sjiS3i7tcKCkflWFEkHfuAgM= +github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= @@ -764,6 +830,10 @@ github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/in-toto/attestation v1.2.0 h1:aPRUZ3azbqD7yEBD5fP3TD8Dszf+YHo284SOcpahjQk= +github.com/in-toto/attestation v1.2.0/go.mod h1:r79G45gOmzPismgObLSL+rZTFxUgZLOQJI6LofTZgXk= +github.com/in-toto/in-toto-golang v0.11.0 h1:nfidMYBFx+E0lnmX5KUnN2Pdm8zdNKal1ayjJuzzRoA= +github.com/in-toto/in-toto-golang v0.11.0/go.mod h1:u3PjTnwFKjp5a1YCcw8SJg0G+tMeKfVoWsWeFMDCMtw= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -781,15 +851,18 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6 github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +github.com/jedisct1/go-minisign v0.0.0-20211028175153-1c139d1cc84b h1:ZGiXF8sz7PDk6RgkP+A/SFfUD0ZR/AgG6SpRNEDKZy8= +github.com/jedisct1/go-minisign v0.0.0-20211028175153-1c139d1cc84b/go.mod h1:hQmNrgofl+IY/8L+n20H6E6PWBBTokdsv+q49j0QhsU= github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 h1:IPJ3dvxmJ4uczJe5YQdrYB16oTJlGSC/OyZDqUk9xX4= github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag= +github.com/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP4mnWdTY= +github.com/jellydator/ttlcache/v3 v3.4.0/go.mod h1:Hw9EgjymziQD3yGsQdf1FqFdpp7YjFMd4Srg5EJlgD4= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 h1:liMMTbpW34dhU4az1GN0pTPADwNmvoRSeoZ6PItiqnY= +github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -803,11 +876,13 @@ github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953 h1:WdAeg/imY2JF github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953/go.mod h1:6o+UrvuZWc4UTyBhQf0LGjW9Ld7qJxLz/OqvSOWWlEc= github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY= github.com/kevinburke/ssh_config v1.6.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= +github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= +github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= -github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= +github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= @@ -826,7 +901,11 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 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/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= +github.com/letsencrypt/boulder v0.20260309.0 h1:kZynrxK3QfqLGx6hhoz+Rfs3hgltJs1p9Mp+4+VwnY0= +github.com/letsencrypt/boulder v0.20260309.0/go.mod h1:yG8lj8pNPZ8taq3oNdTpfBS+eC74IaEuiewqzVpXiWE= github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= @@ -836,8 +915,6 @@ github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPK github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786 h1:2ZKn+w/BJeL43sCxI2jhPLRv73oVVOjEKZjKkflyqxg= @@ -878,7 +955,6 @@ github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZz github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc= github.com/minio/minlz v1.0.1 h1:OUZUzXcib8diiX+JYxyRLIdomyZYzHct6EShOKtQY2A= github.com/minio/minlz v1.0.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/cli v1.1.5 h1:OxRIeJXpAMztws/XHlN2vu6imG5Dpq+j61AzAX5fLng= github.com/mitchellh/cli v1.1.5/go.mod h1:v8+iFts2sPIKUV1ltktPXMCC8fumSKFItNcD2cLtRR4= @@ -894,14 +970,12 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= -github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -916,10 +990,10 @@ github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3N github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= -github.com/moby/moby/api v1.54.0 h1:7kbUgyiKcoBhm0UrWbdrMs7RX8dnwzURKVbZGy2GnL0= -github.com/moby/moby/api v1.54.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc= -github.com/moby/moby/client v0.3.0 h1:UUGL5okry+Aomj3WhGt9Aigl3ZOxZGqR7XPo+RLPlKs= -github.com/moby/moby/client v0.3.0/go.mod h1:HJgFbJRvogDQjbM8fqc1MCEm4mIAGMLjXbgwoZp6jCQ= +github.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg= +github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= +github.com/moby/moby/client v0.4.1 h1:DMQgisVoMkmMs7fp3ROSdiBnoAu8+vo3GggFl06M/wY= +github.com/moby/moby/client v0.4.1/go.mod h1:z52C9O2POPOsnxZAy//WtKcQ32P+jT/NGeXu/7nfjGQ= github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= @@ -945,6 +1019,8 @@ github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIf github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +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/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/nix-community/go-nix v0.0.0-20250101154619-4bdde671e0a1 h1:kpt9ZfKcm+EDG4s40hMwE//d5SBgDjUOrITReV2u4aA= @@ -959,6 +1035,8 @@ github.com/nywilken/go-cty v1.13.3 h1:03U99oXf3j3g9xgqAE3YGpixCjM8Mg09KZ0Ji9LzX0 github.com/nywilken/go-cty v1.13.3/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s= +github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc= github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0= github.com/olekukonko/errors v1.2.0 h1:10Zcn4GeV59t/EGqJc8fUjtFT/FuUh5bTMzZ1XwmCRo= @@ -975,27 +1053,26 @@ github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJw github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opencontainers/runtime-spec v1.3.0 h1:YZupQUdctfhpZy3TM39nN9Ika5CBWT5diQ8ibYCRkxg= github.com/opencontainers/runtime-spec v1.3.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/selinux v1.13.1 h1:A8nNeceYngH9Ow++M+VVEwJVpdFmrlxsN22F+ISDCJE= -github.com/opencontainers/selinux v1.13.1/go.mod h1:S10WXZ/osk2kWOYKy1x2f/eXF5ZHJoUs8UU/2caNRbg= -github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= -github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/packer-community/winrmcp v0.0.0-20180921211025-c76d91c1e7db h1:9uViuKtx1jrlXLBW/pMnhOfzn3iSEdLase/But/IZRU= github.com/packer-community/winrmcp v0.0.0-20180921211025-c76d91c1e7db/go.mod h1:f6Izs6JvFTdnRbziASagjZ2vmf55NSIkC/weStxCHqk= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pborman/indent v1.2.1 h1:lFiviAbISHv3Rf0jcuh489bi06hj98JsVMtIDZQb9yM= github.com/pborman/indent v1.2.1/go.mod h1:FitS+t35kIYtB5xWTZAPhnmrxcciEEOdbyrrpz5K6Vw= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= -github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pelletier/go-toml/v2 v2.3.0 h1:k59bC/lIZREW0/iVaQR8nDHxVq8OVlIzYCOJf421CaM= +github.com/pelletier/go-toml/v2 v2.3.0/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU= github.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -1029,8 +1106,8 @@ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8b github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= +github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= @@ -1044,12 +1121,11 @@ github.com/rust-secure-code/go-rustaudit v0.0.0-20250226111315-e20ec32e963c h1:8 github.com/rust-secure-code/go-rustaudit v0.0.0-20250226111315-e20ec32e963c/go.mod h1:kwM/7r/rVluTE8qJbHAffduuqmSv4knVQT2IajGvSiA= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= -github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= -github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= +github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= +github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA= @@ -1058,16 +1134,24 @@ github.com/sanity-io/litter v1.5.8 h1:uM/2lKrWdGbRXDrIq08Lh9XtVYoeGtcQxk9rtQ7+rY github.com/sanity-io/litter v1.5.8/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg= github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI= +github.com/sassoftware/relic v7.2.1+incompatible h1:Pwyh1F3I0r4clFJXkSI8bOyJINGqpgjJU3DYAZeI05A= +github.com/sassoftware/relic v7.2.1+incompatible/go.mod h1:CWfAxv73/iLZ17rbyhIEq3K9hs5w6FpNMdUT//qR+zk= +github.com/sassoftware/relic/v7 v7.6.2 h1:rS44Lbv9G9eXsukknS4mSjIAuuX+lMq/FnStgmZlUv4= +github.com/sassoftware/relic/v7 v7.6.2/go.mod h1:kjmP0IBVkJZ6gXeAu35/KCEfca//+PKM6vTAsyDPY+k= github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e h1:7q6NSFZDeGfvvtIRwBrU/aegEYJYmvev0cHAwo17zZQ= github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e/go.mod h1:DkpGd78rljTxKAnTDPFqXSGxvETQnJyuSOQwsHycqfs= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sebdah/goldie/v2 v2.8.0 h1:dZb9wR8q5++oplmEiJT+U/5KyotVD+HNGCAc5gNr8rc= github.com/sebdah/goldie/v2 v2.8.0/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= +github.com/secure-systems-lab/go-securesystemslib v0.11.0 h1:iuCR9kcMFD4QurdKrGvPLoKZLv9YvwPYVr0473BdtFs= +github.com/secure-systems-lab/go-securesystemslib v0.11.0/go.mod h1:+PMOTjUGwHj2vcZ+TFKlb1tXRbrdWE1LYDT5i9JC80Q= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 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/v3 v3.23.4 h1:hZwmDxZs7Ewt75DV81r4pFMqbq+di2cbt9FsQBqLD2o= github.com/shirou/gopsutil/v3 v3.23.4/go.mod h1:ZcGxyfzAMRevhUR2+cfhXDH6gQdFYE/t8j1nsU4mPI8= github.com/shoenig/go-m1cpu v0.1.5 h1:LF57Z/Fpb/WdGLjt2HZilNnmZOxg/q2bSKTQhgbrLrQ= @@ -1077,6 +1161,26 @@ github.com/shoenig/test v0.6.3/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnj github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/sigstore/protobuf-specs v0.5.1 h1:/5OPaNuolRJmQfeZLayJGFXMpsRJEdgC6ah1/+7Px7U= +github.com/sigstore/protobuf-specs v0.5.1/go.mod h1:DRBzpFuE+LnvQMN10/dU6nBeKwVLGEQ6o2FovN2Rats= +github.com/sigstore/rekor v1.5.2 h1:k6pX4o1zFAzAvDbXiVIp5IHj1b0wcDaxsbsbNpuRO8o= +github.com/sigstore/rekor v1.5.2/go.mod h1:WkMnITBccOFauPkT6yte74tF5gC83pefKRGZvNOsbjI= +github.com/sigstore/rekor-tiles/v2 v2.2.2-0.20260601073857-5d098a2b6443 h1:/CO8F6m3Bo/f59bZo5dv1sTIfUnQqVnepIdDV24KoDw= +github.com/sigstore/rekor-tiles/v2 v2.2.2-0.20260601073857-5d098a2b6443/go.mod h1:w1h8wF8vq9lHjmtRdwJiEaoVxhP+WHIMpj4M39pkzp0= +github.com/sigstore/sigstore v1.10.8 h1:1Mgkxvkw4AXMfIP1DOjc6kw0GkUgA8pGVpveN/EfOq4= +github.com/sigstore/sigstore v1.10.8/go.mod h1:f9+B/4iaYimvUkySyb2mvc73n3RLqNn24grHZM/ET8M= +github.com/sigstore/sigstore-go v1.2.1 h1:YWP/rDbBaEBvtbkj6xtwsSj38ZCFEhTVVadNOXjVe3A= +github.com/sigstore/sigstore-go v1.2.1/go.mod h1:I8BqVwAb/SaQJ5pBu5IDFY+ksq8O/1/kCag8XUgrsko= +github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.8 h1:tofVQ+UWJgad/69I5zbqxdFCN5gpIn9tRQP7iBzIpBw= +github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.8/go.mod h1:73AfJE8H6w5KGCFPBu4x/OG+i1Yxgmh0L/FtV7prd88= +github.com/sigstore/sigstore/pkg/signature/kms/azure v1.10.8 h1:8Mt7J36GcUEmbiJaiFhz2tud5ZIgkfVVCe2H/WJCHmw= +github.com/sigstore/sigstore/pkg/signature/kms/azure v1.10.8/go.mod h1:YiTpAsxoWXhF9KlLOVWCh7BckN5cYO8X01WufDq1ido= +github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.10.8 h1:MxpAIMZVzn0Tpbarc9ax1I498oQBp7oYSMgoMSsOmKI= +github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.10.8/go.mod h1:bnAUEkFNam6STvkVZhptVwWzWR5pS24CEtQ+lhxu7S0= +github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.10.8 h1:1DGe4/clcdOnkz5MINEczWlmEvjUtZd+AjPPT/cBhQ8= +github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.10.8/go.mod h1:6IDFhpgxtzqbnzrFkyegbj7RfWwKeRrb3/+xAD1Wp+Y= +github.com/sigstore/timestamp-authority/v2 v2.1.2 h1:7DDhnknLL4w8VwomyvW2W8qblOS9LDR8oihna+jc7Ls= +github.com/sigstore/timestamp-authority/v2 v2.1.2/go.mod h1:o6rAVZceFyejClIj/uStRNIemP16bVMZtbMmhk6pr0U= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= @@ -1090,8 +1194,8 @@ github.com/smallnest/ringbuffer v0.0.0-20241116012123-461381446e3d h1:3VwvTjiRPA github.com/smallnest/ringbuffer v0.0.0-20241116012123-461381446e3d/go.mod h1:tAG61zBM1DYRaGIPloumExGvScf08oHuo0kFoOqdbT0= github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik= github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU= -github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= -github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +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/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb h1:bLo8hvc8XFm9J47r690TUKBzcjSWdJDxmjXJZ+/f92U= github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM= @@ -1103,8 +1207,8 @@ github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= -github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= @@ -1114,8 +1218,8 @@ github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An 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.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= -github.com/spf13/viper v1.20.0 h1:zrxIyR3RQIOsarIrgL8+sAvALXul9jeEPa06Y0Ph6vY= -github.com/spf13/viper v1.20.0/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= +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/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -1148,6 +1252,10 @@ github.com/terminalstatic/go-xsd-validate v0.1.6 h1:TenYeQ3eY631qNi1/cTmLH/s2slH github.com/terminalstatic/go-xsd-validate v0.1.6/go.mod h1:18lsvYFofBflqCrvo1umpABZ99+GneNTw2kEEc8UPJw= github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= +github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI= +github.com/theupdateframework/go-tuf v0.7.0/go.mod h1:uEB7WSY+7ZIugK6R1hiBMBjQftaFzn7ZCDJcp1tCUug= +github.com/theupdateframework/go-tuf/v2 v2.4.2-0.20260407074541-7e8f69f906ef h1:jJac5InhEfD0Z46/d5RayZjoavf/se7bPZpOgg8GLrM= +github.com/theupdateframework/go-tuf/v2 v2.4.2-0.20260407074541-7e8f69f906ef/go.mod h1:cLUSJ2cgR194lNWfp+TJT4P8PX7qGleCXdudqlCMtOE= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= @@ -1158,10 +1266,24 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tidwall/transform v0.0.0-20201103190739-32f242e2dbde h1:AMNpJRc7P+GTwVbl8DkK2I9I8BBUzNiHuH/tlxrpan0= github.com/tidwall/transform v0.0.0-20201103190739-32f242e2dbde/go.mod h1:MvrEmduDUz4ST5pGZ7CABCnOU5f3ZiOAZzT6b1A6nX8= +github.com/tink-crypto/tink-go-awskms/v3 v3.0.0 h1:XSohRhCkXAVI0iaCnWB/GS05TEmpnKurQmzaY1jzt3Y= +github.com/tink-crypto/tink-go-awskms/v3 v3.0.0/go.mod h1:+7MXsShLzVbSQ6dI0Pe4JuZM52jD1jQ1itAygd/MDsA= +github.com/tink-crypto/tink-go-gcpkms/v2 v2.2.0 h1:3B9i6XBXNTRspfkTC0asN5W0K6GhOSgcujNiECNRNb0= +github.com/tink-crypto/tink-go-gcpkms/v2 v2.2.0/go.mod h1:jY5YN2BqD/KSCHM9SqZPIpJNG/u3zwfLXHgws4x2IRw= +github.com/tink-crypto/tink-go-hcvault/v2 v2.5.0 h1:eXuNqgrcYelxU1MVikOJDP3wTS5lvihM4ntoAbAMfvs= +github.com/tink-crypto/tink-go-hcvault/v2 v2.5.0/go.mod h1:3RhcxAqek6xUlRFmJifvU4CYLZN60KMQdIKqpZAZJG0= +github.com/tink-crypto/tink-go/v2 v2.6.0 h1:+KHNBHhWH33Vn+igZWcsgdEPUxKwBMEe0QC60t388v4= +github.com/tink-crypto/tink-go/v2 v2.6.0/go.mod h1:2WbBA6pfNsAfBwDCggboaHeB2X29wkU8XHtGwh2YIk8= +github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0= +github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs= github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms= github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= +github.com/transparency-dev/formats v0.1.1 h1:4bVHJc+KdBgpA1OJD1yjI+g0i5Z1graCppTMH8lWKJI= +github.com/transparency-dev/formats v0.1.1/go.mod h1:qtZ8goRuJ8FTBG9c9+Bj0rn2rUG7eG/AUTkr+Aw3jFw= +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/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0= github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ= @@ -1172,8 +1294,6 @@ github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/vbatts/go-mtree v0.7.0 h1:ytmOc3MTRidZiBi9VBCyZ2BHe4fZS47L5v7BVXDWW4E= github.com/vbatts/go-mtree v0.7.0/go.mod h1:EjdpFC+LZy1TXbRGNa1MKKgjQ+7ew3foMFJK8o4/TdY= -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/vifraa/gopom v1.0.0 h1:L9XlKbyvid8PAIK8nr0lihMApJQg/12OBvMA28BcWh0= github.com/vifraa/gopom v1.0.0/go.mod h1:oPa1dcrGrtlO37WPDBm5SqHAT+wTgF8An1Q71Z6Vv4o= github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= @@ -1198,6 +1318,18 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= +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= +github.com/ysmood/goob v0.4.0/go.mod h1:u6yx7ZhS4Exf2MwciFr6nIM8knHQIE22lFpWHnfql18= +github.com/ysmood/got v0.40.0 h1:ZQk1B55zIvS7zflRrkGfPDrPG3d7+JOza1ZkNxcc74Q= +github.com/ysmood/got v0.40.0/go.mod h1:W7DdpuX6skL3NszLmAsC5hT7JAhuLZhByVzHTq874Qg= +github.com/ysmood/gson v0.7.3 h1:QFkWbTH8MxyUTKPkVWAENJhxqdBa4lYTQWqZCiLG6kE= +github.com/ysmood/gson v0.7.3/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg= +github.com/ysmood/leakless v0.9.0 h1:qxCG5VirSBvmi3uynXFkcnLMzkphdh3xx5FtrORwDCU= +github.com/ysmood/leakless v0.9.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -1206,6 +1338,8 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms= +github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= github.com/zclconf/go-cty-yaml v1.0.1 h1:up11wlgAaDvlAGENcFDnZgkn0qUJurso7k6EpURKNF8= @@ -1213,8 +1347,6 @@ github.com/zclconf/go-cty-yaml v1.0.1/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgK go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= -go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU= -go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -1226,34 +1358,40 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE= -go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= -go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= -go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0 h1:ZrPRak/kS4xI3AVXy8F7pipuDXmDsrO8Lg+yQjBLjw0= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0/go.mod h1:3y6kQCWztq6hyW8Z9YxQDDm0Je9AJoFar2G0yDcmhRk= -go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= -go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= -go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= -go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= -go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= -go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= -go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= -go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.opentelemetry.io/contrib/detectors/gcp v1.42.0 h1:kpt2PEJuOuqYkPcktfJqWWDjTEd/FNgrxcniL7kQrXQ= +go.opentelemetry.io/contrib/detectors/gcp v1.42.0/go.mod h1:W9zQ439utxymRrXsUOzZbFX4JhLxXU4+ZnCt8GG7yA8= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 h1:0Qx7VGBacMm9ZENQ7TnNObTYI4ShC+lHI16seduaxZo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0/go.mod h1:Sje3i3MjSPKTSPvVWCaL8ugBzJwik3u4smCjUeuupqg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0 h1:TC+BewnDpeiAmcscXbGMfxkO+mwYUwE/VySwvw88PfA= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0/go.mod h1:J/ZyF4vfPwsSr9xJSPyQ4LqtcTPULFR64KwTikGLe+A= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA= +go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk= +go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.step.sm/crypto v0.77.7 h1:6azC+pD678Vjju8yXnMDHCZJ+HzFaEmL3sCryiezTIA= +go.step.sm/crypto v0.77.7/go.mod h1:OW/2sEHwTtDKq70PvSQ5B0JGy/CrLyDKOiVy3YvZMTQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= -go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= +go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= +go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= +go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc= @@ -1360,7 +1498,6 @@ golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= @@ -1483,7 +1620,6 @@ golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= @@ -1497,7 +1633,6 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= @@ -1567,10 +1702,10 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= -gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -1603,8 +1738,8 @@ google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdr google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= -google.golang.org/api v0.271.0 h1:cIPN4qcUc61jlh7oXu6pwOQqbJW2GqYh5PS6rB2C/JY= -google.golang.org/api v0.271.0/go.mod h1:CGT29bhwkbF+i11qkRUJb2KMKqcJ1hdFceEIRd9u64Q= +google.golang.org/api v0.280.0 h1:F4OfEHZhZh6a7uTufJAXXVd/2TQ8EjM4vZH+jX/vFYk= +google.golang.org/api v0.280.0/go.mod h1:oGKmPZRDoD3vdkf6MA7F4VNkR1rxCiuaPSkhsf3EolU= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1675,12 +1810,12 @@ google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM= -google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM= -google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0= -google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0= +google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68 h1:PvEgGJf9C/1u5CHkInMg7UFYYUoiaQmW2LbtH0pjB78= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1708,8 +1843,8 @@ google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= -google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= +google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1724,8 +1859,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= +google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1760,6 +1895,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= +k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis= modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc= @@ -1793,3 +1930,7 @@ pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= +software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k= +software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= diff --git a/hcl2template/types.build.post-processor.go b/hcl2template/types.build.post-processor.go index 6eb7dd322..375d3965a 100644 --- a/hcl2template/types.build.post-processor.go +++ b/hcl2template/types.build.post-processor.go @@ -76,6 +76,7 @@ func (cfg *PackerConfig) startPostProcessor(source SourceUseBlock, pp *PostProce builderVars["packer_debug"] = strconv.FormatBool(cfg.debug) builderVars["packer_force"] = strconv.FormatBool(cfg.force) builderVars["packer_on_error"] = cfg.onError + builderVars["packer_sensitive_variables"] = cfg.sensitiveInputVariableKeys() hclPostProcessor := &HCL2PostProcessor{ PostProcessor: postProcessor, diff --git a/internal/attestation/bundle.go b/internal/attestation/bundle.go new file mode 100644 index 000000000..dfea31c58 --- /dev/null +++ b/internal/attestation/bundle.go @@ -0,0 +1,22 @@ +// Copyright IBM Corp. 2024, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +package attestation + +import ( + "context" + "fmt" +) + +type bundleSigner interface { + SignBundle(ctx context.Context, payloadType string, payload []byte, cfg BackendConfig) (Envelope, []byte, error) +} + +func BuildBundleForSigner(ctx context.Context, signer Signer, cfg BackendConfig, payloadType string, payload []byte) (Envelope, []byte, error) { + bundler, ok := signer.(bundleSigner) + if !ok { + return Envelope{}, nil, fmt.Errorf("signing_mode %q does not support Sigstore bundle emission", cfg.Mode) + } + + return bundler.SignBundle(ctx, payloadType, payload, cfg) +} diff --git a/internal/attestation/dsse.go b/internal/attestation/dsse.go new file mode 100644 index 000000000..e52135d99 --- /dev/null +++ b/internal/attestation/dsse.go @@ -0,0 +1,67 @@ +// Copyright IBM Corp. 2024, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +package attestation + +import ( + "encoding/base64" + "encoding/json" + "fmt" +) + +const InTotoPayloadType = "application/vnd.in-toto+json" + +type Envelope struct { + PayloadType string `json:"payloadType"` + Payload string `json:"payload"` + Signatures []EnvelopeSignature `json:"signatures"` +} + +type EnvelopeSignature struct { + KeyID string `json:"keyid,omitempty"` + Sig string `json:"sig"` + Cert string `json:"cert,omitempty"` +} + +func MarshalPayload(value interface{}) ([]byte, error) { + return json.Marshal(value) +} + +func PreAuthEncode(payloadType string, payload []byte) []byte { + return []byte(fmt.Sprintf("DSSEv1 %d %s %d %s", len(payloadType), payloadType, len(payload), payload)) +} + +func NewEnvelope(payloadType string, payload []byte, signature Signature) Envelope { + envelope := Envelope{ + PayloadType: payloadType, + Payload: base64.StdEncoding.EncodeToString(payload), + Signatures: []EnvelopeSignature{{ + KeyID: signature.KeyID, + Sig: base64.StdEncoding.EncodeToString(signature.Sig), + }}, + } + + if len(signature.CertPEM) > 0 { + envelope.Signatures[0].Cert = string(signature.CertPEM) + } + + return envelope +} + +func DecodeEnvelopePayload(envelope Envelope) ([]byte, error) { + decoded, err := base64.StdEncoding.DecodeString(envelope.Payload) + if err != nil { + return nil, fmt.Errorf("decode envelope payload: %w", err) + } + + return decoded, nil +} + +func DecodeEnvelopeSignature(signature EnvelopeSignature) ([]byte, error) { + decoded, err := base64.StdEncoding.DecodeString(signature.Sig) + if err != nil { + return nil, fmt.Errorf("decode envelope signature: %w", err) + } + + return decoded, nil +} diff --git a/internal/attestation/sign_key.go b/internal/attestation/sign_key.go new file mode 100644 index 000000000..d30d26087 --- /dev/null +++ b/internal/attestation/sign_key.go @@ -0,0 +1,238 @@ +// Copyright IBM Corp. 2024, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +package attestation + +import ( + "context" + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/hex" + "encoding/pem" + "fmt" + "os" +) + +func init() { + RegisterSigner(SigningModeKey, newPEMSigner) +} + +type pemSigner struct { + signer crypto.Signer + verifier *pemVerifier +} + +type pemVerifier struct { + publicKey crypto.PublicKey + keyID string +} + +func newPEMSigner(_ context.Context, cfg BackendConfig) (Signer, error) { + if cfg.SignerRef == "" { + return nil, fmt.Errorf("signing_mode %q requires signer", SigningModeKey) + } + + signer, verifier, err := loadPEMSigner(cfg.SignerRef) + if err != nil { + return nil, err + } + + return &pemSigner{signer: signer, verifier: verifier}, nil +} + +func (s *pemSigner) Sign(_ context.Context, payloadType string, payload []byte) (Signature, error) { + pae := PreAuthEncode(payloadType, payload) + + var message []byte + var opts crypto.SignerOpts + if _, ok := s.signer.Public().(ed25519.PublicKey); ok { + message = pae + opts = crypto.Hash(0) + } else { + digest := sha256.Sum256(pae) + message = digest[:] + opts = crypto.SHA256 + } + + signature, err := s.signer.Sign(rand.Reader, message, opts) + if err != nil { + return Signature{}, fmt.Errorf("sign payload: %w", err) + } + + return Signature{ + KeyID: s.verifier.KeyID(), + Sig: signature, + }, nil +} + +func (s *pemSigner) Verifier(context.Context, BackendConfig) (Verifier, error) { + return s.verifier, nil +} + +func (v *pemVerifier) Verify(_ context.Context, payloadType string, payload, signature []byte) error { + pae := PreAuthEncode(payloadType, payload) + + switch publicKey := v.publicKey.(type) { + case *rsa.PublicKey: + digest := sha256.Sum256(pae) + return rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, digest[:], signature) + case *ecdsa.PublicKey: + digest := sha256.Sum256(pae) + if !ecdsa.VerifyASN1(publicKey, digest[:], signature) { + return fmt.Errorf("ECDSA verification failed") + } + return nil + case ed25519.PublicKey: + if !ed25519.Verify(publicKey, pae, signature) { + return fmt.Errorf("Ed25519 verification failed") + } + return nil + default: + return fmt.Errorf("unsupported public key type %T", v.publicKey) + } +} + +func (v *pemVerifier) KeyID() string { + return v.keyID +} + +func LoadPEMVerifier(path string) (Verifier, error) { + contents, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("read verifier %q: %w", path, err) + } + + publicKey, rawVerifier, err := loadPEMPublicKey(contents) + if err != nil { + return nil, fmt.Errorf("load verifier %q: %w", path, err) + } + + return &pemVerifier{ + publicKey: publicKey, + keyID: sha256Hex(rawVerifier), + }, nil +} + +func LoadPEMVerifierBytes(contents []byte) (*pemVerifier, error) { + publicKey, rawVerifier, err := loadPEMPublicKey(contents) + if err != nil { + return nil, err + } + + return &pemVerifier{ + publicKey: publicKey, + keyID: sha256Hex(rawVerifier), + }, nil + +} + +func loadPEMSigner(path string) (crypto.Signer, *pemVerifier, error) { + contents, err := os.ReadFile(path) + if err != nil { + return nil, nil, fmt.Errorf("read signer %q: %w", path, err) + } + + block, _ := pem.Decode(contents) + if block == nil { + return nil, nil, fmt.Errorf("decode signer %q: no PEM block found", path) + } + + var signer crypto.Signer + if key, err := x509.ParsePKCS8PrivateKey(block.Bytes); err == nil { + var ok bool + signer, ok = key.(crypto.Signer) + if !ok { + return nil, nil, fmt.Errorf("signer %q does not implement crypto.Signer", path) + } + } else if key, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil { + signer = key + } else if key, err := x509.ParseECPrivateKey(block.Bytes); err == nil { + signer = key + } else { + return nil, nil, fmt.Errorf("unsupported private key in signer %q", path) + } + + publicKeyPEM, err := marshalPublicKeyPEM(signer.Public()) + if err != nil { + return nil, nil, err + } + + verifier, err := LoadPEMVerifierBytes(publicKeyPEM) + if err != nil { + return nil, nil, err + } + + return signer, verifier, nil +} + +func loadPEMPublicKey(contents []byte) (crypto.PublicKey, []byte, error) { + block, _ := pem.Decode(contents) + if block == nil { + return nil, nil, fmt.Errorf("no PEM block found") + } + + if publicKey, err := x509.ParsePKIXPublicKey(block.Bytes); err == nil { + return publicKey, pem.EncodeToMemory(block), nil + } + if certificate, err := x509.ParseCertificate(block.Bytes); err == nil { + return certificate.PublicKey, pem.EncodeToMemory(block), nil + } + if privateKey, verifier, err := loadPEMPrivateKeyAsPublic(contents); err == nil { + return privateKey, verifier, nil + } + + return nil, nil, fmt.Errorf("unsupported PEM verifier data") +} + +func loadPEMPrivateKeyAsPublic(contents []byte) (crypto.PublicKey, []byte, error) { + block, _ := pem.Decode(contents) + if block == nil { + return nil, nil, fmt.Errorf("no PEM block found") + } + + var signer crypto.Signer + if key, err := x509.ParsePKCS8PrivateKey(block.Bytes); err == nil { + var ok bool + signer, ok = key.(crypto.Signer) + if !ok { + return nil, nil, fmt.Errorf("private key does not implement crypto.Signer") + } + } else if key, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil { + signer = key + } else if key, err := x509.ParseECPrivateKey(block.Bytes); err == nil { + signer = key + } else { + return nil, nil, fmt.Errorf("unsupported private key data") + } + + publicKeyPEM, err := marshalPublicKeyPEM(signer.Public()) + if err != nil { + return nil, nil, err + } + + publicKey, _, err := loadPEMPublicKey(publicKeyPEM) + if err != nil { + return nil, nil, err + } + + return publicKey, publicKeyPEM, nil +} + +func marshalPublicKeyPEM(publicKey crypto.PublicKey) ([]byte, error) { + encoded, err := x509.MarshalPKIXPublicKey(publicKey) + if err != nil { + return nil, fmt.Errorf("marshal public key: %w", err) + } + + return pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: encoded}), nil +} + +func sha256Hex(value []byte) string { + digest := sha256.Sum256(value) + return hex.EncodeToString(digest[:]) +} diff --git a/internal/attestation/sign_key_test.go b/internal/attestation/sign_key_test.go new file mode 100644 index 000000000..1f9d28bd2 --- /dev/null +++ b/internal/attestation/sign_key_test.go @@ -0,0 +1,110 @@ +// Copyright IBM Corp. 2024, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +package attestation + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "encoding/pem" + "os" + "path/filepath" + "testing" +) + +func TestPEMSignerAndVerifier(t *testing.T) { + privateKeyPath, publicKeyPath := writeECDSAKeypair(t) + + signer, err := NewSigner(context.Background(), BackendConfig{ + Mode: SigningModeKey, + SignerRef: privateKeyPath, + }) + if err != nil { + t.Fatalf("create signer: %v", err) + } + + verifier, err := NewVerifier(context.Background(), BackendConfig{ + Mode: SigningModeKey, + VerifierRef: publicKeyPath, + }, signer) + if err != nil { + t.Fatalf("create verifier: %v", err) + } + + payload := []byte(`{"hello":"world"}`) + signature, err := signer.Sign(context.Background(), InTotoPayloadType, payload) + if err != nil { + t.Fatalf("sign payload: %v", err) + } + + envelope := NewEnvelope(InTotoPayloadType, payload, signature) + if err := VerifyEnvelope(context.Background(), envelope, verifier); err != nil { + t.Fatalf("verify envelope: %v", err) + } +} + +func TestVerifierOverrideMismatchFails(t *testing.T) { + privateKeyPath, _ := writeECDSAKeypair(t) + _, mismatchedPublicKeyPath := writeECDSAKeypair(t) + + signer, err := NewSigner(context.Background(), BackendConfig{ + Mode: SigningModeKey, + SignerRef: privateKeyPath, + }) + if err != nil { + t.Fatalf("create signer: %v", err) + } + + verifier, err := NewVerifier(context.Background(), BackendConfig{ + Mode: SigningModeKey, + VerifierRef: mismatchedPublicKeyPath, + }, signer) + if err != nil { + t.Fatalf("create verifier: %v", err) + } + + signature, err := signer.Sign(context.Background(), InTotoPayloadType, []byte(`{"hello":"world"}`)) + if err != nil { + t.Fatalf("sign payload: %v", err) + } + + envelope := NewEnvelope(InTotoPayloadType, []byte(`{"hello":"world"}`), signature) + if err := VerifyEnvelope(context.Background(), envelope, verifier); err == nil { + t.Fatalf("expected verifier mismatch to fail") + } +} + +func writeECDSAKeypair(t *testing.T) (string, string) { + t.Helper() + + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("generate private key: %v", err) + } + + privateKeyDER, err := x509.MarshalECPrivateKey(privateKey) + if err != nil { + t.Fatalf("marshal private key: %v", err) + } + + publicKeyDER, err := x509.MarshalPKIXPublicKey(privateKey.Public()) + if err != nil { + t.Fatalf("marshal public key: %v", err) + } + + dir := t.TempDir() + privateKeyPath := filepath.Join(dir, "signer.pem") + publicKeyPath := filepath.Join(dir, "verifier.pem") + + if err := os.WriteFile(privateKeyPath, pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: privateKeyDER}), 0600); err != nil { + t.Fatalf("write private key: %v", err) + } + if err := os.WriteFile(publicKeyPath, pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: publicKeyDER}), 0600); err != nil { + t.Fatalf("write public key: %v", err) + } + + return privateKeyPath, publicKeyPath +} diff --git a/internal/attestation/sign_keyless.go b/internal/attestation/sign_keyless.go new file mode 100644 index 000000000..7102887c7 --- /dev/null +++ b/internal/attestation/sign_keyless.go @@ -0,0 +1,360 @@ +// Copyright IBM Corp. 2024, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +package attestation + +import ( + "context" + "crypto/x509" + "encoding/base64" + "encoding/hex" + "encoding/json" + "encoding/pem" + "fmt" + "net/http" + "net/url" + "strings" + "time" + + sigstorebundle "github.com/sigstore/sigstore-go/pkg/bundle" + fulciocertificate "github.com/sigstore/sigstore-go/pkg/fulcio/certificate" + sigstoreroot "github.com/sigstore/sigstore-go/pkg/root" + sigstoregosign "github.com/sigstore/sigstore-go/pkg/sign" + sigstoreverify "github.com/sigstore/sigstore-go/pkg/verify" +) + +const defaultFulcioURL = "https://fulcio.sigstore.dev" +const defaultRekorURL = "https://rekor.sigstore.dev" + +var newKeylessEphemeralKeypair = func() (sigstoregosign.Keypair, error) { + return sigstoregosign.NewEphemeralKeypair(nil) +} + +var newKeylessFulcio = func(baseURL string) sigstoregosign.CertificateProvider { + return sigstoregosign.NewFulcio(&sigstoregosign.FulcioOptions{BaseURL: baseURL}) +} + +var newKeylessBundle = sigstoregosign.Bundle + +var newKeylessRekor = func(baseURL string) sigstoregosign.Transparency { + return sigstoregosign.NewRekor(&sigstoregosign.RekorOptions{BaseURL: baseURL}) +} + +var loadKeylessTrustedMaterial = func(cfg BackendConfig) (sigstoreroot.TrustedMaterial, error) { + trustedRootPath := strings.TrimSpace(cfg.TrustedRootPath) + if trustedRootPath == "" { + return sigstoreroot.FetchTrustedRoot() + } + + return sigstoreroot.NewTrustedRootFromPath(trustedRootPath) +} + +var verifyKeylessCertificate = func(certificate *x509.Certificate, trustedMaterial sigstoreroot.TrustedMaterial, expectedIdentity, expectedOIDCIssuer, trustedRootPath string) error { + chains, err := sigstoreverify.VerifyLeafCertificate(time.Now().UTC(), certificate, trustedMaterial) + if err != nil { + return fmt.Errorf("verify Fulcio certificate chain: %w", err) + } + + // When using the public Sigstore root (no custom trusted root configured), + // require a valid SCT so certificates issued outside a public CT log are rejected. + if strings.TrimSpace(trustedRootPath) == "" { + if err := sigstoreverify.VerifySignedCertificateTimestamp(chains, 1, trustedMaterial); err != nil { + return fmt.Errorf("verify Fulcio certificate SCT: %w", err) + } + } + + summary, err := fulciocertificate.SummarizeCertificate(certificate) + if err != nil { + return fmt.Errorf("summarize Fulcio certificate: %w", err) + } + + identity, err := sigstoreverify.NewShortCertificateIdentity(expectedOIDCIssuer, "", expectedIdentity, "") + if err != nil { + return fmt.Errorf("build keyless identity policy: %w", err) + } + if err := identity.Verify(summary); err != nil { + return fmt.Errorf("verify keyless certificate identity: %w", err) + } + + return nil +} + +func init() { + RegisterSigner(SigningModeKeyless, newKeylessSigner) +} + +type keylessSigner struct { + keypair sigstoregosign.Keypair + certPEM []byte + cert *x509.Certificate + verifier Verifier + keyID string +} + +func newKeylessSigner(ctx context.Context, cfg BackendConfig) (Signer, error) { + fulcioURL := strings.TrimSpace(cfg.FulcioURL) + if fulcioURL == "" { + fulcioURL = defaultFulcioURL + } + + idToken, err := resolveAmbientIDToken(ctx, cfg.Env) + if err != nil { + return nil, err + } + + keypair, err := newKeylessEphemeralKeypair() + if err != nil { + return nil, fmt.Errorf("generate ephemeral keypair: %w", err) + } + + fulcio := newKeylessFulcio(fulcioURL) + certDER, err := fulcio.GetCertificate(ctx, keypair, &sigstoregosign.CertificateProviderOptions{IDToken: idToken}) + if err != nil { + return nil, fmt.Errorf("request Fulcio certificate: %w", err) + } + + certificate, err := x509.ParseCertificate(certDER) + if err != nil { + return nil, fmt.Errorf("parse Fulcio certificate: %w", err) + } + + certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) + verifier, err := newSigstoreVerifierFromPublicKey(certificate.PublicKey) + if err != nil { + return nil, fmt.Errorf("create keyless verifier: %w", err) + } + + return &keylessSigner{ + keypair: keypair, + certPEM: certPEM, + cert: certificate, + verifier: verifier, + keyID: hex.EncodeToString(keypair.GetHint()), + }, nil +} + +func (s *keylessSigner) Sign(ctx context.Context, payloadType string, payload []byte) (Signature, error) { + signature, _, err := s.keypair.SignData(ctx, PreAuthEncode(payloadType, payload)) + if err != nil { + return Signature{}, fmt.Errorf("sign payload with keyless signer: %w", err) + } + + return Signature{ + KeyID: s.keyID, + Sig: signature, + CertPEM: append([]byte(nil), s.certPEM...), + }, nil +} + +func (s *keylessSigner) SignBundle(ctx context.Context, payloadType string, payload []byte, cfg BackendConfig) (Envelope, []byte, error) { + content := &sigstoregosign.DSSEData{Data: payload, PayloadType: payloadType} + options := sigstoregosign.BundleOptions{ + CertificateProvider: staticCertificateProvider{certDER: append([]byte(nil), s.cert.Raw...)}, + Context: ctx, + } + + if cfg.UploadTlog { + rekorURL := strings.TrimSpace(cfg.RekorURL) + if rekorURL == "" { + rekorURL = defaultRekorURL + } + + trustedMaterial, err := loadKeylessTrustedMaterial(cfg) + if err != nil { + return Envelope{}, nil, fmt.Errorf("load keyless trusted root: %w", err) + } + + options.TransparencyLogs = []sigstoregosign.Transparency{newKeylessRekor(rekorURL)} + options.TrustedRoot = trustedMaterial + } + + protobufBundle, err := newKeylessBundle(content, s.keypair, options) + if err != nil { + return Envelope{}, nil, fmt.Errorf("build Sigstore bundle: %w", err) + } + + bundleWrapper, err := sigstorebundle.NewBundle(protobufBundle) + if err != nil { + return Envelope{}, nil, fmt.Errorf("decode Sigstore bundle: %w", err) + } + + bundleEnvelope, err := bundleWrapper.Envelope() + if err != nil { + return Envelope{}, nil, fmt.Errorf("extract envelope from Sigstore bundle: %w", err) + } + + rawEnvelope := bundleEnvelope.RawEnvelope() + if rawEnvelope == nil { + return Envelope{}, nil, fmt.Errorf("sigstore bundle does not contain a DSSE envelope") + } + + bundleJSON, err := bundleWrapper.MarshalJSON() + if err != nil { + return Envelope{}, nil, fmt.Errorf("marshal Sigstore bundle: %w", err) + } + + envelope := Envelope{ + PayloadType: rawEnvelope.PayloadType, + Payload: rawEnvelope.Payload, + Signatures: []EnvelopeSignature{{ + KeyID: s.keyID, + Sig: base64.StdEncoding.EncodeToString(bundleEnvelope.Signature()), + Cert: string(s.certPEM), + }}, + } + + return envelope, bundleJSON, nil +} + +func (s *keylessSigner) Verifier(ctx context.Context, cfg BackendConfig) (Verifier, error) { + return newKeylessVerifier(cfg, s.cert) +} + +func newKeylessVerifierForEnvelope(cfg BackendConfig, envelope Envelope) (Verifier, error) { + certificate, err := certificateFromEnvelope(envelope) + if err != nil { + return nil, err + } + + return newKeylessVerifier(cfg, certificate) +} + +func newKeylessVerifier(cfg BackendConfig, certificate *x509.Certificate) (Verifier, error) { + if strings.TrimSpace(cfg.KeylessIdentity) == "" || strings.TrimSpace(cfg.KeylessOIDCIssuer) == "" { + return nil, fmt.Errorf("signing_mode %q requires keyless_identity and keyless_oidc_issuer unless verifier is explicitly configured", SigningModeKeyless) + } + + trustedMaterial, err := loadKeylessTrustedMaterial(cfg) + if err != nil { + return nil, fmt.Errorf("load keyless trusted root: %w", err) + } + + signatureVerifier, err := newSigstoreVerifierFromPublicKey(certificate.PublicKey) + if err != nil { + return nil, fmt.Errorf("create keyless verifier: %w", err) + } + + return &keylessVerifier{ + certificate: certificate, + signatureVerifier: signatureVerifier, + trustedMaterial: trustedMaterial, + identity: cfg.KeylessIdentity, + oidcIssuer: cfg.KeylessOIDCIssuer, + trustedRootPath: cfg.TrustedRootPath, + }, nil +} + +type keylessVerifier struct { + certificate *x509.Certificate + signatureVerifier Verifier + trustedMaterial sigstoreroot.TrustedMaterial + identity string + oidcIssuer string + trustedRootPath string +} + +func (v *keylessVerifier) Verify(ctx context.Context, payloadType string, payload, signature []byte) error { + if err := verifyKeylessCertificate(v.certificate, v.trustedMaterial, v.identity, v.oidcIssuer, v.trustedRootPath); err != nil { + return err + } + + return v.signatureVerifier.Verify(ctx, payloadType, payload, signature) +} + +func (v *keylessVerifier) KeyID() string { + return v.signatureVerifier.KeyID() +} + +func certificateFromEnvelope(envelope Envelope) (*x509.Certificate, error) { + for _, signature := range envelope.Signatures { + if strings.TrimSpace(signature.Cert) == "" { + continue + } + + block, _ := pem.Decode([]byte(signature.Cert)) + if block == nil { + return nil, fmt.Errorf("decode keyless certificate: no PEM block found") + } + + certificate, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, fmt.Errorf("parse keyless certificate: %w", err) + } + + return certificate, nil + } + + return nil, fmt.Errorf("keyless attestation does not contain a signing certificate") +} + +type staticCertificateProvider struct { + certDER []byte +} + +func (p staticCertificateProvider) GetCertificate(context.Context, sigstoregosign.Keypair, *sigstoregosign.CertificateProviderOptions) ([]byte, error) { + if len(p.certDER) == 0 { + return nil, fmt.Errorf("static certificate provider is missing a certificate") + } + + return append([]byte(nil), p.certDER...), nil +} + +func resolveAmbientIDToken(ctx context.Context, env map[string]string) (string, error) { + if token := strings.TrimSpace(env["SIGSTORE_ID_TOKEN"]); token != "" { + return token, nil + } + if token := strings.TrimSpace(env["CI_JOB_JWT_V2"]); token != "" { + return token, nil + } + if token := strings.TrimSpace(env["CI_JOB_JWT"]); token != "" { + return token, nil + } + if token, err := resolveGitHubActionsIDToken(ctx, env); err == nil && token != "" { + return token, nil + } + + return "", fmt.Errorf("signing_mode %q requires an ambient OIDC token; set SIGSTORE_ID_TOKEN, use a CI-provided OIDC token, or switch to signing_mode=\"none\" or a key-backed mode", SigningModeKeyless) +} + +func resolveGitHubActionsIDToken(ctx context.Context, env map[string]string) (string, error) { + requestURL := strings.TrimSpace(env["ACTIONS_ID_TOKEN_REQUEST_URL"]) + requestToken := strings.TrimSpace(env["ACTIONS_ID_TOKEN_REQUEST_TOKEN"]) + if requestURL == "" || requestToken == "" { + return "", nil + } + + parsedURL, err := url.Parse(requestURL) + if err != nil { + return "", fmt.Errorf("parse GitHub OIDC request URL: %w", err) + } + query := parsedURL.Query() + if query.Get("audience") == "" { + query.Set("audience", "sigstore") + parsedURL.RawQuery = query.Encode() + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, parsedURL.String(), nil) + if err != nil { + return "", fmt.Errorf("create GitHub OIDC request: %w", err) + } + req.Header.Set("Authorization", "Bearer "+requestToken) + + client := &http.Client{Timeout: 30 * time.Second} + resp, err := client.Do(req) + if err != nil { + return "", fmt.Errorf("request GitHub OIDC token: %w", err) + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + return "", fmt.Errorf("request GitHub OIDC token: unexpected status %s", resp.Status) + } + + var payload struct { + Value string `json:"value"` + } + if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil { + return "", fmt.Errorf("decode GitHub OIDC token response: %w", err) + } + return strings.TrimSpace(payload.Value), nil +} diff --git a/internal/attestation/sign_kms.go b/internal/attestation/sign_kms.go new file mode 100644 index 000000000..33e958c3b --- /dev/null +++ b/internal/attestation/sign_kms.go @@ -0,0 +1,140 @@ +// Copyright IBM Corp. 2024, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +package attestation + +import ( + "bytes" + "context" + "crypto" + "errors" + "fmt" + "strings" + + sigstoresignature "github.com/sigstore/sigstore/pkg/signature" + sigstorekms "github.com/sigstore/sigstore/pkg/signature/kms" +) + +var newKMSSignerVerifier = func(ctx context.Context, keyResourceID string) (sigstorekms.SignerVerifier, error) { + return sigstorekms.Get(ctx, keyResourceID, crypto.SHA256) +} + +func init() { + RegisterSigner(SigningModeKMS, newKMSSigner) +} + +type kmsSigner struct { + signerVerifier sigstorekms.SignerVerifier + verifier Verifier + keyID string +} + +func newKMSSigner(ctx context.Context, cfg BackendConfig) (Signer, error) { + if cfg.SignerRef == "" { + return nil, fmt.Errorf("signing_mode %q requires signer or key", SigningModeKMS) + } + + signerVerifier, err := newKMSSignerVerifier(ctx, cfg.SignerRef) + if err != nil { + var notFound *sigstorekms.ProviderNotFoundError + if errors.As(err, ¬Found) { + return nil, fmt.Errorf("initialize KMS signer %q: %w%s", cfg.SignerRef, err, kmsProviderBuildHint(cfg.SignerRef)) + } + return nil, fmt.Errorf("initialize KMS signer %q: %w", cfg.SignerRef, err) + } + + publicKey, err := signerVerifier.PublicKey() + if err != nil { + return nil, fmt.Errorf("load KMS public key %q: %w", cfg.SignerRef, err) + } + + verifier, err := newSigstoreVerifierFromPublicKey(publicKey) + if err != nil { + return nil, fmt.Errorf("create KMS verifier %q: %w", cfg.SignerRef, err) + } + + return &kmsSigner{ + signerVerifier: signerVerifier, + verifier: verifier, + keyID: verifier.KeyID(), + }, nil +} + +func (s *kmsSigner) Sign(_ context.Context, payloadType string, payload []byte) (Signature, error) { + encoded := PreAuthEncode(payloadType, payload) + signature, err := s.signerVerifier.SignMessage(bytes.NewReader(encoded)) + if err != nil { + return Signature{}, fmt.Errorf("sign payload with KMS: %w", err) + } + + return Signature{ + KeyID: s.keyID, + Sig: signature, + }, nil +} + +func (s *kmsSigner) Verifier(context.Context, BackendConfig) (Verifier, error) { + return s.verifier, nil +} + +// kmsProviderBuildTags maps a KMS/Vault URI scheme to the build tag that +// compiles its provider into the binary. Providers are included by default; +// builds using the "kms_cherrypick" tag opt in to individual providers. +var kmsProviderBuildTags = map[string]string{ + "awskms": "kms_aws", + "gcpkms": "kms_gcp", + "azurekms": "kms_azure", + "hashivault": "kms_hashivault", +} + +// kmsProviderBuildHint returns guidance when a recognized KMS provider scheme is +// requested but no provider is registered, which happens when the binary was +// built with "kms_cherrypick" and did not opt that provider in. +func kmsProviderBuildHint(ref string) string { + scheme := ref + if idx := strings.Index(ref, "://"); idx >= 0 { + scheme = ref[:idx] + } + + tag, ok := kmsProviderBuildTags[scheme] + if !ok { + return "" + } + + return fmt.Sprintf("; the %s KMS provider is not compiled into this build (rebuild without \"kms_cherrypick\", or with -tags 'kms_cherrypick %s')", scheme, tag) +} + +func newSigstoreVerifierFromPublicKey(publicKey crypto.PublicKey) (Verifier, error) { + verifier, err := sigstoresignature.LoadDefaultVerifier(publicKey) + if err != nil { + return nil, fmt.Errorf("load sigstore verifier: %w", err) + } + + publicKeyPEM, err := marshalPublicKeyPEM(publicKey) + if err != nil { + return nil, err + } + + return &sigstoreVerifier{ + verifier: verifier, + keyID: sha256Hex(publicKeyPEM), + }, nil +} + +type sigstoreVerifier struct { + verifier sigstoresignature.Verifier + keyID string +} + +func (v *sigstoreVerifier) Verify(_ context.Context, payloadType string, payload, signature []byte) error { + encoded := PreAuthEncode(payloadType, payload) + if err := v.verifier.VerifySignature(bytes.NewReader(signature), bytes.NewReader(encoded)); err != nil { + return err + } + + return nil +} + +func (v *sigstoreVerifier) KeyID() string { + return v.keyID +} diff --git a/internal/attestation/sign_kms_keyless_test.go b/internal/attestation/sign_kms_keyless_test.go new file mode 100644 index 000000000..d9abc3460 --- /dev/null +++ b/internal/attestation/sign_kms_keyless_test.go @@ -0,0 +1,762 @@ +// Copyright IBM Corp. 2024, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +package attestation + +import ( + "context" + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/json" + "fmt" + "math/big" + "net/url" + "os" + "path/filepath" + "strings" + "testing" + "time" + + internalprovenance "github.com/hashicorp/packer/internal/provenance" + protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1" + sigstorebundle "github.com/sigstore/sigstore-go/pkg/bundle" + fulciocertificate "github.com/sigstore/sigstore-go/pkg/fulcio/certificate" + sigstoreroot "github.com/sigstore/sigstore-go/pkg/root" + sigstoregosign "github.com/sigstore/sigstore-go/pkg/sign" + sigstoresignature "github.com/sigstore/sigstore/pkg/signature" + sigstorekms "github.com/sigstore/sigstore/pkg/signature/kms" +) + +func TestKMSSignerAndVerifier(t *testing.T) { + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("generate private key: %v", err) + } + + localSignerVerifier, err := sigstoresignature.LoadDefaultSignerVerifier(privateKey) + if err != nil { + t.Fatalf("load local signer verifier: %v", err) + } + + originalFactory := newKMSSignerVerifier + newKMSSignerVerifier = func(ctx context.Context, keyResourceID string) (sigstorekms.SignerVerifier, error) { + if got, want := keyResourceID, "awskms://alias/example"; got != want { + t.Fatalf("unexpected KMS key resource %q, want %q", got, want) + } + return &fakeKMSSignerVerifier{ + SignerVerifier: localSignerVerifier, + publicKey: privateKey.Public(), + }, nil + } + t.Cleanup(func() { + newKMSSignerVerifier = originalFactory + }) + + signer, err := NewSigner(context.Background(), BackendConfig{Mode: SigningModeKMS, SignerRef: "awskms://alias/example"}) + if err != nil { + t.Fatalf("create KMS signer: %v", err) + } + + verifier, err := NewVerifier(context.Background(), BackendConfig{Mode: SigningModeKMS}, signer) + if err != nil { + t.Fatalf("create KMS verifier: %v", err) + } + + payload := []byte(`{"hello":"kms"}`) + signature, err := signer.Sign(context.Background(), InTotoPayloadType, payload) + if err != nil { + t.Fatalf("sign payload: %v", err) + } + if signature.KeyID == "" { + t.Fatalf("expected KMS signature key ID to be populated") + } + + envelope := NewEnvelope(InTotoPayloadType, payload, signature) + if err := VerifyEnvelope(context.Background(), envelope, verifier); err != nil { + t.Fatalf("verify envelope: %v", err) + } +} + +func TestKeylessSignerAndVerifier(t *testing.T) { + originalKeypairFactory := newKeylessEphemeralKeypair + originalFulcioFactory := newKeylessFulcio + originalTrustedMaterialLoader := loadKeylessTrustedMaterial + originalCertificateVerifier := verifyKeylessCertificate + + newKeylessEphemeralKeypair = func() (sigstoregosign.Keypair, error) { + return sigstoregosign.NewEphemeralKeypair(nil) + } + newKeylessFulcio = func(baseURL string) sigstoregosign.CertificateProvider { + if got, want := baseURL, "https://fulcio.example.test"; got != want { + t.Fatalf("unexpected Fulcio URL %q, want %q", got, want) + } + return fakeCertificateProvider{t: t, wantToken: "test-oidc-token"} + } + loadKeylessTrustedMaterial = func(cfg BackendConfig) (sigstoreroot.TrustedMaterial, error) { + if got, want := cfg.TrustedRootPath, "/tmp/test-root.json"; got != want { + t.Fatalf("unexpected trusted root path %q, want %q", got, want) + } + return nil, nil + } + verifyKeylessCertificate = func(certificate *x509.Certificate, trustedMaterial sigstoreroot.TrustedMaterial, expectedIdentity, expectedOIDCIssuer, trustedRootPath string) error { + summary, err := fulciocertificate.SummarizeCertificate(certificate) + if err != nil { + t.Fatalf("summarize certificate: %v", err) + } + if got, want := summary.SubjectAlternativeName, "https://github.com/hashicorp/packer/.github/workflows/build.yml@refs/heads/main"; got != want { + t.Fatalf("unexpected certificate SAN %q, want %q", got, want) + } + if got, want := summary.Issuer, "https://token.actions.githubusercontent.com"; got != want { + t.Fatalf("unexpected certificate OIDC issuer %q, want %q", got, want) + } + if got, want := expectedIdentity, "https://github.com/hashicorp/packer/.github/workflows/build.yml@refs/heads/main"; got != want { + t.Fatalf("unexpected expected identity %q, want %q", got, want) + } + if got, want := expectedOIDCIssuer, "https://token.actions.githubusercontent.com"; got != want { + t.Fatalf("unexpected expected OIDC issuer %q, want %q", got, want) + } + return nil + } + t.Cleanup(func() { + newKeylessEphemeralKeypair = originalKeypairFactory + newKeylessFulcio = originalFulcioFactory + loadKeylessTrustedMaterial = originalTrustedMaterialLoader + verifyKeylessCertificate = originalCertificateVerifier + }) + + signer, err := NewSigner(context.Background(), BackendConfig{ + Mode: SigningModeKeyless, + Env: map[string]string{"SIGSTORE_ID_TOKEN": "test-oidc-token"}, + FulcioURL: "https://fulcio.example.test", + }) + if err != nil { + t.Fatalf("create keyless signer: %v", err) + } + + verifier, err := NewVerifier(context.Background(), BackendConfig{ + Mode: SigningModeKeyless, + TrustedRootPath: "/tmp/test-root.json", + KeylessIdentity: "https://github.com/hashicorp/packer/.github/workflows/build.yml@refs/heads/main", + KeylessOIDCIssuer: "https://token.actions.githubusercontent.com", + }, signer) + if err != nil { + t.Fatalf("create keyless verifier: %v", err) + } + + payload := []byte(`{"hello":"keyless"}`) + signature, err := signer.Sign(context.Background(), InTotoPayloadType, payload) + if err != nil { + t.Fatalf("sign payload: %v", err) + } + if len(signature.CertPEM) == 0 { + t.Fatalf("expected keyless signature to embed a certificate") + } + + envelope := NewEnvelope(InTotoPayloadType, payload, signature) + if err := VerifyEnvelope(context.Background(), envelope, verifier); err != nil { + t.Fatalf("verify envelope: %v", err) + } +} + +func TestBuildBundleForKeylessSigner(t *testing.T) { + originalKeypairFactory := newKeylessEphemeralKeypair + originalFulcioFactory := newKeylessFulcio + originalBundleFactory := newKeylessBundle + originalRekorFactory := newKeylessRekor + originalTrustedMaterialLoader := loadKeylessTrustedMaterial + + newKeylessEphemeralKeypair = func() (sigstoregosign.Keypair, error) { + return sigstoregosign.NewEphemeralKeypair(nil) + } + newKeylessFulcio = func(string) sigstoregosign.CertificateProvider { + return fakeCertificateProvider{t: t, wantToken: "test-oidc-token"} + } + loadKeylessTrustedMaterial = func(cfg BackendConfig) (sigstoreroot.TrustedMaterial, error) { + if got, want := cfg.TrustedRootPath, "/tmp/test-root.json"; got != want { + t.Fatalf("unexpected trusted root path %q, want %q", got, want) + } + return nil, nil + } + rekorCalled := false + newKeylessRekor = func(baseURL string) sigstoregosign.Transparency { + if got, want := baseURL, "https://rekor.example.test"; got != want { + t.Fatalf("unexpected Rekor URL %q, want %q", got, want) + } + rekorCalled = true + return fakeTransparency{} + } + newKeylessBundle = func(content sigstoregosign.Content, keypair sigstoregosign.Keypair, opts sigstoregosign.BundleOptions) (*protobundle.Bundle, error) { + if len(opts.TransparencyLogs) != 1 { + t.Fatalf("expected one transparency log, got %d", len(opts.TransparencyLogs)) + } + return sigstoregosign.Bundle(content, keypair, sigstoregosign.BundleOptions{ + CertificateProvider: opts.CertificateProvider, + Context: opts.Context, + TrustedRoot: opts.TrustedRoot, + }) + } + t.Cleanup(func() { + newKeylessEphemeralKeypair = originalKeypairFactory + newKeylessFulcio = originalFulcioFactory + newKeylessBundle = originalBundleFactory + newKeylessRekor = originalRekorFactory + loadKeylessTrustedMaterial = originalTrustedMaterialLoader + }) + + signer, err := NewSigner(context.Background(), BackendConfig{ + Mode: SigningModeKeyless, + Env: map[string]string{"SIGSTORE_ID_TOKEN": "test-oidc-token"}, + FulcioURL: "https://fulcio.example.test", + }) + if err != nil { + t.Fatalf("create keyless signer: %v", err) + } + + envelope, bundleJSON, err := BuildBundleForSigner(context.Background(), signer, BackendConfig{ + Mode: SigningModeKeyless, + UploadTlog: true, + RekorURL: "https://rekor.example.test", + TrustedRootPath: "/tmp/test-root.json", + }, InTotoPayloadType, []byte(`{"hello":"bundle"}`)) + if err != nil { + t.Fatalf("build bundle: %v", err) + } + if !rekorCalled { + t.Fatalf("expected Rekor constructor to be used") + } + if got, want := envelope.PayloadType, InTotoPayloadType; got != want { + t.Fatalf("unexpected envelope payload type %q, want %q", got, want) + } + if len(envelope.Signatures) != 1 || envelope.Signatures[0].Cert == "" { + t.Fatalf("expected bundled envelope to include one certificate-backed signature") + } + + bundlePath := filepath.Join(t.TempDir(), "bundle.json") + if err := os.WriteFile(bundlePath, bundleJSON, 0600); err != nil { + t.Fatalf("write bundle: %v", err) + } + if _, err := sigstorebundle.LoadJSONFromPath(bundlePath); err != nil { + t.Fatalf("load bundle: %v", err) + } +} + +func TestKeylessBundleAndRekorIntegration(t *testing.T) { + if os.Getenv("PACKER_ACC") == "" { + t.Skip("acceptance-style keyless integration test skipped unless PACKER_ACC is set") + } + + env := currentProcessEnv() + if _, err := resolveAmbientIDToken(context.Background(), env); err != nil { + t.Skipf("keyless integration test skipped without an ambient OIDC token: %v", err) + } + + artifactPath := filepath.Join(t.TempDir(), "artifact.txt") + if err := os.WriteFile(artifactPath, []byte("hello live keyless"), 0600); err != nil { + t.Fatalf("write artifact: %v", err) + } + digest, err := sha256File(artifactPath) + if err != nil { + t.Fatalf("hash artifact: %v", err) + } + + statement := internalprovenance.WrapInToto( + []internalprovenance.Subject{{Name: filepath.Base(artifactPath), Digest: internalprovenance.DigestSet{"sha256": digest}}}, + internalprovenance.SLSAProvenanceV1PredicateType, + internalprovenance.BuildSLSAPredicate(internalprovenance.PredicateInput{}), + ) + payload, err := MarshalPayload(statement) + if err != nil { + t.Fatalf("marshal statement: %v", err) + } + + signer, err := NewSigner(context.Background(), BackendConfig{ + Mode: SigningModeKeyless, + Env: env, + }) + if err != nil { + t.Fatalf("create live keyless signer: %v", err) + } + + envelope, bundleJSON, err := BuildBundleForSigner(context.Background(), signer, BackendConfig{ + Mode: SigningModeKeyless, + Env: env, + UploadTlog: true, + }, InTotoPayloadType, payload) + if err != nil { + t.Fatalf("build live Sigstore bundle: %v", err) + } + + certificate, err := certificateFromEnvelope(envelope) + if err != nil { + t.Fatalf("extract certificate from live envelope: %v", err) + } + summary, err := fulciocertificate.SummarizeCertificate(certificate) + if err != nil { + t.Fatalf("summarize live certificate: %v", err) + } + if summary.SubjectAlternativeName == "" || summary.Issuer == "" { + t.Fatalf("expected live certificate to contain SAN and issuer, got SAN=%q issuer=%q", summary.SubjectAlternativeName, summary.Issuer) + } + + envelopePath := filepath.Join(t.TempDir(), "attestation.json") + if err := os.WriteFile(envelopePath, mustJSONMarshal(t, envelope), 0600); err != nil { + t.Fatalf("write live envelope: %v", err) + } + bundlePath := filepath.Join(t.TempDir(), "attestation.sigstore.json") + if err := os.WriteFile(bundlePath, bundleJSON, 0600); err != nil { + t.Fatalf("write live bundle: %v", err) + } + if _, err := sigstorebundle.LoadJSONFromPath(bundlePath); err != nil { + t.Fatalf("load live bundle: %v", err) + } + + verifiedStatement, err := VerifyAttestationFile(context.Background(), envelopePath, BackendConfig{ + Mode: SigningModeKeyless, + KeylessIdentity: summary.SubjectAlternativeName, + KeylessOIDCIssuer: summary.Issuer, + }, VerificationPolicy{ + PredicateType: internalprovenance.SLSAProvenanceV1PredicateType, + ArtifactPath: artifactPath, + SigstoreBundlePath: bundlePath, + RequireTransparencyLog: true, + }) + if err != nil { + t.Fatalf("verify live attestation with Rekor bundle: %v", err) + } + if got, want := verifiedStatement.PredicateType, internalprovenance.SLSAProvenanceV1PredicateType; got != want { + t.Fatalf("unexpected predicate type %q, want %q", got, want) + } +} + +func TestKeylessSignerRequiresAmbientToken(t *testing.T) { + _, err := NewSigner(context.Background(), BackendConfig{Mode: SigningModeKeyless, Env: map[string]string{}}) + if err == nil { + t.Fatalf("expected keyless signer creation to fail without an ambient token") + } + if !strings.Contains(err.Error(), "ambient OIDC token") { + t.Fatalf("unexpected keyless token error: %v", err) + } +} + +func TestKeylessVerifierRequiresIdentityPolicy(t *testing.T) { + originalKeypairFactory := newKeylessEphemeralKeypair + originalFulcioFactory := newKeylessFulcio + originalTrustedMaterialLoader := loadKeylessTrustedMaterial + + newKeylessEphemeralKeypair = func() (sigstoregosign.Keypair, error) { + return sigstoregosign.NewEphemeralKeypair(nil) + } + newKeylessFulcio = func(string) sigstoregosign.CertificateProvider { + return fakeCertificateProvider{t: t, wantToken: "test-oidc-token"} + } + loadKeylessTrustedMaterial = func(cfg BackendConfig) (sigstoreroot.TrustedMaterial, error) { + return nil, nil + } + t.Cleanup(func() { + newKeylessEphemeralKeypair = originalKeypairFactory + newKeylessFulcio = originalFulcioFactory + loadKeylessTrustedMaterial = originalTrustedMaterialLoader + }) + + signer, err := NewSigner(context.Background(), BackendConfig{ + Mode: SigningModeKeyless, + Env: map[string]string{"SIGSTORE_ID_TOKEN": "test-oidc-token"}, + FulcioURL: "https://fulcio.example.test", + }) + if err != nil { + t.Fatalf("create keyless signer: %v", err) + } + + _, err = NewVerifier(context.Background(), BackendConfig{Mode: SigningModeKeyless}, signer) + if err == nil { + t.Fatalf("expected keyless verifier creation to fail without identity policy") + } + if !strings.Contains(err.Error(), "keyless_identity") { + t.Fatalf("unexpected keyless verifier error: %v", err) + } +} + +func TestVerifyAttestationFileWithKeylessEnvelope(t *testing.T) { + originalKeypairFactory := newKeylessEphemeralKeypair + originalFulcioFactory := newKeylessFulcio + originalTrustedMaterialLoader := loadKeylessTrustedMaterial + originalCertificateVerifier := verifyKeylessCertificate + + newKeylessEphemeralKeypair = func() (sigstoregosign.Keypair, error) { + return sigstoregosign.NewEphemeralKeypair(nil) + } + newKeylessFulcio = func(string) sigstoregosign.CertificateProvider { + return fakeCertificateProvider{t: t, wantToken: "test-oidc-token"} + } + loadKeylessTrustedMaterial = func(cfg BackendConfig) (sigstoreroot.TrustedMaterial, error) { + if got, want := cfg.TrustedRootPath, "/tmp/test-root.json"; got != want { + t.Fatalf("unexpected trusted root path %q, want %q", got, want) + } + return nil, nil + } + verifyKeylessCertificate = func(certificate *x509.Certificate, trustedMaterial sigstoreroot.TrustedMaterial, expectedIdentity, expectedOIDCIssuer, trustedRootPath string) error { + summary, err := fulciocertificate.SummarizeCertificate(certificate) + if err != nil { + t.Fatalf("summarize certificate: %v", err) + } + if got, want := summary.SubjectAlternativeName, "https://github.com/hashicorp/packer/.github/workflows/build.yml@refs/heads/main"; got != want { + t.Fatalf("unexpected certificate SAN %q, want %q", got, want) + } + if got, want := expectedIdentity, "https://github.com/hashicorp/packer/.github/workflows/build.yml@refs/heads/main"; got != want { + t.Fatalf("unexpected expected identity %q, want %q", got, want) + } + if got, want := expectedOIDCIssuer, "https://token.actions.githubusercontent.com"; got != want { + t.Fatalf("unexpected expected OIDC issuer %q, want %q", got, want) + } + return nil + } + t.Cleanup(func() { + newKeylessEphemeralKeypair = originalKeypairFactory + newKeylessFulcio = originalFulcioFactory + loadKeylessTrustedMaterial = originalTrustedMaterialLoader + verifyKeylessCertificate = originalCertificateVerifier + }) + + artifactPath := filepath.Join(t.TempDir(), "artifact.txt") + if err := os.WriteFile(artifactPath, []byte("hello keyless"), 0600); err != nil { + t.Fatalf("write artifact: %v", err) + } + digest, err := sha256File(artifactPath) + if err != nil { + t.Fatalf("hash artifact: %v", err) + } + + statement := internalprovenance.WrapInToto( + []internalprovenance.Subject{{Name: filepath.Base(artifactPath), Digest: internalprovenance.DigestSet{"sha256": digest}}}, + internalprovenance.SLSAProvenanceV1PredicateType, + internalprovenance.BuildSLSAPredicate(internalprovenance.PredicateInput{ + BuilderID: "https://github.com/hashicorp/packer/.github/workflows/build.yml@refs/heads/main", + ResolvedDependencies: []internalprovenance.ResolvedDependency{{URI: "git+https://github.com/hashicorp/packer@refs/heads/main"}}, + }), + ) + payload, err := MarshalPayload(statement) + if err != nil { + t.Fatalf("marshal statement: %v", err) + } + + signer, err := NewSigner(context.Background(), BackendConfig{ + Mode: SigningModeKeyless, + Env: map[string]string{"SIGSTORE_ID_TOKEN": "test-oidc-token"}, + FulcioURL: "https://fulcio.example.test", + }) + if err != nil { + t.Fatalf("create keyless signer: %v", err) + } + + signature, err := signer.Sign(context.Background(), InTotoPayloadType, payload) + if err != nil { + t.Fatalf("sign statement: %v", err) + } + + envelopePath := filepath.Join(t.TempDir(), "attestation.json") + envelopeJSON, err := json.Marshal(NewEnvelope(InTotoPayloadType, payload, signature)) + if err != nil { + t.Fatalf("marshal envelope: %v", err) + } + if err := os.WriteFile(envelopePath, envelopeJSON, 0600); err != nil { + t.Fatalf("write envelope: %v", err) + } + + verifiedStatement, err := VerifyAttestationFile(context.Background(), envelopePath, BackendConfig{ + Mode: SigningModeKeyless, + TrustedRootPath: "/tmp/test-root.json", + KeylessIdentity: "https://github.com/hashicorp/packer/.github/workflows/build.yml@refs/heads/main", + KeylessOIDCIssuer: "https://token.actions.githubusercontent.com", + }, VerificationPolicy{ + PredicateType: internalprovenance.SLSAProvenanceV1PredicateType, + BuilderID: "https://github.com/hashicorp/packer/.github/workflows/build.yml@refs/heads/main", + SourceURI: "git+https://github.com/hashicorp/packer@refs/heads/main", + ArtifactPath: artifactPath, + }) + if err != nil { + t.Fatalf("verify attestation file: %v", err) + } + if got, want := verifiedStatement.PredicateType, internalprovenance.SLSAProvenanceV1PredicateType; got != want { + t.Fatalf("unexpected predicate type %q, want %q", got, want) + } +} + +func TestVerifyAttestationFileRequiresBundleForTransparencyChecks(t *testing.T) { + artifactPath := filepath.Join(t.TempDir(), "artifact.txt") + if err := os.WriteFile(artifactPath, []byte("hello keyless"), 0600); err != nil { + t.Fatalf("write artifact: %v", err) + } + digest, err := sha256File(artifactPath) + if err != nil { + t.Fatalf("hash artifact: %v", err) + } + + statement := internalprovenance.WrapInToto( + []internalprovenance.Subject{{Name: filepath.Base(artifactPath), Digest: internalprovenance.DigestSet{"sha256": digest}}}, + internalprovenance.SLSAProvenanceV1PredicateType, + internalprovenance.BuildSLSAPredicate(internalprovenance.PredicateInput{}), + ) + payload, err := MarshalPayload(statement) + if err != nil { + t.Fatalf("marshal statement: %v", err) + } + + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("generate private key: %v", err) + } + verifier, err := newSigstoreVerifierFromPublicKey(privateKey.Public()) + if err != nil { + t.Fatalf("create verifier: %v", err) + } + sig, err := privateKey.Sign(rand.Reader, PreAuthEncode(InTotoPayloadType, payload), crypto.SHA256) + if err != nil { + t.Fatalf("sign payload: %v", err) + } + + envelopePath := filepath.Join(t.TempDir(), "attestation.json") + envelopeJSON, err := json.Marshal(NewEnvelope(InTotoPayloadType, payload, Signature{Sig: sig, CertPEM: []byte("-----BEGIN CERTIFICATE-----\nZmFrZQ==\n-----END CERTIFICATE-----\n")})) + if err != nil { + t.Fatalf("marshal envelope: %v", err) + } + if err := os.WriteFile(envelopePath, envelopeJSON, 0600); err != nil { + t.Fatalf("write envelope: %v", err) + } + + _ = verifier + _, err = VerifyAttestationFile(context.Background(), envelopePath, BackendConfig{ + Mode: SigningModeKeyless, + KeylessIdentity: "https://github.com/hashicorp/packer/.github/workflows/build.yml@refs/heads/main", + KeylessOIDCIssuer: "https://token.actions.githubusercontent.com", + }, VerificationPolicy{RequireTransparencyLog: true}) + if err == nil || !strings.Contains(err.Error(), "requires -bundle") { + t.Fatalf("expected missing bundle error, got %v", err) + } +} + +func TestVerifyAttestationFileWithBundleRequirements(t *testing.T) { + originalKeypairFactory := newKeylessEphemeralKeypair + originalFulcioFactory := newKeylessFulcio + originalTrustedMaterialLoader := loadKeylessTrustedMaterial + originalCertificateVerifier := verifyKeylessCertificate + originalBundleVerifier := verifySigstoreBundleEvidence + + newKeylessEphemeralKeypair = func() (sigstoregosign.Keypair, error) { + return sigstoregosign.NewEphemeralKeypair(nil) + } + newKeylessFulcio = func(string) sigstoregosign.CertificateProvider { + return fakeCertificateProvider{t: t, wantToken: "test-oidc-token"} + } + loadKeylessTrustedMaterial = func(cfg BackendConfig) (sigstoreroot.TrustedMaterial, error) { + return nil, nil + } + verifyKeylessCertificate = func(*x509.Certificate, sigstoreroot.TrustedMaterial, string, string, string) error { + return nil + } + called := false + verifySigstoreBundleEvidence = func(envelope Envelope, cfg BackendConfig, policy VerificationPolicy) error { + called = true + if got, want := policy.SigstoreBundlePath, "bundle.json"; got != want { + t.Fatalf("unexpected bundle path %q, want %q", got, want) + } + if !policy.RequireTransparencyLog || !policy.RequireObserverTimestamp { + t.Fatalf("expected Rekor and timestamp requirements to be set") + } + if len(envelope.Signatures) != 1 { + t.Fatalf("unexpected signature count %d", len(envelope.Signatures)) + } + if got, want := cfg.KeylessIdentity, "https://github.com/hashicorp/packer/.github/workflows/build.yml@refs/heads/main"; got != want { + t.Fatalf("unexpected keyless identity %q, want %q", got, want) + } + return nil + } + t.Cleanup(func() { + newKeylessEphemeralKeypair = originalKeypairFactory + newKeylessFulcio = originalFulcioFactory + loadKeylessTrustedMaterial = originalTrustedMaterialLoader + verifyKeylessCertificate = originalCertificateVerifier + verifySigstoreBundleEvidence = originalBundleVerifier + }) + + artifactPath := filepath.Join(t.TempDir(), "artifact.txt") + if err := os.WriteFile(artifactPath, []byte("hello keyless"), 0600); err != nil { + t.Fatalf("write artifact: %v", err) + } + digest, err := sha256File(artifactPath) + if err != nil { + t.Fatalf("hash artifact: %v", err) + } + + statement := internalprovenance.WrapInToto( + []internalprovenance.Subject{{Name: filepath.Base(artifactPath), Digest: internalprovenance.DigestSet{"sha256": digest}}}, + internalprovenance.SLSAProvenanceV1PredicateType, + internalprovenance.BuildSLSAPredicate(internalprovenance.PredicateInput{}), + ) + payload, err := MarshalPayload(statement) + if err != nil { + t.Fatalf("marshal statement: %v", err) + } + + signer, err := NewSigner(context.Background(), BackendConfig{ + Mode: SigningModeKeyless, + Env: map[string]string{"SIGSTORE_ID_TOKEN": "test-oidc-token"}, + FulcioURL: "https://fulcio.example.test", + }) + if err != nil { + t.Fatalf("create keyless signer: %v", err) + } + + signature, err := signer.Sign(context.Background(), InTotoPayloadType, payload) + if err != nil { + t.Fatalf("sign statement: %v", err) + } + + envelopePath := filepath.Join(t.TempDir(), "attestation.json") + envelopeJSON, err := json.Marshal(NewEnvelope(InTotoPayloadType, payload, signature)) + if err != nil { + t.Fatalf("marshal envelope: %v", err) + } + if err := os.WriteFile(envelopePath, envelopeJSON, 0600); err != nil { + t.Fatalf("write envelope: %v", err) + } + + _, err = VerifyAttestationFile(context.Background(), envelopePath, BackendConfig{ + Mode: SigningModeKeyless, + KeylessIdentity: "https://github.com/hashicorp/packer/.github/workflows/build.yml@refs/heads/main", + KeylessOIDCIssuer: "https://token.actions.githubusercontent.com", + }, VerificationPolicy{ + SigstoreBundlePath: "bundle.json", + RequireTransparencyLog: true, + RequireObserverTimestamp: true, + }) + if err != nil { + t.Fatalf("verify attestation with bundle requirements: %v", err) + } + if !called { + t.Fatalf("expected Sigstore bundle verifier to be called") + } + _ = sigstorebundle.Bundle{} +} + +type fakeTransparency struct{} + +func (fakeTransparency) GetTransparencyLogEntry(context.Context, []byte, *protobundle.Bundle) error { + return nil +} + +func currentProcessEnv() map[string]string { + env := make(map[string]string) + for _, item := range os.Environ() { + parts := strings.SplitN(item, "=", 2) + if len(parts) != 2 { + continue + } + env[parts[0]] = parts[1] + } + + return env +} + +func mustJSONMarshal(t *testing.T, value interface{}) []byte { + t.Helper() + + encoded, err := json.Marshal(value) + if err != nil { + t.Fatalf("marshal JSON: %v", err) + } + + return encoded +} + +type fakeKMSSignerVerifier struct { + sigstoresignature.SignerVerifier + publicKey crypto.PublicKey +} + +func (f *fakeKMSSignerVerifier) CreateKey(context.Context, string) (crypto.PublicKey, error) { + return f.publicKey, nil +} + +func (f *fakeKMSSignerVerifier) CryptoSigner(context.Context, func(error)) (crypto.Signer, crypto.SignerOpts, error) { + return nil, nil, fmt.Errorf("not implemented in tests") +} + +func (f *fakeKMSSignerVerifier) SupportedAlgorithms() []string { + return []string{"ecdsa-p256-sha256"} +} + +func (f *fakeKMSSignerVerifier) DefaultAlgorithm() string { + return "ecdsa-p256-sha256" +} + +type fakeCertificateProvider struct { + t *testing.T + wantToken string +} + +func (f fakeCertificateProvider) GetCertificate(_ context.Context, keypair sigstoregosign.Keypair, opts *sigstoregosign.CertificateProviderOptions) ([]byte, error) { + f.t.Helper() + if opts == nil { + f.t.Fatalf("expected certificate options to be provided") + } + if got, want := opts.IDToken, f.wantToken; got != want { + f.t.Fatalf("unexpected Fulcio ID token %q, want %q", got, want) + } + return createCertificateDER(f.t, keypair.GetPublicKey()), nil +} + +func createCertificateDER(t *testing.T, publicKey crypto.PublicKey) []byte { + t.Helper() + + issuerKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("generate issuer key: %v", err) + } + + template := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + CommonName: "packer-keyless-test", + }, + URIs: []*url.URL{mustParseURL(t, "https://github.com/hashicorp/packer/.github/workflows/build.yml@refs/heads/main")}, + ExtraExtensions: []pkix.Extension{buildIssuerExtension(t, "https://token.actions.githubusercontent.com")}, + NotBefore: time.Now().Add(-time.Minute), + NotAfter: time.Now().Add(time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature, + BasicConstraintsValid: true, + } + + der, err := x509.CreateCertificate(rand.Reader, template, template, publicKey, issuerKey) + if err != nil { + t.Fatalf("create certificate: %v", err) + } + + return der +} + +func buildIssuerExtension(t *testing.T, issuer string) pkix.Extension { + t.Helper() + + value, err := asn1.MarshalWithParams(issuer, "utf8") + if err != nil { + t.Fatalf("marshal issuer extension: %v", err) + } + + return pkix.Extension{Id: fulciocertificate.OIDIssuerV2, Value: value} +} + +func mustParseURL(t *testing.T, rawURL string) *url.URL { + t.Helper() + + parsedURL, err := url.Parse(rawURL) + if err != nil { + t.Fatalf("parse url %q: %v", rawURL, err) + } + + return parsedURL +} diff --git a/internal/attestation/sign_kms_provider_aws.go b/internal/attestation/sign_kms_provider_aws.go new file mode 100644 index 000000000..4535359fb --- /dev/null +++ b/internal/attestation/sign_kms_provider_aws.go @@ -0,0 +1,8 @@ +// Copyright IBM Corp. 2024, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +//go:build kms_cherrypick && kms_aws + +package attestation + +import _ "github.com/sigstore/sigstore/pkg/signature/kms/aws" diff --git a/internal/attestation/sign_kms_provider_azure.go b/internal/attestation/sign_kms_provider_azure.go new file mode 100644 index 000000000..04272cc49 --- /dev/null +++ b/internal/attestation/sign_kms_provider_azure.go @@ -0,0 +1,8 @@ +// Copyright IBM Corp. 2024, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +//go:build kms_cherrypick && kms_azure + +package attestation + +import _ "github.com/sigstore/sigstore/pkg/signature/kms/azure" diff --git a/internal/attestation/sign_kms_provider_gcp.go b/internal/attestation/sign_kms_provider_gcp.go new file mode 100644 index 000000000..441117e2e --- /dev/null +++ b/internal/attestation/sign_kms_provider_gcp.go @@ -0,0 +1,8 @@ +// Copyright IBM Corp. 2024, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +//go:build kms_cherrypick && kms_gcp + +package attestation + +import _ "github.com/sigstore/sigstore/pkg/signature/kms/gcp" diff --git a/internal/attestation/sign_kms_provider_hashivault.go b/internal/attestation/sign_kms_provider_hashivault.go new file mode 100644 index 000000000..fd4f5e074 --- /dev/null +++ b/internal/attestation/sign_kms_provider_hashivault.go @@ -0,0 +1,8 @@ +// Copyright IBM Corp. 2024, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +//go:build kms_cherrypick && kms_hashivault + +package attestation + +import _ "github.com/sigstore/sigstore/pkg/signature/kms/hashivault" diff --git a/internal/attestation/sign_kms_providers_default.go b/internal/attestation/sign_kms_providers_default.go new file mode 100644 index 000000000..c18015ba6 --- /dev/null +++ b/internal/attestation/sign_kms_providers_default.go @@ -0,0 +1,18 @@ +// Copyright IBM Corp. 2024, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +//go:build !kms_cherrypick + +// Default builds include every KMS/Vault provider so that all signing_mode=kms +// URIs work out of the box. Builds using the "kms_cherrypick" tag exclude this +// file and instead opt into individual providers via the per-provider tags +// (kms_aws, kms_azure, kms_gcp, kms_hashivault). + +package attestation + +import ( + _ "github.com/sigstore/sigstore/pkg/signature/kms/aws" + _ "github.com/sigstore/sigstore/pkg/signature/kms/azure" + _ "github.com/sigstore/sigstore/pkg/signature/kms/gcp" + _ "github.com/sigstore/sigstore/pkg/signature/kms/hashivault" +) diff --git a/internal/attestation/sign_kms_providers_test.go b/internal/attestation/sign_kms_providers_test.go new file mode 100644 index 000000000..564e96a1d --- /dev/null +++ b/internal/attestation/sign_kms_providers_test.go @@ -0,0 +1,37 @@ +// Copyright IBM Corp. 2024, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +//go:build !kms_cherrypick + +package attestation + +import ( + "strings" + "testing" + + sigstorekms "github.com/sigstore/sigstore/pkg/signature/kms" +) + +func TestDefaultBuildRegistersAllKMSProviders(t *testing.T) { + registered := map[string]bool{} + for _, scheme := range sigstorekms.SupportedProviders() { + registered[scheme] = true + } + + for _, scheme := range []string{"awskms://", "azurekms://", "gcpkms://", "hashivault://"} { + if !registered[scheme] { + t.Errorf("default build is missing KMS provider %q (registered: %v)", scheme, sigstorekms.SupportedProviders()) + } + } +} + +func TestKMSProviderBuildHint(t *testing.T) { + hint := kmsProviderBuildHint("awskms://alias/example") + if !strings.Contains(hint, "kms_aws") || !strings.Contains(hint, "awskms") { + t.Fatalf("unexpected hint for awskms reference: %q", hint) + } + + if got := kmsProviderBuildHint("file:///tmp/key.pem"); got != "" { + t.Fatalf("expected no build hint for non-KMS reference, got %q", got) + } +} diff --git a/internal/attestation/signer.go b/internal/attestation/signer.go new file mode 100644 index 000000000..233a61e84 --- /dev/null +++ b/internal/attestation/signer.go @@ -0,0 +1,94 @@ +// Copyright IBM Corp. 2024, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +package attestation + +import ( + "context" + "fmt" +) + +const ( + SigningModeNone = "none" + SigningModeKey = "key" + SigningModeKMS = "kms" + SigningModeKeyless = "keyless" +) + +type Signature struct { + KeyID string + Sig []byte + CertPEM []byte +} + +type Signer interface { + Sign(ctx context.Context, payloadType string, payload []byte) (Signature, error) + Verifier(ctx context.Context, cfg BackendConfig) (Verifier, error) +} + +type Verifier interface { + Verify(ctx context.Context, payloadType string, payload, signature []byte) error + KeyID() string +} + +type BackendConfig struct { + Mode string + SignerRef string + VerifierRef string + Env map[string]string + FulcioURL string + RekorURL string + UploadTlog bool + TrustedRootPath string + KeylessIdentity string + KeylessOIDCIssuer string +} + +type signerFactory func(context.Context, BackendConfig) (Signer, error) + +var signerFactories = map[string]signerFactory{} + +func RegisterSigner(mode string, factory signerFactory) { + signerFactories[mode] = factory +} + +func NewSigner(ctx context.Context, cfg BackendConfig) (Signer, error) { + factory, ok := signerFactories[cfg.Mode] + if !ok { + return nil, fmt.Errorf("signing_mode %q is not implemented", cfg.Mode) + } + + return factory(ctx, cfg) +} + +func NewVerifier(ctx context.Context, cfg BackendConfig, signer Signer) (Verifier, error) { + if cfg.VerifierRef != "" { + return LoadPEMVerifier(cfg.VerifierRef) + } + + return signer.Verifier(ctx, cfg) +} + +func VerifyEnvelope(ctx context.Context, envelope Envelope, verifier Verifier) error { + if len(envelope.Signatures) == 0 { + return fmt.Errorf("envelope has no signatures") + } + + payload, err := DecodeEnvelopePayload(envelope) + if err != nil { + return err + } + + for _, signature := range envelope.Signatures { + decodedSignature, decodeErr := DecodeEnvelopeSignature(signature) + if decodeErr != nil { + return decodeErr + } + + if verifyErr := verifier.Verify(ctx, envelope.PayloadType, payload, decodedSignature); verifyErr == nil { + return nil + } + } + + return fmt.Errorf("signature verification failed") +} diff --git a/internal/attestation/verify.go b/internal/attestation/verify.go new file mode 100644 index 000000000..d7e6086d5 --- /dev/null +++ b/internal/attestation/verify.go @@ -0,0 +1,412 @@ +// Copyright IBM Corp. 2024, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +package attestation + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + internalprovenance "github.com/hashicorp/packer/internal/provenance" + sigstorebundle "github.com/sigstore/sigstore-go/pkg/bundle" + sigstoreverify "github.com/sigstore/sigstore-go/pkg/verify" +) + +type VerificationPolicy struct { + PredicateType string + BuilderID string + SourceURI string + ArtifactPath string + SigstoreBundlePath string + RequireTransparencyLog bool + RequireObserverTimestamp bool +} + +var loadSigstoreBundle = sigstorebundle.LoadJSONFromPath + +var newSigstoreBundleVerifier = sigstoreverify.NewVerifier + +var verifySigstoreBundleEvidence = func(envelope Envelope, cfg BackendConfig, policy VerificationPolicy) error { + return verifySigstoreBundleEvidenceImpl(envelope, cfg, policy) +} + +func VerifyAttestationFile(ctx context.Context, path string, cfg BackendConfig, policy VerificationPolicy) (*internalprovenance.Statement, error) { + contents, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("read attestation %q: %w", path, err) + } + + var envelope Envelope + if err := json.Unmarshal(contents, &envelope); err != nil { + return nil, fmt.Errorf("decode attestation envelope %q: %w", path, err) + } + + if envelope.PayloadType != InTotoPayloadType { + return nil, fmt.Errorf("attestation %q has unexpected payloadType %q (want %q)", + path, envelope.PayloadType, InTotoPayloadType) + } + + if err := verifyEnvelopeSignature(ctx, path, cfg, policy, envelope); err != nil { + return nil, err + } + + payload, err := DecodeEnvelopePayload(envelope) + if err != nil { + return nil, err + } + + statement, err := verifyPolicy(payload, policy) + if err != nil { + return nil, err + } + + return statement, nil +} + +func verifyEnvelopeSignature(ctx context.Context, path string, cfg BackendConfig, policy VerificationPolicy, envelope Envelope) error { + // An explicit Rekor or timestamp policy always uses bundle-based verification. + if requiresSigstoreBundle(policy) { + return verifySigstoreBundleEvidence(envelope, cfg, policy) + } + + // Keyless attestations are signed with a short-lived Fulcio certificate that + // appears expired against wall-clock time moments after signing. When a + // Sigstore bundle carrying transparency-log or timestamp evidence is + // available, prefer it so the certificate is validated as of the signing + // time recorded in that evidence rather than the current time. + if envelopeHasCertificate(envelope) { + if bundlePath := resolveSigstoreBundlePath(policy, path); bundlePath != "" && bundleAnchorsSigningTime(bundlePath) { + policy.SigstoreBundlePath = bundlePath + return verifySigstoreBundleEvidence(envelope, cfg, policy) + } + } + + verifier, err := verifierForEnvelope(ctx, cfg, envelope) + if err != nil { + return err + } + + if err := VerifyEnvelope(ctx, envelope, verifier); err != nil { + return fmt.Errorf("verify attestation envelope %q: %w", path, err) + } + + return nil +} + +// resolveSigstoreBundlePath returns an explicitly configured bundle path, or the +// conventional ".sigstore.json" sidecar written alongside signed +// attestations when it exists on disk. +func resolveSigstoreBundlePath(policy VerificationPolicy, attestationPath string) string { + if trimmed := strings.TrimSpace(policy.SigstoreBundlePath); trimmed != "" { + return trimmed + } + + candidate := defaultSigstoreBundlePath(attestationPath) + if info, err := os.Stat(candidate); err == nil && !info.IsDir() { + return candidate + } + + return "" +} + +func defaultSigstoreBundlePath(attestationPath string) string { + if strings.HasSuffix(attestationPath, ".json") { + return strings.TrimSuffix(attestationPath, ".json") + ".sigstore.json" + } + + return attestationPath + ".sigstore.json" +} + +// bundleAnchorsSigningTime reports whether the bundle carries a trusted time +// source (a transparency-log entry or an RFC3161 timestamp) that can validate +// the signing certificate as of signing time. +var bundleAnchorsSigningTime = func(bundlePath string) bool { + bundle, err := loadSigstoreBundle(bundlePath) + if err != nil { + return false + } + + if entries, err := bundle.TlogEntries(); err == nil && len(entries) > 0 { + return true + } + + if timestamps, err := bundle.Timestamps(); err == nil && len(timestamps) > 0 { + return true + } + + return false +} + +func verifierForEnvelope(ctx context.Context, cfg BackendConfig, envelope Envelope) (Verifier, error) { + mode := normalizeVerificationMode(cfg, envelope) + + if cfg.VerifierRef != "" { + if mode == SigningModeKeyless || envelopeHasCertificate(envelope) { + return nil, fmt.Errorf("verifier overrides are not supported for keyless attestations; verify with keyless_identity and keyless_oidc_issuer instead") + } + return LoadPEMVerifier(cfg.VerifierRef) + } + + switch mode { + case SigningModeKey: + if cfg.SignerRef == "" { + return nil, fmt.Errorf("attestation verification for signing_mode %q requires verifier or key", SigningModeKey) + } + return LoadPEMVerifier(cfg.SignerRef) + case SigningModeKMS: + if cfg.SignerRef == "" { + return nil, fmt.Errorf("attestation verification for signing_mode %q requires key or verifier", SigningModeKMS) + } + signer, err := NewSigner(ctx, cfg) + if err != nil { + return nil, err + } + return signer.Verifier(ctx, cfg) + case SigningModeKeyless: + return newKeylessVerifierForEnvelope(cfg, envelope) + default: + return nil, fmt.Errorf("unable to determine attestation signing mode; set signing_mode or verifier explicitly") + } +} + +func normalizeVerificationMode(cfg BackendConfig, envelope Envelope) string { + if cfg.Mode != "" { + return cfg.Mode + } + + if envelopeHasCertificate(envelope) { + return SigningModeKeyless + } + + if isRecognizedKMSReference(cfg.SignerRef) { + return SigningModeKMS + } + + if cfg.SignerRef != "" || cfg.VerifierRef != "" { + return SigningModeKey + } + + return "" +} + +func envelopeHasCertificate(envelope Envelope) bool { + for _, signature := range envelope.Signatures { + if strings.TrimSpace(signature.Cert) != "" { + return true + } + } + + return false +} + +func isRecognizedKMSReference(value string) bool { + for _, prefix := range []string{"awskms://", "gcpkms://", "azurekms://", "hashivault://"} { + if strings.HasPrefix(value, prefix) { + return true + } + } + + return false +} + +func verifyPolicy(payload []byte, policy VerificationPolicy) (*internalprovenance.Statement, error) { + var statement internalprovenance.Statement + if err := json.Unmarshal(payload, &statement); err != nil { + return nil, fmt.Errorf("decode attestation statement: %w", err) + } + + if statement.Type != internalprovenance.StatementType { + return nil, fmt.Errorf("unexpected attestation statement type %q", statement.Type) + } + + if policy.PredicateType != "" && statement.PredicateType != policy.PredicateType { + return nil, fmt.Errorf("attestation predicate type %q does not match expected %q", statement.PredicateType, policy.PredicateType) + } + + if policy.ArtifactPath != "" { + if err := verifyArtifactSubject(statement.Subject, policy.ArtifactPath); err != nil { + return nil, err + } + } + + if policy.BuilderID != "" || policy.SourceURI != "" { + if statement.PredicateType != internalprovenance.SLSAProvenanceV1PredicateType { + return nil, fmt.Errorf("builder and source policy checks require predicate type %q, got %q", internalprovenance.SLSAProvenanceV1PredicateType, statement.PredicateType) + } + + var typedStatement struct { + Type string `json:"_type"` + Subject []internalprovenance.Subject `json:"subject"` + PredicateType string `json:"predicateType"` + Predicate internalprovenance.SLSAProvenancePredicate `json:"predicate"` + } + if err := json.Unmarshal(payload, &typedStatement); err != nil { + return nil, fmt.Errorf("decode SLSA predicate for policy verification: %w", err) + } + + if policy.BuilderID != "" && typedStatement.Predicate.RunDetails.Builder.ID != policy.BuilderID { + return nil, fmt.Errorf("attestation builder id %q does not match expected %q", typedStatement.Predicate.RunDetails.Builder.ID, policy.BuilderID) + } + + if policy.SourceURI != "" { + matched := false + for _, dependency := range typedStatement.Predicate.BuildDefinition.ResolvedDependencies { + if dependency.URI == policy.SourceURI { + matched = true + break + } + } + if !matched { + return nil, fmt.Errorf("attestation does not contain expected source URI %q", policy.SourceURI) + } + } + } + + return &statement, nil +} + +func requiresSigstoreBundle(policy VerificationPolicy) bool { + return policy.RequireTransparencyLog || policy.RequireObserverTimestamp +} + +func verifySigstoreBundleEvidenceImpl(envelope Envelope, cfg BackendConfig, policy VerificationPolicy) error { + if strings.TrimSpace(policy.SigstoreBundlePath) == "" { + return fmt.Errorf("bundle-based Rekor or timestamp verification requires -bundle") + } + + if normalizeVerificationMode(cfg, envelope) != SigningModeKeyless && !envelopeHasCertificate(envelope) { + return fmt.Errorf("bundle-based Rekor or timestamp verification currently requires a keyless attestation") + } + + if strings.TrimSpace(cfg.KeylessIdentity) == "" || strings.TrimSpace(cfg.KeylessOIDCIssuer) == "" { + return fmt.Errorf("bundle-based Rekor or timestamp verification requires keyless_identity and keyless_oidc_issuer") + } + + trustedMaterial, err := loadKeylessTrustedMaterial(cfg) + if err != nil { + return fmt.Errorf("load keyless trusted root: %w", err) + } + + bundle, err := loadSigstoreBundle(policy.SigstoreBundlePath) + if err != nil { + return fmt.Errorf("load Sigstore bundle %q: %w", policy.SigstoreBundlePath, err) + } + + if err := ensureBundleMatchesEnvelope(bundle, envelope); err != nil { + return err + } + + verifierOptions := []sigstoreverify.VerifierOption{} + if policy.RequireTransparencyLog { + verifierOptions = append(verifierOptions, sigstoreverify.WithTransparencyLog(1)) + } + if policy.RequireObserverTimestamp { + verifierOptions = append(verifierOptions, sigstoreverify.WithObserverTimestamps(1)) + } + if len(verifierOptions) == 0 { + // A trusted time source is required to validate the short-lived Fulcio + // certificate as of signing time; default to observer timestamps when the + // caller has not explicitly required Rekor or timestamp evidence. + verifierOptions = append(verifierOptions, sigstoreverify.WithObserverTimestamps(1)) + } + + verifier, err := newSigstoreBundleVerifier(trustedMaterial, verifierOptions...) + if err != nil { + return fmt.Errorf("create Sigstore bundle verifier: %w", err) + } + + artifactPolicy := sigstoreverify.WithoutArtifactUnsafe() + if policy.ArtifactPath != "" { + artifact, err := os.Open(policy.ArtifactPath) + if err != nil { + return fmt.Errorf("open artifact %q for bundle verification: %w", policy.ArtifactPath, err) + } + defer func() { _ = artifact.Close() }() + artifactPolicy = sigstoreverify.WithArtifact(artifact) + } + + identity, err := sigstoreverify.NewShortCertificateIdentity(cfg.KeylessOIDCIssuer, "", cfg.KeylessIdentity, "") + if err != nil { + return fmt.Errorf("build keyless identity policy: %w", err) + } + + policyBuilder := sigstoreverify.NewPolicy(artifactPolicy, sigstoreverify.WithCertificateIdentity(identity)) + if _, err := verifier.Verify(bundle, policyBuilder); err != nil { + return fmt.Errorf("verify Sigstore bundle %q: %w", policy.SigstoreBundlePath, err) + } + + return nil +} + +func ensureBundleMatchesEnvelope(bundle *sigstorebundle.Bundle, envelope Envelope) error { + bundleEnvelope, err := bundle.Envelope() + if err != nil { + return fmt.Errorf("extract DSSE envelope from Sigstore bundle: %w", err) + } + + rawEnvelope := bundleEnvelope.RawEnvelope() + if rawEnvelope == nil { + return fmt.Errorf("sigstore bundle does not contain a DSSE envelope") + } + + if rawEnvelope.PayloadType != envelope.PayloadType || rawEnvelope.Payload != envelope.Payload { + return fmt.Errorf("sigstore bundle payload does not match attestation") + } + + if len(envelope.Signatures) == 0 { + return fmt.Errorf("attestation envelope has no signatures") + } + + bundleSignature := bundleEnvelope.Signature() + for i, envelopeSignature := range envelope.Signatures { + signature, err := DecodeEnvelopeSignature(envelopeSignature) + if err != nil { + return fmt.Errorf("decode attestation envelope signature %d: %w", i, err) + } + + if bytes.Equal(bundleSignature, signature) { + return nil + } + } + + return fmt.Errorf("sigstore bundle signature does not match any attestation signature") +} + +func verifyArtifactSubject(subjects []internalprovenance.Subject, artifactPath string) error { + digest, err := sha256File(artifactPath) + if err != nil { + return fmt.Errorf("hash artifact %q: %w", artifactPath, err) + } + + artifactName := filepath.Base(artifactPath) + for _, subject := range subjects { + if subject.Name == artifactName && strings.EqualFold(subject.Digest["sha256"], digest) { + return nil + } + } + + return fmt.Errorf("attestation subject does not match artifact %q", artifactPath) +} + +func sha256File(path string) (string, error) { + file, err := os.Open(path) + if err != nil { + return "", err + } + defer func() { _ = file.Close() }() + + hasher := sha256.New() + if _, err := io.Copy(hasher, file); err != nil { + return "", err + } + + return hex.EncodeToString(hasher.Sum(nil)), nil +} diff --git a/internal/attestation/verify_autodiscover_test.go b/internal/attestation/verify_autodiscover_test.go new file mode 100644 index 000000000..6cbceeefe --- /dev/null +++ b/internal/attestation/verify_autodiscover_test.go @@ -0,0 +1,175 @@ +// Copyright IBM Corp. 2024, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +package attestation + +import ( + "context" + "encoding/json" + "os" + "path/filepath" + "testing" + + internalprovenance "github.com/hashicorp/packer/internal/provenance" +) + +func TestDefaultSigstoreBundlePath(t *testing.T) { + cases := map[string]string{ + "artifact.provenance.json": "artifact.provenance.sigstore.json", + "dir/attestation.json": "dir/attestation.sigstore.json", + "attestation": "attestation.sigstore.json", + } + + for input, want := range cases { + if got := defaultSigstoreBundlePath(input); got != want { + t.Fatalf("defaultSigstoreBundlePath(%q) = %q, want %q", input, got, want) + } + } +} + +func TestResolveSigstoreBundlePath(t *testing.T) { + dir := t.TempDir() + attestationPath := filepath.Join(dir, "attestation.json") + + // Explicit path always wins. + if got := resolveSigstoreBundlePath(VerificationPolicy{SigstoreBundlePath: "explicit.json"}, attestationPath); got != "explicit.json" { + t.Fatalf("explicit bundle path not honored, got %q", got) + } + + // No sidecar on disk means no auto-discovered path. + if got := resolveSigstoreBundlePath(VerificationPolicy{}, attestationPath); got != "" { + t.Fatalf("expected empty path when sidecar is absent, got %q", got) + } + + // A sidecar next to the attestation is auto-discovered. + sidecarPath := defaultSigstoreBundlePath(attestationPath) + if err := os.WriteFile(sidecarPath, []byte("{}"), 0600); err != nil { + t.Fatalf("write sidecar: %v", err) + } + if got := resolveSigstoreBundlePath(VerificationPolicy{}, attestationPath); got != sidecarPath { + t.Fatalf("expected auto-discovered sidecar %q, got %q", sidecarPath, got) + } +} + +func TestVerifyAttestationFileAutoDiscoversKeylessBundle(t *testing.T) { + originalAnchor := bundleAnchorsSigningTime + originalBundleVerifier := verifySigstoreBundleEvidence + t.Cleanup(func() { + bundleAnchorsSigningTime = originalAnchor + verifySigstoreBundleEvidence = originalBundleVerifier + }) + + dir := t.TempDir() + attestationPath := filepath.Join(dir, "attestation.json") + sidecarPath := defaultSigstoreBundlePath(attestationPath) + + statement := internalprovenance.WrapInToto( + []internalprovenance.Subject{{Name: "artifact.txt", Digest: internalprovenance.DigestSet{"sha256": "abc"}}}, + internalprovenance.SLSAProvenanceV1PredicateType, + internalprovenance.BuildSLSAPredicate(internalprovenance.PredicateInput{}), + ) + payload, err := MarshalPayload(statement) + if err != nil { + t.Fatalf("marshal statement: %v", err) + } + + envelope := NewEnvelope(InTotoPayloadType, payload, Signature{ + Sig: []byte("signature"), + CertPEM: []byte("-----BEGIN CERTIFICATE-----\nZmFrZQ==\n-----END CERTIFICATE-----\n"), + }) + envelopeJSON, err := json.Marshal(envelope) + if err != nil { + t.Fatalf("marshal envelope: %v", err) + } + if err := os.WriteFile(attestationPath, envelopeJSON, 0600); err != nil { + t.Fatalf("write attestation: %v", err) + } + if err := os.WriteFile(sidecarPath, []byte("{}"), 0600); err != nil { + t.Fatalf("write sidecar bundle: %v", err) + } + + bundleAnchorsSigningTime = func(path string) bool { + if path != sidecarPath { + t.Fatalf("unexpected bundle path passed to anchor check %q, want %q", path, sidecarPath) + } + return true + } + + called := false + verifySigstoreBundleEvidence = func(_ Envelope, _ BackendConfig, policy VerificationPolicy) error { + called = true + if policy.SigstoreBundlePath != sidecarPath { + t.Fatalf("bundle verification used path %q, want auto-discovered %q", policy.SigstoreBundlePath, sidecarPath) + } + return nil + } + + if _, err := VerifyAttestationFile(context.Background(), attestationPath, BackendConfig{ + Mode: SigningModeKeyless, + KeylessIdentity: "identity", + KeylessOIDCIssuer: "issuer", + }, VerificationPolicy{}); err != nil { + t.Fatalf("verify attestation file: %v", err) + } + + if !called { + t.Fatalf("expected auto-discovered bundle verification to run") + } +} + +func TestVerifyAttestationFileSkipsBundleWithoutTimeAnchor(t *testing.T) { + originalAnchor := bundleAnchorsSigningTime + originalBundleVerifier := verifySigstoreBundleEvidence + t.Cleanup(func() { + bundleAnchorsSigningTime = originalAnchor + verifySigstoreBundleEvidence = originalBundleVerifier + }) + + dir := t.TempDir() + attestationPath := filepath.Join(dir, "attestation.json") + sidecarPath := defaultSigstoreBundlePath(attestationPath) + + statement := internalprovenance.WrapInToto( + []internalprovenance.Subject{{Name: "artifact.txt", Digest: internalprovenance.DigestSet{"sha256": "abc"}}}, + internalprovenance.SLSAProvenanceV1PredicateType, + internalprovenance.BuildSLSAPredicate(internalprovenance.PredicateInput{}), + ) + payload, err := MarshalPayload(statement) + if err != nil { + t.Fatalf("marshal statement: %v", err) + } + + envelope := NewEnvelope(InTotoPayloadType, payload, Signature{ + Sig: []byte("signature"), + CertPEM: []byte("-----BEGIN CERTIFICATE-----\nZmFrZQ==\n-----END CERTIFICATE-----\n"), + }) + envelopeJSON, err := json.Marshal(envelope) + if err != nil { + t.Fatalf("marshal envelope: %v", err) + } + if err := os.WriteFile(attestationPath, envelopeJSON, 0600); err != nil { + t.Fatalf("write attestation: %v", err) + } + if err := os.WriteFile(sidecarPath, []byte("{}"), 0600); err != nil { + t.Fatalf("write sidecar bundle: %v", err) + } + + // The sidecar exists but carries no trusted time source, so bundle-based + // verification must not be used; the legacy certificate path runs instead. + bundleAnchorsSigningTime = func(string) bool { return false } + verifySigstoreBundleEvidence = func(Envelope, BackendConfig, VerificationPolicy) error { + t.Fatalf("bundle verification must not run when the bundle lacks a time anchor") + return nil + } + + // The fallback path attempts certificate-based verification with a fake + // certificate, which fails; the important assertion is that bundle + // verification was not selected. + if _, err := VerifyAttestationFile(context.Background(), attestationPath, BackendConfig{ + Mode: SigningModeKeyless, + KeylessIdentity: "identity", + KeylessOIDCIssuer: "issuer", + }, VerificationPolicy{}); err == nil { + t.Fatalf("expected fallback certificate verification to fail on the fake certificate") + } +} diff --git a/internal/attestation/verify_test.go b/internal/attestation/verify_test.go new file mode 100644 index 000000000..1488d9eab --- /dev/null +++ b/internal/attestation/verify_test.go @@ -0,0 +1,95 @@ +// Copyright IBM Corp. 2024, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +package attestation + +import ( + "encoding/base64" + "strings" + "testing" + + protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1" + protodsse "github.com/sigstore/protobuf-specs/gen/pb-go/dsse" + sigstorebundle "github.com/sigstore/sigstore-go/pkg/bundle" +) + +// newDSSEBundle builds a minimal Sigstore bundle wrapping a DSSE envelope with +// the provided raw payload and signature. It bypasses bundle.NewBundle because +// that constructor requires full verification material; ensureBundleMatchesEnvelope +// only inspects the DSSE content. +func newDSSEBundle(payload, signature []byte) *sigstorebundle.Bundle { + return &sigstorebundle.Bundle{ + Bundle: &protobundle.Bundle{ + Content: &protobundle.Bundle_DsseEnvelope{ + DsseEnvelope: &protodsse.Envelope{ + Payload: payload, + PayloadType: InTotoPayloadType, + Signatures: []*protodsse.Signature{{Sig: signature}}, + }, + }, + }, + } +} + +func TestEnsureBundleMatchesEnvelopeMatchesNonFirstSignature(t *testing.T) { + payload := []byte(`{"hello":"world"}`) + bundleSignature := []byte("the-real-signature") + + bundle := newDSSEBundle(payload, bundleSignature) + + // The bundle signature is the second envelope signature, not the first. + envelope := Envelope{ + PayloadType: InTotoPayloadType, + Payload: base64.StdEncoding.EncodeToString(payload), + Signatures: []EnvelopeSignature{ + {Sig: base64.StdEncoding.EncodeToString([]byte("a-different-signature"))}, + {Sig: base64.StdEncoding.EncodeToString(bundleSignature)}, + }, + } + + if err := ensureBundleMatchesEnvelope(bundle, envelope); err != nil { + t.Fatalf("expected bundle to match a later envelope signature, got error: %v", err) + } +} + +func TestEnsureBundleMatchesEnvelopeRejectsWhenNoSignatureMatches(t *testing.T) { + payload := []byte(`{"hello":"world"}`) + bundle := newDSSEBundle(payload, []byte("the-real-signature")) + + envelope := Envelope{ + PayloadType: InTotoPayloadType, + Payload: base64.StdEncoding.EncodeToString(payload), + Signatures: []EnvelopeSignature{ + {Sig: base64.StdEncoding.EncodeToString([]byte("a-different-signature"))}, + {Sig: base64.StdEncoding.EncodeToString([]byte("another-mismatch"))}, + }, + } + + err := ensureBundleMatchesEnvelope(bundle, envelope) + if err == nil { + t.Fatalf("expected mismatch error when no envelope signature matches the bundle") + } + if !strings.Contains(err.Error(), "does not match any attestation signature") { + t.Fatalf("unexpected error: %v", err) + } +} + +func TestEnsureBundleMatchesEnvelopeRejectsPayloadMismatch(t *testing.T) { + bundle := newDSSEBundle([]byte(`{"hello":"world"}`), []byte("sig")) + + envelope := Envelope{ + PayloadType: InTotoPayloadType, + Payload: base64.StdEncoding.EncodeToString([]byte(`{"hello":"tampered"}`)), + Signatures: []EnvelopeSignature{ + {Sig: base64.StdEncoding.EncodeToString([]byte("sig"))}, + }, + } + + err := ensureBundleMatchesEnvelope(bundle, envelope) + if err == nil { + t.Fatalf("expected payload mismatch to be rejected") + } + if !strings.Contains(err.Error(), "payload does not match") { + t.Fatalf("unexpected error: %v", err) + } +} diff --git a/internal/provenance/gitinfo.go b/internal/provenance/gitinfo.go new file mode 100644 index 000000000..7fb8d85fa --- /dev/null +++ b/internal/provenance/gitinfo.go @@ -0,0 +1,175 @@ +// Copyright IBM Corp. 2024, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +package provenance + +import ( + "fmt" + "net/url" + "os/exec" + "path/filepath" + "strings" +) + +type gitRunner func(workingDir string, args ...string) (string, error) + +func DetectBuilderID(env map[string]string) string { + for _, key := range []string{"GITHUB_WORKFLOW_REF", "CI_JOB_URL", "CI_PIPELINE_URL", "BUILD_URL"} { + if value := strings.TrimSpace(env[key]); value != "" { + return value + } + } + + return DefaultLocalBuilderID +} + +func DetectInvocationID(env map[string]string) string { + for _, key := range []string{"GITHUB_RUN_ID", "CI_PIPELINE_ID", "CI_JOB_ID", "BUILD_ID"} { + if value := strings.TrimSpace(env[key]); value != "" { + return value + } + } + + return "" +} + +func DetectGitDependency(workingDir string, env map[string]string) (ResolvedDependency, bool) { + return detectGitDependency(workingDir, env, runGitCommand) +} + +func detectGitDependency(workingDir string, env map[string]string, runner gitRunner) (ResolvedDependency, bool) { + if dependency, ok := detectGitHubDependency(env); ok { + return dependency, true + } + + if dependency, ok := detectGitLabDependency(env); ok { + return dependency, true + } + + return detectLocalGitDependency(workingDir, runner) +} + +func detectGitHubDependency(env map[string]string) (ResolvedDependency, bool) { + repository := strings.TrimSpace(env["GITHUB_REPOSITORY"]) + commit := strings.TrimSpace(env["GITHUB_SHA"]) + if repository == "" || commit == "" { + return ResolvedDependency{}, false + } + + serverURL := strings.TrimRight(strings.TrimSpace(env["GITHUB_SERVER_URL"]), "/") + if serverURL == "" { + serverURL = "https://github.com" + } + + uri := fmt.Sprintf("git+%s/%s", serverURL, strings.TrimLeft(repository, "/")) + if ref := strings.TrimSpace(env["GITHUB_REF"]); ref != "" { + uri += "@" + ref + } + + return ResolvedDependency{ + URI: uri, + Digest: DigestSet{ + "gitCommit": commit, + }, + }, true +} + +func detectGitLabDependency(env map[string]string) (ResolvedDependency, bool) { + projectURL := strings.TrimSpace(env["CI_PROJECT_URL"]) + commit := strings.TrimSpace(env["CI_COMMIT_SHA"]) + if projectURL == "" || commit == "" { + return ResolvedDependency{}, false + } + + uri := "git+" + strings.TrimRight(projectURL, "/") + if ref := strings.TrimSpace(env["CI_COMMIT_REF_NAME"]); ref != "" { + uri += "@refs/heads/" + ref + } + + return ResolvedDependency{ + URI: uri, + Digest: DigestSet{ + "gitCommit": commit, + }, + }, true +} + +func detectLocalGitDependency(workingDir string, runner gitRunner) (ResolvedDependency, bool) { + if runner == nil { + return ResolvedDependency{}, false + } + + commit, err := runner(workingDir, "rev-parse", "HEAD") + if err != nil || commit == "" { + return ResolvedDependency{}, false + } + + repositoryURL, err := runner(workingDir, "config", "--get", "remote.origin.url") + if err != nil || repositoryURL == "" { + topLevel, topLevelErr := runner(workingDir, "rev-parse", "--show-toplevel") + if topLevelErr != nil || topLevel == "" { + repositoryURL = "" + } else { + repositoryURL = localGitFileURI(topLevel) + } + } + + if repositoryURL == "" { + return ResolvedDependency{}, false + } + + repositoryURL = sanitizeGitRemoteURL(repositoryURL) + + uri := "git+" + repositoryURL + if branch, branchErr := runner(workingDir, "rev-parse", "--abbrev-ref", "HEAD"); branchErr == nil && branch != "" && branch != "HEAD" { + uri += "@refs/heads/" + branch + } + + return ResolvedDependency{ + URI: uri, + Digest: DigestSet{ + "gitCommit": commit, + }, + }, true +} + +func sanitizeGitRemoteURL(raw string) string { + // SCP-style remotes (git@host:repo) carry no userinfo — leave them unchanged + if !strings.Contains(raw, "://") { + return raw + } + u, err := url.Parse(raw) + if err != nil { + return raw + } + u.User = nil // strips embedded username and password/token + return u.String() +} + +func localGitFileURI(path string) string { + cleaned := filepath.Clean(path) + if cleaned == "" { + return "" + } + + slashed := filepath.ToSlash(cleaned) + if !strings.HasPrefix(slashed, "/") { + slashed = "/" + slashed + } + + return (&url.URL{Scheme: "file", Path: slashed}).String() +} + +func runGitCommand(workingDir string, args ...string) (string, error) { + command := exec.Command("git", args...) + if workingDir != "" { + command.Dir = workingDir + } + + output, err := command.Output() + if err != nil { + return "", err + } + + return strings.TrimSpace(string(output)), nil +} diff --git a/internal/provenance/gitinfo_test.go b/internal/provenance/gitinfo_test.go new file mode 100644 index 000000000..8daf831c1 --- /dev/null +++ b/internal/provenance/gitinfo_test.go @@ -0,0 +1,85 @@ +// Copyright IBM Corp. 2024, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +package provenance + +import ( + "fmt" + "strings" + "testing" +) + +func TestDetectBuilderID(t *testing.T) { + env := map[string]string{"GITHUB_WORKFLOW_REF": "acme/images/.github/workflows/build.yml@refs/heads/main"} + if got, want := DetectBuilderID(env), env["GITHUB_WORKFLOW_REF"]; got != want { + t.Fatalf("unexpected builder id %q, want %q", got, want) + } + + if got, want := DetectBuilderID(map[string]string{}), DefaultLocalBuilderID; got != want { + t.Fatalf("unexpected default builder id %q, want %q", got, want) + } +} + +func TestDetectInvocationID(t *testing.T) { + env := map[string]string{"CI_PIPELINE_ID": "pipeline-42"} + if got, want := DetectInvocationID(env), "pipeline-42"; got != want { + t.Fatalf("unexpected invocation id %q, want %q", got, want) + } +} + +func TestDetectGitDependencyFromGitHubEnv(t *testing.T) { + dependency, ok := detectGitDependency("", map[string]string{ + "GITHUB_REPOSITORY": "acme/images", + "GITHUB_SHA": "deadbeef", + "GITHUB_REF": "refs/heads/main", + }, nil) + if !ok { + t.Fatalf("expected github dependency to be detected") + } + + if got, want := dependency.URI, "git+https://github.com/acme/images@refs/heads/main"; got != want { + t.Fatalf("unexpected uri %q, want %q", got, want) + } + if got, want := dependency.Digest["gitCommit"], "deadbeef"; got != want { + t.Fatalf("unexpected commit %q, want %q", got, want) + } +} + +func TestDetectGitDependencyFallsBackToLocalGit(t *testing.T) { + runner := func(_ string, args ...string) (string, error) { + switch strings.Join(args, " ") { + case "rev-parse HEAD": + return "cafebabe", nil + case "config --get remote.origin.url": + return "", fmt.Errorf("missing remote") + case "rev-parse --show-toplevel": + return "/workspace/packer", nil + case "rev-parse --abbrev-ref HEAD": + return "main", nil + default: + return "", fmt.Errorf("unexpected git args %q", strings.Join(args, " ")) + } + } + + dependency, ok := detectGitDependency("/workspace/packer", map[string]string{}, runner) + if !ok { + t.Fatalf("expected local git dependency to be detected") + } + + if got, want := dependency.URI, "git+file:///workspace/packer@refs/heads/main"; got != want { + t.Fatalf("unexpected uri %q, want %q", got, want) + } + if got, want := dependency.Digest["gitCommit"], "cafebabe"; got != want { + t.Fatalf("unexpected commit %q, want %q", got, want) + } +} + +func TestDetectGitDependencyGracefullySkipsWhenUnavailable(t *testing.T) { + runner := func(_ string, _ ...string) (string, error) { + return "", fmt.Errorf("git unavailable") + } + + if _, ok := detectGitDependency("/workspace/packer", map[string]string{}, runner); ok { + t.Fatalf("expected dependency detection to skip unavailable git context") + } +} diff --git a/internal/provenance/predicate.go b/internal/provenance/predicate.go new file mode 100644 index 000000000..939f9b4fa --- /dev/null +++ b/internal/provenance/predicate.go @@ -0,0 +1,115 @@ +// Copyright IBM Corp. 2024, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +package provenance + +import packerversion "github.com/hashicorp/packer/version" + +const ( + DefaultBuildType = "https://packer.io/buildtypes/hcl2/v1" + DefaultLocalBuilderID = "https://packer.io/local-build" + SLSAProvenanceV1PredicateType = "https://slsa.dev/provenance/v1" +) + +type PredicateInput struct { + BuildType string + ExternalParameters map[string]interface{} + InternalParameters map[string]interface{} + ResolvedDependencies []ResolvedDependency + BuilderID string + Byproducts []Byproduct + InvocationID string + StartedOn string + FinishedOn string +} + +type SLSAProvenancePredicate struct { + BuildDefinition BuildDefinition `json:"buildDefinition"` + RunDetails RunDetails `json:"runDetails"` +} + +type BuildDefinition struct { + BuildType string `json:"buildType"` + ExternalParameters map[string]interface{} `json:"externalParameters"` + InternalParameters map[string]interface{} `json:"internalParameters,omitempty"` + ResolvedDependencies []ResolvedDependency `json:"resolvedDependencies,omitempty"` +} + +type ResolvedDependency struct { + URI string `json:"uri"` + Digest DigestSet `json:"digest,omitempty"` +} + +type RunDetails struct { + Builder Builder `json:"builder"` + Metadata Metadata `json:"metadata,omitempty"` + Byproducts []Byproduct `json:"byproducts,omitempty"` +} + +type Builder struct { + ID string `json:"id"` + Version map[string]string `json:"version,omitempty"` +} + +type Metadata struct { + InvocationID string `json:"invocationId,omitempty"` + StartedOn string `json:"startedOn,omitempty"` + FinishedOn string `json:"finishedOn,omitempty"` +} + +type Byproduct struct { + Name string `json:"name"` + Content interface{} `json:"content,omitempty"` +} + +func BuildSLSAPredicate(input PredicateInput) SLSAProvenancePredicate { + buildType := input.BuildType + if buildType == "" { + buildType = DefaultBuildType + } + + builderID := input.BuilderID + if builderID == "" { + builderID = DefaultLocalBuilderID + } + + externalParameters := map[string]interface{}{} + for key, value := range input.ExternalParameters { + externalParameters[key] = value + } + + internalParameters := map[string]interface{}{ + "packerVersion": packerversion.String(), + } + for key, value := range input.InternalParameters { + internalParameters[key] = value + } + + predicate := SLSAProvenancePredicate{ + BuildDefinition: BuildDefinition{ + BuildType: buildType, + ExternalParameters: externalParameters, + InternalParameters: internalParameters, + ResolvedDependencies: input.ResolvedDependencies, + }, + RunDetails: RunDetails{ + Builder: Builder{ + ID: builderID, + Version: map[string]string{ + "packer": packerversion.String(), + }, + }, + Byproducts: input.Byproducts, + }, + } + + if input.InvocationID != "" || input.StartedOn != "" || input.FinishedOn != "" { + predicate.RunDetails.Metadata = Metadata{ + InvocationID: input.InvocationID, + StartedOn: input.StartedOn, + FinishedOn: input.FinishedOn, + } + } + + return predicate +} diff --git a/internal/provenance/predicate_test.go b/internal/provenance/predicate_test.go new file mode 100644 index 000000000..e4629b0d2 --- /dev/null +++ b/internal/provenance/predicate_test.go @@ -0,0 +1,49 @@ +// Copyright IBM Corp. 2024, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +package provenance + +import "testing" + +func TestBuildSLSAPredicateDefaults(t *testing.T) { + predicate := BuildSLSAPredicate(PredicateInput{}) + + if got, want := predicate.BuildDefinition.BuildType, DefaultBuildType; got != want { + t.Fatalf("unexpected build type %q, want %q", got, want) + } + + if got, want := predicate.RunDetails.Builder.ID, DefaultLocalBuilderID; got != want { + t.Fatalf("unexpected builder id %q, want %q", got, want) + } + + if predicate.BuildDefinition.InternalParameters["packerVersion"] == "" { + t.Fatalf("expected packerVersion to be populated") + } + + if predicate.RunDetails.Builder.Version["packer"] == "" { + t.Fatalf("expected builder version to be populated") + } +} + +func TestBuildSLSAPredicateIncludesByproducts(t *testing.T) { + predicate := BuildSLSAPredicate(PredicateInput{ + BuildType: "https://packer.io/buildtypes/json/v1", + Byproducts: []Byproduct{{ + Name: "cloud-artifact-identity", + Content: map[string]interface{}{ + "builderId": "packer.null", + }, + }}, + }) + + if got, want := len(predicate.RunDetails.Byproducts), 1; got != want { + t.Fatalf("unexpected byproduct count %d, want %d", got, want) + } + + if got, want := predicate.BuildDefinition.BuildType, "https://packer.io/buildtypes/json/v1"; got != want { + t.Fatalf("unexpected build type %q, want %q", got, want) + } + if got, want := predicate.RunDetails.Byproducts[0].Name, "cloud-artifact-identity"; got != want { + t.Fatalf("unexpected byproduct name %q, want %q", got, want) + } +} diff --git a/internal/provenance/statement.go b/internal/provenance/statement.go new file mode 100644 index 000000000..af3c01565 --- /dev/null +++ b/internal/provenance/statement.go @@ -0,0 +1,22 @@ +// Copyright IBM Corp. 2024, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +package provenance + +const StatementType = "https://in-toto.io/Statement/v1" + +type Statement struct { + Type string `json:"_type"` + Subject []Subject `json:"subject"` + PredicateType string `json:"predicateType"` + Predicate interface{} `json:"predicate"` +} + +func WrapInToto(subjects []Subject, predicateType string, predicate interface{}) Statement { + return Statement{ + Type: StatementType, + Subject: subjects, + PredicateType: predicateType, + Predicate: predicate, + } +} diff --git a/internal/provenance/statement_test.go b/internal/provenance/statement_test.go new file mode 100644 index 000000000..86462f8bc --- /dev/null +++ b/internal/provenance/statement_test.go @@ -0,0 +1,26 @@ +// Copyright IBM Corp. 2024, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +package provenance + +import "testing" + +func TestWrapInToto(t *testing.T) { + statement := WrapInToto( + []Subject{{Name: "artifact.bin", Digest: DigestSet{"sha256": "abc123"}}}, + SLSAProvenanceV1PredicateType, + BuildSLSAPredicate(PredicateInput{}), + ) + + if got, want := statement.Type, StatementType; got != want { + t.Fatalf("unexpected statement type %q, want %q", got, want) + } + + if got, want := statement.PredicateType, SLSAProvenanceV1PredicateType; got != want { + t.Fatalf("unexpected predicate type %q, want %q", got, want) + } + + if got, want := len(statement.Subject), 1; got != want { + t.Fatalf("unexpected subject count %d, want %d", got, want) + } +} diff --git a/internal/provenance/subject.go b/internal/provenance/subject.go new file mode 100644 index 000000000..28f24ed46 --- /dev/null +++ b/internal/provenance/subject.go @@ -0,0 +1,130 @@ +// Copyright IBM Corp. 2024, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +package provenance + +import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + + packersdk "github.com/hashicorp/packer-plugin-sdk/packer" + registryimage "github.com/hashicorp/packer-plugin-sdk/packer/registry/image" +) + +type DigestSet map[string]string + +type Subject struct { + Name string `json:"name"` + Digest DigestSet `json:"digest"` +} + +func DeriveSubjects(artifact packersdk.Artifact) ([]Subject, error) { + return deriveSubjects(artifact) +} + +func DeriveIdentityRecord(artifact packersdk.Artifact) (map[string]interface{}, error) { + return deriveIdentityRecord(artifact) +} + +func deriveSubjects(artifact packersdk.Artifact) ([]Subject, error) { + if artifact == nil { + return nil, fmt.Errorf("artifact is nil") + } + + files := artifact.Files() + if len(files) > 0 { + subjects := make([]Subject, 0, len(files)) + for _, file := range files { + digest, err := sha256File(file) + if err != nil { + return nil, fmt.Errorf("hash %q: %w", file, err) + } + + subjects = append(subjects, Subject{ + Name: filepath.Base(file), + Digest: DigestSet{ + "sha256": digest, + }, + }) + } + + return subjects, nil + } + + identity, err := deriveIdentityRecord(artifact) + if err != nil { + return nil, err + } + + canonicalIdentity, err := json.Marshal(identity) + if err != nil { + return nil, fmt.Errorf("marshal artifact identity: %w", err) + } + + digest := sha256.Sum256(canonicalIdentity) + + return []Subject{{ + Name: fmt.Sprintf("%s:%s", artifact.BuilderId(), artifact.Id()), + Digest: DigestSet{ + "sha256": hex.EncodeToString(digest[:]), + }, + }}, nil +} + +func deriveIdentityRecord(artifact packersdk.Artifact) (map[string]interface{}, error) { + if artifact == nil { + return nil, fmt.Errorf("artifact is nil") + } + + record := map[string]interface{}{ + "builderId": artifact.BuilderId(), + "id": artifact.Id(), + } + + state := artifact.State(registryimage.ArtifactStateURI) + if state == nil { + return record, nil + } + + normalizedState, err := normalizeJSONValue(state) + if err != nil { + return record, nil + } + + record["state"] = normalizedState + return record, nil +} + +func normalizeJSONValue(value interface{}) (interface{}, error) { + encoded, err := json.Marshal(value) + if err != nil { + return nil, err + } + + var decoded interface{} + if err := json.Unmarshal(encoded, &decoded); err != nil { + return nil, err + } + + return decoded, nil +} + +func sha256File(path string) (string, error) { + file, err := os.Open(path) + if err != nil { + return "", err + } + defer func() { _ = file.Close() }() + + hasher := sha256.New() + if _, err := io.Copy(hasher, file); err != nil { + return "", err + } + + return hex.EncodeToString(hasher.Sum(nil)), nil +} diff --git a/internal/provenance/subject_test.go b/internal/provenance/subject_test.go new file mode 100644 index 000000000..39007709c --- /dev/null +++ b/internal/provenance/subject_test.go @@ -0,0 +1,128 @@ +// Copyright IBM Corp. 2024, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +package provenance + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "path/filepath" + "strings" + "testing" + + packersdk "github.com/hashicorp/packer-plugin-sdk/packer" + registryimage "github.com/hashicorp/packer-plugin-sdk/packer/registry/image" + "github.com/hashicorp/packer-plugin-sdk/template" + filebuilder "github.com/hashicorp/packer/builder/file" + nullbuilder "github.com/hashicorp/packer/builder/null" +) + +func TestDeriveSubjectsFromFiles(t *testing.T) { + artifact := buildFileArtifact(t) + defer func() { _ = artifact.Destroy() }() + + subjects, err := deriveSubjects(artifact) + if err != nil { + t.Fatalf("derive subjects: %v", err) + } + + if len(subjects) != 1 { + t.Fatalf("expected one subject, got %d", len(subjects)) + } + + if got, want := subjects[0].Name, "package.txt"; got != want { + t.Fatalf("unexpected subject name %q, want %q", got, want) + } + + expectedDigest := sha256.Sum256([]byte("Hello world!")) + if got, want := subjects[0].Digest["sha256"], hex.EncodeToString(expectedDigest[:]); got != want { + t.Fatalf("unexpected digest %q, want %q", got, want) + } +} + +func TestDeriveSubjectsFromIdentity(t *testing.T) { + artifact := new(nullbuilder.NullArtifact) + + subjects, err := deriveSubjects(artifact) + if err != nil { + t.Fatalf("derive subjects: %v", err) + } + + if len(subjects) != 1 { + t.Fatalf("expected one subject, got %d", len(subjects)) + } + + if got, want := subjects[0].Name, artifact.BuilderId()+":"+artifact.Id(); got != want { + t.Fatalf("unexpected subject name %q, want %q", got, want) + } + + identity, err := deriveIdentityRecord(artifact) + if err != nil { + t.Fatalf("derive identity: %v", err) + } + + if _, ok := identity["state"]; !ok { + t.Fatalf("expected identity state for cloud-style artifact") + } + + encodedIdentity, err := json.Marshal(identity) + if err != nil { + t.Fatalf("marshal identity: %v", err) + } + + expectedDigest := sha256.Sum256(encodedIdentity) + if got, want := subjects[0].Digest["sha256"], hex.EncodeToString(expectedDigest[:]); got != want { + t.Fatalf("unexpected digest %q, want %q", got, want) + } + + state := artifact.State(registryimage.ArtifactStateURI) + if state == nil { + t.Fatalf("expected registry state") + } +} + +func buildFileArtifact(t *testing.T) packersdk.Artifact { + t.Helper() + + target := filepath.Join(t.TempDir(), "package.txt") + config := mustTemplateJSON(t, map[string]interface{}{ + "builders": []map[string]string{{ + "type": "file", + "target": target, + "content": "Hello world!", + }}, + }) + tpl, err := template.Parse(strings.NewReader(config)) + if err != nil { + t.Fatalf("parse template: %v", err) + } + + var builder filebuilder.Builder + _, warnings, err := builder.Prepare(tpl.Builders["file"].Config) + if err != nil { + t.Fatalf("prepare builder: %v", err) + } + if len(warnings) > 0 { + t.Fatalf("unexpected warnings: %v", warnings) + } + + artifact, err := builder.Run(context.Background(), packersdk.TestUi(t), nil) + if err != nil { + t.Fatalf("run builder: %v", err) + } + + return artifact +} + +func mustTemplateJSON(t *testing.T, value interface{}) string { + t.Helper() + + encoded, err := json.Marshal(value) + if err != nil { + t.Fatalf("marshal template config: %v", err) + } + + return string(encoded) +} diff --git a/post-processor/provenance/README.md b/post-processor/provenance/README.md new file mode 100644 index 000000000..c2741785b --- /dev/null +++ b/post-processor/provenance/README.md @@ -0,0 +1,115 @@ +# Provenance Post-Processor + +The `provenance` post-processor writes in-toto attestations for the incoming +artifact. + +It supports: + +- SLSA provenance statements enriched with source-control and CI metadata. +- SBOM sidecars plus SBOM attestations. +- Optional DSSE signing with a configurable signer and verifier. + +Signing defaults to `none`, which writes the unsigned JSON statement. + +## Source detection + +The provenance statement records the build's source repository as a resolved +dependency. Packer detects this from the Git repository containing the current +working directory (the directory Packer runs in). Set `source_uri` to override +the detected value, for example when the build runs outside the source checkout +or the remote URL should be normalized. + +Signing modes: + +- `key` signs with a PEM private key and verifies with either the signer's + derived public key or an explicit verifier PEM. +- `kms` signs with a KMS or Vault URI such as `awskms://...`, `gcpkms://...`, + `azurekms://...`, or `hashivault://...`. Verification uses the fetched KMS + public key or an explicit verifier PEM. +- `keyless` signs with an ephemeral keypair and a Fulcio-issued certificate. + It requires an ambient OIDC token, such as `SIGSTORE_ID_TOKEN`, or a CI + provider token that can be exchanged for a Sigstore identity. When using the + built-in verifier path, also configure the expected signing identity and OIDC + issuer. Set an optional trusted-root JSON path to pin verification to a + specific Sigstore root; otherwise the public Sigstore trusted root is fetched. + Keyless signing can also emit a Sigstore bundle sidecar and upload to Rekor. + +Example: + +```hcl +post-processor "provenance" { + build_type = "https://packer.io/buildtypes/hcl2/v1" + template = "ubuntu.pkr.hcl" + only_builds = ["qemu.ubuntu"] + user_variables = { + region = "us-east-1" + } + sbom = true +} +``` + +Signed example: + +```hcl +post-processor "provenance" { + signing_mode = "key" + signer = "keys/provenance-signing.pem" + verifier = "keys/provenance-signing.pub.pem" + sbom = true +} +``` + +KMS example: + +```hcl +post-processor "provenance" { + signing_mode = "kms" + signer = "awskms://alias/packer-provenance" + sbom = true +} +``` + +Keyless example: + +```hcl +post-processor "provenance" { + signing_mode = "keyless" + fulcio_url = "https://fulcio.sigstore.dev" + rekor_url = "https://rekor.sigstore.dev" + upload_tlog = true + keyless_identity = "https://github.com/hashicorp/packer/.github/workflows/build.yml@refs/heads/main" + keyless_oidc_issuer = "https://token.actions.githubusercontent.com" + trusted_root_path = "sigstore-trusted-root.json" +} +``` + +With keyless signing, Packer writes an additional `*.sigstore.json` sidecar next +to each signed attestation. When `upload_tlog = true`, that bundle includes +Rekor-backed transparency evidence for `packer verify-attestation -bundle ...`. + +During a build, keyless verification enforces the Fulcio certificate chain and +the configured identity policy. For Rekor-backed verification, run +`packer verify-attestation` with the generated Sigstore bundle and the +`-require-rekor` and/or `-require-timestamp` flags. + +## SLSA levels and CI + +SLSA Build levels are mostly properties of the build platform, not the build +tool. Packer is a tool, so its reach is: + +| SLSA Build level | Requirement | Packer's role | What Packer provides | +|---|---|---|---| +| **L1** | Provenance exists and is distributed | Fully in Packer | Provenance generation | +| **L2** | Provenance signed by a hosted platform | Packer signs via CI OIDC identity | Keyless signing in CI | +| **L3** | Hardened platform; build steps cannot reach the signing key | Platform property; Packer is compatible | Delegated-signing pattern | +| **L4** | — | Not defined in SLSA v1.0 | — | + +Packer generates SLSA Provenance v1 and reaches Build L1, and L2 when run on a +hosted CI with keyless signing. L3 is a property of the build platform: it +requires the signing key to be unreachable by the build steps. Packer does not +confer L3 on its own, but the delegated-signing pattern is compatible with an L3 +platform. + +Reference GitHub Actions workflows for both the L2 keyless pattern and the +L3-compatible delegated-signing pattern live under +[`examples/ci/`](../../examples/ci/). \ No newline at end of file diff --git a/post-processor/provenance/hcl2_configure_test.go b/post-processor/provenance/hcl2_configure_test.go new file mode 100644 index 000000000..2ee675da1 --- /dev/null +++ b/post-processor/provenance/hcl2_configure_test.go @@ -0,0 +1,57 @@ +// Copyright IBM Corp. 2024, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +package provenance + +import ( + "testing" + + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hcldec" + "github.com/hashicorp/hcl/v2/hclsyntax" +) + +// TestConfigureHCL2DefaultProvenanceEnabled reproduces the HCL2 decode path used +// by packer core (hcldec.Decode -> cty.Value -> Configure) to ensure the +// default-true `provenance` gate survives when the field is omitted in HCL. +func TestConfigureHCL2DefaultProvenanceEnabled(t *testing.T) { + src := `output_dir = "out"` + body, diags := hclsyntax.ParseConfig([]byte(src), "test.pkr.hcl", hcl.Pos{Line: 1, Column: 1}) + if diags.HasErrors() { + t.Fatalf("parse hcl: %s", diags) + } + + var pp PostProcessor + spec := pp.ConfigSpec() + + val, decodeDiags := hcldec.Decode(body.Body, spec, nil) + if decodeDiags.HasErrors() { + t.Fatalf("decode hcl2 spec: %s", decodeDiags) + } + + if err := pp.Configure(val); err != nil { + t.Fatalf("configure post-processor: %v", err) + } + + if pp.config.Provenance.False() { + t.Fatalf("expected provenance to remain enabled by default under HCL2 decode") + } + if pp.config.SBOMFormat == "" { + t.Fatalf("expected SBOMFormat default to survive HCL2 decode, got empty") + } + if pp.config.SBOMScope == "" { + t.Fatalf("expected SBOMScope default to survive HCL2 decode, got empty") + } + if pp.config.BuildType == "" { + t.Fatalf("expected BuildType default to survive HCL2 decode, got empty") + } + if pp.config.SigningMode == "" { + t.Fatalf("expected SigningMode default to survive HCL2 decode, got empty") + } + if pp.config.FulcioURL == "" { + t.Fatalf("expected FulcioURL default to survive HCL2 decode, got empty") + } + if pp.config.RekorURL == "" { + t.Fatalf("expected RekorURL default to survive HCL2 decode, got empty") + } +} diff --git a/post-processor/provenance/post-processor.go b/post-processor/provenance/post-processor.go new file mode 100644 index 000000000..071e3739e --- /dev/null +++ b/post-processor/provenance/post-processor.go @@ -0,0 +1,747 @@ +// Copyright IBM Corp. 2024, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +//go:generate packer-sdc mapstructure-to-hcl2 -type Config +//go:generate packer-sdc struct-markdown + +package provenance + +import ( + "bytes" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "os" + "path/filepath" + "sort" + "strings" + "time" + "unicode" + + "github.com/hashicorp/go-uuid" + "github.com/hashicorp/hcl/v2/hcldec" + "github.com/hashicorp/packer-plugin-sdk/common" + packersdk "github.com/hashicorp/packer-plugin-sdk/packer" + "github.com/hashicorp/packer-plugin-sdk/template/config" + "github.com/hashicorp/packer-plugin-sdk/template/interpolate" + internalattestation "github.com/hashicorp/packer/internal/attestation" + internalprovenance "github.com/hashicorp/packer/internal/provenance" + internalsbom "github.com/hashicorp/packer/internal/sbom" +) + +var buildSigstoreBundleForSigner = internalattestation.BuildBundleForSigner + +type Config struct { + common.PackerConfig `mapstructure:",squash"` + + // Whether to emit provenance attestations. Enabled by default; set to + // `false` to skip the post-processor and pass the artifact through unchanged. + Provenance config.Trilean `mapstructure:"provenance"` + // The SLSA `buildType` URI recorded in the provenance predicate. Defaults to + // `https://packer.io/buildtypes/hcl2/v1`. + BuildType string `mapstructure:"build_type"` + // Directory where attestation sidecar files are written. Defaults to the + // directory containing the artifact's first file, or the current directory + // for artifacts without local files. + OutputDir string `mapstructure:"output_dir"` + // Path to the Packer template that produced the artifact. Recorded as an + // external parameter in the provenance predicate. + TemplatePath string `mapstructure:"template"` + // The list of builds this artifact came from. Recorded as an external + // parameter in the provenance predicate. + OnlyBuilds []string `mapstructure:"only_builds"` + // Additional user variables to record as external parameters in the + // provenance predicate. Values for variables named in + // `packer_sensitive_variables` are redacted. + UserVariables map[string]string `mapstructure:"user_variables"` + // Overrides the auto-detected source repository URI recorded as a resolved + // dependency. By default the source is detected from the Git repository + // containing the current working directory or from CI environment variables. + SourceURI string `mapstructure:"source_uri"` + // Whether to also generate a software bill of materials (SBOM) and a + // corresponding SBOM attestation alongside the provenance statement. + SBOM bool `mapstructure:"sbom"` + // The SBOM output format, either `cyclonedx` (default) or `spdx`. + SBOMFormat string `mapstructure:"sbom_format"` + // The path to scan when generating the SBOM. Defaults to the artifact's + // files or their common parent directory. Required when the artifact files + // span multiple directories. + SBOMScanPath string `mapstructure:"sbom_scan_path"` + // The SBOM scan scope, either `squashed` (default) or `all-layers`. + SBOMScope string `mapstructure:"sbom_scope"` + // Glob patterns of paths to exclude from the SBOM scan. + SBOMExclude []string `mapstructure:"sbom_exclude"` + // The signing mode for attestations: `none` (default, unsigned JSON), + // `key` (local PEM key), `kms` (KMS or Vault URI), or `keyless` (Sigstore + // Fulcio). + SigningMode string `mapstructure:"signing_mode"` + // The signer reference. A PEM private key path for `key` mode, or a KMS or + // Vault URI such as `awskms://...`, `gcpkms://...`, `azurekms://...`, or + // `hashivault://...` for `kms` mode. + Signer string `mapstructure:"signer"` + // An alias for `signer`. When both are set they must be equal. + Key string `mapstructure:"key"` + // The PEM verifier path used to verify the signature in `key` and `kms` + // modes. Defaults to the signer's derived public key. + Verifier string `mapstructure:"verifier"` + // The Fulcio certificate authority URL used for `keyless` signing. Defaults + // to `https://fulcio.sigstore.dev`. + FulcioURL string `mapstructure:"fulcio_url"` + // The Rekor transparency log URL used when `upload_tlog` is enabled. + // Defaults to `https://rekor.sigstore.dev`. + RekorURL string `mapstructure:"rekor_url"` + // Whether to upload the `keyless` signature to the Rekor transparency log + // and emit a Sigstore bundle carrying the transparency evidence. + UploadTlog bool `mapstructure:"upload_tlog"` + // An optional path to a Sigstore trusted-root JSON file used to pin keyless + // verification. When unset, the public Sigstore trusted root is fetched. + TrustedRootPath string `mapstructure:"trusted_root_path"` + // The expected signing identity for `keyless` mode, such as the workflow + // ref `https://github.com/OWNER/REPO/.github/workflows/build.yml@refs/heads/main`. + // Required for keyless signing. + KeylessIdentity string `mapstructure:"keyless_identity"` + // The expected OIDC issuer for `keyless` mode, such as + // `https://token.actions.githubusercontent.com`. Required for keyless signing. + KeylessOIDCIssuer string `mapstructure:"keyless_oidc_issuer"` + + ctx interpolate.Context +} + +type PostProcessor struct { + config Config + now func() time.Time + env map[string]string + workingDir string + generateSBOM func(context.Context, internalsbom.Config) ([]byte, error) + signingResourcesFn func(context.Context, internalattestation.BackendConfig) (internalattestation.Signer, internalattestation.Verifier, error) +} + +func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } + +func (p *PostProcessor) Configure(raws ...interface{}) error { + err := config.Decode(&p.config, &config.DecodeOpts{ + PluginType: "packer.post-processor.provenance", + Interpolate: true, + InterpolateContext: &p.config.ctx, + InterpolateFilter: &interpolate.RenderFilter{ + Exclude: []string{}, + }, + }, raws...) + if err != nil { + return err + } + + // Defaults are applied after decoding because the HCL2 decode path zeroes + // unset fields, which would otherwise clobber any pre-decode defaults. + if p.config.BuildType == "" { + p.config.BuildType = internalprovenance.DefaultBuildType + } + if p.config.SBOMFormat == "" { + p.config.SBOMFormat = string(internalsbom.FormatCycloneDX) + } + if p.config.SBOMScope == "" { + p.config.SBOMScope = internalsbom.ScopeSquashed + } + if p.config.SigningMode == "" { + p.config.SigningMode = internalattestation.SigningModeNone + } + if p.config.FulcioURL == "" { + p.config.FulcioURL = "https://fulcio.sigstore.dev" + } + if p.config.RekorURL == "" { + p.config.RekorURL = "https://rekor.sigstore.dev" + } + + if p.config.OutputDir != "" { + if err := interpolate.Validate(p.config.OutputDir, &p.config.ctx); err != nil { + return fmt.Errorf("error parsing output_dir template: %w", err) + } + } + + if _, err := p.signingBackendConfig(); err != nil { + return err + } + + if p.generateSBOM == nil { + p.generateSBOM = func(ctx context.Context, cfg internalsbom.Config) ([]byte, error) { + return internalsbom.NewGenerator(cfg).Generate(ctx) + } + } + + return nil +} + +func (p *PostProcessor) PostProcess(ctx context.Context, ui packersdk.Ui, source packersdk.Artifact) (packersdk.Artifact, bool, bool, error) { + if p.config.Provenance.False() { + return source, true, true, nil + } + + env := p.currentEnv() + + select { + case <-ctx.Done(): + return source, true, true, ctx.Err() + default: + } + + subjects, err := internalprovenance.DeriveSubjects(source) + if err != nil { + return source, true, true, err + } + + var byproducts []internalprovenance.Byproduct + if len(source.Files()) == 0 { + identityRecord, err := internalprovenance.DeriveIdentityRecord(source) + if err != nil { + return source, true, true, err + } + + identityBytes, err := json.Marshal(identityRecord) + if err != nil { + return source, true, true, err + } + byproducts = append(byproducts, internalprovenance.Byproduct{ + Name: "cloud-artifact-identity", + Content: base64.StdEncoding.EncodeToString(identityBytes), + }) + } + + invocationID := internalprovenance.DetectInvocationID(env) + if invocationID == "" { + invocationID, _ = uuid.GenerateUUID() + } + + predicate := internalprovenance.BuildSLSAPredicate(internalprovenance.PredicateInput{ + BuildType: p.config.BuildType, + ExternalParameters: p.externalParameters(env), + InternalParameters: p.internalParameters(), + ResolvedDependencies: p.resolvedDependencies(env), + BuilderID: internalprovenance.DetectBuilderID(env), + Byproducts: byproducts, + InvocationID: invocationID, + }) + statement := internalprovenance.WrapInToto(subjects, internalprovenance.SLSAProvenanceV1PredicateType, predicate) + + paths, err := p.outputPaths(source) + if err != nil { + return source, true, true, err + } + + if err := p.writeAttestation(ctx, ui, statement, paths.ProvenanceStatement); err != nil { + return source, true, true, err + } + + if p.config.SBOM { + if err := p.writeSBOMAttestation(ctx, ui, source, subjects, paths); err != nil { + return source, true, true, err + } + } + + return source, true, true, nil +} + +const ( + predicateTypeCycloneDX = "https://cyclonedx.org/bom" + predicateTypeSPDX = "https://spdx.dev/Document" +) + +// redactedSensitiveValue replaces sensitive user-variable values in the +// provenance predicate so secrets are never written to the attestation. +const redactedSensitiveValue = "[sensitive value redacted]" + +type outputPaths struct { + BaseDir string + Stem string + ProvenanceStatement string + SBOMRaw string + SBOMAttestation string +} + +func (p *PostProcessor) writeSBOMAttestation(ctx context.Context, ui packersdk.Ui, source packersdk.Artifact, subjects []internalprovenance.Subject, paths outputPaths) error { + format, rawSBOM, err := p.resolveSBOM(ctx, source, paths) + if err != nil { + return err + } + + predicate, predicateType, err := buildSBOMPredicate(rawSBOM, format) + if err != nil { + return err + } + + statement := internalprovenance.WrapInToto(subjects, predicateType, predicate) + if err := p.writeAttestation(ctx, ui, statement, paths.SBOMAttestation); err != nil { + return err + } + + ui.Say(fmt.Sprintf("Wrote SBOM to %s", paths.SBOMRaw)) + return nil +} + +func (p *PostProcessor) writeAttestation(ctx context.Context, ui packersdk.Ui, statement interface{}, outputPath string) error { + if p.config.SigningMode == internalattestation.SigningModeNone { + payload, err := json.MarshalIndent(statement, "", " ") + if err != nil { + return fmt.Errorf("marshal attestation payload: %w", err) + } + + if err := atomicWriteFile(outputPath, payload, 0664); err != nil { + return fmt.Errorf("write attestation %q: %w", outputPath, err) + } + + ui.Say(fmt.Sprintf("Wrote attestation to %s", outputPath)) + return nil + } + + backendConfig, err := p.signingBackendConfig() + if err != nil { + return err + } + + signer, verifier, err := p.signingResources(ctx, backendConfig) + if err != nil { + return err + } + + payload, err := internalattestation.MarshalPayload(statement) + if err != nil { + return fmt.Errorf("marshal canonical attestation payload: %w", err) + } + + bundlePath := sigstoreBundleOutputPath(outputPath) + bundleJSON := []byte(nil) + var envelope internalattestation.Envelope + if backendConfig.Mode == internalattestation.SigningModeKeyless { + envelope, bundleJSON, err = buildSigstoreBundleForSigner(ctx, signer, backendConfig, internalattestation.InTotoPayloadType, payload) + if err != nil { + return fmt.Errorf("sign attestation with Sigstore bundle: %w", err) + } + } else { + signature, signErr := signer.Sign(ctx, internalattestation.InTotoPayloadType, payload) + if signErr != nil { + return fmt.Errorf("sign attestation: %w", signErr) + } + envelope = internalattestation.NewEnvelope(internalattestation.InTotoPayloadType, payload, signature) + } + + if err := internalattestation.VerifyEnvelope(ctx, envelope, verifier); err != nil { + return fmt.Errorf("verify signed attestation: %w", err) + } + + output, err := json.MarshalIndent(envelope, "", " ") + if err != nil { + return fmt.Errorf("marshal signed envelope: %w", err) + } + + if err := atomicWriteFile(outputPath, output, 0664); err != nil { + return fmt.Errorf("write attestation %q: %w", outputPath, err) + } + + if len(bundleJSON) > 0 { + if err := atomicWriteFile(bundlePath, bundleJSON, 0664); err != nil { + return fmt.Errorf("write Sigstore bundle %q: %w", bundlePath, err) + } + ui.Say(fmt.Sprintf("Wrote Sigstore bundle to %s", bundlePath)) + } + + ui.Say(fmt.Sprintf("Wrote attestation to %s", outputPath)) + return nil +} + +func (p *PostProcessor) signingResources(ctx context.Context, backendConfig internalattestation.BackendConfig) (internalattestation.Signer, internalattestation.Verifier, error) { + if p.signingResourcesFn != nil { + return p.signingResourcesFn(ctx, backendConfig) + } + + if backendConfig.Mode == internalattestation.SigningModeNone { + return nil, nil, nil + } + + signer, err := internalattestation.NewSigner(ctx, backendConfig) + if err != nil { + return nil, nil, err + } + + verifier, err := internalattestation.NewVerifier(ctx, backendConfig, signer) + if err != nil { + return nil, nil, err + } + + return signer, verifier, nil +} + +func (p *PostProcessor) signingBackendConfig() (internalattestation.BackendConfig, error) { + mode := p.config.SigningMode + if mode == "" { + mode = internalattestation.SigningModeNone + } + + signerRef := p.config.Signer + if p.config.Key != "" { + if signerRef != "" && signerRef != p.config.Key { + return internalattestation.BackendConfig{}, fmt.Errorf("signer and key must match when both are set") + } + signerRef = p.config.Key + } + + switch mode { + case internalattestation.SigningModeNone: + return internalattestation.BackendConfig{Mode: mode}, nil + case internalattestation.SigningModeKey: + if signerRef == "" { + return internalattestation.BackendConfig{}, fmt.Errorf("signing_mode %q requires signer or key", mode) + } + return internalattestation.BackendConfig{ + Mode: mode, + SignerRef: signerRef, + VerifierRef: p.config.Verifier, + Env: p.currentEnv(), + }, nil + case internalattestation.SigningModeKMS: + if signerRef == "" { + return internalattestation.BackendConfig{}, fmt.Errorf("signing_mode %q requires signer or key", mode) + } + if !isRecognizedKMSSigner(signerRef) { + return internalattestation.BackendConfig{}, fmt.Errorf("signing_mode %q requires a recognized KMS or Vault URI: awskms://, gcpkms://, azurekms://, or hashivault://", mode) + } + return internalattestation.BackendConfig{ + Mode: mode, + SignerRef: signerRef, + VerifierRef: p.config.Verifier, + Env: p.currentEnv(), + }, nil + case internalattestation.SigningModeKeyless: + if p.config.Verifier != "" { + return internalattestation.BackendConfig{}, fmt.Errorf("signing_mode %q does not support verifier overrides; keyless attestations are verified against keyless_identity and keyless_oidc_issuer", mode) + } + if strings.TrimSpace(p.config.KeylessIdentity) == "" || strings.TrimSpace(p.config.KeylessOIDCIssuer) == "" { + return internalattestation.BackendConfig{}, fmt.Errorf("signing_mode %q requires keyless_identity and keyless_oidc_issuer", mode) + } + return internalattestation.BackendConfig{ + Mode: mode, + Env: p.currentEnv(), + FulcioURL: p.config.FulcioURL, + RekorURL: p.config.RekorURL, + UploadTlog: p.config.UploadTlog, + TrustedRootPath: p.config.TrustedRootPath, + KeylessIdentity: p.config.KeylessIdentity, + KeylessOIDCIssuer: p.config.KeylessOIDCIssuer, + }, nil + default: + return internalattestation.BackendConfig{}, fmt.Errorf("signing_mode %q is not implemented", mode) + } +} + +func isRecognizedKMSSigner(value string) bool { + for _, prefix := range []string{"awskms://", "gcpkms://", "azurekms://", "hashivault://"} { + if strings.HasPrefix(value, prefix) { + return true + } + } + + return false +} + +func (p *PostProcessor) resolveSBOM(ctx context.Context, source packersdk.Artifact, paths outputPaths) (internalsbom.Format, []byte, error) { + // The SBOM is always regenerated so it reflects the artifact being attested. + // Reusing a pre-existing SBOM file could attest stale contents if the + // artifact changed between runs. + format, err := internalsbom.ParseFormatFromArgs(p.config.SBOMFormat) + if err != nil { + return "", nil, err + } + + scanPath, err := p.resolveSBOMScanPath(source) + if err != nil { + return "", nil, err + } + + rawSBOM, err := p.generateSBOM(ctx, internalsbom.Config{ + ScanPath: scanPath, + Format: format, + Scope: p.config.SBOMScope, + Exclude: append([]string(nil), p.config.SBOMExclude...), + }) + if err != nil { + return "", nil, fmt.Errorf("generate SBOM: %w", err) + } + + if err := atomicWriteFile(paths.SBOMRaw, rawSBOM, 0664); err != nil { + return "", nil, fmt.Errorf("write SBOM %q: %w", paths.SBOMRaw, err) + } + + return format, rawSBOM, nil +} + +func (p *PostProcessor) resolveSBOMScanPath(source packersdk.Artifact) (string, error) { + if p.config.SBOMScanPath != "" { + return p.config.SBOMScanPath, nil + } + + files := source.Files() + if len(files) == 1 { + return files[0], nil + } + if len(files) > 1 { + parent := filepath.Dir(files[0]) + for _, file := range files[1:] { + if filepath.Dir(file) != parent { + return "", fmt.Errorf("sbom=true requires sbom_scan_path when artifact files span multiple directories") + } + } + return parent, nil + } + + return "", fmt.Errorf("sbom=true requires local artifact files or sbom_scan_path") +} + +func buildSBOMPredicate(rawSBOM []byte, format internalsbom.Format) (interface{}, string, error) { + decoder := json.NewDecoder(bytes.NewReader(rawSBOM)) + decoder.UseNumber() + + var predicate interface{} + if err := decoder.Decode(&predicate); err != nil { + return nil, "", fmt.Errorf("decode SBOM payload: %w", err) + } + + switch format { + case internalsbom.FormatCycloneDX: + return predicate, predicateTypeCycloneDX, nil + case internalsbom.FormatSPDX: + return predicate, predicateTypeSPDX, nil + default: + return nil, "", fmt.Errorf("unsupported SBOM format %q", format) + } +} + +func (p *PostProcessor) externalParameters(env map[string]string) map[string]interface{} { + externalParameters := map[string]interface{}{} + + if p.config.TemplatePath != "" { + externalParameters["template"] = p.config.TemplatePath + } + if len(p.config.OnlyBuilds) > 0 { + externalParameters["onlyBuilds"] = append([]string(nil), p.config.OnlyBuilds...) + } + + userVariables := collectUserVariables(env) + for key, value := range p.config.UserVariables { + userVariables[key] = value + } + redactSensitiveVariables(userVariables, p.config.PackerSensitiveVars) + if len(userVariables) > 0 { + externalParameters["userVariables"] = userVariables + } + + if len(externalParameters) == 0 { + return nil + } + + return externalParameters +} + +func (p *PostProcessor) internalParameters() map[string]interface{} { + return map[string]interface{}{ + "packerBuildName": p.config.PackerBuildName, + "packerBuilderType": p.config.PackerBuilderType, + } +} + +func (p *PostProcessor) resolvedDependencies(env map[string]string) []internalprovenance.ResolvedDependency { + workingDir := p.currentWorkingDir() + dependency, ok := internalprovenance.DetectGitDependency(workingDir, env) + if p.config.SourceURI != "" { + if ok { + dependency.URI = p.config.SourceURI + } else { + dependency = internalprovenance.ResolvedDependency{URI: p.config.SourceURI} + ok = true + } + } + if !ok { + return nil + } + + return []internalprovenance.ResolvedDependency{dependency} +} + +func (p *PostProcessor) currentEnv() map[string]string { + if p.env != nil { + copiedEnv := make(map[string]string, len(p.env)) + for key, value := range p.env { + copiedEnv[key] = value + } + return copiedEnv + } + + env := make(map[string]string) + for _, item := range os.Environ() { + parts := strings.SplitN(item, "=", 2) + if len(parts) != 2 { + continue + } + env[parts[0]] = parts[1] + } + + return env +} + +func (p *PostProcessor) currentWorkingDir() string { + if p.workingDir != "" { + return p.workingDir + } + + workingDir, err := os.Getwd() + if err != nil { + return "" + } + + return workingDir +} + +func collectUserVariables(env map[string]string) map[string]string { + userVariables := map[string]string{} + keys := make([]string, 0) + for key := range env { + if strings.HasPrefix(key, "PKR_VAR_") { + keys = append(keys, key) + } + } + sort.Strings(keys) + + for _, key := range keys { + userVariables[strings.TrimPrefix(key, "PKR_VAR_")] = env[key] + } + + return userVariables +} + +// redactSensitiveVariables replaces the values of any user variables whose names +// were marked sensitive (packer_sensitive_variables) so that secrets are not +// embedded in the provenance predicate, per SLSA guidance. +func redactSensitiveVariables(userVariables map[string]string, sensitiveKeys []string) { + for _, key := range sensitiveKeys { + if _, ok := userVariables[key]; ok { + userVariables[key] = redactedSensitiveValue + } + } +} + +func (p *PostProcessor) outputPaths(source packersdk.Artifact) (outputPaths, error) { + baseDir := p.config.OutputDir + if baseDir == "" && len(source.Files()) > 0 { + baseDir = filepath.Dir(source.Files()[0]) + } + if baseDir == "" { + baseDir = "." + } + + if err := os.MkdirAll(baseDir, 0755); err != nil { + return outputPaths{}, fmt.Errorf("create output dir %q: %w", baseDir, err) + } + + name := p.outputStem(source) + sbomFormat := internalsbom.FormatCycloneDX + if parsed, err := internalsbom.ParseFormatFromArgs(p.config.SBOMFormat); err == nil { + sbomFormat = parsed + } + sbomRaw := filepath.Join(baseDir, name+".sbom.cdx.json") + if sbomFormat == internalsbom.FormatSPDX { + sbomRaw = filepath.Join(baseDir, name+".sbom.spdx.json") + } + + return outputPaths{ + BaseDir: baseDir, + Stem: name, + ProvenanceStatement: filepath.Join(baseDir, name+".provenance.json"), + SBOMRaw: sbomRaw, + SBOMAttestation: filepath.Join(baseDir, name+".sbom.att.json"), + }, nil +} + +// outputStem returns the base filename used for all provenance outputs. When a +// build name is available it is prefixed so that parallel builds writing to a +// shared output directory cannot collide on the same output paths. +func (p *PostProcessor) outputStem(source packersdk.Artifact) string { + base := artifactStem(source) + + buildName := sanitizeFilename(strings.TrimSpace(p.config.PackerBuildName)) + if buildName == "" || base == buildName || strings.HasPrefix(base, buildName+".") { + return base + } + + return buildName + "." + base +} + +func artifactStem(source packersdk.Artifact) string { + if files := source.Files(); len(files) > 0 { + return filepath.Base(files[0]) + } + + return sanitizeFilename(fmt.Sprintf("%s-%s", source.BuilderId(), source.Id())) +} + +func sigstoreBundleOutputPath(attestationPath string) string { + if strings.HasSuffix(attestationPath, ".json") { + return strings.TrimSuffix(attestationPath, ".json") + ".sigstore.json" + } + + return attestationPath + ".sigstore.json" +} + +func sanitizeFilename(value string) string { + return strings.Map(func(r rune) rune { + switch { + case unicode.IsLetter(r), unicode.IsDigit(r): + return r + case r == '.', r == '-', r == '_': + return r + default: + return '_' + } + }, value) +} + +// atomicWriteFile writes data to path atomically by writing to a temporary file +// in the same directory and renaming it into place. This prevents partially +// written or interleaved outputs when builds run in parallel, and ensures a +// crash mid-write cannot leave a corrupt attestation on disk. +func atomicWriteFile(path string, data []byte, perm os.FileMode) error { + dir := filepath.Dir(path) + + tmp, err := os.CreateTemp(dir, "."+filepath.Base(path)+".tmp-*") + if err != nil { + return err + } + tmpName := tmp.Name() + + committed := false + defer func() { + if !committed { + _ = os.Remove(tmpName) + } + }() + + if _, err := tmp.Write(data); err != nil { + _ = tmp.Close() + return err + } + if err := tmp.Chmod(perm); err != nil { + _ = tmp.Close() + return err + } + if err := tmp.Sync(); err != nil { + _ = tmp.Close() + return err + } + if err := tmp.Close(); err != nil { + return err + } + + if err := os.Rename(tmpName, path); err != nil { + return err + } + committed = true + + return nil +} diff --git a/post-processor/provenance/post-processor.hcl2spec.go b/post-processor/provenance/post-processor.hcl2spec.go new file mode 100644 index 000000000..63d59bb68 --- /dev/null +++ b/post-processor/provenance/post-processor.hcl2spec.go @@ -0,0 +1,89 @@ +// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT. + +package provenance + +import ( + "github.com/hashicorp/hcl/v2/hcldec" + "github.com/zclconf/go-cty/cty" +) + +// FlatConfig is an auto-generated flat version of Config. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatConfig struct { + PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"` + PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"` + PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"` + PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"` + PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"` + PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"` + PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"` + PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"` + Provenance *bool `mapstructure:"provenance" cty:"provenance" hcl:"provenance"` + BuildType *string `mapstructure:"build_type" cty:"build_type" hcl:"build_type"` + OutputDir *string `mapstructure:"output_dir" cty:"output_dir" hcl:"output_dir"` + TemplatePath *string `mapstructure:"template" cty:"template" hcl:"template"` + OnlyBuilds []string `mapstructure:"only_builds" cty:"only_builds" hcl:"only_builds"` + UserVariables map[string]string `mapstructure:"user_variables" cty:"user_variables" hcl:"user_variables"` + SourceURI *string `mapstructure:"source_uri" cty:"source_uri" hcl:"source_uri"` + SBOM *bool `mapstructure:"sbom" cty:"sbom" hcl:"sbom"` + SBOMFormat *string `mapstructure:"sbom_format" cty:"sbom_format" hcl:"sbom_format"` + SBOMScanPath *string `mapstructure:"sbom_scan_path" cty:"sbom_scan_path" hcl:"sbom_scan_path"` + SBOMScope *string `mapstructure:"sbom_scope" cty:"sbom_scope" hcl:"sbom_scope"` + SBOMExclude []string `mapstructure:"sbom_exclude" cty:"sbom_exclude" hcl:"sbom_exclude"` + SigningMode *string `mapstructure:"signing_mode" cty:"signing_mode" hcl:"signing_mode"` + Signer *string `mapstructure:"signer" cty:"signer" hcl:"signer"` + Key *string `mapstructure:"key" cty:"key" hcl:"key"` + Verifier *string `mapstructure:"verifier" cty:"verifier" hcl:"verifier"` + FulcioURL *string `mapstructure:"fulcio_url" cty:"fulcio_url" hcl:"fulcio_url"` + RekorURL *string `mapstructure:"rekor_url" cty:"rekor_url" hcl:"rekor_url"` + UploadTlog *bool `mapstructure:"upload_tlog" cty:"upload_tlog" hcl:"upload_tlog"` + TrustedRootPath *string `mapstructure:"trusted_root_path" cty:"trusted_root_path" hcl:"trusted_root_path"` + KeylessIdentity *string `mapstructure:"keyless_identity" cty:"keyless_identity" hcl:"keyless_identity"` + KeylessOIDCIssuer *string `mapstructure:"keyless_oidc_issuer" cty:"keyless_oidc_issuer" hcl:"keyless_oidc_issuer"` +} + +// FlatMapstructure returns a new FlatConfig. +// FlatConfig is an auto-generated flat version of Config. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} + +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. +func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, + "packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false}, + "packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false}, + "packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false}, + "packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false}, + "packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false}, + "packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false}, + "packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false}, + "provenance": &hcldec.AttrSpec{Name: "provenance", Type: cty.Bool, Required: false}, + "build_type": &hcldec.AttrSpec{Name: "build_type", Type: cty.String, Required: false}, + "output_dir": &hcldec.AttrSpec{Name: "output_dir", Type: cty.String, Required: false}, + "template": &hcldec.AttrSpec{Name: "template", Type: cty.String, Required: false}, + "only_builds": &hcldec.AttrSpec{Name: "only_builds", Type: cty.List(cty.String), Required: false}, + "user_variables": &hcldec.AttrSpec{Name: "user_variables", Type: cty.Map(cty.String), Required: false}, + "source_uri": &hcldec.AttrSpec{Name: "source_uri", Type: cty.String, Required: false}, + "sbom": &hcldec.AttrSpec{Name: "sbom", Type: cty.Bool, Required: false}, + "sbom_format": &hcldec.AttrSpec{Name: "sbom_format", Type: cty.String, Required: false}, + "sbom_scan_path": &hcldec.AttrSpec{Name: "sbom_scan_path", Type: cty.String, Required: false}, + "sbom_scope": &hcldec.AttrSpec{Name: "sbom_scope", Type: cty.String, Required: false}, + "sbom_exclude": &hcldec.AttrSpec{Name: "sbom_exclude", Type: cty.List(cty.String), Required: false}, + "signing_mode": &hcldec.AttrSpec{Name: "signing_mode", Type: cty.String, Required: false}, + "signer": &hcldec.AttrSpec{Name: "signer", Type: cty.String, Required: false}, + "key": &hcldec.AttrSpec{Name: "key", Type: cty.String, Required: false}, + "verifier": &hcldec.AttrSpec{Name: "verifier", Type: cty.String, Required: false}, + "fulcio_url": &hcldec.AttrSpec{Name: "fulcio_url", Type: cty.String, Required: false}, + "rekor_url": &hcldec.AttrSpec{Name: "rekor_url", Type: cty.String, Required: false}, + "upload_tlog": &hcldec.AttrSpec{Name: "upload_tlog", Type: cty.Bool, Required: false}, + "trusted_root_path": &hcldec.AttrSpec{Name: "trusted_root_path", Type: cty.String, Required: false}, + "keyless_identity": &hcldec.AttrSpec{Name: "keyless_identity", Type: cty.String, Required: false}, + "keyless_oidc_issuer": &hcldec.AttrSpec{Name: "keyless_oidc_issuer", Type: cty.String, Required: false}, + } + return s +} diff --git a/post-processor/provenance/post-processor_test.go b/post-processor/provenance/post-processor_test.go new file mode 100644 index 000000000..d3c0412ca --- /dev/null +++ b/post-processor/provenance/post-processor_test.go @@ -0,0 +1,840 @@ +// Copyright IBM Corp. 2024, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +package provenance + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "encoding/json" + "encoding/pem" + "os" + "path/filepath" + "slices" + "strings" + "testing" + "time" + + packersdk "github.com/hashicorp/packer-plugin-sdk/packer" + "github.com/hashicorp/packer-plugin-sdk/template" + filebuilder "github.com/hashicorp/packer/builder/file" + internalattestation "github.com/hashicorp/packer/internal/attestation" + internalprovenance "github.com/hashicorp/packer/internal/provenance" + internalsbom "github.com/hashicorp/packer/internal/sbom" +) + +func TestPostProcessorWritesUnsignedStatementAndPreservesArtifact(t *testing.T) { + artifact := buildFileArtifact(t) + defer func() { _ = artifact.Destroy() }() + + outputDir := t.TempDir() + config := mustTemplateJSON(t, map[string]interface{}{ + "post-processors": []map[string]string{{ + "type": "provenance", + "output_dir": outputDir, + }}, + }) + tpl, err := template.Parse(strings.NewReader(config)) + if err != nil { + t.Fatalf("parse template: %v", err) + } + + var postProcessor PostProcessor + if err := postProcessor.Configure(tpl.PostProcessors[0][0].Config); err != nil { + t.Fatalf("configure post-processor: %v", err) + } + + returnedArtifact, keep, mustKeep, err := postProcessor.PostProcess(context.Background(), packersdk.TestUi(t), artifact) + if err != nil { + t.Fatalf("post-process artifact: %v", err) + } + + if returnedArtifact != artifact { + t.Fatalf("expected original artifact to be preserved") + } + if !keep || !mustKeep { + t.Fatalf("expected keep and mustKeep to be true") + } + + statementPath := filepath.Join(outputDir, "package.txt.provenance.json") + contents, err := os.ReadFile(statementPath) + if err != nil { + t.Fatalf("read provenance statement: %v", err) + } + + var statement internalprovenance.Statement + if err := json.Unmarshal(contents, &statement); err != nil { + t.Fatalf("unmarshal statement: %v", err) + } + + if got, want := statement.Type, internalprovenance.StatementType; got != want { + t.Fatalf("unexpected statement type %q, want %q", got, want) + } + if got, want := statement.PredicateType, internalprovenance.SLSAProvenanceV1PredicateType; got != want { + t.Fatalf("unexpected predicate type %q, want %q", got, want) + } + if got, want := len(statement.Subject), 1; got != want { + t.Fatalf("unexpected subject count %d, want %d", got, want) + } +} + +// TestPostProcessorChainsWithoutModifyingArtifact verifies that the provenance +// post-processor is a transparent pass-through: it returns the same artifact it +// received, unmodified, so a downstream post-processor in the chain observes the +// identical input it would have without provenance in the chain. +func TestPostProcessorChainsWithoutModifyingArtifact(t *testing.T) { + artifact := buildFileArtifact(t) + defer func() { _ = artifact.Destroy() }() + + // Record the artifact's observable state and file contents before running. + builderIDBefore := artifact.BuilderId() + idBefore := artifact.Id() + stringBefore := artifact.String() + filesBefore := append([]string(nil), artifact.Files()...) + contentsBefore := make(map[string]string, len(filesBefore)) + for _, file := range filesBefore { + contentsBefore[file] = readFileString(t, file) + } + + outputDir := t.TempDir() + config := mustTemplateJSON(t, map[string]interface{}{ + "post-processors": []map[string]string{{ + "type": "provenance", + "output_dir": outputDir, + }}, + }) + tpl, err := template.Parse(strings.NewReader(config)) + if err != nil { + t.Fatalf("parse template: %v", err) + } + + var postProcessor PostProcessor + if err := postProcessor.Configure(tpl.PostProcessors[0][0].Config); err != nil { + t.Fatalf("configure post-processor: %v", err) + } + + returnedArtifact, keep, mustKeep, err := postProcessor.PostProcess(context.Background(), packersdk.TestUi(t), artifact) + if err != nil { + t.Fatalf("post-process artifact: %v", err) + } + + // The next post-processor in the chain must receive the same artifact. + if returnedArtifact != artifact { + t.Fatalf("expected the same artifact instance to be returned for chaining") + } + if !keep || !mustKeep { + t.Fatalf("expected keep and mustKeep to be true so the artifact survives the chain") + } + + // The returned artifact's observable state must be unchanged. + if got := returnedArtifact.BuilderId(); got != builderIDBefore { + t.Fatalf("builder id changed: got %q, want %q", got, builderIDBefore) + } + if got := returnedArtifact.Id(); got != idBefore { + t.Fatalf("artifact id changed: got %q, want %q", got, idBefore) + } + if got := returnedArtifact.String(); got != stringBefore { + t.Fatalf("artifact string changed: got %q, want %q", got, stringBefore) + } + if got := returnedArtifact.Files(); !slices.Equal(got, filesBefore) { + t.Fatalf("artifact files changed: got %v, want %v", got, filesBefore) + } + + // A downstream consumer must still see the original, unmodified files. + for _, file := range returnedArtifact.Files() { + if got := readFileString(t, file); got != contentsBefore[file] { + t.Fatalf("artifact file %q contents changed: got %q, want %q", file, got, contentsBefore[file]) + } + } +} + +func TestPostProcessorEnrichesPredicateFromConfigAndCIEnv(t *testing.T) { + artifact := buildFileArtifact(t) + defer func() { _ = artifact.Destroy() }() + + outputDir := t.TempDir() + config := mustTemplateJSON(t, map[string]interface{}{ + "post-processors": []map[string]interface{}{{ + "type": "provenance", + "output_dir": outputDir, + "template": "ubuntu.pkr.hcl", + "only_builds": []string{"qemu.ubuntu"}, + "user_variables": map[string]string{"role": "web"}, + }}, + }) + tpl, err := template.Parse(strings.NewReader(config)) + if err != nil { + t.Fatalf("parse template: %v", err) + } + + var postProcessor PostProcessor + if err := postProcessor.Configure(tpl.PostProcessors[0][0].Config); err != nil { + t.Fatalf("configure post-processor: %v", err) + } + + times := []time.Time{ + time.Date(2026, time.July, 4, 10, 0, 0, 0, time.UTC), + time.Date(2026, time.July, 4, 10, 12, 0, 0, time.UTC), + } + timeIndex := 0 + postProcessor.now = func() time.Time { + current := times[timeIndex] + if timeIndex < len(times)-1 { + timeIndex++ + } + return current + } + postProcessor.env = map[string]string{ + "GITHUB_REPOSITORY": "acme/images", + "GITHUB_SHA": "deadbeef", + "GITHUB_REF": "refs/heads/main", + "GITHUB_WORKFLOW_REF": "acme/images/.github/workflows/build.yml@refs/heads/main", + "GITHUB_RUN_ID": "run-42", + "PKR_VAR_region": "us-east-1", + } + postProcessor.workingDir = "/workspace/packer" + + _, _, _, err = postProcessor.PostProcess(context.Background(), packersdk.TestUi(t), artifact) + if err != nil { + t.Fatalf("post-process artifact: %v", err) + } + + statementPath := filepath.Join(outputDir, "package.txt.provenance.json") + contents, err := os.ReadFile(statementPath) + if err != nil { + t.Fatalf("read provenance statement: %v", err) + } + + var statement struct { + Type string `json:"_type"` + PredicateType string `json:"predicateType"` + Subject []internalprovenance.Subject `json:"subject"` + Predicate internalprovenance.SLSAProvenancePredicate `json:"predicate"` + } + if err := json.Unmarshal(contents, &statement); err != nil { + t.Fatalf("unmarshal statement: %v", err) + } + + if got, want := statement.Predicate.RunDetails.Builder.ID, "acme/images/.github/workflows/build.yml@refs/heads/main"; got != want { + t.Fatalf("unexpected builder id %q, want %q", got, want) + } + if got, want := statement.Predicate.RunDetails.Metadata.InvocationID, "run-42"; got != want { + t.Fatalf("unexpected invocation id %q, want %q", got, want) + } + + externalParameters := statement.Predicate.BuildDefinition.ExternalParameters + if got, want := externalParameters["template"], "ubuntu.pkr.hcl"; got != want { + t.Fatalf("unexpected template %v, want %q", got, want) + } + if got, want := statement.Predicate.BuildDefinition.ResolvedDependencies[0].URI, "git+https://github.com/acme/images@refs/heads/main"; got != want { + t.Fatalf("unexpected source uri %q, want %q", got, want) + } + if got, want := statement.Predicate.BuildDefinition.ResolvedDependencies[0].Digest["gitCommit"], "deadbeef"; got != want { + t.Fatalf("unexpected source digest %q, want %q", got, want) + } + + userVariables, ok := externalParameters["userVariables"].(map[string]interface{}) + if !ok { + t.Fatalf("expected userVariables map, got %T", externalParameters["userVariables"]) + } + if got, want := userVariables["region"], "us-east-1"; got != want { + t.Fatalf("unexpected env user variable %v, want %q", got, want) + } + if got, want := userVariables["role"], "web"; got != want { + t.Fatalf("unexpected config user variable %v, want %q", got, want) + } + + onlyBuilds, ok := externalParameters["onlyBuilds"].([]interface{}) + if !ok || len(onlyBuilds) != 1 || onlyBuilds[0] != "qemu.ubuntu" { + t.Fatalf("unexpected onlyBuilds value %#v", externalParameters["onlyBuilds"]) + } +} + +func TestExternalParametersRedactsSensitiveVariables(t *testing.T) { + var pp PostProcessor + pp.config.UserVariables = map[string]string{"password": "s3cr3t", "region": "us-east-1"} + pp.config.PackerSensitiveVars = []string{"password", "api_token"} + + env := map[string]string{"PKR_VAR_api_token": "tok-value"} + + external := pp.externalParameters(env) + userVariables, ok := external["userVariables"].(map[string]string) + if !ok { + t.Fatalf("expected userVariables map, got %T", external["userVariables"]) + } + + if got := userVariables["password"]; got != redactedSensitiveValue { + t.Fatalf("expected sensitive config variable to be redacted, got %q", got) + } + if got := userVariables["api_token"]; got != redactedSensitiveValue { + t.Fatalf("expected sensitive PKR_VAR variable to be redacted, got %q", got) + } + if got, want := userVariables["region"], "us-east-1"; got != want { + t.Fatalf("expected non-sensitive variable to be preserved, got %q want %q", got, want) + } +} + +func TestPostProcessorWritesSBOMAttestation(t *testing.T) { + artifact := buildFileArtifact(t) + defer func() { _ = artifact.Destroy() }() + + outputDir := t.TempDir() + config := mustTemplateJSON(t, map[string]interface{}{ + "post-processors": []map[string]interface{}{{ + "type": "provenance", + "output_dir": outputDir, + "sbom": true, + }}, + }) + tpl, err := template.Parse(strings.NewReader(config)) + if err != nil { + t.Fatalf("parse template: %v", err) + } + + var postProcessor PostProcessor + if err := postProcessor.Configure(tpl.PostProcessors[0][0].Config); err != nil { + t.Fatalf("configure post-processor: %v", err) + } + postProcessor.generateSBOM = func(context.Context, internalsbom.Config) ([]byte, error) { + return []byte(`{"bomFormat":"CycloneDX","specVersion":"1.5"}`), nil + } + + _, _, _, err = postProcessor.PostProcess(context.Background(), packersdk.TestUi(t), artifact) + if err != nil { + t.Fatalf("post-process artifact: %v", err) + } + + if _, err := os.Stat(filepath.Join(outputDir, "package.txt.sbom.cdx.json")); err != nil { + t.Fatalf("expected raw sbom output: %v", err) + } + + contents, err := os.ReadFile(filepath.Join(outputDir, "package.txt.sbom.att.json")) + if err != nil { + t.Fatalf("read sbom attestation: %v", err) + } + + var statement internalprovenance.Statement + if err := json.Unmarshal(contents, &statement); err != nil { + t.Fatalf("unmarshal sbom attestation: %v", err) + } + if got, want := statement.PredicateType, "https://cyclonedx.org/bom"; got != want { + t.Fatalf("unexpected SBOM predicate type %q, want %q", got, want) + } +} + +func TestPostProcessorRegeneratesStaleSBOM(t *testing.T) { + artifact := buildFileArtifact(t) + defer func() { _ = artifact.Destroy() }() + + outputDir := t.TempDir() + staleSBOM := []byte(`{"bomFormat":"CycloneDX","specVersion":"1.5","stale":true}`) + if err := os.WriteFile(filepath.Join(outputDir, "package.txt.sbom.cdx.json"), staleSBOM, 0664); err != nil { + t.Fatalf("write stale sbom: %v", err) + } + + config := mustTemplateJSON(t, map[string]interface{}{ + "post-processors": []map[string]interface{}{{ + "type": "provenance", + "output_dir": outputDir, + "sbom": true, + }}, + }) + tpl, err := template.Parse(strings.NewReader(config)) + if err != nil { + t.Fatalf("parse template: %v", err) + } + + var postProcessor PostProcessor + if err := postProcessor.Configure(tpl.PostProcessors[0][0].Config); err != nil { + t.Fatalf("configure post-processor: %v", err) + } + freshSBOM := []byte(`{"bomFormat":"CycloneDX","specVersion":"1.5","fresh":true}`) + generated := false + postProcessor.generateSBOM = func(context.Context, internalsbom.Config) ([]byte, error) { + generated = true + return freshSBOM, nil + } + + _, _, _, err = postProcessor.PostProcess(context.Background(), packersdk.TestUi(t), artifact) + if err != nil { + t.Fatalf("post-process artifact: %v", err) + } + + if !generated { + t.Fatalf("expected SBOM to be regenerated rather than reused") + } + if got, want := readFileString(t, filepath.Join(outputDir, "package.txt.sbom.cdx.json")), string(freshSBOM); got != want { + t.Fatalf("expected stale SBOM to be overwritten with freshly generated contents") + } + + contents, err := os.ReadFile(filepath.Join(outputDir, "package.txt.sbom.att.json")) + if err != nil { + t.Fatalf("read sbom attestation: %v", err) + } + + var statement internalprovenance.Statement + if err := json.Unmarshal(contents, &statement); err != nil { + t.Fatalf("unmarshal sbom attestation: %v", err) + } + if got, want := statement.PredicateType, "https://cyclonedx.org/bom"; got != want { + t.Fatalf("unexpected SBOM predicate type %q, want %q", got, want) + } +} + +func TestPostProcessorSignsAttestationsWithConfiguredVerifier(t *testing.T) { + artifact := buildFileArtifact(t) + defer func() { _ = artifact.Destroy() }() + + privateKeyPath, publicKeyPath := writeSigningKeypair(t) + outputDir := t.TempDir() + config := mustTemplateJSON(t, map[string]interface{}{ + "post-processors": []map[string]interface{}{{ + "type": "provenance", + "output_dir": outputDir, + "signing_mode": "key", + "signer": privateKeyPath, + "verifier": publicKeyPath, + "sbom": true, + }}, + }) + tpl, err := template.Parse(strings.NewReader(config)) + if err != nil { + t.Fatalf("parse template: %v", err) + } + + var postProcessor PostProcessor + if err := postProcessor.Configure(tpl.PostProcessors[0][0].Config); err != nil { + t.Fatalf("configure post-processor: %v", err) + } + postProcessor.generateSBOM = func(context.Context, internalsbom.Config) ([]byte, error) { + return []byte(`{"bomFormat":"CycloneDX","specVersion":"1.5"}`), nil + } + + _, _, _, err = postProcessor.PostProcess(context.Background(), packersdk.TestUi(t), artifact) + if err != nil { + t.Fatalf("post-process artifact: %v", err) + } + + assertSignedEnvelope(t, filepath.Join(outputDir, "package.txt.provenance.json")) + assertSignedEnvelope(t, filepath.Join(outputDir, "package.txt.sbom.att.json")) + if _, err := os.Stat(filepath.Join(outputDir, "package.txt.sbom.cdx.json")); err != nil { + t.Fatalf("expected raw sbom output: %v", err) + } +} + +func TestPostProcessorRejectsMismatchedVerifier(t *testing.T) { + artifact := buildFileArtifact(t) + defer func() { _ = artifact.Destroy() }() + + privateKeyPath, _ := writeSigningKeypair(t) + _, mismatchedVerifierPath := writeSigningKeypair(t) + outputDir := t.TempDir() + config := mustTemplateJSON(t, map[string]interface{}{ + "post-processors": []map[string]interface{}{{ + "type": "provenance", + "output_dir": outputDir, + "signing_mode": "key", + "signer": privateKeyPath, + "verifier": mismatchedVerifierPath, + }}, + }) + tpl, err := template.Parse(strings.NewReader(config)) + if err != nil { + t.Fatalf("parse template: %v", err) + } + + var postProcessor PostProcessor + if err := postProcessor.Configure(tpl.PostProcessors[0][0].Config); err != nil { + t.Fatalf("configure post-processor: %v", err) + } + + _, _, _, err = postProcessor.PostProcess(context.Background(), packersdk.TestUi(t), artifact) + if err == nil { + t.Fatalf("expected post-process to fail with mismatched verifier") + } +} + +func TestPostProcessorWritesSigstoreBundleForKeylessAttestations(t *testing.T) { + artifact := buildFileArtifact(t) + defer func() { _ = artifact.Destroy() }() + + outputDir := t.TempDir() + config := mustTemplateJSON(t, map[string]interface{}{ + "post-processors": []map[string]interface{}{{ + "type": "provenance", + "output_dir": outputDir, + "signing_mode": "keyless", + "upload_tlog": false, + "keyless_identity": "https://github.com/hashicorp/packer/.github/workflows/build.yml@refs/heads/main", + "keyless_oidc_issuer": "https://token.actions.githubusercontent.com", + }}, + }) + tpl, err := template.Parse(strings.NewReader(config)) + if err != nil { + t.Fatalf("parse template: %v", err) + } + + originalBundleBuilder := buildSigstoreBundleForSigner + buildSigstoreBundleForSigner = func(context.Context, internalattestation.Signer, internalattestation.BackendConfig, string, []byte) (internalattestation.Envelope, []byte, error) { + return internalattestation.Envelope{ + PayloadType: internalattestation.InTotoPayloadType, + Payload: "cGF5bG9hZA==", + Signatures: []internalattestation.EnvelopeSignature{{Sig: "c2ln", Cert: "cert"}}, + }, []byte(`{"mediaType":"application/vnd.dev.sigstore.bundle+json;version=0.3"}`), nil + } + t.Cleanup(func() { buildSigstoreBundleForSigner = originalBundleBuilder }) + + var postProcessor PostProcessor + if err := postProcessor.Configure(tpl.PostProcessors[0][0].Config); err != nil { + t.Fatalf("configure post-processor: %v", err) + } + postProcessor.signingResourcesFn = func(context.Context, internalattestation.BackendConfig) (internalattestation.Signer, internalattestation.Verifier, error) { + return fakeEnvelopeSigner{}, fakeEnvelopeVerifier{}, nil + } + + _, _, _, err = postProcessor.PostProcess(context.Background(), packersdk.TestUi(t), artifact) + if err != nil { + t.Fatalf("post-process artifact: %v", err) + } + + assertSignedEnvelope(t, filepath.Join(outputDir, "package.txt.provenance.json")) + if _, err := os.Stat(filepath.Join(outputDir, "package.txt.provenance.sigstore.json")); err != nil { + t.Fatalf("expected Sigstore bundle output: %v", err) + } + if _, err := os.Stat(filepath.Join(outputDir, "package.txt.sbom.att.sigstore.json")); !os.IsNotExist(err) { + t.Fatalf("expected no SBOM bundle sidecar when sbom is disabled, got %v", err) + } + if got, want := strings.TrimSpace(readFileString(t, filepath.Join(outputDir, "package.txt.provenance.sigstore.json"))), `{"mediaType":"application/vnd.dev.sigstore.bundle+json;version=0.3"}`; got != want { + t.Fatalf("unexpected bundle contents %q, want %q", got, want) + } +} + +func TestSigningBackendConfigAcceptsKMSReferences(t *testing.T) { + postProcessor := PostProcessor{} + postProcessor.config.SigningMode = internalattestation.SigningModeKMS + postProcessor.config.Signer = "awskms://alias/example" + postProcessor.config.Verifier = "keys/provenance-signing.pub.pem" + postProcessor.env = map[string]string{"AWS_REGION": "us-east-1"} + + backendConfig, err := postProcessor.signingBackendConfig() + if err != nil { + t.Fatalf("build signing backend config: %v", err) + } + + if got, want := backendConfig.Mode, internalattestation.SigningModeKMS; got != want { + t.Fatalf("unexpected mode %q, want %q", got, want) + } + if got, want := backendConfig.SignerRef, "awskms://alias/example"; got != want { + t.Fatalf("unexpected signer ref %q, want %q", got, want) + } + if got, want := backendConfig.VerifierRef, "keys/provenance-signing.pub.pem"; got != want { + t.Fatalf("unexpected verifier ref %q, want %q", got, want) + } + if got, want := backendConfig.Env["AWS_REGION"], "us-east-1"; got != want { + t.Fatalf("unexpected copied environment %q, want %q", got, want) + } +} + +func TestSigningBackendConfigRejectsUnknownKMSReferences(t *testing.T) { + postProcessor := PostProcessor{} + postProcessor.config.SigningMode = internalattestation.SigningModeKMS + postProcessor.config.Signer = "kms://example" + + _, err := postProcessor.signingBackendConfig() + if err == nil { + t.Fatalf("expected unknown KMS reference to be rejected") + } + if !strings.Contains(err.Error(), "recognized KMS or Vault URI") { + t.Fatalf("unexpected KMS validation error: %v", err) + } +} + +func TestSigningBackendConfigIncludesKeylessFulcioURL(t *testing.T) { + postProcessor := PostProcessor{} + postProcessor.config.SigningMode = internalattestation.SigningModeKeyless + postProcessor.config.FulcioURL = "https://fulcio.example.test" + postProcessor.config.KeylessIdentity = "https://github.com/org/repo/.github/workflows/build.yml@refs/heads/main" + postProcessor.config.KeylessOIDCIssuer = "https://token.actions.githubusercontent.com" + postProcessor.env = map[string]string{"SIGSTORE_ID_TOKEN": "token"} + + backendConfig, err := postProcessor.signingBackendConfig() + if err != nil { + t.Fatalf("build signing backend config: %v", err) + } + + if got, want := backendConfig.Mode, internalattestation.SigningModeKeyless; got != want { + t.Fatalf("unexpected mode %q, want %q", got, want) + } + if got, want := backendConfig.FulcioURL, "https://fulcio.example.test"; got != want { + t.Fatalf("unexpected Fulcio URL %q, want %q", got, want) + } + if backendConfig.VerifierRef != "" { + t.Fatalf("keyless backend config must not carry a verifier ref, got %q", backendConfig.VerifierRef) + } + if got, want := backendConfig.Env["SIGSTORE_ID_TOKEN"], "token"; got != want { + t.Fatalf("unexpected copied environment %q, want %q", got, want) + } +} + +func TestSigningBackendConfigRejectsKeylessVerifierOverride(t *testing.T) { + postProcessor := PostProcessor{} + postProcessor.config.SigningMode = internalattestation.SigningModeKeyless + postProcessor.config.Verifier = "keys/provenance-signing.pub.pem" + postProcessor.config.KeylessIdentity = "https://github.com/org/repo/.github/workflows/build.yml@refs/heads/main" + postProcessor.config.KeylessOIDCIssuer = "https://token.actions.githubusercontent.com" + postProcessor.env = map[string]string{"SIGSTORE_ID_TOKEN": "token"} + + _, err := postProcessor.signingBackendConfig() + if err == nil { + t.Fatalf("expected keyless config with verifier override to fail") + } + if !strings.Contains(err.Error(), "verifier overrides") { + t.Fatalf("unexpected keyless verifier override error: %v", err) + } +} + +func TestSigningBackendConfigRejectsKeylessWithoutIdentityPolicy(t *testing.T) { + postProcessor := PostProcessor{} + postProcessor.config.SigningMode = internalattestation.SigningModeKeyless + postProcessor.config.FulcioURL = "https://fulcio.example.test" + postProcessor.env = map[string]string{"SIGSTORE_ID_TOKEN": "token"} + + _, err := postProcessor.signingBackendConfig() + if err == nil { + t.Fatalf("expected keyless config without identity policy to fail") + } + if !strings.Contains(err.Error(), "keyless_identity") { + t.Fatalf("unexpected keyless validation error: %v", err) + } +} + +func TestSigningBackendConfigIncludesKeylessPolicy(t *testing.T) { + postProcessor := PostProcessor{} + postProcessor.config.SigningMode = internalattestation.SigningModeKeyless + postProcessor.config.FulcioURL = "https://fulcio.example.test" + postProcessor.config.KeylessIdentity = "https://github.com/hashicorp/packer/.github/workflows/build.yml@refs/heads/main" + postProcessor.config.KeylessOIDCIssuer = "https://token.actions.githubusercontent.com" + postProcessor.config.TrustedRootPath = "testdata/trusted-root.json" + postProcessor.env = map[string]string{"SIGSTORE_ID_TOKEN": "token"} + + backendConfig, err := postProcessor.signingBackendConfig() + if err != nil { + t.Fatalf("build keyless signing backend config: %v", err) + } + + if got, want := backendConfig.KeylessIdentity, "https://github.com/hashicorp/packer/.github/workflows/build.yml@refs/heads/main"; got != want { + t.Fatalf("unexpected keyless identity %q, want %q", got, want) + } + if got, want := backendConfig.KeylessOIDCIssuer, "https://token.actions.githubusercontent.com"; got != want { + t.Fatalf("unexpected keyless OIDC issuer %q, want %q", got, want) + } + if got, want := backendConfig.TrustedRootPath, "testdata/trusted-root.json"; got != want { + t.Fatalf("unexpected trusted root path %q, want %q", got, want) + } +} + +func TestSigningBackendConfigIncludesKeylessRekorSettings(t *testing.T) { + postProcessor := PostProcessor{} + postProcessor.config.SigningMode = internalattestation.SigningModeKeyless + postProcessor.config.FulcioURL = "https://fulcio.example.test" + postProcessor.config.RekorURL = "https://rekor.example.test" + postProcessor.config.UploadTlog = true + postProcessor.config.KeylessIdentity = "https://github.com/hashicorp/packer/.github/workflows/build.yml@refs/heads/main" + postProcessor.config.KeylessOIDCIssuer = "https://token.actions.githubusercontent.com" + postProcessor.env = map[string]string{"SIGSTORE_ID_TOKEN": "token"} + + backendConfig, err := postProcessor.signingBackendConfig() + if err != nil { + t.Fatalf("build keyless signing backend config: %v", err) + } + + if got, want := backendConfig.RekorURL, "https://rekor.example.test"; got != want { + t.Fatalf("unexpected Rekor URL %q, want %q", got, want) + } + if !backendConfig.UploadTlog { + t.Fatalf("expected upload_tlog to be enabled") + } +} + +func assertSignedEnvelope(t *testing.T, path string) { + t.Helper() + contents, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read signed envelope %q: %v", path, err) + } + + var envelope internalattestation.Envelope + if err := json.Unmarshal(contents, &envelope); err != nil { + t.Fatalf("unmarshal envelope %q: %v", path, err) + } + if got, want := envelope.PayloadType, internalattestation.InTotoPayloadType; got != want { + t.Fatalf("unexpected payload type %q, want %q", got, want) + } + if len(envelope.Signatures) != 1 { + t.Fatalf("expected exactly one signature in %q", path) + } +} + +func writeSigningKeypair(t *testing.T) (string, string) { + t.Helper() + + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("generate private key: %v", err) + } + + privateKeyDER, err := x509.MarshalECPrivateKey(privateKey) + if err != nil { + t.Fatalf("marshal private key: %v", err) + } + + publicKeyDER, err := x509.MarshalPKIXPublicKey(privateKey.Public()) + if err != nil { + t.Fatalf("marshal public key: %v", err) + } + + dir := t.TempDir() + privateKeyPath := filepath.Join(dir, "signer.pem") + publicKeyPath := filepath.Join(dir, "verifier.pem") + + if err := os.WriteFile(privateKeyPath, pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: privateKeyDER}), 0600); err != nil { + t.Fatalf("write private key: %v", err) + } + if err := os.WriteFile(publicKeyPath, pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: publicKeyDER}), 0600); err != nil { + t.Fatalf("write public key: %v", err) + } + + return privateKeyPath, publicKeyPath +} + +type fakeEnvelopeSigner struct{} + +func (fakeEnvelopeSigner) Sign(context.Context, string, []byte) (internalattestation.Signature, error) { + return internalattestation.Signature{Sig: []byte("sig")}, nil +} + +func (fakeEnvelopeSigner) Verifier(context.Context, internalattestation.BackendConfig) (internalattestation.Verifier, error) { + return fakeEnvelopeVerifier{}, nil +} + +type fakeEnvelopeVerifier struct{} + +func (fakeEnvelopeVerifier) Verify(context.Context, string, []byte, []byte) error { + return nil +} + +func (fakeEnvelopeVerifier) KeyID() string { + return "" +} + +func readFileString(t *testing.T, path string) string { + t.Helper() + contents, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read file %q: %v", path, err) + } + return string(contents) +} + +func TestOutputStemDisambiguatesByBuildName(t *testing.T) { + artifact := buildFileArtifact(t) + defer func() { _ = artifact.Destroy() }() + + var pp PostProcessor + + // Without a build name the stem is just the artifact's base filename. + if got, want := pp.outputStem(artifact), "package.txt"; got != want { + t.Fatalf("without build name: got %q, want %q", got, want) + } + + // A build name is prefixed so parallel builds sharing an output_dir do not + // collide on the same output paths. + pp.config.PackerBuildName = "amazon-ebs.linux" + if got, want := pp.outputStem(artifact), "amazon-ebs.linux.package.txt"; got != want { + t.Fatalf("with build name: got %q, want %q", got, want) + } + + paths, err := pp.outputPaths(artifact) + if err != nil { + t.Fatalf("output paths: %v", err) + } + if got, want := filepath.Base(paths.ProvenanceStatement), "amazon-ebs.linux.package.txt.provenance.json"; got != want { + t.Fatalf("unexpected provenance path %q, want %q", got, want) + } +} + +func TestAtomicWriteFileReplacesExistingWithoutLeftovers(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "out.json") + if err := os.WriteFile(path, []byte("stale"), 0644); err != nil { + t.Fatalf("seed file: %v", err) + } + + if err := atomicWriteFile(path, []byte("fresh-content"), 0664); err != nil { + t.Fatalf("atomic write: %v", err) + } + + got, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read back: %v", err) + } + if string(got) != "fresh-content" { + t.Fatalf("unexpected contents %q", string(got)) + } + + // No temporary files should be left behind. + entries, err := os.ReadDir(dir) + if err != nil { + t.Fatalf("read dir: %v", err) + } + if len(entries) != 1 || entries[0].Name() != "out.json" { + t.Fatalf("expected only out.json in directory, found %v", entries) + } +} + +func buildFileArtifact(t *testing.T) packersdk.Artifact { + t.Helper() + + target := filepath.Join(t.TempDir(), "package.txt") + config := mustTemplateJSON(t, map[string]interface{}{ + "builders": []map[string]string{{ + "type": "file", + "target": target, + "content": "Hello world!", + }}, + }) + tpl, err := template.Parse(strings.NewReader(config)) + if err != nil { + t.Fatalf("parse template: %v", err) + } + + var builder filebuilder.Builder + _, warnings, err := builder.Prepare(tpl.Builders["file"].Config) + if err != nil { + t.Fatalf("prepare builder: %v", err) + } + if len(warnings) > 0 { + t.Fatalf("unexpected warnings: %v", warnings) + } + + artifact, err := builder.Run(context.Background(), packersdk.TestUi(t), nil) + if err != nil { + t.Fatalf("run builder: %v", err) + } + + return artifact +} + +func mustTemplateJSON(t *testing.T, value interface{}) string { + t.Helper() + + encoded, err := json.Marshal(value) + if err != nil { + t.Fatalf("marshal template config: %v", err) + } + + return string(encoded) +}