Files
Packer-Cn/command/verify_attestation.go
Tanmay Jain 014f8d1bbe 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
2026-07-21 13:40:42 +05:30

156 lines
5.9 KiB
Go

// 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,
}
}