1
0
mirror of https://github.com/docker/buildx.git synced 2026-10-23 17:29:03 +03:00
Files
buildx/replay/errors.go
Tonis Tiigi 278c48c18c replay: address review feedback
- verify digests when reading the image index, attestation manifest and
  provenance, so replay uses the content covered by the signature
- deny sources whose URI is not recorded in the provenance
- validate every line of .intoto.jsonl inputs and require in-toto
  statements
- reject stdin, OCI layout, local and Bake target contexts, local
  mode=delete outputs, --platform all, and recorded --network=host up
  front
- keep a recorded --network=none unless --network is set
- build the dry-run plan from the actual build options and validate
  outputs in dry-run
- error on a policy config with both a callback and policy files
- add Git context and multi-platform replay integration tests

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2026-09-30 09:44:48 -07:00

271 lines
9.0 KiB
Go

package replay
import (
"fmt"
"sort"
"strings"
"github.com/pkg/errors"
)
// UnreplayableLocalContextError signals that the original build used a local
// filesystem context which replay cannot reproduce.
type UnreplayableLocalContextError struct {
LocalSources []string
}
func (e *UnreplayableLocalContextError) Error() string {
const hint = "only builds from a Git repository or HTTP(S) URL context can be replayed"
if len(e.LocalSources) == 0 {
return "image was built from local inputs that replay cannot fetch; " + hint
}
return fmt.Sprintf("image was built from local inputs that replay cannot fetch (%s); %s", strings.Join(e.LocalSources, ", "), hint)
}
// ErrUnreplayableLocalContext constructs an UnreplayableLocalContextError.
func ErrUnreplayableLocalContext(sources []string) error {
sorted := append([]string(nil), sources...)
sort.Strings(sorted)
return errors.WithStack(&UnreplayableLocalContextError{LocalSources: sorted})
}
// MissingSecretError is returned when provenance declares required secrets
// that the user did not provide.
type MissingSecretError struct {
IDs []string
}
func (e *MissingSecretError) Error() string {
return fmt.Sprintf("missing required secrets: %s (pass them with --secret id=<id>,src=<path>)", strings.Join(e.IDs, ", "))
}
// ErrMissingSecret constructs a MissingSecretError.
func ErrMissingSecret(ids []string) error {
sorted := append([]string(nil), ids...)
sort.Strings(sorted)
return errors.WithStack(&MissingSecretError{IDs: sorted})
}
// ExtraSecretError is returned when the user supplies secrets that the
// provenance does not declare.
type ExtraSecretError struct {
IDs []string
}
func (e *ExtraSecretError) Error() string {
return fmt.Sprintf("extra secrets not declared in provenance: %s", strings.Join(e.IDs, ", "))
}
// ErrExtraSecret constructs an ExtraSecretError.
func ErrExtraSecret(ids []string) error {
sorted := append([]string(nil), ids...)
sort.Strings(sorted)
return errors.WithStack(&ExtraSecretError{IDs: sorted})
}
// MissingSSHError is returned when provenance declares required SSH agents
// that the user did not provide.
type MissingSSHError struct {
IDs []string
}
func (e *MissingSSHError) Error() string {
return fmt.Sprintf("missing required ssh entries: %s (pass them with --ssh <id>=<socket|key>)", strings.Join(e.IDs, ", "))
}
// ErrMissingSSH constructs a MissingSSHError.
func ErrMissingSSH(ids []string) error {
sorted := append([]string(nil), ids...)
sort.Strings(sorted)
return errors.WithStack(&MissingSSHError{IDs: sorted})
}
// ExtraSSHError is returned when the user supplies SSH agents that the
// provenance does not declare.
type ExtraSSHError struct {
IDs []string
}
func (e *ExtraSSHError) Error() string {
return fmt.Sprintf("extra ssh entries not declared in provenance: %s", strings.Join(e.IDs, ", "))
}
// ErrExtraSSH constructs an ExtraSSHError.
func ErrExtraSSH(ids []string) error {
sorted := append([]string(nil), ids...)
sort.Strings(sorted)
return errors.WithStack(&ExtraSSHError{IDs: sorted})
}
// MaterialNotFoundError indicates a provenance material that the resolver
// could not locate in any configured store.
type MaterialNotFoundError struct {
URI string
Digest string
}
func (e *MaterialNotFoundError) Error() string {
return fmt.Sprintf("material not found: uri=%q digest=%q", e.URI, e.Digest)
}
// ErrMaterialNotFound constructs a MaterialNotFoundError.
func ErrMaterialNotFound(uri, dgst string) error {
return errors.WithStack(&MaterialNotFoundError{URI: uri, Digest: dgst})
}
// CompareMismatchError is returned by `replay verify` when the replayed
// artifact does not match the subject. The wrapped Report may be nil when no
// structured diff is available (digest comparison).
type CompareMismatchError struct {
// Report is typed as any so callers can surface either the basic compare
// tree or a future richer report format without breaking the error type.
Report any
Reason string
}
func (e *CompareMismatchError) Error() string {
if e.Reason != "" {
return fmt.Sprintf("replay mismatch: %s", e.Reason)
}
return "replay mismatch"
}
// ErrCompareMismatch constructs a CompareMismatchError.
func ErrCompareMismatch(reason string, report any) error {
return errors.WithStack(&CompareMismatchError{Reason: reason, Report: report})
}
// NotImplementedError marks a feature that is not yet implemented.
type NotImplementedError struct {
Feature string
}
func (e *NotImplementedError) Error() string {
return fmt.Sprintf("not implemented: %s", e.Feature)
}
// ErrNotImplemented constructs a NotImplementedError.
func ErrNotImplemented(feature string) error {
return errors.WithStack(&NotImplementedError{Feature: feature})
}
// UnsupportedSubjectError signals that the supplied subject kind is not
// compatible with the invoked subcommand.
type UnsupportedSubjectError struct {
Kind string
}
func (e *UnsupportedSubjectError) Error() string {
return fmt.Sprintf("unsupported subject: %s", e.Kind)
}
// ErrUnsupportedSubject constructs an UnsupportedSubjectError.
func ErrUnsupportedSubject(kind string) error {
return errors.WithStack(&UnsupportedSubjectError{Kind: kind})
}
// NoProvenanceError is returned when no SLSA provenance attestation could be
// found for a subject.
type NoProvenanceError struct {
Subject string
// Manifest is set when the subject is an image manifest referenced
// directly rather than through its image index.
Manifest bool
}
func (e *NoProvenanceError) Error() string {
hint := "build the image with --provenance=mode=max to make it replayable"
if e.Manifest {
hint = "if this is a platform manifest of a multi-platform image, use the image index reference instead; otherwise " + hint
}
if e.Subject == "" {
return "no SLSA provenance attestation found; " + hint
}
return fmt.Sprintf("no SLSA provenance attestation found for %s; %s", e.Subject, hint)
}
// ErrNoProvenance constructs a NoProvenanceError.
func ErrNoProvenance(subject string) error {
return errors.WithStack(&NoProvenanceError{Subject: subject})
}
// ErrNoProvenanceForManifest constructs a NoProvenanceError for an image
// manifest referenced directly.
func ErrNoProvenanceForManifest(subject string) error {
return errors.WithStack(&NoProvenanceError{Subject: subject, Manifest: true})
}
// UnsupportedPredicateError signals that the attached predicate is not SLSA
// provenance.
type UnsupportedPredicateError struct {
PredicateType string
}
func (e *UnsupportedPredicateError) Error() string {
return fmt.Sprintf("unsupported predicate type %q; replay requires SLSA provenance (v0.2 or v1)", e.PredicateType)
}
// ErrUnsupportedPredicate constructs an UnsupportedPredicateError.
func ErrUnsupportedPredicate(predicateType string) error {
return errors.WithStack(&UnsupportedPredicateError{PredicateType: predicateType})
}
// SignatureVerificationRequiredError is returned when a signed envelope
// cannot be verified from the available trust material. Replay never silently
// unwraps such an attestation.
type SignatureVerificationRequiredError struct {
// Source is the user-visible input that carries the signed envelope
// (file path for attestation-file inputs).
Source string
// Envelope describes the detected envelope shape ("dsse" or
// "sigstore-bundle") so the user can tell what was rejected.
Envelope string
}
func (e *SignatureVerificationRequiredError) Error() string {
src := e.Source
if src == "" {
src = "attestation"
}
env := e.Envelope
if env == "" {
env = "signed envelope"
}
return fmt.Sprintf("%s for %s carries signatures but cannot be verified from the available trust material; refusing to accept unverified signed attestation", env, src)
}
// ErrSignatureVerificationRequired constructs a
// SignatureVerificationRequiredError.
func ErrSignatureVerificationRequired(source, envelope string) error {
return errors.WithStack(&SignatureVerificationRequiredError{Source: source, Envelope: envelope})
}
// MinModeProvenanceError is returned when the provenance was recorded with
// mode=min, which omits the build arguments, secrets and SSH needed to
// reconstruct the build.
type MinModeProvenanceError struct{}
func (e *MinModeProvenanceError) Error() string {
return "provenance was recorded with mode=min, which omits build arguments, secrets and SSH needed for replay; build the image with --provenance=mode=max to make it replayable"
}
// ErrMinModeProvenance constructs a MinModeProvenanceError.
func ErrMinModeProvenance() error {
return errors.WithStack(&MinModeProvenanceError{})
}
// UnpinnedContextError is returned when the provenance does not record a
// digest for a Git subdirectory build context, so replay cannot pin it.
type UnpinnedContextError struct {
URI string
}
func (e *UnpinnedContextError) Error() string {
return fmt.Sprintf("cannot pin the Git build context %s: provenance from BuildKit before v0.25 does not record a digest for subdirectory contexts; use --replay-mode=frontend to replay without pinning sources", e.URI)
}
// ErrUnpinnedContext constructs an UnpinnedContextError.
func ErrUnpinnedContext(uri string) error {
return errors.WithStack(&UnpinnedContextError{URI: uri})
}