Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cmd/validate/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ func validateImageCmd(validate imageValidationFunc) *cobra.Command {
},
IgnoreRekor: data.ignoreRekor,
SkipImageSigCheck: data.skipImageSigCheck,
SkipAttSigCheck: data.skipAttSigCheck,
PolicyRef: data.policyConfiguration,
PublicKey: data.publicKey,
RekorURL: data.rekorURL,
Expand Down Expand Up @@ -498,6 +499,9 @@ func validateImageCmd(validate imageValidationFunc) *cobra.Command {
cmd.Flags().BoolVar(&data.skipImageSigCheck, "skip-image-sig-check", data.skipImageSigCheck,
"Skip image signature validation checks.")

cmd.Flags().BoolVar(&data.skipAttSigCheck, "skip-att-sig-check", data.skipAttSigCheck,
"Skip attestation signature validation checks.")

cmd.Flags().StringVar(&data.certificateIdentity, "certificate-identity", data.certificateIdentity,
"URL of the certificate identity for keyless verification")

Expand Down Expand Up @@ -636,6 +640,7 @@ type imageData struct {
input string
ignoreRekor bool
skipImageSigCheck bool
skipAttSigCheck bool
output []string
outputFile string
policy policy.Policy
Expand Down
1 change: 1 addition & 0 deletions docs/modules/ROOT/pages/ec_validate_image.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ mark (?) sign, for example: --output text=output.txt?show-successes=false
* inline JSON ('{sources: {...}, identity: {...}}')")
-k, --public-key:: path to the public key. Overrides publicKey from EnterpriseContractPolicy
-r, --rekor-url:: Rekor URL. Overrides rekorURL from EnterpriseContractPolicy
--skip-att-sig-check:: Skip attestation signature validation checks. (Default: false)
--skip-image-sig-check:: Skip image signature validation checks. (Default: false)
--snapshot:: Provide the AppStudio Snapshot as a source of the images to validate, as inline
JSON of the "spec" or a reference to a Kubernetes object [<namespace>/]<name>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/santhosh-tekuri/jsonschema/v5"
"github.com/sigstore/cosign/v3/pkg/cosign"
cosignOCI "github.com/sigstore/cosign/v3/pkg/oci"
ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote"
log "github.com/sirupsen/logrus"
"github.com/spf13/afero"

Expand Down Expand Up @@ -246,6 +247,74 @@ func (a *ApplicationSnapshotImage) ValidateAttestationSignature(ctx context.Cont
return nil
}

// FetchAttestationsWithoutVerification fetches attestations from the registry
// without performing signature verification. This is used when --skip-att-sig-check
// is enabled but we still need the attestation data for policy evaluation.
func (a *ApplicationSnapshotImage) FetchAttestationsWithoutVerification(ctx context.Context) error {
if trace.IsEnabled() {
region := trace.StartRegion(ctx, "ec:fetch-attestations-without-verification")
defer region.End()
}

remoteOpts := oci.CreateRemoteOptions(ctx)
signedEntity, err := ociremote.SignedEntity(a.reference, ociremote.WithRemoteOptions(remoteOpts...))
if err != nil {
return fmt.Errorf("failed to fetch signed entity: %w", err)
}

layers, err := signedEntity.Attestations()
if err != nil {
return fmt.Errorf("failed to fetch attestations: %w", err)
}

// Check if using bundles
useBundles := a.hasBundles(ctx)
if useBundles {
sigs, err := layers.Get()
if err != nil {
return fmt.Errorf("failed to get attestation signatures: %w", err)
}
return a.parseAttestationsFromBundles(sigs)
}

// Parse non-bundle attestations
sigs, err := layers.Get()
if err != nil {
return fmt.Errorf("failed to get attestation signatures: %w", err)
}

for _, sig := range sigs {
att, err := attestation.ProvenanceFromSignature(sig)
if err != nil {
return fmt.Errorf("unable to parse untyped provenance: %w", err)
}
t := att.PredicateType()
log.Debugf("Found attestation with predicateType: %s", t)
switch t {
case attestation.PredicateSLSAProvenance:
sp, err := attestation.SLSAProvenanceFromSignature(sig)
if err != nil {
return fmt.Errorf("unable to parse as SLSA v0.2: %w", err)
}
a.attestations = append(a.attestations, sp)

case attestation.PredicateSLSAProvenanceV1:
sp, err := attestation.SLSAProvenanceFromSignatureV1(sig)
if err != nil {
return fmt.Errorf("unable to parse as SLSA v1: %w", err)
}
a.attestations = append(a.attestations, sp)

case attestation.PredicateSpdxDocument:
a.attestations = append(a.attestations, att)

default:
a.attestations = append(a.attestations, att)
}
}
return nil
}

// parseAttestationsFromBundles extracts attestations from Sigstore bundles.
// Bundle-wrapped layers report an incorrect media type, so we unmarshal the
// DSSE envelope from the raw payload directly.
Expand Down
15 changes: 12 additions & 3 deletions internal/image/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,18 @@ func ValidateImage(ctx context.Context, comp app.SnapshotComponent, snap *app.Sn
out.SetImageSignatureCheckFromError(a.ValidateImageSignature(ctx))
}

out.SetAttestationSignatureCheckFromError(a.ValidateAttestationSignature(ctx))
if !out.AttestationSignatureCheck.Passed {
return out, nil
// Handle attestation signature validation
if p.SkipAttSigCheck() {
log.Debug("Attestation signature check skipped, fetching attestations without verification")
if err := a.FetchAttestationsWithoutVerification(ctx); err != nil {
log.Debugf("Failed to fetch attestations without verification: %v", err)
// Continue with validation even if attestation fetch fails
}
Comment on lines +87 to +90
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t suppress attestation fetch failures in skip mode

Line 87 currently swallows registry fetch errors and continues, which can surface misleading downstream results instead of the real retrieval failure. Surface this failure in output and stop this component’s validation path.

Proposed change
 	if p.SkipAttSigCheck() {
 		log.Debug("Attestation signature check skipped, fetching attestations without verification")
 		if err := a.FetchAttestationsWithoutVerification(ctx); err != nil {
-			log.Debugf("Failed to fetch attestations without verification: %v", err)
-			// Continue with validation even if attestation fetch fails
+			out.SetAttestationSyntaxCheckFromError(fmt.Errorf("unable to fetch attestations without verification: %w", err))
+			return out, nil
 		}
 	} else {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/image/validate.go` around lines 87 - 90, The attestation fetch error
is being suppressed in the validation path; change the behavior in the block
that calls a.FetchAttestationsWithoutVerification(ctx) so that instead of only
logging and continuing (the log.Debugf("Failed to fetch attestations without
verification: %v", err) path) you return or propagate a wrapped error to abort
this component's validation; locate the call to
a.FetchAttestationsWithoutVerification and replace the silent-continue with an
immediate return of the error (or fmt.Errorf/Wrap with context) so callers see
the registry fetch failure.

} else {
out.SetAttestationSignatureCheckFromError(a.ValidateAttestationSignature(ctx))
if !out.AttestationSignatureCheck.Passed {
return out, nil
}
}

out.Signatures = a.Signatures()
Expand Down
8 changes: 8 additions & 0 deletions internal/policy/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ type Policy interface {
Spec() ecc.EnterpriseContractPolicySpec
EffectiveTime() time.Time
SkipImageSigCheck() bool
SkipAttSigCheck() bool
AttestationTime(time.Time)
Identity() cosign.Identity
Keyless() bool
Expand All @@ -91,6 +92,7 @@ type policy struct {
identity cosign.Identity
ignoreRekor bool
skipImageSigCheck bool
skipAttSigCheck bool
}

// PublicKeyPEM returns the PublicKey in PEM format. When SigVerifier is not
Expand Down Expand Up @@ -169,6 +171,7 @@ type Options struct {
Identity cosign.Identity
IgnoreRekor bool
SkipImageSigCheck bool
SkipAttSigCheck bool
PolicyRef string
PublicKey string
RekorURL string
Expand Down Expand Up @@ -266,6 +269,7 @@ func NewPolicy(ctx context.Context, opts Options) (Policy, error) {

p.ignoreRekor = opts.IgnoreRekor
p.skipImageSigCheck = opts.SkipImageSigCheck
p.skipAttSigCheck = opts.SkipAttSigCheck

if opts.PublicKey != "" && opts.PublicKey != p.PublicKey {
p.PublicKey = opts.PublicKey
Expand Down Expand Up @@ -409,6 +413,10 @@ func (p policy) SkipImageSigCheck() bool {
return p.skipImageSigCheck
}

func (p policy) SkipAttSigCheck() bool {
return p.skipAttSigCheck
}

func isNow(choosenTime string) bool {
return strings.EqualFold(choosenTime, Now)
}
Expand Down
Loading