diff --git a/cmd/cosign/cli/attest.go b/cmd/cosign/cli/attest.go index 86f59b608e6d..3748eb016365 100644 --- a/cmd/cosign/cli/attest.go +++ b/cmd/cosign/cli/attest.go @@ -71,7 +71,7 @@ func Attest() *cobra.Command { } for _, img := range args { if err := attest.AttestCmd(cmd.Context(), ko, o.Registry, img, o.Cert, o.NoUpload, - o.Predicate.Path, o.Force, o.Predicate.Type, o.Timeout); err != nil { + o.Predicate.Path, o.Force, o.Predicate.Type,o.Replace, o.Timeout); err != nil { return errors.Wrapf(err, "signing %s", img) } } diff --git a/cmd/cosign/cli/attest/attest.go b/cmd/cosign/cli/attest/attest.go index 86efcefe1f39..1f2ff29386d0 100644 --- a/cmd/cosign/cli/attest/attest.go +++ b/cmd/cosign/cli/attest/attest.go @@ -45,7 +45,7 @@ import ( //nolint func AttestCmd(ctx context.Context, ko sign.KeyOpts, regOpts options.RegistryOptions, imageRef string, certPath string, - noUpload bool, predicatePath string, force bool, predicateType string, timeout time.Duration) error { + noUpload bool, predicatePath string, force bool, predicateType string, replace bool, timeout time.Duration) error { // A key file or token is required unless we're in experimental mode! if options.EnableExperimental() { if options.NOf(ko.KeyRef, ko.Sk) > 1 { @@ -148,8 +148,18 @@ func AttestCmd(ctx context.Context, ko sign.KeyOpts, regOpts options.RegistryOpt return err } + + signOpts := []mutate.SignOption{ + mutate.WithDupeDetector(dd), + } + + if replace { + ro := cremote.NewReplaceOp(predicateURI) + signOpts = append(signOpts,mutate.WithReplaceOp(ro)) + } + // Attach the attestation to the entity. - newSE, err := mutate.AttachAttestationToEntity(se, sig, mutate.WithDupeDetector(dd)) + newSE, err := mutate.AttachAttestationToEntity(se, sig, signOpts...) if err != nil { return err } diff --git a/cmd/cosign/cli/options/attest.go b/cmd/cosign/cli/options/attest.go index 251a73afaca1..120aa72f3878 100644 --- a/cmd/cosign/cli/options/attest.go +++ b/cmd/cosign/cli/options/attest.go @@ -28,6 +28,7 @@ type AttestOptions struct { NoUpload bool Force bool Recursive bool + Replace bool Timeout time.Duration Rekor RekorOptions @@ -64,6 +65,10 @@ func (o *AttestOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().BoolVarP(&o.Recursive, "recursive", "r", false, "if a multi-arch image is specified, additionally sign each discrete image") + + cmd.Flags().BoolVarP(&o.Replace, "replace", "", false, + "") + cmd.Flags().DurationVar(&o.Timeout, "timeout", time.Second*30, "HTTP Timeout defaults to 30 seconds") } diff --git a/doc/cosign_attest.md b/doc/cosign_attest.md index 7ce69840ba38..87e501781e1e 100644 --- a/doc/cosign_attest.md +++ b/doc/cosign_attest.md @@ -53,6 +53,7 @@ cosign attest [flags] --predicate string path to the predicate file. -r, --recursive if a multi-arch image is specified, additionally sign each discrete image --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") + --replace --sk whether to use a hardware security key --slot string security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management) --timeout duration HTTP Timeout defaults to 30 seconds (default 30s) diff --git a/pkg/cosign/remote/remote.go b/pkg/cosign/remote/remote.go index fa947d55a90b..06b3c7fe3c98 100644 --- a/pkg/cosign/remote/remote.go +++ b/pkg/cosign/remote/remote.go @@ -18,6 +18,8 @@ package remote import ( "bytes" "encoding/base64" + "encoding/json" + "fmt" "github.com/sigstore/cosign/pkg/oci" "github.com/sigstore/cosign/pkg/oci/mutate" @@ -31,11 +33,21 @@ func NewDupeDetector(v signature.Verifier) mutate.DupeDetector { return &dd{verifier: v} } +func NewReplaceOp(predicateURI string) mutate.ReplaceOp { + return &ro{predicateURI: predicateURI} +} + type dd struct { verifier signature.Verifier } +type ro struct { + predicateURI string + replace bool +} + var _ mutate.DupeDetector = (*dd)(nil) +var _ mutate.ReplaceOp = (*ro)(nil) func (dd *dd) Find(sigImage oci.Signatures, newSig oci.Signature) (oci.Signature, error) { newDigest, err := newSig.Digest() @@ -97,3 +109,55 @@ LayerLoop: } return nil, nil } + +func (r *ro) Replace(signatures oci.Signatures, o oci.Signature) (oci.Signatures, error) { + sigs, err := signatures.Get() + if err != nil { + return nil, err + } + + ros := &replaceOCISignatures{Signatures: signatures} + + sigsCopy := make([]oci.Signature, 0, len(sigs)) + sigsCopy = append(sigsCopy, o) + + if len(sigs) == 0 { + ros.attestations = append(ros.attestations, sigsCopy...) + return ros,nil + } + + for _, s := range sigs { + var payloadData map[string]interface{} + + p, err := s.Payload() + if err != nil { + return nil, fmt.Errorf("could not get payload: %w", err) + } + + err = json.Unmarshal(p, &payloadData) + if err != nil { + return nil, fmt.Errorf("unmarshal payload data: %w", err) + } + + if r.predicateURI == payloadData["payloadType"] { + fmt.Println("same found") + continue + } else { + fmt.Println("adding") + sigsCopy = append(sigsCopy, s) + } + } + + ros.attestations = append(ros.attestations, sigsCopy...) + + return ros, nil +} + +type replaceOCISignatures struct { + oci.Signatures + attestations []oci.Signature +} + +func (r *replaceOCISignatures) Get() ([]oci.Signature, error) { + return r.attestations, nil +} diff --git a/pkg/oci/mutate/mutate.go b/pkg/oci/mutate/mutate.go index 65cd7f728320..0552e85cc225 100644 --- a/pkg/oci/mutate/mutate.go +++ b/pkg/oci/mutate/mutate.go @@ -211,6 +211,13 @@ func (si *signedImage) Attestations() (oci.Signatures, error) { return base, nil } } + if si.so.ro != nil { + if replace, err := si.so.ro.Replace(base, si.att); err != nil { + return nil, err + }else { + return AppendSignatures(replace) + } + } return AppendSignatures(base, si.att) } @@ -281,6 +288,23 @@ func (sii *signedImageIndex) Attestations() (oci.Signatures, error) { return base, nil } } + if sii.so.ro != nil { + if replace, err := sii.so.ro.Replace(base, sii.att); err != nil { + return nil, err + }else { + //sigs, err := base.Get() + //if err != nil { + // return nil, err + //} + // + //if len(sigs) == 0 { + // return AppendSignatures(replace,sii.att) + //}else{ + // return ReplaceSignatures(replace) + //} + return ReplaceSignatures(replace) + } + } return AppendSignatures(base, sii.att) } diff --git a/pkg/oci/mutate/options.go b/pkg/oci/mutate/options.go index 3deb3a1ea07e..458865dc2f3f 100644 --- a/pkg/oci/mutate/options.go +++ b/pkg/oci/mutate/options.go @@ -22,10 +22,15 @@ type DupeDetector interface { Find(oci.Signatures, oci.Signature) (oci.Signature, error) } +type ReplaceOp interface { + Replace(oci.Signatures, oci.Signature) (oci.Signatures, error) +} + type SignOption func(*signOpts) type signOpts struct { dd DupeDetector + ro ReplaceOp } func makeSignOpts(opts ...SignOption) *signOpts { @@ -43,3 +48,9 @@ func WithDupeDetector(dd DupeDetector) SignOption { so.dd = dd } } + +func WithReplaceOp(ro ReplaceOp) SignOption { + return func(so *signOpts) { + so.ro = ro + } +} diff --git a/pkg/oci/mutate/signatures.go b/pkg/oci/mutate/signatures.go index 2ea9b79d011e..cee8285ae55b 100644 --- a/pkg/oci/mutate/signatures.go +++ b/pkg/oci/mutate/signatures.go @@ -17,6 +17,7 @@ package mutate import ( v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/empty" "github.com/google/go-containerregistry/pkg/v1/mutate" "github.com/sigstore/cosign/pkg/oci" ) @@ -46,6 +47,43 @@ func AppendSignatures(base oci.Signatures, sigs ...oci.Signature) (oci.Signature }, nil } +// ReplaceSignatures produces a new oci.Signatures provided by the base signatures +// replaced with the new oci.Signatures. +func ReplaceSignatures(base oci.Signatures) (oci.Signatures, error) { + sigs, err := base.Get() + if err != nil { + return nil, err + } + adds := make([]mutate.Addendum, 0, len(sigs)) + for _, sig := range sigs { + ann, err := sig.Annotations() + if err != nil { + return nil, err + } + adds = append(adds, mutate.Addendum{ + Layer: sig, + Annotations: ann, + }) + } + img, err := mutate.Append(empty.Image, adds...) + if err != nil { + return nil, err + } + return &sigAppender{ + Image: img, + base: base, + sigs: sigs, + }, nil +} + +//func SetSignatures(base oci.Signatures, sigs ...oci.Signature) oci.Signatures { +// return &sigAppender{ +// Image: img, +// base: base, +// sigs: sigs, +// } +//} + type sigAppender struct { v1.Image base oci.Signatures