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 }