Skip to content
Draft
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 @@ -248,6 +248,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
}
} 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