Browse code

Revert client signature

Supports multiple tag push with daemon signature

Fixes #10444

Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)

Derek McGowan authored on 2015/01/31 07:20:32
Showing 8 changed files
... ...
@@ -46,7 +46,6 @@ import (
46 46
 	"github.com/docker/docker/registry"
47 47
 	"github.com/docker/docker/runconfig"
48 48
 	"github.com/docker/docker/utils"
49
-	"github.com/docker/libtrust"
50 49
 )
51 50
 
52 51
 const (
... ...
@@ -1191,10 +1190,6 @@ func (cli *DockerCli) CmdPush(args ...string) error {
1191 1191
 	name := cmd.Arg(0)
1192 1192
 
1193 1193
 	cli.LoadConfigFile()
1194
-	trustKey, err := api.LoadOrCreateTrustKey(cli.keyFile)
1195
-	if err != nil {
1196
-		log.Fatal(err)
1197
-	}
1198 1194
 
1199 1195
 	remote, tag := parsers.ParseRepositoryTag(name)
1200 1196
 
... ...
@@ -1220,25 +1215,6 @@ func (cli *DockerCli) CmdPush(args ...string) error {
1220 1220
 	v := url.Values{}
1221 1221
 	v.Set("tag", tag)
1222 1222
 
1223
-	body, _, err := readBody(cli.call("GET", "/images/"+remote+"/manifest?"+v.Encode(), nil, false))
1224
-	if err != nil {
1225
-		return err
1226
-	}
1227
-
1228
-	js, err := libtrust.NewJSONSignature(body)
1229
-	if err != nil {
1230
-		return err
1231
-	}
1232
-	err = js.Sign(trustKey)
1233
-	if err != nil {
1234
-		return err
1235
-	}
1236
-
1237
-	signedBody, err := js.PrettySignature("signatures")
1238
-	if err != nil {
1239
-		return err
1240
-	}
1241
-
1242 1223
 	push := func(authConfig registry.AuthConfig) error {
1243 1224
 		buf, err := json.Marshal(authConfig)
1244 1225
 		if err != nil {
... ...
@@ -1248,7 +1224,7 @@ func (cli *DockerCli) CmdPush(args ...string) error {
1248 1248
 			base64.URLEncoding.EncodeToString(buf),
1249 1249
 		}
1250 1250
 
1251
-		return cli.stream("POST", "/images/"+remote+"/push?"+v.Encode(), bytes.NewReader(signedBody), cli.out, map[string][]string{
1251
+		return cli.stream("POST", "/images/"+remote+"/push?"+v.Encode(), nil, cli.out, map[string][]string{
1252 1252
 			"X-Registry-Auth": registryAuthHeader,
1253 1253
 		})
1254 1254
 	}
... ...
@@ -621,18 +621,6 @@ func getImagesSearch(eng *engine.Engine, version version.Version, w http.Respons
621 621
 	return job.Run()
622 622
 }
623 623
 
624
-func getImageManifest(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
625
-	if err := parseForm(r); err != nil {
626
-		return err
627
-	}
628
-
629
-	job := eng.Job("image_manifest", vars["name"])
630
-	job.Setenv("tag", r.Form.Get("tag"))
631
-	job.Stdout.Add(utils.NewWriteFlusher(w))
632
-
633
-	return job.Run()
634
-}
635
-
636 624
 func postImagesPush(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
637 625
 	if vars == nil {
638 626
 		return fmt.Errorf("Missing parameter")
... ...
@@ -664,15 +652,9 @@ func postImagesPush(eng *engine.Engine, version version.Version, w http.Response
664 664
 		}
665 665
 	}
666 666
 
667
-	manifest, err := ioutil.ReadAll(r.Body)
668
-	if err != nil {
669
-		return err
670
-	}
671
-
672 667
 	job := eng.Job("push", vars["name"])
673 668
 	job.SetenvJson("metaHeaders", metaHeaders)
674 669
 	job.SetenvJson("authConfig", authConfig)
675
-	job.Setenv("manifest", string(manifest))
676 670
 	job.Setenv("tag", r.Form.Get("tag"))
677 671
 	if version.GreaterThan("1.0") {
678 672
 		job.SetenvBool("json", true)
... ...
@@ -1325,7 +1307,6 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion st
1325 1325
 			"/images/viz":                     getImagesViz,
1326 1326
 			"/images/search":                  getImagesSearch,
1327 1327
 			"/images/get":                     getImagesGet,
1328
-			"/images/{name:.*}/manifest":      getImageManifest,
1329 1328
 			"/images/{name:.*}/get":           getImagesGet,
1330 1329
 			"/images/{name:.*}/history":       getImagesHistory,
1331 1330
 			"/images/{name:.*}/json":          getImagesByName,
... ...
@@ -15,35 +15,6 @@ import (
15 15
 	"github.com/docker/libtrust"
16 16
 )
17 17
 
18
-func (s *TagStore) CmdManifest(job *engine.Job) engine.Status {
19
-	if len(job.Args) != 1 {
20
-		return job.Errorf("usage: %s NAME", job.Name)
21
-	}
22
-	name := job.Args[0]
23
-	tag := job.Getenv("tag")
24
-	if tag == "" {
25
-		tag = "latest"
26
-	}
27
-
28
-	// Resolve the Repository name from fqn to endpoint + name
29
-	repoInfo, err := registry.ParseRepositoryInfo(name)
30
-	if err != nil {
31
-		return job.Error(err)
32
-	}
33
-
34
-	manifestBytes, err := s.newManifest(name, repoInfo.RemoteName, tag)
35
-	if err != nil {
36
-		return job.Error(err)
37
-	}
38
-
39
-	_, err = job.Stdout.Write(manifestBytes)
40
-	if err != nil {
41
-		return job.Error(err)
42
-	}
43
-
44
-	return engine.StatusOK
45
-}
46
-
47 18
 func (s *TagStore) newManifest(localName, remoteName, tag string) ([]byte, error) {
48 19
 	manifest := &registry.ManifestData{
49 20
 		Name:          remoteName,
... ...
@@ -130,7 +101,12 @@ func (s *TagStore) newManifest(localName, remoteName, tag string) ([]byte, error
130 130
 	return manifestBytes, nil
131 131
 }
132 132
 
133
-func (s *TagStore) verifyManifest(eng *engine.Engine, manifestBytes []byte) (*registry.ManifestData, bool, error) {
133
+// loadManifest loads a manifest from a byte array and verifies its content.
134
+// The signature must be verified or an error is returned. If the manifest
135
+// contains no signatures by a trusted key for the name in the manifest, the
136
+// image is not considered verified. The parsed manifest object and a boolean
137
+// for whether the manifest is verified is returned.
138
+func (s *TagStore) loadManifest(eng *engine.Engine, manifestBytes []byte) (*registry.ManifestData, bool, error) {
134 139
 	sig, err := libtrust.ParsePrettySignature(manifestBytes, "signatures")
135 140
 	if err != nil {
136 141
 		return nil, false, fmt.Errorf("error parsing payload: %s", err)
... ...
@@ -417,7 +417,7 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri
417 417
 		return false, err
418 418
 	}
419 419
 
420
-	manifest, verified, err := s.verifyManifest(eng, manifestBytes)
420
+	manifest, verified, err := s.loadManifest(eng, manifestBytes)
421 421
 	if err != nil {
422 422
 		return false, fmt.Errorf("error verifying manifest: %s", err)
423 423
 	}
... ...
@@ -65,6 +65,25 @@ func (s *TagStore) getImageList(localRepo map[string]string, requestedTag string
65 65
 	return imageList, tagsByImage, nil
66 66
 }
67 67
 
68
+func (s *TagStore) getImageTags(localName, askedTag string) ([]string, error) {
69
+	localRepo, err := s.Get(localName)
70
+	if err != nil {
71
+		return nil, err
72
+	}
73
+	log.Debugf("Checking %s against %#v", askedTag, localRepo)
74
+	if len(askedTag) > 0 {
75
+		if _, ok := localRepo[askedTag]; !ok {
76
+			return nil, fmt.Errorf("Tag does not exist for %s:%s", localName, askedTag)
77
+		}
78
+		return []string{askedTag}, nil
79
+	}
80
+	var tags []string
81
+	for tag := range localRepo {
82
+		tags = append(tags, tag)
83
+	}
84
+	return tags, nil
85
+}
86
+
68 87
 // createImageIndex returns an index of an image's layer IDs and tags.
69 88
 func (s *TagStore) createImageIndex(images []string, tags map[string][]string) []*registry.ImgData {
70 89
 	var imageIndex []*registry.ImgData
... ...
@@ -251,7 +270,7 @@ func (s *TagStore) pushImage(r *registry.Session, out io.Writer, imgID, ep strin
251 251
 	return imgData.Checksum, nil
252 252
 }
253 253
 
254
-func (s *TagStore) pushV2Repository(r *registry.Session, eng *engine.Engine, out io.Writer, repoInfo *registry.RepositoryInfo, manifestBytes, tag string, sf *utils.StreamFormatter) error {
254
+func (s *TagStore) pushV2Repository(r *registry.Session, eng *engine.Engine, out io.Writer, repoInfo *registry.RepositoryInfo, tag string, sf *utils.StreamFormatter) error {
255 255
 	if repoInfo.Official {
256 256
 		j := eng.Job("trust_update_base")
257 257
 		if err := j.Run(); err != nil {
... ...
@@ -263,13 +282,22 @@ func (s *TagStore) pushV2Repository(r *registry.Session, eng *engine.Engine, out
263 263
 	if err != nil {
264 264
 		return fmt.Errorf("error getting registry endpoint: %s", err)
265 265
 	}
266
+
267
+	tags, err := s.getImageTags(repoInfo.LocalName, tag)
268
+	if err != nil {
269
+		return err
270
+	}
271
+	if len(tags) == 0 {
272
+		return fmt.Errorf("No tags to push for %s", repoInfo.LocalName)
273
+	}
274
+
266 275
 	auth, err := r.GetV2Authorization(endpoint, repoInfo.RemoteName, false)
267 276
 	if err != nil {
268 277
 		return fmt.Errorf("error getting authorization: %s", err)
269 278
 	}
270 279
 
271
-	// if no manifest is given, generate and sign with the key associated with the local tag store
272
-	if len(manifestBytes) == 0 {
280
+	for _, tag := range tags {
281
+		log.Debugf("Pushing %s:%s to v2 repository", repoInfo.LocalName, tag)
273 282
 		mBytes, err := s.newManifest(repoInfo.LocalName, repoInfo.RemoteName, tag)
274 283
 		if err != nil {
275 284
 			return err
... ...
@@ -287,63 +315,66 @@ func (s *TagStore) pushV2Repository(r *registry.Session, eng *engine.Engine, out
287 287
 		if err != nil {
288 288
 			return err
289 289
 		}
290
-		log.Infof("Signed manifest using daemon's key: %s", s.trustKey.KeyID())
291
-
292
-		manifestBytes = string(signedBody)
293
-	}
294
-
295
-	manifest, verified, err := s.verifyManifest(eng, []byte(manifestBytes))
296
-	if err != nil {
297
-		return fmt.Errorf("error verifying manifest: %s", err)
298
-	}
299
-
300
-	if err := checkValidManifest(manifest); err != nil {
301
-		return fmt.Errorf("invalid manifest: %s", err)
302
-	}
303
-
304
-	if !verified {
305
-		log.Debugf("Pushing unverified image")
306
-	}
290
+		log.Infof("Signed manifest for %s:%s using daemon's key: %s", repoInfo.LocalName, tag, s.trustKey.KeyID())
307 291
 
308
-	for i := len(manifest.FSLayers) - 1; i >= 0; i-- {
309
-		var (
310
-			sumStr  = manifest.FSLayers[i].BlobSum
311
-			imgJSON = []byte(manifest.History[i].V1Compatibility)
312
-		)
292
+		manifestBytes := string(signedBody)
313 293
 
314
-		sumParts := strings.SplitN(sumStr, ":", 2)
315
-		if len(sumParts) < 2 {
316
-			return fmt.Errorf("Invalid checksum: %s", sumStr)
294
+		manifest, verified, err := s.loadManifest(eng, signedBody)
295
+		if err != nil {
296
+			return fmt.Errorf("error verifying manifest: %s", err)
317 297
 		}
318
-		manifestSum := sumParts[1]
319 298
 
320
-		img, err := image.NewImgJSON(imgJSON)
321
-		if err != nil {
322
-			return fmt.Errorf("Failed to parse json: %s", err)
299
+		if err := checkValidManifest(manifest); err != nil {
300
+			return fmt.Errorf("invalid manifest: %s", err)
323 301
 		}
324 302
 
325
-		// Call mount blob
326
-		exists, err := r.HeadV2ImageBlob(endpoint, repoInfo.RemoteName, sumParts[0], manifestSum, auth)
327
-		if err != nil {
328
-			out.Write(sf.FormatProgress(utils.TruncateID(img.ID), "Image push failed", nil))
329
-			return err
303
+		if verified {
304
+			log.Infof("Pushing verified image, key %s is registered for %q", s.trustKey.KeyID(), repoInfo.RemoteName)
330 305
 		}
331 306
 
332
-		if !exists {
333
-			if err := s.PushV2Image(r, img, endpoint, repoInfo.RemoteName, sumParts[0], manifestSum, sf, out, auth); err != nil {
307
+		for i := len(manifest.FSLayers) - 1; i >= 0; i-- {
308
+			var (
309
+				sumStr  = manifest.FSLayers[i].BlobSum
310
+				imgJSON = []byte(manifest.History[i].V1Compatibility)
311
+			)
312
+
313
+			sumParts := strings.SplitN(sumStr, ":", 2)
314
+			if len(sumParts) < 2 {
315
+				return fmt.Errorf("Invalid checksum: %s", sumStr)
316
+			}
317
+			manifestSum := sumParts[1]
318
+
319
+			img, err := image.NewImgJSON(imgJSON)
320
+			if err != nil {
321
+				return fmt.Errorf("Failed to parse json: %s", err)
322
+			}
323
+
324
+			// Call mount blob
325
+			exists, err := r.HeadV2ImageBlob(endpoint, repoInfo.RemoteName, sumParts[0], manifestSum, auth)
326
+			if err != nil {
327
+				out.Write(sf.FormatProgress(utils.TruncateID(img.ID), "Image push failed", nil))
334 328
 				return err
335 329
 			}
336
-		} else {
337
-			out.Write(sf.FormatProgress(utils.TruncateID(img.ID), "Image already exists", nil))
330
+
331
+			if !exists {
332
+				if err := s.pushV2Image(r, img, endpoint, repoInfo.RemoteName, sumParts[0], manifestSum, sf, out, auth); err != nil {
333
+					return err
334
+				}
335
+			} else {
336
+				out.Write(sf.FormatProgress(utils.TruncateID(img.ID), "Image already exists", nil))
337
+			}
338 338
 		}
339
-	}
340 339
 
341
-	// push the manifest
342
-	return r.PutV2ImageManifest(endpoint, repoInfo.RemoteName, tag, bytes.NewReader([]byte(manifestBytes)), auth)
340
+		// push the manifest
341
+		if err := r.PutV2ImageManifest(endpoint, repoInfo.RemoteName, tag, bytes.NewReader([]byte(manifestBytes)), auth); err != nil {
342
+			return err
343
+		}
344
+	}
345
+	return nil
343 346
 }
344 347
 
345 348
 // PushV2Image pushes the image content to the v2 registry, first buffering the contents to disk
346
-func (s *TagStore) PushV2Image(r *registry.Session, img *image.Image, endpoint *registry.Endpoint, imageName, sumType, sumStr string, sf *utils.StreamFormatter, out io.Writer, auth *registry.RequestAuthorization) error {
349
+func (s *TagStore) pushV2Image(r *registry.Session, img *image.Image, endpoint *registry.Endpoint, imageName, sumType, sumStr string, sf *utils.StreamFormatter, out io.Writer, auth *registry.RequestAuthorization) error {
347 350
 	out.Write(sf.FormatProgress(utils.TruncateID(img.ID), "Buffering to Disk", nil))
348 351
 
349 352
 	image, err := s.graph.Get(img.ID)
... ...
@@ -398,7 +429,6 @@ func (s *TagStore) CmdPush(job *engine.Job) engine.Status {
398 398
 	}
399 399
 
400 400
 	tag := job.Getenv("tag")
401
-	manifestBytes := job.Getenv("manifest")
402 401
 	job.GetenvJson("authConfig", authConfig)
403 402
 	job.GetenvJson("metaHeaders", &metaHeaders)
404 403
 
... ...
@@ -418,12 +448,8 @@ func (s *TagStore) CmdPush(job *engine.Job) engine.Status {
418 418
 		return job.Error(err2)
419 419
 	}
420 420
 
421
-	if len(tag) == 0 {
422
-		tag = DEFAULTTAG
423
-	}
424
-
425 421
 	if repoInfo.Index.Official || endpoint.Version == registry.APIVersion2 {
426
-		err := s.pushV2Repository(r, job.Eng, job.Stdout, repoInfo, manifestBytes, tag, sf)
422
+		err := s.pushV2Repository(r, job.Eng, job.Stdout, repoInfo, tag, sf)
427 423
 		if err == nil {
428 424
 			return engine.StatusOK
429 425
 		}
... ...
@@ -25,7 +25,6 @@ func (s *TagStore) Install(eng *engine.Engine) error {
25 25
 		"import":         s.CmdImport,
26 26
 		"pull":           s.CmdPull,
27 27
 		"push":           s.CmdPush,
28
-		"image_manifest": s.CmdManifest,
29 28
 	} {
30 29
 		if err := eng.Register(name, handler); err != nil {
31 30
 			return fmt.Errorf("Could not register %q: %v", name, err)
... ...
@@ -24,6 +24,7 @@ func TestPullImageWithAliases(t *testing.T) {
24 24
 		if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "tag", "busybox", repo)); err != nil {
25 25
 			t.Fatalf("Failed to tag image %v: error %v, output %q", repos, err, out)
26 26
 		}
27
+		defer deleteImages(repo)
27 28
 		if out, err := exec.Command(dockerBinary, "push", repo).CombinedOutput(); err != nil {
28 29
 			t.Fatalf("Failed to push image %v: error %v, output %q", err, string(out))
29 30
 		}
... ...
@@ -40,7 +41,6 @@ func TestPullImageWithAliases(t *testing.T) {
40 40
 	if out, _, err := runCommandWithOutput(pullCmd); err != nil {
41 41
 		t.Fatalf("Failed to pull %v: error %v, output %q", repoName, err, out)
42 42
 	}
43
-	defer deleteImages(repos[0])
44 43
 	if err := exec.Command(dockerBinary, "inspect", repos[0]).Run(); err != nil {
45 44
 		t.Fatalf("Image %v was not pulled down", repos[0])
46 45
 	}
... ...
@@ -45,7 +45,7 @@ func TestPushUntagged(t *testing.T) {
45 45
 
46 46
 	repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
47 47
 
48
-	expected := "does not exist"
48
+	expected := "No tags to push"
49 49
 	pushCmd := exec.Command(dockerBinary, "push", repoName)
50 50
 	if out, _, err := runCommandWithOutput(pushCmd); err == nil {
51 51
 		t.Fatalf("pushing the image to the private registry should have failed: outuput %q", out)
... ...
@@ -55,6 +55,46 @@ func TestPushUntagged(t *testing.T) {
55 55
 	logDone("push - untagged image")
56 56
 }
57 57
 
58
+func TestPushBadTag(t *testing.T) {
59
+	defer setupRegistry(t)()
60
+
61
+	repoName := fmt.Sprintf("%v/dockercli/busybox:latest", privateRegistryURL)
62
+
63
+	expected := "does not exist"
64
+	pushCmd := exec.Command(dockerBinary, "push", repoName)
65
+	if out, _, err := runCommandWithOutput(pushCmd); err == nil {
66
+		t.Fatalf("pushing the image to the private registry should have failed: outuput %q", out)
67
+	} else if !strings.Contains(out, expected) {
68
+		t.Fatalf("pushing the image failed with an unexpected message: expected %q, got %q", expected, out)
69
+	}
70
+	logDone("push - image with bad tag")
71
+}
72
+
73
+func TestPushMultipleTags(t *testing.T) {
74
+	defer setupRegistry(t)()
75
+
76
+	repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
77
+	repoTag1 := fmt.Sprintf("%v/dockercli/busybox:t1", privateRegistryURL)
78
+	repoTag2 := fmt.Sprintf("%v/dockercli/busybox:t2", privateRegistryURL)
79
+	// tag the image to upload it tot he private registry
80
+	tagCmd1 := exec.Command(dockerBinary, "tag", "busybox", repoTag1)
81
+	if out, _, err := runCommandWithOutput(tagCmd1); err != nil {
82
+		t.Fatalf("image tagging failed: %s, %v", out, err)
83
+	}
84
+	defer deleteImages(repoTag1)
85
+	tagCmd2 := exec.Command(dockerBinary, "tag", "busybox", repoTag2)
86
+	if out, _, err := runCommandWithOutput(tagCmd2); err != nil {
87
+		t.Fatalf("image tagging failed: %s, %v", out, err)
88
+	}
89
+	defer deleteImages(repoTag2)
90
+
91
+	pushCmd := exec.Command(dockerBinary, "push", repoName)
92
+	if out, _, err := runCommandWithOutput(pushCmd); err != nil {
93
+		t.Fatalf("pushing the image to the private registry has failed: %s, %v", out, err)
94
+	}
95
+	logDone("push - multiple tags to private registry")
96
+}
97
+
58 98
 func TestPushInterrupt(t *testing.T) {
59 99
 	defer setupRegistry(t)()
60 100