Skip to content

Commit

Permalink
feat: replace option for same attestation (#1039)
Browse files Browse the repository at this point in the history
* feat: replace option for same attestation

Signed-off-by: Furkan <furkan.turkal@trendyol.com>
Signed-off-by: Batuhan Apaydın <batuhan.apaydin@trendyol.com>

* feat: add gofmt, goimports check

Signed-off-by: Batuhan Apaydın <batuhan.apaydin@trendyol.com>

Co-authored-by: Furkan <furkan.turkal@trendyol.com>
  • Loading branch information
developer-guy and Dentrax authored Nov 15, 2021
1 parent 5468ddc commit ccc4468
Show file tree
Hide file tree
Showing 11 changed files with 164 additions and 7 deletions.
24 changes: 24 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ else
GOBIN=$(shell go env GOBIN)
endif

GOFILES ?= $(shell find . -type f -name '*.go' -not -path "./vendor/*")

# Set version variables for LDFLAGS
PROJECT_ID ?= projectsigstore
RUNTIME_IMAGE ?= gcr.io/distroless/static
Expand Down Expand Up @@ -55,6 +57,28 @@ export KO_DOCKER_REPO=$(KO_PREFIX)
.PHONY: all lint test clean cosign cross
all: cosign

log-%:
@grep -h -E '^$*:.*?## .*$$' $(MAKEFILE_LIST) | \
awk \
'BEGIN { \
FS = ":.*?## " \
}; \
{ \
printf "\033[36m==> %s\033[0m\n", $$2 \
}'

.PHONY: checkfmt
checkfmt: SHELL := /usr/bin/env bash
checkfmt: ## Check formatting of all go files
@ $(MAKE) --no-print-directory log-$@
$(shell test -z "$(shell gofmt -l $(GOFILES) | tee /dev/stderr)")
$(shell test -z "$(shell goimports -l $(GOFILES) | tee /dev/stderr)")

.PHONY: fmt
fmt: ## Format all go files
@ $(MAKE) --no-print-directory log-$@
goimports -w $(GOFILES)

cosign: $(SRCS)
CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -o $@ ./cmd/cosign

Expand Down
2 changes: 1 addition & 1 deletion cmd/cosign/cli/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down
13 changes: 11 additions & 2 deletions cmd/cosign/cli/attest/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -148,8 +148,17 @@ 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
}
Expand Down
4 changes: 4 additions & 0 deletions cmd/cosign/cli/options/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type AttestOptions struct {
NoUpload bool
Force bool
Recursive bool
Replace bool
Timeout time.Duration

Rekor RekorOptions
Expand Down Expand Up @@ -64,6 +65,9 @@ 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")
}
1 change: 1 addition & 0 deletions doc/cosign_attest.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 64 additions & 0 deletions pkg/cosign/remote/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ package remote
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"os"

"github.com/sigstore/cosign/pkg/oci"
"github.com/sigstore/cosign/pkg/oci/mutate"
Expand All @@ -31,11 +34,20 @@ 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
}

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()
Expand Down Expand Up @@ -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.Fprintln(os.Stderr, "Attestation already present, not adding new one.")
continue
} else {
fmt.Fprintln(os.Stderr, "Attestation not found, adding new attestation.")
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
}
14 changes: 14 additions & 0 deletions pkg/oci/mutate/mutate.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,13 @@ func (si *signedImage) Attestations() (oci.Signatures, error) {
return base, nil
}
}
if si.so.ro != nil {
replace, err := si.so.ro.Replace(base, si.att)
if err != nil {
return nil, err
}
return AppendSignatures(replace)
}
return AppendSignatures(base, si.att)
}

Expand Down Expand Up @@ -281,6 +288,13 @@ func (sii *signedImageIndex) Attestations() (oci.Signatures, error) {
return base, nil
}
}
if sii.so.ro != nil {
replace, err := sii.so.ro.Replace(base, sii.att)
if err != nil {
return nil, err
}
return ReplaceSignatures(replace)
}
return AppendSignatures(base, sii.att)
}

Expand Down
11 changes: 11 additions & 0 deletions pkg/oci/mutate/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -43,3 +48,9 @@ func WithDupeDetector(dd DupeDetector) SignOption {
so.dd = dd
}
}

func WithReplaceOp(ro ReplaceOp) SignOption {
return func(so *signOpts) {
so.ro = ro
}
}
30 changes: 30 additions & 0 deletions pkg/oci/mutate/signatures.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -46,6 +47,35 @@ 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
}

type sigAppender struct {
v1.Image
base oci.Signatures
Expand Down
3 changes: 2 additions & 1 deletion test/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"path/filepath"
"testing"
"time"
ftime "time"

"github.com/google/go-cmp/cmp"
"github.com/google/go-containerregistry/pkg/authn"
Expand Down Expand Up @@ -184,7 +185,7 @@ func TestAttestVerify(t *testing.T) {
// Now attest the image
ko := sign.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc}
must(attest.AttestCmd(ctx, ko, options.RegistryOptions{}, imgName, "", false, slsaAttestationPath, false,
"custom", time.Duration(30*time.Second)), t)
"custom", false, ftime.Duration(30*time.Second)), t)

// Use cue to verify attestation
policyPath := filepath.Join(td, "policy.cue")
Expand Down
5 changes: 2 additions & 3 deletions test/piv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// +build resetyubikey
// +build e2e
// +build !pivkeydisabled
//go:build resetyubikey && e2e && !pivkeydisabled
// +build resetyubikey,e2e,!pivkeydisabled

// DANGER
// This test requires a yubikey to be present. It WILL reset the yubikey to exercise functionality.
Expand Down

0 comments on commit ccc4468

Please sign in to comment.