github.com/venafi-iw/cosign@v1.3.4/test/e2e_test.go (about)

     1  //
     2  // Copyright 2021 The Sigstore Authors.
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  //     http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  
    16  //go:build e2e
    17  // +build e2e
    18  
    19  package test
    20  
    21  import (
    22  	"bytes"
    23  	"context"
    24  	"crypto"
    25  	"encoding/base64"
    26  	"encoding/json"
    27  	"fmt"
    28  	"io"
    29  	"net/http/httptest"
    30  	"net/url"
    31  	"os"
    32  	"path"
    33  	"path/filepath"
    34  	"testing"
    35  	"time"
    36  	ftime "time"
    37  
    38  	"github.com/google/go-cmp/cmp"
    39  	"github.com/google/go-containerregistry/pkg/authn"
    40  	"github.com/google/go-containerregistry/pkg/name"
    41  	"github.com/google/go-containerregistry/pkg/registry"
    42  	"github.com/google/go-containerregistry/pkg/v1/random"
    43  	"github.com/google/go-containerregistry/pkg/v1/remote"
    44  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    45  
    46  	"github.com/sigstore/cosign/cmd/cosign/cli"
    47  	"github.com/sigstore/cosign/cmd/cosign/cli/attach"
    48  	"github.com/sigstore/cosign/cmd/cosign/cli/attest"
    49  	"github.com/sigstore/cosign/cmd/cosign/cli/download"
    50  	"github.com/sigstore/cosign/cmd/cosign/cli/generate"
    51  	"github.com/sigstore/cosign/cmd/cosign/cli/options"
    52  	"github.com/sigstore/cosign/cmd/cosign/cli/publickey"
    53  	"github.com/sigstore/cosign/cmd/cosign/cli/sign"
    54  	"github.com/sigstore/cosign/cmd/cosign/cli/upload"
    55  	cliverify "github.com/sigstore/cosign/cmd/cosign/cli/verify"
    56  	"github.com/sigstore/cosign/pkg/cosign"
    57  	"github.com/sigstore/cosign/pkg/cosign/kubernetes"
    58  	cremote "github.com/sigstore/cosign/pkg/cosign/remote"
    59  	ociremote "github.com/sigstore/cosign/pkg/oci/remote"
    60  	"github.com/sigstore/cosign/pkg/sget"
    61  	sigs "github.com/sigstore/cosign/pkg/signature"
    62  	"github.com/sigstore/sigstore/pkg/signature/payload"
    63  )
    64  
    65  const (
    66  	serverEnv = "REKOR_SERVER"
    67  	rekorURL  = "https://rekor.sigstore.dev"
    68  )
    69  
    70  var keyPass = []byte("hello")
    71  
    72  var passFunc = func(_ bool) ([]byte, error) {
    73  	return keyPass, nil
    74  }
    75  
    76  var verify = func(keyRef, imageRef string, checkClaims bool, annotations map[string]interface{}, attachment string) error {
    77  	cmd := cliverify.VerifyCommand{
    78  		KeyRef:        keyRef,
    79  		RekorURL:      rekorURL,
    80  		CheckClaims:   checkClaims,
    81  		Annotations:   sigs.AnnotationsMap{Annotations: annotations},
    82  		Attachment:    attachment,
    83  		HashAlgorithm: crypto.SHA256,
    84  	}
    85  
    86  	args := []string{imageRef}
    87  
    88  	return cmd.Exec(context.Background(), args)
    89  }
    90  
    91  // Used to verify local images stored on disk
    92  var verifyLocal = func(keyRef, path string, checkClaims bool, annotations map[string]interface{}, attachment string) error {
    93  	cmd := cliverify.VerifyCommand{
    94  		KeyRef:        keyRef,
    95  		CheckClaims:   checkClaims,
    96  		Annotations:   sigs.AnnotationsMap{Annotations: annotations},
    97  		Attachment:    attachment,
    98  		HashAlgorithm: crypto.SHA256,
    99  		LocalImage:    true,
   100  	}
   101  
   102  	args := []string{path}
   103  
   104  	return cmd.Exec(context.Background(), args)
   105  }
   106  
   107  func TestSignVerify(t *testing.T) {
   108  	repo, stop := reg(t)
   109  	defer stop()
   110  	td := t.TempDir()
   111  
   112  	imgName := path.Join(repo, "cosign-e2e")
   113  
   114  	_, _, cleanup := mkimage(t, imgName)
   115  	defer cleanup()
   116  
   117  	_, privKeyPath, pubKeyPath := keypair(t, td)
   118  
   119  	ctx := context.Background()
   120  	// Verify should fail at first
   121  	mustErr(verify(pubKeyPath, imgName, true, nil, ""), t)
   122  	// So should download
   123  	mustErr(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t)
   124  
   125  	// Now sign the image
   126  	ko := sign.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc}
   127  	must(sign.SignCmd(ctx, ko, options.RegistryOptions{}, nil, []string{imgName}, "", true, "", "", "", false, false, ""), t)
   128  
   129  	// Now verify and download should work!
   130  	must(verify(pubKeyPath, imgName, true, nil, ""), t)
   131  	must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t)
   132  
   133  	// Look for a specific annotation
   134  	mustErr(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar"}, ""), t)
   135  
   136  	// Sign the image with an annotation
   137  	annotations := map[string]interface{}{"foo": "bar"}
   138  	must(sign.SignCmd(ctx, ko, options.RegistryOptions{}, annotations, []string{imgName}, "", true, "", "", "", false, false, ""), t)
   139  
   140  	// It should match this time.
   141  	must(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar"}, ""), t)
   142  
   143  	// But two doesn't work
   144  	mustErr(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar", "baz": "bat"}, ""), t)
   145  }
   146  
   147  func TestSignVerifyClean(t *testing.T) {
   148  	repo, stop := reg(t)
   149  	defer stop()
   150  	td := t.TempDir()
   151  
   152  	imgName := path.Join(repo, "cosign-e2e")
   153  
   154  	_, _, _ = mkimage(t, imgName)
   155  
   156  	_, privKeyPath, pubKeyPath := keypair(t, td)
   157  
   158  	ctx := context.Background()
   159  
   160  	// Now sign the image
   161  	ko := sign.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc}
   162  	must(sign.SignCmd(ctx, ko, options.RegistryOptions{}, nil, []string{imgName}, "", true, "", "", "", false, false, ""), t)
   163  
   164  	// Now verify and download should work!
   165  	must(verify(pubKeyPath, imgName, true, nil, ""), t)
   166  	must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t)
   167  
   168  	// Now clean signature from the given image
   169  	must(cli.CleanCmd(ctx, options.RegistryOptions{}, imgName), t)
   170  
   171  	// It doesn't work
   172  	mustErr(verify(pubKeyPath, imgName, true, nil, ""), t)
   173  }
   174  
   175  func TestAttestVerify(t *testing.T) {
   176  	repo, stop := reg(t)
   177  	defer stop()
   178  	td := t.TempDir()
   179  
   180  	imgName := path.Join(repo, "cosign-attest-e2e")
   181  
   182  	_, _, cleanup := mkimage(t, imgName)
   183  	defer cleanup()
   184  
   185  	_, privKeyPath, pubKeyPath := keypair(t, td)
   186  
   187  	ctx := context.Background()
   188  
   189  	// Verify should fail at first
   190  	verifyAttestation := cliverify.VerifyAttestationCommand{
   191  		KeyRef: pubKeyPath,
   192  	}
   193  
   194  	// Fail case when using without type and policy flag
   195  	mustErr(verifyAttestation.Exec(ctx, []string{imgName}), t)
   196  
   197  	slsaAttestation := `{ "builder": { "id": "2" }, "recipe": {} }`
   198  	slsaAttestationPath := filepath.Join(td, "attestation.slsa.json")
   199  	if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil {
   200  		t.Fatal(err)
   201  	}
   202  
   203  	// Now attest the image
   204  	ko := sign.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc}
   205  	must(attest.AttestCmd(ctx, ko, options.RegistryOptions{}, imgName, "", false, slsaAttestationPath, false,
   206  		"custom", false, ftime.Duration(30*time.Second)), t)
   207  
   208  	// Use cue to verify attestation
   209  	policyPath := filepath.Join(td, "policy.cue")
   210  	verifyAttestation.PredicateType = "slsaprovenance"
   211  	verifyAttestation.Policies = []string{policyPath}
   212  
   213  	// Fail case
   214  	cuePolicy := `builder: id: "1"`
   215  	if err := os.WriteFile(policyPath, []byte(cuePolicy), 0600); err != nil {
   216  		t.Fatal(err)
   217  	}
   218  
   219  	// Success case
   220  	cuePolicy = `builder: id: "2"`
   221  	if err := os.WriteFile(policyPath, []byte(cuePolicy), 0600); err != nil {
   222  		t.Fatal(err)
   223  	}
   224  	must(verifyAttestation.Exec(ctx, []string{imgName}), t)
   225  
   226  	// Look for a specific annotation
   227  	mustErr(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar"}, ""), t)
   228  }
   229  
   230  func TestBundle(t *testing.T) {
   231  	// turn on the tlog
   232  	defer setenv(t, options.ExperimentalEnv, "1")()
   233  
   234  	repo, stop := reg(t)
   235  	defer stop()
   236  	td := t.TempDir()
   237  
   238  	imgName := path.Join(repo, "cosign-e2e")
   239  
   240  	_, _, cleanup := mkimage(t, imgName)
   241  	defer cleanup()
   242  
   243  	_, privKeyPath, pubKeyPath := keypair(t, td)
   244  
   245  	ctx := context.Background()
   246  
   247  	ko := sign.KeyOpts{
   248  		KeyRef:   privKeyPath,
   249  		PassFunc: passFunc,
   250  		RekorURL: rekorURL,
   251  	}
   252  
   253  	// Sign the image
   254  	must(sign.SignCmd(ctx, ko, options.RegistryOptions{}, nil, []string{imgName}, "", true, "", "", "", false, false, ""), t)
   255  	// Make sure verify works
   256  	must(verify(pubKeyPath, imgName, true, nil, ""), t)
   257  
   258  	// Make sure offline verification works with bundling
   259  	// use rekor prod since we have hardcoded the public key
   260  	os.Setenv(serverEnv, "notreal")
   261  	must(verify(pubKeyPath, imgName, true, nil, ""), t)
   262  }
   263  
   264  func TestDuplicateSign(t *testing.T) {
   265  	repo, stop := reg(t)
   266  	defer stop()
   267  	td := t.TempDir()
   268  
   269  	imgName := path.Join(repo, "cosign-e2e")
   270  
   271  	ref, _, cleanup := mkimage(t, imgName)
   272  	defer cleanup()
   273  
   274  	_, privKeyPath, pubKeyPath := keypair(t, td)
   275  
   276  	ctx := context.Background()
   277  	// Verify should fail at first
   278  	mustErr(verify(pubKeyPath, imgName, true, nil, ""), t)
   279  	// So should download
   280  	mustErr(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t)
   281  
   282  	// Now sign the image
   283  	ko := sign.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc}
   284  	must(sign.SignCmd(ctx, ko, options.RegistryOptions{}, nil, []string{imgName}, "", true, "", "", "", false, false, ""), t)
   285  
   286  	// Now verify and download should work!
   287  	must(verify(pubKeyPath, imgName, true, nil, ""), t)
   288  	must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t)
   289  
   290  	// Signing again should work just fine...
   291  	must(sign.SignCmd(ctx, ko, options.RegistryOptions{}, nil, []string{imgName}, "", true, "", "", "", false, false, ""), t)
   292  
   293  	se, err := ociremote.SignedEntity(ref, ociremote.WithRemoteOptions(registryClientOpts(ctx)...))
   294  	must(err, t)
   295  	sigs, err := se.Signatures()
   296  	must(err, t)
   297  	signatures, err := sigs.Get()
   298  	must(err, t)
   299  
   300  	if len(signatures) > 1 {
   301  		t.Errorf("expected there to only be one signature, got %v", signatures)
   302  	}
   303  }
   304  
   305  func TestKeyURLVerify(t *testing.T) {
   306  	// TODO: re-enable once distroless images are being signed by the new client
   307  	t.Skip()
   308  	// Verify that an image can be verified via key url
   309  	keyRef := "https://raw.githubusercontent.com/GoogleContainerTools/distroless/main/cosign.pub"
   310  	img := "gcr.io/distroless/base:latest"
   311  
   312  	must(verify(keyRef, img, true, nil, ""), t)
   313  }
   314  
   315  func TestGenerateKeyPairEnvVar(t *testing.T) {
   316  	defer setenv(t, "COSIGN_PASSWORD", "foo")()
   317  	keys, err := cosign.GenerateKeyPair(generate.GetPass)
   318  	if err != nil {
   319  		t.Fatal(err)
   320  	}
   321  	if _, err := cosign.LoadECDSAPrivateKey(keys.PrivateBytes, []byte("foo")); err != nil {
   322  		t.Fatal(err)
   323  	}
   324  }
   325  
   326  func TestGenerateKeyPairK8s(t *testing.T) {
   327  	td := t.TempDir()
   328  	wd, err := os.Getwd()
   329  	if err != nil {
   330  		t.Fatal(err)
   331  	}
   332  	if err := os.Chdir(td); err != nil {
   333  		t.Fatal(err)
   334  	}
   335  	defer func() {
   336  		os.Chdir(wd)
   337  	}()
   338  	password := "foo"
   339  	defer setenv(t, "COSIGN_PASSWORD", password)()
   340  	ctx := context.Background()
   341  	name := "cosign-secret"
   342  	namespace := "default"
   343  	if err := kubernetes.KeyPairSecret(ctx, fmt.Sprintf("k8s://%s/%s", namespace, name), generate.GetPass); err != nil {
   344  		t.Fatal(err)
   345  	}
   346  	// make sure the secret actually exists
   347  	client, err := kubernetes.Client()
   348  	if err != nil {
   349  		t.Fatal(err)
   350  	}
   351  	s, err := client.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{})
   352  	if err != nil {
   353  		t.Fatal(err)
   354  	}
   355  	if v, ok := s.Data["cosign.password"]; !ok || string(v) != password {
   356  		t.Fatalf("password is incorrect, got %v expected %v", v, "foo")
   357  	}
   358  }
   359  
   360  func TestMultipleSignatures(t *testing.T) {
   361  	repo, stop := reg(t)
   362  	defer stop()
   363  
   364  	td1 := t.TempDir()
   365  	td2 := t.TempDir()
   366  
   367  	imgName := path.Join(repo, "cosign-e2e")
   368  
   369  	_, _, cleanup := mkimage(t, imgName)
   370  	defer cleanup()
   371  
   372  	_, priv1, pub1 := keypair(t, td1)
   373  	_, priv2, pub2 := keypair(t, td2)
   374  
   375  	ctx := context.Background()
   376  
   377  	// Verify should fail at first for both keys
   378  	mustErr(verify(pub1, imgName, true, nil, ""), t)
   379  	mustErr(verify(pub2, imgName, true, nil, ""), t)
   380  
   381  	// Now sign the image with one key
   382  	ko := sign.KeyOpts{KeyRef: priv1, PassFunc: passFunc}
   383  	must(sign.SignCmd(ctx, ko, options.RegistryOptions{}, nil, []string{imgName}, "", true, "", "", "", false, false, ""), t)
   384  	// Now verify should work with that one, but not the other
   385  	must(verify(pub1, imgName, true, nil, ""), t)
   386  	mustErr(verify(pub2, imgName, true, nil, ""), t)
   387  
   388  	// Now sign with the other key too
   389  	ko.KeyRef = priv2
   390  	must(sign.SignCmd(ctx, ko, options.RegistryOptions{}, nil, []string{imgName}, "", true, "", "", "", false, false, ""), t)
   391  
   392  	// Now verify should work with both
   393  	must(verify(pub1, imgName, true, nil, ""), t)
   394  	must(verify(pub2, imgName, true, nil, ""), t)
   395  }
   396  
   397  func TestSignBlob(t *testing.T) {
   398  	blob := "someblob"
   399  	td1 := t.TempDir()
   400  	td2 := t.TempDir()
   401  	t.Cleanup(func() {
   402  		os.RemoveAll(td1)
   403  		os.RemoveAll(td2)
   404  	})
   405  	bp := filepath.Join(td1, blob)
   406  
   407  	if err := os.WriteFile(bp, []byte(blob), 0644); err != nil {
   408  		t.Fatal(err)
   409  	}
   410  
   411  	_, privKeyPath1, pubKeyPath1 := keypair(t, td1)
   412  	_, _, pubKeyPath2 := keypair(t, td2)
   413  
   414  	ctx := context.Background()
   415  
   416  	ko1 := sign.KeyOpts{
   417  		KeyRef: pubKeyPath1,
   418  	}
   419  	ko2 := sign.KeyOpts{
   420  		KeyRef: pubKeyPath2,
   421  	}
   422  	// Verify should fail on a bad input
   423  	mustErr(cliverify.VerifyBlobCmd(ctx, ko1, "", "badsig", blob), t)
   424  	mustErr(cliverify.VerifyBlobCmd(ctx, ko2, "", "badsig", blob), t)
   425  
   426  	// Now sign the blob with one key
   427  	ko := sign.KeyOpts{
   428  		KeyRef:   privKeyPath1,
   429  		PassFunc: passFunc,
   430  	}
   431  	sig, err := sign.SignBlobCmd(ctx, ko, options.RegistryOptions{}, bp, true, "", "", time.Duration(30*time.Second))
   432  	if err != nil {
   433  		t.Fatal(err)
   434  	}
   435  	// Now verify should work with that one, but not the other
   436  	must(cliverify.VerifyBlobCmd(ctx, ko1, "", string(sig), bp), t)
   437  	mustErr(cliverify.VerifyBlobCmd(ctx, ko2, "", string(sig), bp), t)
   438  }
   439  
   440  func TestGenerate(t *testing.T) {
   441  	repo, stop := reg(t)
   442  	defer stop()
   443  
   444  	imgName := path.Join(repo, "cosign-e2e")
   445  	_, desc, cleanup := mkimage(t, imgName)
   446  	defer cleanup()
   447  
   448  	// Generate the payload for the image, and check the digest.
   449  	b := bytes.Buffer{}
   450  	must(generate.GenerateCmd(context.Background(), options.RegistryOptions{}, imgName, nil, &b), t)
   451  	ss := payload.SimpleContainerImage{}
   452  	must(json.Unmarshal(b.Bytes(), &ss), t)
   453  
   454  	equals(desc.Digest.String(), ss.Critical.Image.DockerManifestDigest, t)
   455  
   456  	// Now try with some annotations.
   457  	b.Reset()
   458  	a := map[string]interface{}{"foo": "bar"}
   459  	must(generate.GenerateCmd(context.Background(), options.RegistryOptions{}, imgName, a, &b), t)
   460  	must(json.Unmarshal(b.Bytes(), &ss), t)
   461  
   462  	equals(desc.Digest.String(), ss.Critical.Image.DockerManifestDigest, t)
   463  	equals(ss.Optional["foo"], "bar", t)
   464  }
   465  
   466  func keypair(t *testing.T, td string) (*cosign.Keys, string, string) {
   467  	wd, err := os.Getwd()
   468  	if err != nil {
   469  		t.Fatal(err)
   470  	}
   471  	if err := os.Chdir(td); err != nil {
   472  		t.Fatal(err)
   473  	}
   474  	defer func() {
   475  		os.Chdir(wd)
   476  	}()
   477  	keys, err := cosign.GenerateKeyPair(passFunc)
   478  	if err != nil {
   479  		t.Fatal(err)
   480  	}
   481  
   482  	privKeyPath := filepath.Join(td, "cosign.key")
   483  	if err := os.WriteFile(privKeyPath, keys.PrivateBytes, 0600); err != nil {
   484  		t.Fatal(err)
   485  	}
   486  
   487  	pubKeyPath := filepath.Join(td, "cosign.pub")
   488  	if err := os.WriteFile(pubKeyPath, keys.PublicBytes, 0600); err != nil {
   489  		t.Fatal(err)
   490  	}
   491  	return keys, privKeyPath, pubKeyPath
   492  }
   493  
   494  func TestUploadDownload(t *testing.T) {
   495  	repo, stop := reg(t)
   496  	defer stop()
   497  	td := t.TempDir()
   498  	ctx := context.Background()
   499  
   500  	testCases := map[string]struct {
   501  		signature     string
   502  		signatureType attach.SignatureArgType
   503  		expectedErr   bool
   504  	}{
   505  		"file containing signature": {
   506  			signature:     "testsignaturefile",
   507  			signatureType: attach.FileSignature,
   508  			expectedErr:   false,
   509  		},
   510  		"raw signature as argument": {
   511  			signature:     "testsignatureraw",
   512  			signatureType: attach.RawSignature,
   513  			expectedErr:   false,
   514  		},
   515  		"empty signature as argument": {
   516  			signature:     "",
   517  			signatureType: attach.RawSignature,
   518  			expectedErr:   true,
   519  		},
   520  	}
   521  
   522  	imgName := path.Join(repo, "cosign-e2e")
   523  	for testName, testCase := range testCases {
   524  		t.Run(testName, func(t *testing.T) {
   525  			ref, _, cleanup := mkimage(t, imgName)
   526  			payload := "testpayload"
   527  			payloadPath := mkfile(payload, td, t)
   528  			signature := base64.StdEncoding.EncodeToString([]byte(testCase.signature))
   529  
   530  			var sigRef string
   531  			if testCase.signatureType == attach.FileSignature {
   532  				sigRef = mkfile(signature, td, t)
   533  			} else {
   534  				sigRef = signature
   535  			}
   536  
   537  			// Upload it!
   538  			err := attach.SignatureCmd(ctx, options.RegistryOptions{}, sigRef, payloadPath, imgName)
   539  			if testCase.expectedErr {
   540  				mustErr(err, t)
   541  			} else {
   542  				must(err, t)
   543  			}
   544  
   545  			// Now download it!
   546  			se, err := ociremote.SignedEntity(ref, ociremote.WithRemoteOptions(registryClientOpts(ctx)...))
   547  			must(err, t)
   548  			sigs, err := se.Signatures()
   549  			must(err, t)
   550  			signatures, err := sigs.Get()
   551  			must(err, t)
   552  
   553  			if testCase.expectedErr {
   554  				if len(signatures) != 0 {
   555  					t.Fatalf("unexpected signatures %d, wanted 0", len(signatures))
   556  				}
   557  			} else {
   558  				if len(signatures) != 1 {
   559  					t.Fatalf("unexpected signatures %d, wanted 1", len(signatures))
   560  				}
   561  
   562  				if b64sig, err := signatures[0].Base64Signature(); err != nil {
   563  					t.Fatalf("Base64Signature() = %v", err)
   564  				} else if diff := cmp.Diff(b64sig, signature); diff != "" {
   565  					t.Error(diff)
   566  				}
   567  
   568  				if p, err := signatures[0].Payload(); err != nil {
   569  					t.Fatalf("Payload() = %v", err)
   570  				} else if diff := cmp.Diff(p, []byte(payload)); diff != "" {
   571  					t.Error(diff)
   572  				}
   573  			}
   574  
   575  			// Now delete it!
   576  			cleanup()
   577  		})
   578  	}
   579  }
   580  
   581  func TestUploadBlob(t *testing.T) {
   582  	repo, stop := reg(t)
   583  	defer stop()
   584  	td := t.TempDir()
   585  	ctx := context.Background()
   586  
   587  	imgName := path.Join(repo, "/cosign-upload-e2e")
   588  	payload := "testpayload"
   589  	payloadPath := mkfile(payload, td, t)
   590  
   591  	// Upload it!
   592  	files := []cremote.File{cremote.FileFromFlag(payloadPath)}
   593  	must(upload.BlobCmd(ctx, options.RegistryOptions{}, files, "", imgName), t)
   594  
   595  	// Check it
   596  	ref, err := name.ParseReference(imgName)
   597  	if err != nil {
   598  		t.Fatal(err)
   599  	}
   600  
   601  	// Now download it with sget (this should fail by tag)
   602  	if err := sget.New(imgName, "", os.Stdout).Do(ctx); err == nil {
   603  		t.Error("expected download to fail")
   604  	}
   605  
   606  	img, err := remote.Image(ref)
   607  	if err != nil {
   608  		t.Fatal(err)
   609  	}
   610  	dgst, err := img.Digest()
   611  	if err != nil {
   612  		t.Fatal(err)
   613  	}
   614  
   615  	result := &bytes.Buffer{}
   616  
   617  	// But pass by digest
   618  	if err := sget.New(imgName+"@"+dgst.String(), "", result).Do(ctx); err != nil {
   619  		t.Fatal(err)
   620  	}
   621  	b, err := io.ReadAll(result)
   622  	if err != nil {
   623  		t.Fatal(err)
   624  	}
   625  	if string(b) != payload {
   626  		t.Errorf("expected contents to be %s, got %s", payload, string(b))
   627  	}
   628  }
   629  
   630  func TestSaveLoad(t *testing.T) {
   631  	tests := []struct {
   632  		description     string
   633  		getSignedEntity func(t *testing.T, n string) (name.Reference, *remote.Descriptor, func())
   634  	}{
   635  		{
   636  			description:     "save and load an image",
   637  			getSignedEntity: mkimage,
   638  		},
   639  		{
   640  			description:     "save and load an image index",
   641  			getSignedEntity: mkimageindex,
   642  		},
   643  	}
   644  	for i, test := range tests {
   645  		t.Run(test.description, func(t *testing.T) {
   646  			repo, stop := reg(t)
   647  			defer stop()
   648  			keysDir := t.TempDir()
   649  
   650  			imgName := path.Join(repo, fmt.Sprintf("save-load-%d", i))
   651  
   652  			_, _, cleanup := test.getSignedEntity(t, imgName)
   653  			defer cleanup()
   654  
   655  			_, privKeyPath, pubKeyPath := keypair(t, keysDir)
   656  
   657  			ctx := context.Background()
   658  			// Now sign the image and verify it
   659  			ko := sign.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc}
   660  			must(sign.SignCmd(ctx, ko, options.RegistryOptions{}, nil, []string{imgName}, "", true, "", "", "", false, false, ""), t)
   661  			must(verify(pubKeyPath, imgName, true, nil, ""), t)
   662  
   663  			// save the image to a temp dir
   664  			imageDir := t.TempDir()
   665  			must(cli.SaveCmd(ctx, options.SaveOptions{Directory: imageDir}, imgName), t)
   666  
   667  			// verify the local image using a local key
   668  			must(verifyLocal(pubKeyPath, imageDir, true, nil, ""), t)
   669  
   670  			// load the image from the temp dir into a new image and verify the new image
   671  			imgName2 := path.Join(repo, fmt.Sprintf("save-load-%d-2", i))
   672  			must(cli.LoadCmd(ctx, options.LoadOptions{Directory: imageDir}, imgName2), t)
   673  			must(verify(pubKeyPath, imgName2, true, nil, ""), t)
   674  		})
   675  	}
   676  }
   677  
   678  func TestSaveLoadAttestation(t *testing.T) {
   679  	repo, stop := reg(t)
   680  	defer stop()
   681  	td := t.TempDir()
   682  
   683  	imgName := path.Join(repo, "save-load")
   684  
   685  	_, _, cleanup := mkimage(t, imgName)
   686  	defer cleanup()
   687  
   688  	_, privKeyPath, pubKeyPath := keypair(t, td)
   689  
   690  	ctx := context.Background()
   691  	// Now sign the image and verify it
   692  	ko := sign.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc}
   693  	must(sign.SignCmd(ctx, ko, options.RegistryOptions{}, nil, []string{imgName}, "", true, "", "", "", false, false, ""), t)
   694  	must(verify(pubKeyPath, imgName, true, nil, ""), t)
   695  
   696  	// now, append an attestation to the image
   697  	slsaAttestation := `{ "builder": { "id": "2" }, "recipe": {} }`
   698  	slsaAttestationPath := filepath.Join(td, "attestation.slsa.json")
   699  	if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil {
   700  		t.Fatal(err)
   701  	}
   702  
   703  	// Now attest the image
   704  	ko = sign.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc}
   705  	must(attest.AttestCmd(ctx, ko, options.RegistryOptions{}, imgName, "", false, slsaAttestationPath, false,
   706  		"custom", false, ftime.Duration(30*time.Second)), t)
   707  
   708  	// save the image to a temp dir
   709  	imageDir := t.TempDir()
   710  	must(cli.SaveCmd(ctx, options.SaveOptions{Directory: imageDir}, imgName), t)
   711  
   712  	// load the image from the temp dir into a new image and verify the new image
   713  	imgName2 := path.Join(repo, "save-load-2")
   714  	must(cli.LoadCmd(ctx, options.LoadOptions{Directory: imageDir}, imgName2), t)
   715  	must(verify(pubKeyPath, imgName2, true, nil, ""), t)
   716  	// Use cue to verify attestation on the new image
   717  	policyPath := filepath.Join(td, "policy.cue")
   718  	verifyAttestation := cliverify.VerifyAttestationCommand{
   719  		KeyRef: pubKeyPath,
   720  	}
   721  	verifyAttestation.PredicateType = "slsaprovenance"
   722  	verifyAttestation.Policies = []string{policyPath}
   723  	// Success case (remote)
   724  	cuePolicy := `builder: id: "2"`
   725  	if err := os.WriteFile(policyPath, []byte(cuePolicy), 0600); err != nil {
   726  		t.Fatal(err)
   727  	}
   728  	must(verifyAttestation.Exec(ctx, []string{imgName2}), t)
   729  	// Success case (local)
   730  	verifyAttestation.LocalImage = true
   731  	must(verifyAttestation.Exec(ctx, []string{imageDir}), t)
   732  }
   733  
   734  func TestAttachSBOM(t *testing.T) {
   735  	repo, stop := reg(t)
   736  	defer stop()
   737  	ctx := context.Background()
   738  
   739  	imgName := path.Join(repo, "sbom-image")
   740  	img, _, cleanup := mkimage(t, imgName)
   741  	defer cleanup()
   742  
   743  	out := bytes.Buffer{}
   744  	_, err := download.SBOMCmd(ctx, options.RegistryOptions{}, img.Name(), &out)
   745  	if err == nil {
   746  		t.Fatal("Expected error")
   747  	}
   748  	t.Log(out.String())
   749  	out.Reset()
   750  
   751  	// Upload it!
   752  	must(attach.SBOMCmd(ctx, options.RegistryOptions{}, "./testdata/bom-go-mod.spdx", "spdx", imgName), t)
   753  
   754  	sboms, err := download.SBOMCmd(ctx, options.RegistryOptions{}, imgName, &out)
   755  	if err != nil {
   756  		t.Fatal(err)
   757  	}
   758  	t.Log(out.String())
   759  	if len(sboms) != 1 {
   760  		t.Fatalf("Expected one sbom, got %d", len(sboms))
   761  	}
   762  	want, err := os.ReadFile("./testdata/bom-go-mod.spdx")
   763  	if err != nil {
   764  		t.Fatal(err)
   765  	}
   766  	if diff := cmp.Diff(string(want), sboms[0]); diff != "" {
   767  		t.Errorf("diff: %s", diff)
   768  	}
   769  
   770  	// Generate key pairs to sign the sbom
   771  	td1 := t.TempDir()
   772  	td2 := t.TempDir()
   773  	_, privKeyPath1, pubKeyPath1 := keypair(t, td1)
   774  	_, _, pubKeyPath2 := keypair(t, td2)
   775  
   776  	// Verify should fail on a bad input
   777  	mustErr(verify(pubKeyPath1, imgName, true, nil, "sbom"), t)
   778  	mustErr(verify(pubKeyPath2, imgName, true, nil, "sbom"), t)
   779  
   780  	// Now sign the sbom with one key
   781  	ko1 := sign.KeyOpts{KeyRef: privKeyPath1, PassFunc: passFunc}
   782  	must(sign.SignCmd(ctx, ko1, options.RegistryOptions{}, nil, []string{imgName}, "", true, "", "", "", false, false, "sbom"), t)
   783  
   784  	// Now verify should work with that one, but not the other
   785  	must(verify(pubKeyPath1, imgName, true, nil, "sbom"), t)
   786  	mustErr(verify(pubKeyPath2, imgName, true, nil, "sbom"), t)
   787  }
   788  
   789  func setenv(t *testing.T, k, v string) func() {
   790  	if err := os.Setenv(k, v); err != nil {
   791  		t.Fatalf("error setting env: %v", err)
   792  	}
   793  	return func() {
   794  		os.Unsetenv(k)
   795  	}
   796  }
   797  
   798  func TestTlog(t *testing.T) {
   799  	repo, stop := reg(t)
   800  	defer stop()
   801  	td := t.TempDir()
   802  
   803  	imgName := path.Join(repo, "cosign-e2e")
   804  
   805  	_, _, cleanup := mkimage(t, imgName)
   806  	defer cleanup()
   807  
   808  	_, privKeyPath, pubKeyPath := keypair(t, td)
   809  
   810  	ctx := context.Background()
   811  	// Verify should fail at first
   812  	mustErr(verify(pubKeyPath, imgName, true, nil, ""), t)
   813  
   814  	// Now sign the image without the tlog
   815  	ko := sign.KeyOpts{
   816  		KeyRef:   privKeyPath,
   817  		PassFunc: passFunc,
   818  		RekorURL: rekorURL,
   819  	}
   820  	must(sign.SignCmd(ctx, ko, options.RegistryOptions{}, nil, []string{imgName}, "", true, "", "", "", false, false, ""), t)
   821  
   822  	// Now verify should work!
   823  	must(verify(pubKeyPath, imgName, true, nil, ""), t)
   824  
   825  	// Now we turn on the tlog!
   826  	defer setenv(t, options.ExperimentalEnv, "1")()
   827  
   828  	// Verify shouldn't work since we haven't put anything in it yet.
   829  	mustErr(verify(pubKeyPath, imgName, true, nil, ""), t)
   830  
   831  	// Sign again with the tlog env var on
   832  	must(sign.SignCmd(ctx, ko, options.RegistryOptions{}, nil, []string{imgName}, "", true, "", "", "", false, false, ""), t)
   833  	// And now verify works!
   834  	must(verify(pubKeyPath, imgName, true, nil, ""), t)
   835  }
   836  
   837  func TestGetPublicKeyCustomOut(t *testing.T) {
   838  	td := t.TempDir()
   839  	keys, privKeyPath, _ := keypair(t, td)
   840  	ctx := context.Background()
   841  
   842  	outFile := "output.pub"
   843  	outPath := filepath.Join(td, outFile)
   844  	outWriter, err := os.OpenFile(outPath, os.O_WRONLY|os.O_CREATE, 0600)
   845  	must(err, t)
   846  
   847  	pk := publickey.Pkopts{
   848  		KeyRef: privKeyPath,
   849  	}
   850  	must(publickey.GetPublicKey(ctx, pk, publickey.NamedWriter{Name: outPath, Writer: outWriter}, passFunc), t)
   851  
   852  	output, err := os.ReadFile(outPath)
   853  	must(err, t)
   854  	equals(keys.PublicBytes, output, t)
   855  }
   856  
   857  func mkfile(contents, td string, t *testing.T) string {
   858  	f, err := os.CreateTemp(td, "")
   859  	if err != nil {
   860  		t.Fatal(err)
   861  	}
   862  	defer f.Close()
   863  	if _, err := f.Write([]byte(contents)); err != nil {
   864  		t.Fatal(err)
   865  	}
   866  	return f.Name()
   867  }
   868  
   869  func mkimage(t *testing.T, n string) (name.Reference, *remote.Descriptor, func()) {
   870  	ref, err := name.ParseReference(n, name.WeakValidation)
   871  	if err != nil {
   872  		t.Fatal(err)
   873  	}
   874  	img, err := random.Image(512, 5)
   875  	if err != nil {
   876  		t.Fatal(err)
   877  	}
   878  
   879  	regClientOpts := registryClientOpts(context.Background())
   880  
   881  	if err := remote.Write(ref, img, regClientOpts...); err != nil {
   882  		t.Fatal(err)
   883  	}
   884  
   885  	remoteImage, err := remote.Get(ref, regClientOpts...)
   886  	if err != nil {
   887  		t.Fatal(err)
   888  	}
   889  
   890  	cleanup := func() {
   891  		_ = remote.Delete(ref, regClientOpts...)
   892  		ref, _ := ociremote.SignatureTag(ref.Context().Digest(remoteImage.Descriptor.Digest.String()), ociremote.WithRemoteOptions(regClientOpts...))
   893  		_ = remote.Delete(ref, regClientOpts...)
   894  	}
   895  	return ref, remoteImage, cleanup
   896  }
   897  
   898  func mkimageindex(t *testing.T, n string) (name.Reference, *remote.Descriptor, func()) {
   899  	ref, err := name.ParseReference(n, name.WeakValidation)
   900  	if err != nil {
   901  		t.Fatal(err)
   902  	}
   903  	ii, err := random.Index(512, 5, 4)
   904  	if err != nil {
   905  		t.Fatal(err)
   906  	}
   907  
   908  	regClientOpts := registryClientOpts(context.Background())
   909  
   910  	if err := remote.WriteIndex(ref, ii, regClientOpts...); err != nil {
   911  		t.Fatal(err)
   912  	}
   913  
   914  	remoteIndex, err := remote.Get(ref, regClientOpts...)
   915  	if err != nil {
   916  		t.Fatal(err)
   917  	}
   918  
   919  	cleanup := func() {
   920  		_ = remote.Delete(ref, regClientOpts...)
   921  		ref, _ := ociremote.SignatureTag(ref.Context().Digest(remoteIndex.Descriptor.Digest.String()), ociremote.WithRemoteOptions(regClientOpts...))
   922  		_ = remote.Delete(ref, regClientOpts...)
   923  	}
   924  	return ref, remoteIndex, cleanup
   925  }
   926  
   927  func must(err error, t *testing.T) {
   928  	t.Helper()
   929  	if err != nil {
   930  		t.Fatal(err)
   931  	}
   932  }
   933  
   934  func mustErr(err error, t *testing.T) {
   935  	t.Helper()
   936  	if err == nil {
   937  		t.Fatal("expected error")
   938  	}
   939  }
   940  
   941  func equals(v1, v2 interface{}, t *testing.T) {
   942  	if diff := cmp.Diff(v1, v2); diff != "" {
   943  		t.Error(diff)
   944  	}
   945  }
   946  
   947  func reg(t *testing.T) (string, func()) {
   948  	repo := os.Getenv("COSIGN_TEST_REPO")
   949  	if repo != "" {
   950  		return repo, func() {}
   951  	}
   952  
   953  	t.Log("COSIGN_TEST_REPO unset, using fake registry")
   954  	r := httptest.NewServer(registry.New())
   955  	u, err := url.Parse(r.URL)
   956  	if err != nil {
   957  		t.Fatal(err)
   958  	}
   959  	return u.Host, r.Close
   960  }
   961  
   962  func registryClientOpts(ctx context.Context) []remote.Option {
   963  	return []remote.Option{
   964  		remote.WithAuthFromKeychain(authn.DefaultKeychain),
   965  		remote.WithContext(ctx),
   966  	}
   967  }