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
12 changes: 4 additions & 8 deletions cmd/sigstore/initialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,18 @@ func sigstoreInitializeCmd(f sigstoreInitializeFunc) *cobra.Command {
Any updated TUF repository will be written to $HOME/.sigstore/root/<mirror_url>.

Trusted keys and certificate used in ec verification (e.g. verifying Fulcio issued certificates
with Fulcio root CA) are pulled form the trusted metadata.

This command is mostly a wrapper around "cosign initialize".
with Fulcio root CA) are pulled from the trusted metadata.
`),

Example: hd.Doc(`
ec initialize -mirror <url> -out <file>

Initialize root with distributed root keys, default mirror, and default out path.
ec initialize
ec sigstore initialize

Initialize with an out-of-band root key file, using the default mirror.
ec initialize -root <url>
ec sigstore initialize --root <url>

Initialize with an out-of-band root key file and custom repository mirror.
ec initialize -mirror <url> -root <url>
ec sigstore initialize --mirror <url> --root <url>
`),

Args: cobra.NoArgs,
Expand Down
4 changes: 2 additions & 2 deletions cmd/validate/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func validateImageCmd(validate imageValidationFunc) *cobra.Command {

ec validate image --image registry/name:tag

Return a zero status code even if there are validation failures:
Return a zero status code even if there are validation failures:

ec validate image --image registry/name:tag --strict=false

Expand Down Expand Up @@ -502,7 +502,7 @@ func validateImageCmd(validate imageValidationFunc) *cobra.Command {
"URL of the certificate OIDC issuer for keyless verification")

cmd.Flags().StringVar(&data.certificateOIDCIssuerRegExp, "certificate-oidc-issuer-regexp", data.certificateOIDCIssuerRegExp,
"Regular expresssion for the URL of the certificate OIDC issuer for keyless verification")
"Regular expression for the URL of the certificate OIDC issuer for keyless verification")

// Deprecated: images replaced this
cmd.Flags().StringVarP(&data.filePath, "file-path", "f", data.filePath,
Expand Down
12 changes: 4 additions & 8 deletions docs/modules/ROOT/pages/ec_sigstore_initialize.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,22 @@ URL reference. This will enable you to point ec to a separate TUF root.
Any updated TUF repository will be written to $HOME/.sigstore/root/<mirror_url>.

Trusted keys and certificate used in ec verification (e.g. verifying Fulcio issued certificates
with Fulcio root CA) are pulled form the trusted metadata.

This command is mostly a wrapper around "cosign initialize".
with Fulcio root CA) are pulled from the trusted metadata.

[source,shell]
----
ec sigstore initialize [flags]
----

== Examples
ec initialize -mirror <url> -out <file>

Initialize root with distributed root keys, default mirror, and default out path.
ec initialize
ec sigstore initialize

Initialize with an out-of-band root key file, using the default mirror.
ec initialize -root <url>
ec sigstore initialize --root <url>

Initialize with an out-of-band root key file and custom repository mirror.
ec initialize -mirror <url> -root <url>
ec sigstore initialize --mirror <url> --root <url>

== Options

Expand Down
4 changes: 2 additions & 2 deletions docs/modules/ROOT/pages/ec_validate_image.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Return a non-zero status code on validation failure:

ec validate image --image registry/name:tag

Return a zero status code even if there are validation failures:
Return a zero status code even if there are validation failures:

ec validate image --image registry/name:tag --strict=false

Expand Down Expand Up @@ -115,7 +115,7 @@ Use a regular expression to match certificate attributes.
--certificate-identity:: URL of the certificate identity for keyless verification
--certificate-identity-regexp:: Regular expression for the URL of the certificate identity for keyless verification
--certificate-oidc-issuer:: URL of the certificate OIDC issuer for keyless verification
--certificate-oidc-issuer-regexp:: Regular expresssion for the URL of the certificate OIDC issuer for keyless verification
--certificate-oidc-issuer-regexp:: Regular expression for the URL of the certificate OIDC issuer for keyless verification
--color:: Enable color when using text output even when the current terminal does not support it (Default: false)
--effective-time:: Run policy checks with the provided time. Useful for testing rules with
effective dates in the future. The value can be "now" (default) - for
Expand Down
26 changes: 26 additions & 0 deletions internal/attestation/attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,32 @@ func ProvenanceFromSignature(sig oci.Signature) (Attestation, error) {
return provenance{statement: statement, data: embedded, signatures: signatures}, nil
}

// ProvenanceFromBundlePayload parses an attestation from a raw DSSE envelope
// JSON payload as returned by the Sigstore bundle verification path.
func ProvenanceFromBundlePayload(dsseJSON []byte) (Attestation, error) {
var payload cosign.AttestationPayload
if err := json.Unmarshal(dsseJSON, &payload); err != nil {
return nil, fmt.Errorf("malformed bundle attestation: %w", err)
}

if payload.PayLoad == "" {
return nil, errors.New("no `payload` data found in bundle attestation")
}

embedded, err := decodedPayload(payload)
if err != nil {
return nil, err
}

//nolint:staticcheck
var statement in_toto.Statement
if err := json.Unmarshal(embedded, &statement); err != nil {
return nil, fmt.Errorf("malformed bundle attestation: %w", err)
}

return provenance{statement: statement, data: embedded}, nil
}
Comment on lines +146 to +170
Copy link
Contributor

Choose a reason for hiding this comment

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

Action required

3. Bundle attestations drop signatures 🐞 Bug ✓ Correctness

Bundle attestation parsing returns attestations without populating .Signatures(), so policy input
.attestations[].signatures becomes empty/omitted for bundle format. This violates the documented
policy-input contract and can break policies that expect attestation signatures to be present.
Agent Prompt
### Issue description
Bundle-derived attestations are added without `.signatures`, violating the documented policy-input schema and breaking policies that expect signature metadata.

### Issue Context
Legacy path uses `ProvenanceFromSignature` which calls `createEntitySignatures`. The bundle path parses a DSSE envelope but never populates `provenance.signatures`.

### Fix Focus Areas
- internal/attestation/attestation.go[146-170]
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[251-281]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


type provenance struct {
//nolint:staticcheck
statement in_toto.Statement
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import (
app "github.com/konflux-ci/application-api/api/v1alpha1"
"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 @@ -120,6 +122,12 @@ func (a *ApplicationSnapshotImage) SetImageURL(url string) error {
return nil
}

func (a *ApplicationSnapshotImage) hasBundles(ctx context.Context) bool {
regOpts := []ociremote.Option{ociremote.WithRemoteOptions(oci.CreateRemoteOptions(ctx)...)}
bundles, _, err := cosign.GetBundles(ctx, a.reference, regOpts)
return err == nil && len(bundles) > 0
}
Comment on lines +125 to +129
Copy link
Contributor

Choose a reason for hiding this comment

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

Action required

1. hasbundles() ignores getbundles error 📘 Rule violation ⛯ Reliability

hasBundles() silently converts cosign.GetBundles() failures into false, which can route
verification down the legacy path without surfacing the underlying failure. This can mask
registry/network/auth problems and produce misleading verification behavior.
Agent Prompt
## Issue description
`hasBundles()` discards errors from `cosign.GetBundles()` and returns `false`, which can silently trigger the legacy verification path and hide real registry/network/auth failures.

## Issue Context
Bundle detection depends on an external call. Per compliance, failure points should be handled explicitly with actionable context (return error and/or log at an appropriate level).

## Fix Focus Areas
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[125-171]
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[192-206]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


func (a *ApplicationSnapshotImage) FetchImageConfig(ctx context.Context) error {
var err error
a.configJSON, err = config.FetchImageConfig(ctx, a.reference)
Expand All @@ -143,38 +151,58 @@ func (a *ApplicationSnapshotImage) FetchImageFiles(ctx context.Context) error {
return err
}

// ValidateImageSignature executes the cosign.VerifyImageSignature method on the ApplicationSnapshotImage image ref.
// ValidateImageSignature verifies the image signature. For images with Sigstore
// bundles (OCI referrers) the new bundle path is used; otherwise the legacy
// tag-based path is used.
func (a *ApplicationSnapshotImage) ValidateImageSignature(ctx context.Context) error {
// Set the ClaimVerifier on a shallow *copy* of CheckOpts to avoid unexpected side-effects
opts := a.checkOpts
opts.ClaimVerifier = cosign.SimpleClaimVerifier
signatures, _, err := oci.NewClient(ctx).VerifyImageSignatures(a.reference, &opts)
client := oci.NewClient(ctx)

var sigs []cosignOCI.Signature
var err error

if a.hasBundles(ctx) {
opts.NewBundleFormat = true
opts.ClaimVerifier = cosign.IntotoSubjectClaimVerifier
sigs, _, err = client.VerifyImageAttestations(a.reference, &opts)
} else {
opts.ClaimVerifier = cosign.SimpleClaimVerifier
sigs, _, err = client.VerifyImageSignatures(a.reference, &opts)
Comment on lines +164 to +170
Copy link
Contributor

Choose a reason for hiding this comment

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

Action required

2. Signature check verifies attestations 🐞 Bug ⛨ Security

For bundle images, ValidateImageSignature verifies attestations (and uses the in-toto claim
verifier) instead of verifying image signatures, so the ImageSignatureCheck can become semantically
incorrect. This risks passing the “image signature” gate based on attestations rather than actual
image signatures.
Agent Prompt
### Issue description
`ValidateImageSignature` switches to attestation verification for bundle images, which changes the meaning of the “image signature check” and can incorrectly pass it based on attestations.

### Issue Context
The system treats image signatures and attestation signatures as separate checks and documents them as distinct concepts.

### Fix Focus Areas
- internal/evaluation_target/application_snapshot_image/application_snapshot_image.go[154-171]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

}
if err != nil {
return err
}

for _, s := range signatures {
for _, s := range sigs {
es, err := signature.NewEntitySignature(s)
if err != nil {
return err
}
a.signatures = append(a.signatures, es)
}

return nil
}

// ValidateAttestationSignature executes the cosign.VerifyImageAttestations method
// ValidateAttestationSignature verifies and collects in-toto attestations
// attached to the image.
func (a *ApplicationSnapshotImage) ValidateAttestationSignature(ctx context.Context) error {
// Set the ClaimVerifier on a shallow *copy* of CheckOpts to avoid unexpected side-effects
opts := a.checkOpts
opts.ClaimVerifier = cosign.IntotoSubjectClaimVerifier

useBundles := a.hasBundles(ctx)
if useBundles {
opts.NewBundleFormat = true
}

layers, _, err := oci.NewClient(ctx).VerifyImageAttestations(a.reference, &opts)
if err != nil {
return err
}

if useBundles {
return a.parseAttestationsFromBundles(layers)
}

// Extract the signatures from the attestations here in order to also validate that
// the signatures do exist in the expected format.
for _, sig := range layers {
Expand Down Expand Up @@ -220,6 +248,40 @@ func (a *ApplicationSnapshotImage) ValidateAttestationSignature(ctx context.Cont
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.
func (a *ApplicationSnapshotImage) parseAttestationsFromBundles(layers []cosignOCI.Signature) error {
for _, sig := range layers {
payload, err := sig.Payload()
if err != nil {
log.Debugf("Skipping bundle entry: cannot read payload: %v", err)
continue
}
var dsseEnvelope struct {
PayloadType string `json:"payloadType"`
Payload string `json:"payload"`
}
if err := json.Unmarshal(payload, &dsseEnvelope); err != nil {
log.Debugf("Skipping bundle entry: not a valid DSSE envelope: %v", err)
continue
}
if dsseEnvelope.PayloadType != "application/vnd.in-toto+json" {
log.Debugf("Skipping bundle entry with payloadType: %s", dsseEnvelope.PayloadType)
continue
}

att, err := attestation.ProvenanceFromBundlePayload(payload)
if err != nil {
return fmt.Errorf("unable to parse bundle attestation: %w", err)
}
t := att.PredicateType()
log.Debugf("Found bundle attestation with predicateType: %s", t)
a.attestations = append(a.attestations, att)
}
return nil
}

// ValidateAttestationSyntax validates the attestations against known JSON
// schemas, errors out if there are no attestations to check to prevent
// successful syntax check of no inputs, must invoke
Expand Down
4 changes: 2 additions & 2 deletions internal/utils/oci/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func initCache() cache.Cache {
}
}

func createRemoteOptions(ctx context.Context) []remote.Option {
func CreateRemoteOptions(ctx context.Context) []remote.Option {
backoff := remote.Backoff{
Duration: echttp.DefaultBackoff.Duration,
Factor: echttp.DefaultBackoff.Factor,
Expand Down Expand Up @@ -123,7 +123,7 @@ func NewClient(ctx context.Context, opts ...remote.Option) Client {

o := opts
if len(opts) == 0 {
o = createRemoteOptions(ctx)
o = CreateRemoteOptions(ctx)
}

return &defaultClient{ctx, o}
Expand Down
Loading