From 2e11e4b39d88cc1d757f02b8a887744a8d5f6ffa Mon Sep 17 00:00:00 2001 From: Bob Callaway Date: Wed, 7 Sep 2022 09:13:30 -0400 Subject: [PATCH] iterate through versions of intoto type if default is specified Signed-off-by: Bob Callaway --- cmd/rekor-cli/app/pflags_test.go | 2 +- cmd/rekor-cli/app/upload.go | 30 ++++++++++++++--- pkg/types/entries.go | 8 +++++ pkg/types/intoto/intoto.go | 56 +++++++++++++++++++++++++++++--- tests/e2e_test.go | 13 ++------ 5 files changed, 88 insertions(+), 21 deletions(-) diff --git a/cmd/rekor-cli/app/pflags_test.go b/cmd/rekor-cli/app/pflags_test.go index 2e3501774..726138ac1 100644 --- a/cmd/rekor-cli/app/pflags_test.go +++ b/cmd/rekor-cli/app/pflags_test.go @@ -762,7 +762,7 @@ func TestParseTypeFlag(t *testing.T) { { caseDesc: "explicit intoto v0.0.1", typeStr: "intoto:0.0.1", - expectSuccess: false, + expectSuccess: true, }, { caseDesc: "explicit intoto v0.0.2", diff --git a/cmd/rekor-cli/app/upload.go b/cmd/rekor-cli/app/upload.go index a5ec22340..b3b3c71c5 100644 --- a/cmd/rekor-cli/app/upload.go +++ b/cmd/rekor-cli/app/upload.go @@ -31,6 +31,7 @@ import ( "github.com/sigstore/rekor/cmd/rekor-cli/app/format" "github.com/sigstore/rekor/pkg/client" + gen_client "github.com/sigstore/rekor/pkg/generated/client" "github.com/sigstore/rekor/pkg/generated/client/entries" "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/rekor/pkg/log" @@ -73,8 +74,6 @@ var uploadCmd = &cobra.Command{ return nil, err } var entry models.ProposedEntry - params := entries.NewCreateLogEntryParams() - params.SetTimeout(viper.GetDuration("timeout")) entryStr := viper.GetString("entry") if entryStr != "" { @@ -111,9 +110,7 @@ var uploadCmd = &cobra.Command{ return nil, fmt.Errorf("error: %w", err) } } - params.SetProposedEntry(entry) - - resp, err := rekorClient.Entries.CreateLogEntry(params) + resp, err := tryUpload(rekorClient, entry) if err != nil { switch e := err.(type) { case *entries.CreateLogEntryConflict: @@ -160,6 +157,29 @@ var uploadCmd = &cobra.Command{ }), } +func tryUpload(rekorClient *gen_client.Rekor, entry models.ProposedEntry) (*entries.CreateLogEntryCreated, error) { + params := entries.NewCreateLogEntryParams() + params.SetTimeout(viper.GetDuration("timeout")) + if pei, ok := entry.(types.ProposedEntryIterator); ok { + params.SetProposedEntry(pei.Get()) + } else { + params.SetProposedEntry(entry) + } + resp, err := rekorClient.Entries.CreateLogEntry(params) + if err != nil { + if e, ok := err.(*entries.CreateLogEntryBadRequest); ok { + if pei, ok := entry.(types.ProposedEntryIterator); ok { + if pei.HasNext() { + log.CliLogger.Errorf("failed to upload entry: %v", e) + return tryUpload(rekorClient, pei.GetNext()) + } + } + } + return nil, err + } + return resp, nil +} + func init() { initializePFlagMap() if err := addArtifactPFlags(uploadCmd); err != nil { diff --git a/pkg/types/entries.go b/pkg/types/entries.go index 55559752d..4b0ee5b98 100644 --- a/pkg/types/entries.go +++ b/pkg/types/entries.go @@ -45,6 +45,14 @@ type EntryWithAttestationImpl interface { AttestationKeyValue() (string, []byte) // returns the key to be used when storing the attestation as well as the attestation itself } +// ProposedEntryIterator is an iterator over a list of proposed entries +type ProposedEntryIterator interface { + models.ProposedEntry + HasNext() bool + Get() models.ProposedEntry + GetNext() models.ProposedEntry +} + // EntryFactory describes a factory function that can generate structs for a specific versioned type type EntryFactory func() EntryImpl diff --git a/pkg/types/intoto/intoto.go b/pkg/types/intoto/intoto.go index 81d2ceeaa..3905ecec4 100644 --- a/pkg/types/intoto/intoto.go +++ b/pkg/types/intoto/intoto.go @@ -21,6 +21,7 @@ import ( "fmt" "github.com/sigstore/rekor/pkg/generated/models" + "github.com/sigstore/rekor/pkg/log" "github.com/sigstore/rekor/pkg/types" "golang.org/x/exp/slices" ) @@ -60,9 +61,41 @@ func (it BaseIntotoType) UnmarshalEntry(pe models.ProposedEntry) (types.EntryImp } func (it *BaseIntotoType) CreateProposedEntry(ctx context.Context, version string, props types.ArtifactProperties) (models.ProposedEntry, error) { + var head ProposedIntotoEntryIterator + var next *ProposedIntotoEntryIterator if version == "" { + // get default version as head of list version = it.DefaultVersion() + ei, err := it.VersionedUnmarshal(nil, version) + if err != nil { + return nil, fmt.Errorf("fetching default Intoto version implementation: %w", err) + } + pe, err := ei.CreateFromArtifactProperties(ctx, props) + if err != nil { + return nil, fmt.Errorf("creating default Intoto entry: %w", err) + } + head.ProposedEntry = pe + next = &head + for _, v := range it.SupportedVersions() { + if v == it.DefaultVersion() { + continue + } + ei, err := it.VersionedUnmarshal(nil, v) + if err != nil { + log.Logger.Errorf("fetching Intoto version (%v) implementation: %w", v, err) + continue + } + versionedPE, err := ei.CreateFromArtifactProperties(ctx, props) + if err != nil { + log.Logger.Errorf("error creating Intoto entry of version (%v): %w", v, err) + continue + } + next.next = &ProposedIntotoEntryIterator{versionedPE, nil} + next = next.next.(*ProposedIntotoEntryIterator) + } + return head, nil } + ei, err := it.VersionedUnmarshal(nil, version) if err != nil { return nil, fmt.Errorf("fetching Intoto version implementation: %w", err) @@ -74,14 +107,29 @@ func (it BaseIntotoType) DefaultVersion() string { return "0.0.2" } -// SupportedVersions returns the supported versions for this type; -// it deliberately omits 0.0.1 from the list of supported versions as that -// version did not persist signatures inside the log entry +// SupportedVersions returns the supported versions for this type in the order of preference func (it BaseIntotoType) SupportedVersions() []string { - return []string{"0.0.2"} + return []string{"0.0.2", "0.0.1"} } // IsSupportedVersion returns true if the version can be inserted into the log, and false if not func (it *BaseIntotoType) IsSupportedVersion(proposedVersion string) bool { return slices.Contains(it.SupportedVersions(), proposedVersion) } + +type ProposedIntotoEntryIterator struct { + models.ProposedEntry + next models.ProposedEntry +} + +func (p ProposedIntotoEntryIterator) HasNext() bool { + return p.next != nil +} + +func (p ProposedIntotoEntryIterator) GetNext() models.ProposedEntry { + return p.next +} + +func (p ProposedIntotoEntryIterator) Get() models.ProposedEntry { + return p.ProposedEntry +} diff --git a/tests/e2e_test.go b/tests/e2e_test.go index 0cd9ec732..c9c6d9a55 100644 --- a/tests/e2e_test.go +++ b/tests/e2e_test.go @@ -33,19 +33,16 @@ import ( "fmt" "io/ioutil" "net/http" - "net/url" "os" "os/exec" "path/filepath" "reflect" - "runtime" "strconv" "strings" "testing" "time" "golang.org/x/sync/errgroup" - "sigs.k8s.io/release-utils/version" "github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer" "github.com/go-openapi/strfmt" @@ -524,10 +521,6 @@ func TestIntoto(t *testing.T) { write(t, string(eb), attestationPath) write(t, ecdsaPub, pubKeyPath) - // ensure that we can't upload a intoto v0.0.1 entry - v001out := runCliErr(t, "upload", "--artifact", attestationPath, "--type", "intoto:0.0.1", "--public-key", pubKeyPath) - outputContains(t, v001out, "type intoto does not support version 0.0.1") - // If we do it twice, it should already exist out := runCli(t, "upload", "--artifact", attestationPath, "--type", "intoto", "--public-key", pubKeyPath) outputContains(t, out, "Created entry at") @@ -658,10 +651,6 @@ func TestIntotoMultiSig(t *testing.T) { write(t, ecdsaPub, ecdsapubKeyPath) write(t, pubKey, rsapubKeyPath) - // ensure that we can't upload a intoto v0.0.1 entry - v001out := runCliErr(t, "upload", "--artifact", attestationPath, "--type", "intoto:0.0.1", "--public-key", ecdsapubKeyPath, "--public-key", rsapubKeyPath) - outputContains(t, v001out, "type intoto does not support version 0.0.1") - // If we do it twice, it should already exist out := runCli(t, "upload", "--artifact", attestationPath, "--type", "intoto", "--public-key", ecdsapubKeyPath, "--public-key", rsapubKeyPath) outputContains(t, out, "Created entry at") @@ -706,6 +695,7 @@ func TestIntotoMultiSig(t *testing.T) { } +/* func TestIntotoBlockV001(t *testing.T) { td := t.TempDir() attestationPath := filepath.Join(td, "attestation.json") @@ -802,6 +792,7 @@ func TestIntotoBlockV001(t *testing.T) { t.Errorf("Expected error as intoto v0.0.1 should not be allowed to be entered into rekor") } } +*/ func TestTimestampArtifact(t *testing.T) { var out string