Docker-DCO-1.1-Signed-off-by: Josh Hawn <josh.hawn@docker.com> (github: jlhawn)
| ... | ... |
@@ -6,8 +6,10 @@ import ( |
| 6 | 6 |
"fmt" |
| 7 | 7 |
|
| 8 | 8 |
log "github.com/Sirupsen/logrus" |
| 9 |
+ "github.com/docker/distribution/digest" |
|
| 9 | 10 |
"github.com/docker/docker/engine" |
| 10 | 11 |
"github.com/docker/docker/registry" |
| 12 |
+ "github.com/docker/docker/utils" |
|
| 11 | 13 |
"github.com/docker/libtrust" |
| 12 | 14 |
) |
| 13 | 15 |
|
| ... | ... |
@@ -16,7 +18,7 @@ import ( |
| 16 | 16 |
// contains no signatures by a trusted key for the name in the manifest, the |
| 17 | 17 |
// image is not considered verified. The parsed manifest object and a boolean |
| 18 | 18 |
// for whether the manifest is verified is returned. |
| 19 |
-func (s *TagStore) loadManifest(eng *engine.Engine, manifestBytes []byte) (*registry.ManifestData, bool, error) {
|
|
| 19 |
+func (s *TagStore) loadManifest(eng *engine.Engine, manifestBytes []byte, dgst, ref string) (*registry.ManifestData, bool, error) {
|
|
| 20 | 20 |
sig, err := libtrust.ParsePrettySignature(manifestBytes, "signatures") |
| 21 | 21 |
if err != nil {
|
| 22 | 22 |
return nil, false, fmt.Errorf("error parsing payload: %s", err)
|
| ... | ... |
@@ -32,6 +34,31 @@ func (s *TagStore) loadManifest(eng *engine.Engine, manifestBytes []byte) (*regi |
| 32 | 32 |
return nil, false, fmt.Errorf("error retrieving payload: %s", err)
|
| 33 | 33 |
} |
| 34 | 34 |
|
| 35 |
+ var manifestDigest digest.Digest |
|
| 36 |
+ |
|
| 37 |
+ if dgst != "" {
|
|
| 38 |
+ manifestDigest, err = digest.ParseDigest(dgst) |
|
| 39 |
+ if err != nil {
|
|
| 40 |
+ return nil, false, fmt.Errorf("invalid manifest digest from registry: %s", err)
|
|
| 41 |
+ } |
|
| 42 |
+ |
|
| 43 |
+ dgstVerifier, err := digest.NewDigestVerifier(manifestDigest) |
|
| 44 |
+ if err != nil {
|
|
| 45 |
+ return nil, false, fmt.Errorf("unable to verify manifest digest from registry: %s", err)
|
|
| 46 |
+ } |
|
| 47 |
+ |
|
| 48 |
+ dgstVerifier.Write(payload) |
|
| 49 |
+ |
|
| 50 |
+ if !dgstVerifier.Verified() {
|
|
| 51 |
+ computedDigest, _ := digest.FromBytes(payload) |
|
| 52 |
+ return nil, false, fmt.Errorf("unable to verify manifest digest: registry has %q, computed %q", manifestDigest, computedDigest)
|
|
| 53 |
+ } |
|
| 54 |
+ } |
|
| 55 |
+ |
|
| 56 |
+ if utils.DigestReference(ref) && ref != manifestDigest.String() {
|
|
| 57 |
+ return nil, false, fmt.Errorf("mismatching image manifest digest: got %q, expected %q", manifestDigest, ref)
|
|
| 58 |
+ } |
|
| 59 |
+ |
|
| 35 | 60 |
var manifest registry.ManifestData |
| 36 | 61 |
if err := json.Unmarshal(payload, &manifest); err != nil {
|
| 37 | 62 |
return nil, false, fmt.Errorf("error unmarshalling manifest: %s", err)
|
| ... | ... |
@@ -430,12 +430,15 @@ func (s *TagStore) pullV2Repository(eng *engine.Engine, r *registry.Session, out |
| 430 | 430 |
|
| 431 | 431 |
func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Writer, endpoint *registry.Endpoint, repoInfo *registry.RepositoryInfo, tag string, sf *utils.StreamFormatter, parallel bool, auth *registry.RequestAuthorization) (bool, error) {
|
| 432 | 432 |
log.Debugf("Pulling tag from V2 registry: %q", tag)
|
| 433 |
+ |
|
| 433 | 434 |
manifestBytes, manifestDigest, err := r.GetV2ImageManifest(endpoint, repoInfo.RemoteName, tag, auth) |
| 434 | 435 |
if err != nil {
|
| 435 | 436 |
return false, err |
| 436 | 437 |
} |
| 437 | 438 |
|
| 438 |
- manifest, verified, err := s.loadManifest(eng, manifestBytes) |
|
| 439 |
+ // loadManifest ensures that the manifest payload has the expected digest |
|
| 440 |
+ // if the tag is a digest reference. |
|
| 441 |
+ manifest, verified, err := s.loadManifest(eng, manifestBytes, manifestDigest, tag) |
|
| 439 | 442 |
if err != nil {
|
| 440 | 443 |
return false, fmt.Errorf("error verifying manifest: %s", err)
|
| 441 | 444 |
} |
| ... | ... |
@@ -605,7 +608,7 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri |
| 605 | 605 |
out.Write(sf.FormatStatus(utils.ImageReference(repoInfo.CanonicalName, tag), "The image you are pulling has been verified. Important: image verification is a tech preview feature and should not be relied on to provide security.")) |
| 606 | 606 |
} |
| 607 | 607 |
|
| 608 |
- if len(manifestDigest) > 0 {
|
|
| 608 |
+ if manifestDigest != "" {
|
|
| 609 | 609 |
out.Write(sf.FormatStatus("", "Digest: %s", manifestDigest))
|
| 610 | 610 |
} |
| 611 | 611 |
|
| ... | ... |
@@ -1,7 +1,6 @@ |
| 1 | 1 |
package graph |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "bytes" |
|
| 5 | 4 |
"crypto/sha256" |
| 6 | 5 |
"encoding/json" |
| 7 | 6 |
"errors" |
| ... | ... |
@@ -432,14 +431,12 @@ func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, o |
| 432 | 432 |
log.Infof("Signed manifest for %s:%s using daemon's key: %s", repoInfo.LocalName, tag, s.trustKey.KeyID())
|
| 433 | 433 |
|
| 434 | 434 |
// push the manifest |
| 435 |
- digest, err := r.PutV2ImageManifest(endpoint, repoInfo.RemoteName, tag, bytes.NewReader(signedBody), auth) |
|
| 435 |
+ digest, err := r.PutV2ImageManifest(endpoint, repoInfo.RemoteName, tag, signedBody, mBytes, auth) |
|
| 436 | 436 |
if err != nil {
|
| 437 | 437 |
return err |
| 438 | 438 |
} |
| 439 | 439 |
|
| 440 |
- if len(digest) > 0 {
|
|
| 441 |
- out.Write(sf.FormatStatus("", "Digest: %s", digest))
|
|
| 442 |
- } |
|
| 440 |
+ out.Write(sf.FormatStatus("", "Digest: %s", digest))
|
|
| 443 | 441 |
} |
| 444 | 442 |
return nil |
| 445 | 443 |
} |
| ... | ... |
@@ -1,6 +1,7 @@ |
| 1 | 1 |
package registry |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "bytes" |
|
| 4 | 5 |
"encoding/json" |
| 5 | 6 |
"fmt" |
| 6 | 7 |
"io" |
| ... | ... |
@@ -8,6 +9,7 @@ import ( |
| 8 | 8 |
"strconv" |
| 9 | 9 |
|
| 10 | 10 |
log "github.com/Sirupsen/logrus" |
| 11 |
+ "github.com/docker/distribution/digest" |
|
| 11 | 12 |
"github.com/docker/docker/registry/v2" |
| 12 | 13 |
"github.com/docker/docker/utils" |
| 13 | 14 |
) |
| ... | ... |
@@ -95,11 +97,12 @@ func (r *Session) GetV2ImageManifest(ep *Endpoint, imageName, tagName string, au |
| 95 | 95 |
return nil, "", utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch for %s:%s", res.StatusCode, imageName, tagName), res)
|
| 96 | 96 |
} |
| 97 | 97 |
|
| 98 |
- buf, err := ioutil.ReadAll(res.Body) |
|
| 98 |
+ manifestBytes, err := ioutil.ReadAll(res.Body) |
|
| 99 | 99 |
if err != nil {
|
| 100 | 100 |
return nil, "", fmt.Errorf("Error while reading the http response: %s", err)
|
| 101 | 101 |
} |
| 102 |
- return buf, res.Header.Get(DockerDigestHeader), nil |
|
| 102 |
+ |
|
| 103 |
+ return manifestBytes, res.Header.Get(DockerDigestHeader), nil |
|
| 103 | 104 |
} |
| 104 | 105 |
|
| 105 | 106 |
// - Succeeded to head image blob (already exists) |
| ... | ... |
@@ -263,7 +266,7 @@ func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName, sumType, sumStr string |
| 263 | 263 |
} |
| 264 | 264 |
|
| 265 | 265 |
// Finally Push the (signed) manifest of the blobs we've just pushed |
| 266 |
-func (r *Session) PutV2ImageManifest(ep *Endpoint, imageName, tagName string, manifestRdr io.Reader, auth *RequestAuthorization) (string, error) {
|
|
| 266 |
+func (r *Session) PutV2ImageManifest(ep *Endpoint, imageName, tagName string, signedManifest, rawManifest []byte, auth *RequestAuthorization) (digest.Digest, error) {
|
|
| 267 | 267 |
routeURL, err := getV2Builder(ep).BuildManifestURL(imageName, tagName) |
| 268 | 268 |
if err != nil {
|
| 269 | 269 |
return "", err |
| ... | ... |
@@ -271,7 +274,7 @@ func (r *Session) PutV2ImageManifest(ep *Endpoint, imageName, tagName string, ma |
| 271 | 271 |
|
| 272 | 272 |
method := "PUT" |
| 273 | 273 |
log.Debugf("[registry] Calling %q %s", method, routeURL)
|
| 274 |
- req, err := r.reqFactory.NewRequest(method, routeURL, manifestRdr) |
|
| 274 |
+ req, err := r.reqFactory.NewRequest(method, routeURL, bytes.NewReader(signedManifest)) |
|
| 275 | 275 |
if err != nil {
|
| 276 | 276 |
return "", err |
| 277 | 277 |
} |
| ... | ... |
@@ -297,7 +300,24 @@ func (r *Session) PutV2ImageManifest(ep *Endpoint, imageName, tagName string, ma |
| 297 | 297 |
return "", utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s:%s manifest", res.StatusCode, imageName, tagName), res)
|
| 298 | 298 |
} |
| 299 | 299 |
|
| 300 |
- return res.Header.Get(DockerDigestHeader), nil |
|
| 300 |
+ hdrDigest, err := digest.ParseDigest(res.Header.Get(DockerDigestHeader)) |
|
| 301 |
+ if err != nil {
|
|
| 302 |
+ return "", fmt.Errorf("invalid manifest digest from registry: %s", err)
|
|
| 303 |
+ } |
|
| 304 |
+ |
|
| 305 |
+ dgstVerifier, err := digest.NewDigestVerifier(hdrDigest) |
|
| 306 |
+ if err != nil {
|
|
| 307 |
+ return "", fmt.Errorf("invalid manifest digest from registry: %s", err)
|
|
| 308 |
+ } |
|
| 309 |
+ |
|
| 310 |
+ dgstVerifier.Write(rawManifest) |
|
| 311 |
+ |
|
| 312 |
+ if !dgstVerifier.Verified() {
|
|
| 313 |
+ computedDigest, _ := digest.FromBytes(rawManifest) |
|
| 314 |
+ return "", fmt.Errorf("unable to verify manifest digest: registry has %q, computed %q", hdrDigest, computedDigest)
|
|
| 315 |
+ } |
|
| 316 |
+ |
|
| 317 |
+ return hdrDigest, nil |
|
| 301 | 318 |
} |
| 302 | 319 |
|
| 303 | 320 |
type remoteTags struct {
|