github.com/gajananan/cosign@v0.2.1/test/e2e_test.go (about)

     1  // Copyright 2021 The Rekor Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // +build e2e
    16  
    17  package test
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"encoding/base64"
    23  	"encoding/json"
    24  	"io/ioutil"
    25  	"net/http/httptest"
    26  	"net/url"
    27  	"os"
    28  	"path"
    29  	"path/filepath"
    30  	"testing"
    31  
    32  	"github.com/sigstore/cosign/pkg/cosign"
    33  	"github.com/sigstore/sigstore/pkg/signature/payload"
    34  
    35  	"github.com/google/go-cmp/cmp"
    36  	"github.com/google/go-containerregistry/pkg/authn"
    37  	"github.com/google/go-containerregistry/pkg/name"
    38  	"github.com/google/go-containerregistry/pkg/registry"
    39  
    40  	"github.com/google/go-containerregistry/pkg/v1/random"
    41  	"github.com/google/go-containerregistry/pkg/v1/remote"
    42  	"github.com/sigstore/cosign/cmd/cosign/cli"
    43  )
    44  
    45  var keyPass = []byte("hello")
    46  
    47  var passFunc = func(_ bool) ([]byte, error) {
    48  	return keyPass, nil
    49  }
    50  
    51  var verify = func(key, imageRef string, checkClaims bool, annotations map[string]interface{}) error {
    52  	cmd := cli.VerifyCommand{
    53  		Key:         key,
    54  		CheckClaims: checkClaims,
    55  		Annotations: &annotations,
    56  	}
    57  
    58  	args := []string{imageRef}
    59  
    60  	return cmd.Exec(context.Background(), args)
    61  }
    62  
    63  func TestSignVerify(t *testing.T) {
    64  	repo, stop := reg(t)
    65  	defer stop()
    66  	td := t.TempDir()
    67  
    68  	imgName := path.Join(repo, "cosign-e2e")
    69  
    70  	_, _, cleanup := mkimage(t, imgName)
    71  	defer cleanup()
    72  
    73  	_, privKeyPath, pubKeyPath := keypair(t, td)
    74  
    75  	ctx := context.Background()
    76  	// Verify should fail at first
    77  	mustErr(verify(pubKeyPath, imgName, true, nil), t)
    78  	// So should download
    79  	mustErr(cli.DownloadCmd(ctx, imgName), t)
    80  
    81  	// Now sign the image
    82  	must(cli.SignCmd(ctx, privKeyPath, imgName, true, "", nil, "", passFunc, false), t)
    83  
    84  	// Now verify and download should work!
    85  	must(verify(pubKeyPath, imgName, true, nil), t)
    86  	must(cli.DownloadCmd(ctx, imgName), t)
    87  
    88  	// Look for a specific annotation
    89  	mustErr(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar"}), t)
    90  
    91  	// Sign the image with an annotation
    92  	must(cli.SignCmd(ctx, privKeyPath, imgName, true, "", map[string]interface{}{"foo": "bar"}, "", passFunc, false), t)
    93  
    94  	// It should match this time.
    95  	must(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar"}), t)
    96  
    97  	// But two doesn't work
    98  	mustErr(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar", "baz": "bat"}), t)
    99  }
   100  
   101  func TestGenerateKeyPairEnvVar(t *testing.T) {
   102  	defer setenv(t, "COSIGN_PASSWORD", "foo")()
   103  	keys, err := cosign.GenerateKeyPair(cli.GetPass)
   104  	if err != nil {
   105  		t.Fatal(err)
   106  	}
   107  	if _, err := cosign.LoadECDSAPrivateKey(keys.PrivateBytes, []byte("foo")); err != nil {
   108  		t.Fatal(err)
   109  	}
   110  }
   111  
   112  func TestMultipleSignatures(t *testing.T) {
   113  	repo, stop := reg(t)
   114  	defer stop()
   115  
   116  	td1 := t.TempDir()
   117  	td2 := t.TempDir()
   118  
   119  	imgName := path.Join(repo, "cosign-e2e")
   120  
   121  	_, _, cleanup := mkimage(t, imgName)
   122  	defer cleanup()
   123  
   124  	_, priv1, pub1 := keypair(t, td1)
   125  	_, priv2, pub2 := keypair(t, td2)
   126  
   127  	ctx := context.Background()
   128  
   129  	// Verify should fail at first for both keys
   130  	mustErr(verify(pub1, imgName, true, nil), t)
   131  	mustErr(verify(pub2, imgName, true, nil), t)
   132  
   133  	// Now sign the image with one key
   134  	must(cli.SignCmd(ctx, priv1, imgName, true, "", nil, "", passFunc, false), t)
   135  	// Now verify should work with that one, but not the other
   136  	must(verify(pub1, imgName, true, nil), t)
   137  	mustErr(verify(pub2, imgName, true, nil), t)
   138  
   139  	// Now sign with the other key too
   140  	must(cli.SignCmd(ctx, priv2, imgName, true, "", nil, "", passFunc, false), t)
   141  
   142  	// Now verify should work with both
   143  	must(verify(pub1, imgName, true, nil), t)
   144  	must(verify(pub2, imgName, true, nil), t)
   145  }
   146  
   147  func TestSignBlob(t *testing.T) {
   148  
   149  	var blob = "someblob"
   150  	td1 := t.TempDir()
   151  	td2 := t.TempDir()
   152  	t.Cleanup(func() {
   153  		os.RemoveAll(td1)
   154  		os.RemoveAll(td2)
   155  	})
   156  	bp := filepath.Join(td1, blob)
   157  
   158  	if err := ioutil.WriteFile(bp, []byte(blob), 0644); err != nil {
   159  		t.Fatal(err)
   160  	}
   161  
   162  	_, privKeyPath1, pubKeyPath1 := keypair(t, td1)
   163  	_, _, pubKeyPath2 := keypair(t, td2)
   164  
   165  	ctx := context.Background()
   166  
   167  	// Verify should fail on a bad input
   168  	mustErr(cli.VerifyBlobCmd(ctx, pubKeyPath1, "", "", "badsig", blob), t)
   169  	mustErr(cli.VerifyBlobCmd(ctx, pubKeyPath2, "", "", "badsig", blob), t)
   170  
   171  	// Now sign the blob with one key
   172  	sig, err := cli.SignBlobCmd(ctx, privKeyPath1, "", bp, true, passFunc)
   173  	if err != nil {
   174  		t.Fatal(err)
   175  	}
   176  	// Now verify should work with that one, but not the other
   177  	must(cli.VerifyBlobCmd(ctx, pubKeyPath1, "", "", string(sig), bp), t)
   178  	mustErr(cli.VerifyBlobCmd(ctx, pubKeyPath2, "", "", string(sig), bp), t)
   179  }
   180  
   181  func TestGenerate(t *testing.T) {
   182  	repo, stop := reg(t)
   183  	defer stop()
   184  
   185  	imgName := path.Join(repo, "cosign-e2e")
   186  	_, desc, cleanup := mkimage(t, imgName)
   187  	defer cleanup()
   188  
   189  	// Generate the payload for the image, and check the digest.
   190  	b := bytes.Buffer{}
   191  	must(cli.GenerateCmd(context.Background(), imgName, nil, &b), t)
   192  	ss := payload.Simple{}
   193  	must(json.Unmarshal(b.Bytes(), &ss), t)
   194  
   195  	equals(desc.Digest.String(), ss.Critical.Image.DockerManifestDigest, t)
   196  
   197  	// Now try with some annotations.
   198  	b.Reset()
   199  	a := map[string]interface{}{"foo": "bar"}
   200  	must(cli.GenerateCmd(context.Background(), imgName, a, &b), t)
   201  	must(json.Unmarshal(b.Bytes(), &ss), t)
   202  
   203  	equals(desc.Digest.String(), ss.Critical.Image.DockerManifestDigest, t)
   204  	equals(ss.Optional["foo"], "bar", t)
   205  }
   206  
   207  func keypair(t *testing.T, td string) (*cosign.Keys, string, string) {
   208  	if err := os.Chdir(td); err != nil {
   209  		t.Fatal(err)
   210  	}
   211  	keys, err := cosign.GenerateKeyPair(passFunc)
   212  	if err != nil {
   213  		t.Fatal(err)
   214  	}
   215  
   216  	privKeyPath := filepath.Join(td, "cosign.key")
   217  	if err := ioutil.WriteFile(privKeyPath, keys.PrivateBytes, 0600); err != nil {
   218  		t.Fatal(err)
   219  	}
   220  
   221  	pubKeyPath := filepath.Join(td, "cosign.pub")
   222  	if err := ioutil.WriteFile(pubKeyPath, keys.PublicBytes, 0600); err != nil {
   223  		t.Fatal(err)
   224  	}
   225  	return keys, privKeyPath, pubKeyPath
   226  }
   227  
   228  func TestUploadDownload(t *testing.T) {
   229  	repo, stop := reg(t)
   230  	defer stop()
   231  	td := t.TempDir()
   232  	ctx := context.Background()
   233  
   234  	testCases := map[string]struct {
   235  		signature     string
   236  		signatureType cli.SignatureArgType
   237  		expectedErr   bool
   238  	}{
   239  		"file containing signature": {
   240  			signature:     "testsignaturefile",
   241  			signatureType: cli.FileSignature,
   242  			expectedErr:   false,
   243  		},
   244  		"raw signature as argument": {
   245  			signature:     "testsignatureraw",
   246  			signatureType: cli.RawSignature,
   247  			expectedErr:   false,
   248  		},
   249  		"empty signature as argument": {
   250  			signature:     "",
   251  			signatureType: cli.RawSignature,
   252  			expectedErr:   true,
   253  		},
   254  	}
   255  
   256  	imgName := path.Join(repo, "cosign-e2e")
   257  	for testName, testCase := range testCases {
   258  		t.Run(testName, func(t *testing.T) {
   259  			ref, _, cleanup := mkimage(t, imgName)
   260  			payload := "testpayload"
   261  			payloadPath := mkfile(payload, td, t)
   262  			signature := base64.StdEncoding.EncodeToString([]byte(testCase.signature))
   263  
   264  			var sigRef string
   265  			if testCase.signatureType == cli.FileSignature {
   266  				sigRef = mkfile(signature, td, t)
   267  			} else {
   268  				sigRef = signature
   269  			}
   270  
   271  			// Upload it!
   272  			err := cli.UploadCmd(ctx, sigRef, payloadPath, imgName)
   273  			if testCase.expectedErr {
   274  				mustErr(err, t)
   275  			} else {
   276  				must(err, t)
   277  			}
   278  
   279  			// Now download it!
   280  			signatures, _, err := cosign.FetchSignatures(ctx, ref)
   281  			if testCase.expectedErr {
   282  				mustErr(err, t)
   283  			} else {
   284  				must(err, t)
   285  
   286  				if len(signatures) != 1 {
   287  					t.Error("unexpected signatures")
   288  				}
   289  				if diff := cmp.Diff(signatures[0].Base64Signature, signature); diff != "" {
   290  					t.Error(diff)
   291  				}
   292  				if diff := cmp.Diff(signatures[0].Payload, []byte(payload)); diff != "" {
   293  					t.Error(diff)
   294  				}
   295  			}
   296  
   297  			// Now delete it!
   298  			cleanup()
   299  		})
   300  	}
   301  
   302  }
   303  
   304  func setenv(t *testing.T, k, v string) func() {
   305  	if err := os.Setenv(k, v); err != nil {
   306  		t.Fatalf("error setitng env: %v", err)
   307  	}
   308  	return func() {
   309  		os.Unsetenv(k)
   310  	}
   311  }
   312  
   313  func TestTlog(t *testing.T) {
   314  	defer setenv(t, cosign.ServerEnv, "http://127.0.0.1:3000")()
   315  
   316  	repo, stop := reg(t)
   317  	defer stop()
   318  	td := t.TempDir()
   319  
   320  	imgName := path.Join(repo, "cosign-e2e")
   321  
   322  	_, _, cleanup := mkimage(t, imgName)
   323  	defer cleanup()
   324  
   325  	_, privKeyPath, pubKeyPath := keypair(t, td)
   326  
   327  	ctx := context.Background()
   328  	// Verify should fail at first
   329  	mustErr(verify(pubKeyPath, imgName, true, nil), t)
   330  
   331  	// Now sign the image without the tlog
   332  	must(cli.SignCmd(ctx, privKeyPath, imgName, true, "", nil, "", passFunc, false), t)
   333  
   334  	// Now verify should work!
   335  	must(verify(pubKeyPath, imgName, true, nil), t)
   336  
   337  	// Now we turn on the tlog!
   338  	defer setenv(t, cosign.ExperimentalEnv, "1")()
   339  
   340  	// Verify shouldn't work since we haven't put anything in it yet.
   341  	mustErr(verify(pubKeyPath, imgName, true, nil), t)
   342  
   343  	// Sign again with the tlog env var on
   344  	must(cli.SignCmd(ctx, privKeyPath, imgName, true, "", nil, "", passFunc, false), t)
   345  	// And now verify works!
   346  	must(verify(pubKeyPath, imgName, true, nil), t)
   347  }
   348  
   349  func TestGetPublicKeyCustomOut(t *testing.T) {
   350  	td := t.TempDir()
   351  	keys, privKeyPath, _ := keypair(t, td)
   352  	ctx := context.Background()
   353  
   354  	f, err := os.Open(privKeyPath)
   355  	must(err, t)
   356  	outFile := "output.pub"
   357  	outPath := filepath.Join(td, outFile)
   358  	outWriter, err := os.OpenFile(outPath, os.O_WRONLY|os.O_CREATE, 0600)
   359  	must(err, t)
   360  	must(cli.GetPublicKey(ctx, f, "", cli.NamedWriter{Name: outPath, Writer: outWriter}, passFunc), t)
   361  
   362  	output, err := ioutil.ReadFile(outFile)
   363  	must(err, t)
   364  	equals(keys.PublicBytes, output, t)
   365  }
   366  
   367  func mkfile(contents, td string, t *testing.T) string {
   368  	f, err := ioutil.TempFile(td, "")
   369  	if err != nil {
   370  		t.Fatal(err)
   371  	}
   372  	defer f.Close()
   373  	if _, err := f.Write([]byte(contents)); err != nil {
   374  		t.Fatal(err)
   375  	}
   376  	return f.Name()
   377  }
   378  
   379  func mkimage(t *testing.T, n string) (name.Reference, *remote.Descriptor, func()) {
   380  	ref, err := name.ParseReference(n, name.WeakValidation)
   381  	if err != nil {
   382  		t.Fatal(err)
   383  	}
   384  	img, err := random.Image(512, 5)
   385  	if err != nil {
   386  		t.Fatal(err)
   387  	}
   388  
   389  	if err := remote.Write(ref, img, remote.WithAuthFromKeychain(authn.DefaultKeychain)); err != nil {
   390  		t.Fatal(err)
   391  	}
   392  
   393  	remoteImage, err := remote.Get(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain))
   394  	if err != nil {
   395  		t.Fatal(err)
   396  	}
   397  
   398  	cleanup := func() {
   399  		_ = remote.Delete(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain))
   400  		munged := cosign.Munge(remoteImage.Descriptor)
   401  		ref, _ := name.ParseReference(munged)
   402  		_ = remote.Delete(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain))
   403  	}
   404  	return ref, remoteImage, cleanup
   405  }
   406  
   407  func must(err error, t *testing.T) {
   408  	t.Helper()
   409  	if err != nil {
   410  		t.Fatal(err)
   411  	}
   412  }
   413  
   414  func mustErr(err error, t *testing.T) {
   415  	t.Helper()
   416  	if err == nil {
   417  		t.Fatal("expected error")
   418  	}
   419  }
   420  
   421  func equals(v1, v2 interface{}, t *testing.T) {
   422  	if diff := cmp.Diff(v1, v2); diff != "" {
   423  		t.Error(diff)
   424  	}
   425  }
   426  
   427  func reg(t *testing.T) (string, func()) {
   428  	repo := os.Getenv("COSIGN_TEST_REPO")
   429  	if repo != "" {
   430  		return repo, func() {}
   431  	}
   432  
   433  	t.Log("COSIGN_TEST_REPO unset, using fake registry")
   434  	r := httptest.NewServer(registry.New())
   435  	u, err := url.Parse(r.URL)
   436  	if err != nil {
   437  		t.Fatal(err)
   438  	}
   439  	return u.Host, r.Close
   440  }