Skip to content

Commit a6d36b8

Browse files
Fix rekor-cli backwards incompatibility & run harness tests against HEAD (#1030)
* Run harness tests against 2 previous versions and HEAD Signed-off-by: Priya Wadhwa <priya@chainguard.dev> * Only collect stdout Signed-off-by: Priya Wadhwa <priya@chainguard.dev> * Update e2e tests Signed-off-by: Priya Wadhwa <priya@chainguard.dev> * iterate through versions of intoto type if default is specified Signed-off-by: Bob Callaway <bcallaway@google.com> * Skip harness tests if rekor-cli is incompatible with rekor-server version Signed-off-by: Priya Wadhwa <priya@chainguard.dev> * Code review comments Signed-off-by: Priya Wadhwa <priya@chainguard.dev> Signed-off-by: Priya Wadhwa <priya@chainguard.dev> Signed-off-by: Bob Callaway <bcallaway@google.com> Co-authored-by: Bob Callaway <bcallaway@google.com>
1 parent a4f2bd0 commit a6d36b8

File tree

7 files changed

+130
-39
lines changed

7 files changed

+130
-39
lines changed

cmd/rekor-cli/app/pflags_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -762,7 +762,7 @@ func TestParseTypeFlag(t *testing.T) {
762762
{
763763
caseDesc: "explicit intoto v0.0.1",
764764
typeStr: "intoto:0.0.1",
765-
expectSuccess: false,
765+
expectSuccess: true,
766766
},
767767
{
768768
caseDesc: "explicit intoto v0.0.2",

cmd/rekor-cli/app/upload.go

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131

3232
"github.com/sigstore/rekor/cmd/rekor-cli/app/format"
3333
"github.com/sigstore/rekor/pkg/client"
34+
gen_client "github.com/sigstore/rekor/pkg/generated/client"
3435
"github.com/sigstore/rekor/pkg/generated/client/entries"
3536
"github.com/sigstore/rekor/pkg/generated/models"
3637
"github.com/sigstore/rekor/pkg/log"
@@ -73,8 +74,6 @@ var uploadCmd = &cobra.Command{
7374
return nil, err
7475
}
7576
var entry models.ProposedEntry
76-
params := entries.NewCreateLogEntryParams()
77-
params.SetTimeout(viper.GetDuration("timeout"))
7877

7978
entryStr := viper.GetString("entry")
8079
if entryStr != "" {
@@ -111,9 +110,7 @@ var uploadCmd = &cobra.Command{
111110
return nil, fmt.Errorf("error: %w", err)
112111
}
113112
}
114-
params.SetProposedEntry(entry)
115-
116-
resp, err := rekorClient.Entries.CreateLogEntry(params)
113+
resp, err := tryUpload(rekorClient, entry)
117114
if err != nil {
118115
switch e := err.(type) {
119116
case *entries.CreateLogEntryConflict:
@@ -160,6 +157,29 @@ var uploadCmd = &cobra.Command{
160157
}),
161158
}
162159

160+
func tryUpload(rekorClient *gen_client.Rekor, entry models.ProposedEntry) (*entries.CreateLogEntryCreated, error) {
161+
params := entries.NewCreateLogEntryParams()
162+
params.SetTimeout(viper.GetDuration("timeout"))
163+
if pei, ok := entry.(types.ProposedEntryIterator); ok {
164+
params.SetProposedEntry(pei.Get())
165+
} else {
166+
params.SetProposedEntry(entry)
167+
}
168+
resp, err := rekorClient.Entries.CreateLogEntry(params)
169+
if err != nil {
170+
if e, ok := err.(*entries.CreateLogEntryBadRequest); ok {
171+
if pei, ok := entry.(types.ProposedEntryIterator); ok {
172+
if pei.HasNext() {
173+
log.CliLogger.Errorf("failed to upload entry: %v", e)
174+
return tryUpload(rekorClient, pei.GetNext())
175+
}
176+
}
177+
}
178+
return nil, err
179+
}
180+
return resp, nil
181+
}
182+
163183
func init() {
164184
initializePFlagMap()
165185
if err := addArtifactPFlags(uploadCmd); err != nil {

pkg/types/entries.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ type EntryWithAttestationImpl interface {
4545
AttestationKeyValue() (string, []byte) // returns the key to be used when storing the attestation as well as the attestation itself
4646
}
4747

48+
// ProposedEntryIterator is an iterator over a list of proposed entries
49+
type ProposedEntryIterator interface {
50+
models.ProposedEntry
51+
HasNext() bool
52+
Get() models.ProposedEntry
53+
GetNext() models.ProposedEntry
54+
}
55+
4856
// EntryFactory describes a factory function that can generate structs for a specific versioned type
4957
type EntryFactory func() EntryImpl
5058

pkg/types/intoto/intoto.go

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"fmt"
2222

2323
"github.com/sigstore/rekor/pkg/generated/models"
24+
"github.com/sigstore/rekor/pkg/log"
2425
"github.com/sigstore/rekor/pkg/types"
2526
"golang.org/x/exp/slices"
2627
)
@@ -60,9 +61,41 @@ func (it BaseIntotoType) UnmarshalEntry(pe models.ProposedEntry) (types.EntryImp
6061
}
6162

6263
func (it *BaseIntotoType) CreateProposedEntry(ctx context.Context, version string, props types.ArtifactProperties) (models.ProposedEntry, error) {
64+
var head ProposedIntotoEntryIterator
65+
var next *ProposedIntotoEntryIterator
6366
if version == "" {
67+
// get default version as head of list
6468
version = it.DefaultVersion()
69+
ei, err := it.VersionedUnmarshal(nil, version)
70+
if err != nil {
71+
return nil, fmt.Errorf("fetching default Intoto version implementation: %w", err)
72+
}
73+
pe, err := ei.CreateFromArtifactProperties(ctx, props)
74+
if err != nil {
75+
return nil, fmt.Errorf("creating default Intoto entry: %w", err)
76+
}
77+
head.ProposedEntry = pe
78+
next = &head
79+
for _, v := range it.SupportedVersions() {
80+
if v == it.DefaultVersion() {
81+
continue
82+
}
83+
ei, err := it.VersionedUnmarshal(nil, v)
84+
if err != nil {
85+
log.ContextLogger(ctx).Errorf("fetching Intoto version (%v) implementation: %w", v, err)
86+
continue
87+
}
88+
versionedPE, err := ei.CreateFromArtifactProperties(ctx, props)
89+
if err != nil {
90+
log.ContextLogger(ctx).Errorf("error creating Intoto entry of version (%v): %w", v, err)
91+
continue
92+
}
93+
next.next = &ProposedIntotoEntryIterator{versionedPE, nil}
94+
next = next.next.(*ProposedIntotoEntryIterator)
95+
}
96+
return head, nil
6597
}
98+
6699
ei, err := it.VersionedUnmarshal(nil, version)
67100
if err != nil {
68101
return nil, fmt.Errorf("fetching Intoto version implementation: %w", err)
@@ -74,14 +107,29 @@ func (it BaseIntotoType) DefaultVersion() string {
74107
return "0.0.2"
75108
}
76109

77-
// SupportedVersions returns the supported versions for this type;
78-
// it deliberately omits 0.0.1 from the list of supported versions as that
79-
// version did not persist signatures inside the log entry
110+
// SupportedVersions returns the supported versions for this type in the order of preference
80111
func (it BaseIntotoType) SupportedVersions() []string {
81-
return []string{"0.0.2"}
112+
return []string{"0.0.2", "0.0.1"}
82113
}
83114

84115
// IsSupportedVersion returns true if the version can be inserted into the log, and false if not
85116
func (it *BaseIntotoType) IsSupportedVersion(proposedVersion string) bool {
86117
return slices.Contains(it.SupportedVersions(), proposedVersion)
87118
}
119+
120+
type ProposedIntotoEntryIterator struct {
121+
models.ProposedEntry
122+
next models.ProposedEntry
123+
}
124+
125+
func (p ProposedIntotoEntryIterator) HasNext() bool {
126+
return p.next != nil
127+
}
128+
129+
func (p ProposedIntotoEntryIterator) GetNext() models.ProposedEntry {
130+
return p.next
131+
}
132+
133+
func (p ProposedIntotoEntryIterator) Get() models.ProposedEntry {
134+
return p.ProposedEntry
135+
}

tests/e2e_test.go

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,16 @@ import (
3333
"fmt"
3434
"io/ioutil"
3535
"net/http"
36-
"net/url"
3736
"os"
3837
"os/exec"
3938
"path/filepath"
4039
"reflect"
41-
"runtime"
4240
"strconv"
4341
"strings"
4442
"testing"
4543
"time"
4644

4745
"golang.org/x/sync/errgroup"
48-
"sigs.k8s.io/release-utils/version"
4946

5047
"github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer"
5148
"github.com/go-openapi/strfmt"
@@ -524,11 +521,6 @@ func TestIntoto(t *testing.T) {
524521
write(t, string(eb), attestationPath)
525522
write(t, ecdsaPub, pubKeyPath)
526523

527-
// ensure that we can't upload a intoto v0.0.1 entry
528-
v001out := runCliErr(t, "upload", "--artifact", attestationPath, "--type", "intoto:0.0.1", "--public-key", pubKeyPath)
529-
outputContains(t, v001out, "type intoto does not support version 0.0.1")
530-
531-
// If we do it twice, it should already exist
532524
out := runCli(t, "upload", "--artifact", attestationPath, "--type", "intoto", "--public-key", pubKeyPath)
533525
outputContains(t, out, "Created entry at")
534526
uuid := getUUIDFromUploadOutput(t, out)
@@ -658,11 +650,6 @@ func TestIntotoMultiSig(t *testing.T) {
658650
write(t, ecdsaPub, ecdsapubKeyPath)
659651
write(t, pubKey, rsapubKeyPath)
660652

661-
// ensure that we can't upload a intoto v0.0.1 entry
662-
v001out := runCliErr(t, "upload", "--artifact", attestationPath, "--type", "intoto:0.0.1", "--public-key", ecdsapubKeyPath, "--public-key", rsapubKeyPath)
663-
outputContains(t, v001out, "type intoto does not support version 0.0.1")
664-
665-
// If we do it twice, it should already exist
666653
out := runCli(t, "upload", "--artifact", attestationPath, "--type", "intoto", "--public-key", ecdsapubKeyPath, "--public-key", rsapubKeyPath)
667654
outputContains(t, out, "Created entry at")
668655
uuid := getUUIDFromUploadOutput(t, out)
@@ -706,6 +693,7 @@ func TestIntotoMultiSig(t *testing.T) {
706693

707694
}
708695

696+
/*
709697
func TestIntotoBlockV001(t *testing.T) {
710698
td := t.TempDir()
711699
attestationPath := filepath.Join(td, "attestation.json")
@@ -795,13 +783,11 @@ func TestIntotoBlockV001(t *testing.T) {
795783
params.SetProposedEntry(entry)
796784
797785
_, err = rekorClient.Entries.CreateLogEntry(params)
798-
if err == nil {
799-
t.Fatal("insertion of v0.0.1 entry should fail")
800-
}
801-
if !strings.Contains(err.Error(), "entry kind 'intoto' does not support inserting entries of version '0.0.1'") {
802-
t.Errorf("Expected error as intoto v0.0.1 should not be allowed to be entered into rekor")
786+
if err != nil {
787+
t.Fatalf("failed inserting v0.0.1 entry: %v", err)
803788
}
804789
}
790+
*/
805791

806792
func TestTimestampArtifact(t *testing.T) {
807793
var out string

tests/harness_test.go

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,12 @@ func TestHarnessAddEntry(t *testing.T) {
7777
uuid := getUUIDFromUploadOutput(t, out)
7878
logIndex := getLogIndexFromUploadOutput(t, out)
7979

80-
// Now we should be able to verify it.
81-
out = runCli(t, "verify", "--type=hashedrekord", "--pki-format=x509", "--artifact-hash", dataSHA, "--signature", sigPath, "--public-key", pubPath)
82-
outputContains(t, out, "Inclusion Proof:")
80+
if !rekorCLIIncompatible() {
81+
// Now we should be able to verify it.
82+
out = runCli(t, "verify", "--type=hashedrekord", "--pki-format=x509", "--artifact-hash", dataSHA, "--signature", sigPath, "--public-key", pubPath)
83+
outputContains(t, out, "Inclusion Proof:")
84+
}
85+
8386
saveEntry(t, logIndex, StoredEntry{UUID: uuid})
8487
}
8588

@@ -150,12 +153,12 @@ func TestHarnessAddIntoto(t *testing.T) {
150153
write(t, ecdsaPub, pubKeyPath)
151154

152155
// If we do it twice, it should already exist
153-
out := runCli(t, "upload", "--artifact", attestationPath, "--type", "intoto", "--public-key", pubKeyPath)
156+
out := runCliStdout(t, "upload", "--artifact", attestationPath, "--type", "intoto", "--public-key", pubKeyPath)
154157
outputContains(t, out, "Created entry at")
155158
uuid := getUUIDFromUploadOutput(t, out)
156159
logIndex := getLogIndexFromUploadOutput(t, out)
157160

158-
out = runCli(t, "get", "--uuid", uuid, "--format=json")
161+
out = runCli(t, "get", "--log-index", fmt.Sprintf("%d", logIndex), "--format=json")
159162
g := getOut{}
160163
if err := json.Unmarshal([]byte(out), &g); err != nil {
161164
t.Fatal(err)
@@ -265,11 +268,16 @@ func TestHarnessGetAllEntriesLogIndex(t *testing.T) {
265268
}
266269

267270
func TestHarnessGetAllEntriesUUID(t *testing.T) {
271+
if rekorCLIIncompatible() {
272+
t.Skipf("Skipping getting entries by UUID, old rekor-cli version %s is incompatible with server version %s", os.Getenv("CLI_VERSION"), os.Getenv("SERVER_VERSION"))
273+
}
274+
268275
treeSize := activeTreeSize(t)
269276
if treeSize == 0 {
270277
t.Fatal("There are 0 entries in the log, there should be at least 2")
271278
}
272279
_, entries := getEntries(t)
280+
273281
for _, e := range entries {
274282
outUUID := runCli(t, "get", "--uuid", e.UUID, "--format", "json")
275283
outEntryID := runCli(t, "get", "--uuid", entryID(t, e.UUID), "--format", "json")
@@ -294,6 +302,9 @@ func TestHarnessGetAllEntriesUUID(t *testing.T) {
294302
}
295303

296304
func entryID(t *testing.T, uuid string) string {
305+
if sharding.ValidateEntryID(uuid) == nil {
306+
return uuid
307+
}
297308
treeID, err := strconv.Atoi(os.Getenv("TREE_ID"))
298309
if err != nil {
299310
t.Fatal(err)
@@ -317,3 +328,14 @@ func activeTreeSize(t *testing.T) int {
317328
}
318329
return s.ActiveTreeSize
319330
}
331+
332+
// Check if we have a new server version and an old CLI version
333+
// since the new server returns an EntryID but the old CLI version expects a UUID
334+
func rekorCLIIncompatible() bool {
335+
if sv := os.Getenv("SERVER_VERSION"); sv != "v0.10.0" && sv != "v0.11.0" {
336+
if cv := os.Getenv("CLI_VERSION"); cv == "v0.10.0" || cv == "v0.11.0" {
337+
return true
338+
}
339+
}
340+
return false
341+
}

tests/rekor-harness.sh

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ function run_tests () {
7575
trap "rm -rf $REKORTMPDIR" EXIT
7676

7777
go clean -testcache
78-
if ! REKORTMPDIR=$REKORTMPDIR go test -run TestHarness -v -tags=e2e ./tests/ ; then
78+
if ! REKORTMPDIR=$REKORTMPDIR SERVER_VERSION=$1 CLI_VERSION=$2 go test -run TestHarness -v -tags=e2e ./tests/ ; then
7979
docker-compose logs --no-color > /tmp/docker-compose.log
8080
exit 1
8181
fi
@@ -87,10 +87,17 @@ function run_tests () {
8787
fi
8888
}
8989

90-
# Get last 3 server versions
90+
# Get last 2 server versions
9191
git fetch origin
92-
NUM_VERSIONS_TO_TEST=3
92+
NUM_VERSIONS_TO_TEST=2
9393
VERSIONS=$(git tag --sort=-version:refname | head -n $NUM_VERSIONS_TO_TEST | tac)
94+
95+
# Also add the commit @ HEAD
96+
HEAD=$(git log --pretty="%H" -n 1 )
97+
echo "Also testing at HEAD at commit $HEAD"
98+
99+
VERSIONS="$VERSIONS $HEAD"
100+
94101
echo $VERSIONS
95102

96103
export REKOR_HARNESS_TMPDIR="$(mktemp -d -t rekor_test_harness.XXXXXX)"
@@ -105,17 +112,17 @@ do
105112
echo "Running tests with server version $server_version and CLI version $cli_version"
106113

107114
build_cli $cli_version
108-
run_tests
115+
run_tests $server_version $cli_version
109116

110117
echo "Tests passed successfully."
111118
echo "======================================================="
112119
done
113120
done
114121

115-
# Since we add two entries to the log for every test, once all tests are run we should have 2*($NUM_VERSIONS_TO_TEST^2) entries
122+
# Since we add two entries to the log for every test, once all tests are run we should have 2*(($NUM_VERSIONS_TO_TEST+1)^2) entries
116123
make rekor-cli
117124
actual=$(./rekor-cli loginfo --rekor_server http://localhost:3000 --format json --store_tree_state=false | jq -r .ActiveTreeSize)
118-
expected=$((2*$NUM_VERSIONS_TO_TEST*$NUM_VERSIONS_TO_TEST))
125+
expected=$((2*(1+$NUM_VERSIONS_TO_TEST)*(1+$NUM_VERSIONS_TO_TEST)))
119126
if [[ ! "$expected" == "$actual" ]]; then
120127
echo "ERROR: Log had $actual entries instead of expected $expected"
121128
exit 1

0 commit comments

Comments
 (0)