From 7b7304c402705ef2a3a859b74188629a366780df Mon Sep 17 00:00:00 2001 From: aalsabag <43807484+aalsabag@users.noreply.github.com> Date: Sat, 13 Jan 2024 13:34:51 -0600 Subject: [PATCH] Allow for option in cosign attest and attest-blob to upload attestation as supported in Rekor (#3466) * adding conditional to upload intoto attestation if type equals intoto Signed-off-by: Ahmed Alsabag * adding new store-attestation flag to upload attestation Signed-off-by: Ahmed Alsabag * adding attest and attest-blob documentation for store-attestation flag Signed-off-by: Ahmed Alsabag * changing flag name to --rekor-entry-type Signed-off-by: Ahmed Alsabag * fixing broken unit tests missing rekor-entry-type Signed-off-by: Ahmed Alsabag * fixing rekor-entry-type e2e tests Signed-off-by: Ahmed Alsabag * fixing rekor-entry-type e2e tests part 2 Signed-off-by: Ahmed Alsabag --------- Signed-off-by: Ahmed Alsabag --- cmd/cosign/cli/attest.go | 1 + cmd/cosign/cli/attest/attest.go | 30 +++-- cmd/cosign/cli/attest/attest_blob.go | 15 ++- cmd/cosign/cli/attest/attest_blob_test.go | 44 +++++++- cmd/cosign/cli/attest_blob.go | 1 + cmd/cosign/cli/options/attest.go | 4 + cmd/cosign/cli/options/attest_blob.go | 5 + doc/cosign_attest-blob.md | 1 + doc/cosign_attest.md | 1 + test/e2e_test.go | 129 ++++++++++++---------- 10 files changed, 157 insertions(+), 74 deletions(-) diff --git a/cmd/cosign/cli/attest.go b/cmd/cosign/cli/attest.go index 325c211d983..73b473a6ab2 100644 --- a/cmd/cosign/cli/attest.go +++ b/cmd/cosign/cli/attest.go @@ -95,6 +95,7 @@ func Attest() *cobra.Command { Replace: o.Replace, Timeout: ro.Timeout, TlogUpload: o.TlogUpload, + RekorEntryType: o.RekorEntryType, } for _, img := range args { diff --git a/cmd/cosign/cli/attest/attest.go b/cmd/cosign/cli/attest/attest.go index 5806c10c275..0f1be95ae91 100644 --- a/cmd/cosign/cli/attest/attest.go +++ b/cmd/cosign/cli/attest/attest.go @@ -71,15 +71,16 @@ func uploadToTlog(ctx context.Context, sv *sign.SignerVerifier, rekorURL string, type AttestCommand struct { options.KeyOpts options.RegistryOptions - CertPath string - CertChainPath string - NoUpload bool - PredicatePath string - PredicateType string - Replace bool - Timeout time.Duration - TlogUpload bool - TSAServerURL string + CertPath string + CertChainPath string + NoUpload bool + PredicatePath string + PredicateType string + Replace bool + Timeout time.Duration + TlogUpload bool + TSAServerURL string + RekorEntryType string } // nolint @@ -93,6 +94,10 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error { return fmt.Errorf("predicate cannot be empty") } + if c.RekorEntryType != "dsse" && c.RekorEntryType != "intoto" { + return fmt.Errorf("unknown value for rekor-entry-type") + } + predicateURI, err := options.ParsePredicateType(c.PredicateType) if err != nil { return err @@ -197,7 +202,12 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error { } if shouldUpload { bundle, err := uploadToTlog(ctx, sv, c.RekorURL, func(r *client.Rekor, b []byte) (*models.LogEntryAnon, error) { - return cosign.TLogUploadDSSEEnvelope(ctx, r, signedPayload, b) + if c.RekorEntryType == "intoto" { + return cosign.TLogUploadInTotoAttestation(ctx, r, signedPayload, b) + } else { + return cosign.TLogUploadDSSEEnvelope(ctx, r, signedPayload, b) + } + }) if err != nil { return err diff --git a/cmd/cosign/cli/attest/attest_blob.go b/cmd/cosign/cli/attest/attest_blob.go index 8b28a655703..a8a69362146 100644 --- a/cmd/cosign/cli/attest/attest_blob.go +++ b/cmd/cosign/cli/attest/attest_blob.go @@ -39,6 +39,7 @@ import ( "github.com/sigstore/cosign/v2/pkg/cosign/attestation" cbundle "github.com/sigstore/cosign/v2/pkg/cosign/bundle" "github.com/sigstore/cosign/v2/pkg/types" + "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" "github.com/sigstore/sigstore/pkg/signature/dsse" @@ -62,6 +63,8 @@ type AttestBlobCommand struct { OutputSignature string OutputAttestation string OutputCertificate string + + RekorEntryType string } // nolint @@ -75,6 +78,10 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error return fmt.Errorf("predicate cannot be empty") } + if c.RekorEntryType != "dsse" && c.RekorEntryType != "intoto" { + return fmt.Errorf("unknown value for rekor-entry-type") + } + if c.Timeout != 0 { var cancelFn context.CancelFunc ctx, cancelFn = context.WithTimeout(ctx, c.Timeout) @@ -182,7 +189,13 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error if err != nil { return err } - entry, err := cosign.TLogUploadDSSEEnvelope(ctx, rekorClient, sig, rekorBytes) + var entry *models.LogEntryAnon + if c.RekorEntryType == "intoto" { + entry, err = cosign.TLogUploadInTotoAttestation(ctx, rekorClient, sig, rekorBytes) + } else { + entry, err = cosign.TLogUploadDSSEEnvelope(ctx, rekorClient, sig, rekorBytes) + } + if err != nil { return err } diff --git a/cmd/cosign/cli/attest/attest_blob_test.go b/cmd/cosign/cli/attest/attest_blob_test.go index a62b0eaea2d..dd695d0e4ad 100644 --- a/cmd/cosign/cli/attest/attest_blob_test.go +++ b/cmd/cosign/cli/attest/attest_blob_test.go @@ -161,11 +161,12 @@ func TestAttestBlobCmdLocalKeyAndCert(t *testing.T) { } { t.Run(tc.name, func(t *testing.T) { at := AttestBlobCommand{ - KeyOpts: options.KeyOpts{KeyRef: tc.keyref}, - CertPath: tc.certref, - CertChainPath: tc.certchainref, - PredicatePath: predicatePath, - PredicateType: predicateType, + KeyOpts: options.KeyOpts{KeyRef: tc.keyref}, + CertPath: tc.certref, + CertChainPath: tc.certchainref, + PredicatePath: predicatePath, + PredicateType: predicateType, + RekorEntryType: "dsse", } err := at.Exec(ctx, blob) if err != nil { @@ -213,6 +214,7 @@ func TestAttestBlob(t *testing.T) { PredicatePath: predicatePath, PredicateType: predicateType, OutputSignature: dssePath, + RekorEntryType: "dsse", } err := at.Exec(ctx, blobPath) if err != nil { @@ -261,3 +263,35 @@ func TestAttestBlob(t *testing.T) { }) } } + +func TestBadRekorEntryType(t *testing.T) { + ctx := context.Background() + td := t.TempDir() + + keys, _ := cosign.GenerateKeyPair(nil) + keyRef := writeFile(t, td, string(keys.PrivateBytes), "key.pem") + + blob := []byte("foo") + blobPath := writeFile(t, td, string(blob), "foo.txt") + + predicates := map[string]string{} + predicates["slsaprovenance"] = makeSLSA02PredicateFile(t, td) + predicates["slsaprovenance1"] = makeSLSA1PredicateFile(t, td) + + for predicateType, predicatePath := range predicates { + t.Run(predicateType, func(t *testing.T) { + dssePath := filepath.Join(td, "dsse.intoto.jsonl") + at := AttestBlobCommand{ + KeyOpts: options.KeyOpts{KeyRef: keyRef}, + PredicatePath: predicatePath, + PredicateType: predicateType, + OutputSignature: dssePath, + RekorEntryType: "badvalue", + } + err := at.Exec(ctx, blobPath) + if err == nil || err.Error() != "unknown value for rekor-entry-type" { + t.Fatal("expected an error due to unknown rekor entry type") + } + }) + } +} diff --git a/cmd/cosign/cli/attest_blob.go b/cmd/cosign/cli/attest_blob.go index 66f904ccb68..3e7c6fe36b4 100644 --- a/cmd/cosign/cli/attest_blob.go +++ b/cmd/cosign/cli/attest_blob.go @@ -85,6 +85,7 @@ func AttestBlob() *cobra.Command { OutputAttestation: o.OutputAttestation, OutputCertificate: o.OutputCertificate, Timeout: ro.Timeout, + RekorEntryType: o.RekorEntryType, } return v.Exec(cmd.Context(), args[0]) }, diff --git a/cmd/cosign/cli/options/attest.go b/cmd/cosign/cli/options/attest.go index cb1a2756926..9090ce1d9c6 100644 --- a/cmd/cosign/cli/options/attest.go +++ b/cmd/cosign/cli/options/attest.go @@ -30,6 +30,7 @@ type AttestOptions struct { SkipConfirmation bool TlogUpload bool TSAServerURL string + RekorEntryType string Rekor RekorOptions Fulcio FulcioOptions @@ -80,6 +81,9 @@ func (o *AttestOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().BoolVar(&o.TlogUpload, "tlog-upload", true, "whether or not to upload to the tlog") + cmd.Flags().StringVar(&o.RekorEntryType, "rekor-entry-type", "dsse", + "specifies the type to be used for a rekor entry upload. Options are intoto or dsse (default). ") + cmd.Flags().StringVar(&o.TSAServerURL, "timestamp-server-url", "", "url to the Timestamp RFC3161 server, default none. Must be the path to the API to request timestamp responses, e.g. https://freetsa.org/tsr") } diff --git a/cmd/cosign/cli/options/attest_blob.go b/cmd/cosign/cli/options/attest_blob.go index e960884af74..593c787eee5 100644 --- a/cmd/cosign/cli/options/attest_blob.go +++ b/cmd/cosign/cli/options/attest_blob.go @@ -37,6 +37,8 @@ type AttestBlobOptions struct { OutputCertificate string BundlePath string + RekorEntryType string + Rekor RekorOptions Fulcio FulcioOptions OIDC OIDCOptions @@ -92,6 +94,9 @@ func (o *AttestBlobOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().BoolVar(&o.TlogUpload, "tlog-upload", true, "whether or not to upload to the tlog") + cmd.Flags().StringVar(&o.RekorEntryType, "rekor-entry-type", "dsse", + "specifies the type to be used for a rekor entry upload. Options are intoto or dsse (default). ") + cmd.Flags().StringVar(&o.TSAServerURL, "timestamp-server-url", "", "url to the Timestamp RFC3161 server, default none. Must be the path to the API to request timestamp responses, e.g. https://freetsa.org/tsr") diff --git a/doc/cosign_attest-blob.md b/doc/cosign_attest-blob.md index 6ecb6322215..515999fecf3 100644 --- a/doc/cosign_attest-blob.md +++ b/doc/cosign_attest-blob.md @@ -52,6 +52,7 @@ cosign attest-blob [flags] --output-certificate string write the certificate to FILE --output-signature string write the signature to FILE --predicate string path to the predicate file. + --rekor-entry-type string specifies the type to be used for a rekor entry upload. Options are intoto or dsse (default). (default "dsse") --rekor-url string address of rekor STL server (default "https://rekor.sigstore.dev") --rfc3161-timestamp-bundle string path to an RFC 3161 timestamp bundle FILE --sk whether to use a hardware security key diff --git a/doc/cosign_attest.md b/doc/cosign_attest.md index 613095a20e9..95a436d2784 100644 --- a/doc/cosign_attest.md +++ b/doc/cosign_attest.md @@ -65,6 +65,7 @@ cosign attest [flags] --registry-password string registry basic auth password --registry-token string registry bearer auth token --registry-username string registry basic auth username + --rekor-entry-type string specifies the type to be used for a rekor entry upload. Options are intoto or dsse (default). (default "dsse") --rekor-url string address of rekor STL server (default "https://rekor.sigstore.dev") --replace --sk whether to use a hardware security key diff --git a/test/e2e_test.go b/test/e2e_test.go index bef1e0db965..9dcf277eea8 100644 --- a/test/e2e_test.go +++ b/test/e2e_test.go @@ -354,10 +354,11 @@ func attestVerify(t *testing.T, predicateType, attestation, goodCue, badCue stri // Now attest the image ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} attestCmd := attest.AttestCommand{ - KeyOpts: ko, - PredicatePath: attestationPath, - PredicateType: predicateType, - Timeout: 30 * time.Second, + KeyOpts: ko, + PredicatePath: attestationPath, + PredicateType: predicateType, + Timeout: 30 * time.Second, + RekorEntryType: "dsse", } must(attestCmd.Exec(ctx, imgName), t) @@ -443,21 +444,23 @@ func TestAttestationDownload(t *testing.T) { // Attest to create a slsa attestation attestCommand := attest.AttestCommand{ - KeyOpts: ko, - PredicatePath: slsaAttestationPath, - PredicateType: "slsaprovenance", - Timeout: 30 * time.Second, - Replace: true, + KeyOpts: ko, + PredicatePath: slsaAttestationPath, + PredicateType: "slsaprovenance", + Timeout: 30 * time.Second, + Replace: true, + RekorEntryType: "dsse", } must(attestCommand.Exec(ctx, imgName), t) // Attest to create a vuln attestation attestCommand = attest.AttestCommand{ - KeyOpts: ko, - PredicatePath: vulnAttestationPath, - PredicateType: "vuln", - Timeout: 30 * time.Second, - Replace: true, + KeyOpts: ko, + PredicatePath: vulnAttestationPath, + PredicateType: "vuln", + Timeout: 30 * time.Second, + Replace: true, + RekorEntryType: "dsse", } must(attestCommand.Exec(ctx, imgName), t) @@ -535,21 +538,23 @@ func TestAttestationDownloadWithPredicateType(t *testing.T) { // Attest to create a slsa attestation attestCommand := attest.AttestCommand{ - KeyOpts: ko, - PredicatePath: slsaAttestationPath, - PredicateType: "slsaprovenance", - Timeout: 30 * time.Second, - Replace: true, + KeyOpts: ko, + PredicatePath: slsaAttestationPath, + PredicateType: "slsaprovenance", + Timeout: 30 * time.Second, + Replace: true, + RekorEntryType: "dsse", } must(attestCommand.Exec(ctx, imgName), t) // Attest to create a vuln attestation attestCommand = attest.AttestCommand{ - KeyOpts: ko, - PredicatePath: vulnAttestationPath, - PredicateType: "vuln", - Timeout: 30 * time.Second, - Replace: true, + KeyOpts: ko, + PredicatePath: vulnAttestationPath, + PredicateType: "vuln", + Timeout: 30 * time.Second, + Replace: true, + RekorEntryType: "dsse", } must(attestCommand.Exec(ctx, imgName), t) @@ -594,11 +599,12 @@ func TestAttestationDownloadWithBadPredicateType(t *testing.T) { // Attest to create a slsa attestation attestCommand := attest.AttestCommand{ - KeyOpts: ko, - PredicatePath: slsaAttestationPath, - PredicateType: "slsaprovenance", - Timeout: 30 * time.Second, - Replace: true, + KeyOpts: ko, + PredicatePath: slsaAttestationPath, + PredicateType: "slsaprovenance", + Timeout: 30 * time.Second, + Replace: true, + RekorEntryType: "dsse", } must(attestCommand.Exec(ctx, imgName), t) @@ -642,11 +648,12 @@ func TestAttestationReplaceCreate(t *testing.T) { // Attest with replace=true to create an attestation attestCommand := attest.AttestCommand{ - KeyOpts: ko, - PredicatePath: slsaAttestationPath, - PredicateType: "slsaprovenance", - Timeout: 30 * time.Second, - Replace: true, + KeyOpts: ko, + PredicatePath: slsaAttestationPath, + PredicateType: "slsaprovenance", + Timeout: 30 * time.Second, + Replace: true, + RekorEntryType: "dsse", } must(attestCommand.Exec(ctx, imgName), t) @@ -694,10 +701,11 @@ func TestAttestationReplace(t *testing.T) { // Attest once with replace=false creating an attestation attestCommand := attest.AttestCommand{ - KeyOpts: ko, - PredicatePath: slsaAttestationPath, - PredicateType: "slsaprovenance", - Timeout: 30 * time.Second, + KeyOpts: ko, + PredicatePath: slsaAttestationPath, + PredicateType: "slsaprovenance", + Timeout: 30 * time.Second, + RekorEntryType: "dsse", } must(attestCommand.Exec(ctx, imgName), t) @@ -713,11 +721,12 @@ func TestAttestationReplace(t *testing.T) { // Attest again with replace=true, replacing the previous attestation attestCommand = attest.AttestCommand{ - KeyOpts: ko, - PredicatePath: slsaAttestationPath, - PredicateType: "slsaprovenance", - Replace: true, - Timeout: 30 * time.Second, + KeyOpts: ko, + PredicatePath: slsaAttestationPath, + PredicateType: "slsaprovenance", + Replace: true, + Timeout: 30 * time.Second, + RekorEntryType: "dsse", } must(attestCommand.Exec(ctx, imgName), t) attestations, err = cosign.FetchAttestationsForReference(ctx, ref, attOpts.PredicateType, ociremoteOpts...) @@ -732,11 +741,12 @@ func TestAttestationReplace(t *testing.T) { // Attest once more replace=true using a different predicate, to ensure it adds a new attestation attestCommand = attest.AttestCommand{ - KeyOpts: ko, - PredicatePath: slsaAttestationPath, - PredicateType: "custom", - Replace: true, - Timeout: 30 * time.Second, + KeyOpts: ko, + PredicatePath: slsaAttestationPath, + PredicateType: "custom", + Replace: true, + Timeout: 30 * time.Second, + RekorEntryType: "dsse", } must(attestCommand.Exec(ctx, imgName), t) @@ -790,12 +800,13 @@ func TestAttestationRFC3161Timestamp(t *testing.T) { // Attest with TSA and skipping tlog creating an attestation attestCommand := attest.AttestCommand{ - KeyOpts: ko, - PredicatePath: slsaAttestationPath, - PredicateType: "slsaprovenance", - Timeout: 30 * time.Second, - TSAServerURL: server.URL + "/api/v1/timestamp", - TlogUpload: false, + KeyOpts: ko, + PredicatePath: slsaAttestationPath, + PredicateType: "slsaprovenance", + Timeout: 30 * time.Second, + TSAServerURL: server.URL + "/api/v1/timestamp", + TlogUpload: false, + RekorEntryType: "dsse", } must(attestCommand.Exec(ctx, imgName), t) @@ -1840,10 +1851,11 @@ func TestSaveLoadAttestation(t *testing.T) { // Now attest the image ko = options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} attestCommand := attest.AttestCommand{ - KeyOpts: ko, - PredicatePath: slsaAttestationPath, - PredicateType: "slsaprovenance", - Timeout: 30 * time.Second, + KeyOpts: ko, + PredicatePath: slsaAttestationPath, + PredicateType: "slsaprovenance", + Timeout: 30 * time.Second, + RekorEntryType: "dsse", } must(attestCommand.Exec(ctx, imgName), t) @@ -2424,6 +2436,7 @@ func TestAttestBlobSignVerify(t *testing.T) { PredicatePath: predicatePath, PredicateType: predicateType, OutputSignature: outputSignature, + RekorEntryType: "dsse", } must(attestBlobCmd.Exec(ctx, bp), t)