Compute the digest in (skopeo inspect) instead of trusting the registry

Compute the digest ourselves, the registry is in general untrusted and
computing it ourserlves is easy enough.

The stop passing the unverifiedCanonicalDigest value around, simplifying
ImageSource.GetManifest and related code.  In particular, remove
retrieveRawManifest and have internal users just call Manifest() now that
we don't need the digest.
This commit is contained in:
Miloslav Trmač 2016-05-16 17:40:16 +02:00
parent e3d257e7b5
commit 60e8d63712
7 changed files with 36 additions and 46 deletions

View File

@ -54,7 +54,7 @@ func copyHandler(context *cli.Context) {
} }
signBy := context.String("sign-by") signBy := context.String("sign-by")
manifest, _, err := src.GetManifest() manifest, err := src.GetManifest()
if err != nil { if err != nil {
logrus.Fatalf("Error reading manifest: %s", err.Error()) logrus.Fatalf("Error reading manifest: %s", err.Error())
} }

View File

@ -7,6 +7,7 @@ import (
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/codegangsta/cli" "github.com/codegangsta/cli"
"github.com/projectatomic/skopeo/dockerutils"
) )
// inspectOutput is the output format of (skopeo inspect), primarily so that we can format it with a simple json.MarshalIndent. // inspectOutput is the output format of (skopeo inspect), primarily so that we can format it with a simple json.MarshalIndent.
@ -37,22 +38,26 @@ var inspectCmd = cli.Command{
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
if c.Bool("raw") { rawManifest, err := img.Manifest()
b, err := img.Manifest()
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
fmt.Println(string(b)) if c.Bool("raw") {
fmt.Println(string(rawManifest))
return return
} }
imgInspect, err := img.Inspect() imgInspect, err := img.Inspect()
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
manifestDigest, err := dockerutils.ManifestDigest(rawManifest)
if err != nil {
logrus.Fatalf("Error computing manifest digest: %s", err.Error())
}
outputData := inspectOutput{ outputData := inspectOutput{
Name: imgInspect.Name, Name: imgInspect.Name,
Tag: imgInspect.Tag, Tag: imgInspect.Tag,
Digest: imgInspect.Digest, Digest: manifestDigest,
RepoTags: imgInspect.RepoTags, RepoTags: imgInspect.RepoTags,
Created: imgInspect.Created, Created: imgInspect.Created,
DockerVersion: imgInspect.DockerVersion, DockerVersion: imgInspect.DockerVersion,

View File

@ -84,13 +84,8 @@ func (s *dirImageSource) IntendedDockerReference() string {
return "" return ""
} }
func (s *dirImageSource) GetManifest() ([]byte, string, error) { func (s *dirImageSource) GetManifest() ([]byte, error) {
manifest, err := ioutil.ReadFile(manifestPath(s.dir)) return ioutil.ReadFile(manifestPath(s.dir))
if err != nil {
return nil, "", err
}
return manifest, "", nil // FIXME? unverifiedCanonicalDigest value - really primarily used by dockerImage
} }
func (s *dirImageSource) GetLayer(digest string) (io.ReadCloser, error) { func (s *dirImageSource) GetLayer(digest string) (io.ReadCloser, error) {

View File

@ -20,8 +20,7 @@ var (
type dockerImage struct { type dockerImage struct {
src *dockerImageSource src *dockerImageSource
digest string cachedManifest []byte // Private cache for Manifest(); nil if not yet known.
rawManifest []byte
cachedSignatures [][]byte // Private cache for Signatures(); nil if not yet known. cachedSignatures [][]byte // Private cache for Signatures(); nil if not yet known.
} }
@ -44,10 +43,14 @@ func (i *dockerImage) IntendedDockerReference() string {
// Manifest is like ImageSource.GetManifest, but the result is cached; it is OK to call this however often you need. // Manifest is like ImageSource.GetManifest, but the result is cached; it is OK to call this however often you need.
func (i *dockerImage) Manifest() ([]byte, error) { func (i *dockerImage) Manifest() ([]byte, error) {
if err := i.retrieveRawManifest(); err != nil { if i.cachedManifest == nil {
m, err := i.src.GetManifest()
if err != nil {
return nil, err return nil, err
} }
return i.rawManifest, nil i.cachedManifest = m
}
return i.cachedManifest, nil
} }
// Signatures is like ImageSource.GetSignatures, but the result is cached; it is OK to call this however often you need. // Signatures is like ImageSource.GetSignatures, but the result is cached; it is OK to call this however often you need.
@ -76,7 +79,7 @@ func (i *dockerImage) Inspect() (*types.DockerImageManifest, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
imgManifest, err := makeImageManifest(i.src.ref.FullName(), ms1, i.digest, tags) imgManifest, err := makeImageManifest(i.src.ref.FullName(), ms1, tags)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -122,7 +125,7 @@ type v1Image struct {
OS string `json:"os,omitempty"` OS string `json:"os,omitempty"`
} }
func makeImageManifest(name string, m *manifestSchema1, dgst string, tagList []string) (*types.DockerImageManifest, error) { func makeImageManifest(name string, m *manifestSchema1, tagList []string) (*types.DockerImageManifest, error) {
v1 := &v1Image{} v1 := &v1Image{}
if err := json.Unmarshal([]byte(m.History[0].V1Compatibility), v1); err != nil { if err := json.Unmarshal([]byte(m.History[0].V1Compatibility), v1); err != nil {
return nil, err return nil, err
@ -130,7 +133,6 @@ func makeImageManifest(name string, m *manifestSchema1, dgst string, tagList []s
return &types.DockerImageManifest{ return &types.DockerImageManifest{
Name: name, Name: name,
Tag: m.Tag, Tag: m.Tag,
Digest: dgst,
RepoTags: tagList, RepoTags: tagList,
DockerVersion: v1.DockerVersion, DockerVersion: v1.DockerVersion,
Created: v1.Created, Created: v1.Created,
@ -181,25 +183,13 @@ func sanitize(s string) string {
return strings.Replace(s, "/", "-", -1) return strings.Replace(s, "/", "-", -1)
} }
func (i *dockerImage) retrieveRawManifest() error {
if i.rawManifest != nil {
return nil
}
manblob, unverifiedCanonicalDigest, err := i.src.GetManifest()
if err != nil {
return err
}
i.rawManifest = manblob
i.digest = unverifiedCanonicalDigest
return nil
}
func (i *dockerImage) getSchema1Manifest() (manifest, error) { func (i *dockerImage) getSchema1Manifest() (manifest, error) {
if err := i.retrieveRawManifest(); err != nil { manblob, err := i.Manifest()
if err != nil {
return nil, err return nil, err
} }
mschema1 := &manifestSchema1{} mschema1 := &manifestSchema1{}
if err := json.Unmarshal(i.rawManifest, mschema1); err != nil { if err := json.Unmarshal(manblob, mschema1); err != nil {
return nil, err return nil, err
} }
if err := fixManifestLayers(mschema1); err != nil { if err := fixManifestLayers(mschema1); err != nil {

View File

@ -55,23 +55,24 @@ func (s *dockerImageSource) IntendedDockerReference() string {
return fmt.Sprintf("%s:%s", s.ref.Name(), s.tag) return fmt.Sprintf("%s:%s", s.ref.Name(), s.tag)
} }
func (s *dockerImageSource) GetManifest() (manifest []byte, unverifiedCanonicalDigest string, err error) { func (s *dockerImageSource) GetManifest() ([]byte, error) {
url := fmt.Sprintf(manifestURL, s.ref.RemoteName(), s.tag) url := fmt.Sprintf(manifestURL, s.ref.RemoteName(), s.tag)
// TODO(runcom) set manifest version header! schema1 for now - then schema2 etc etc and v1 // TODO(runcom) set manifest version header! schema1 for now - then schema2 etc etc and v1
// TODO(runcom) NO, switch on the resulter manifest like Docker is doing // TODO(runcom) NO, switch on the resulter manifest like Docker is doing
res, err := s.c.makeRequest("GET", url, nil, nil) res, err := s.c.makeRequest("GET", url, nil, nil)
if err != nil { if err != nil {
return nil, "", err return nil, err
} }
defer res.Body.Close() defer res.Body.Close()
manblob, err := ioutil.ReadAll(res.Body) manblob, err := ioutil.ReadAll(res.Body)
if err != nil { if err != nil {
return nil, "", err return nil, err
} }
if res.StatusCode != http.StatusOK { if res.StatusCode != http.StatusOK {
return nil, "", errFetchManifest{res.StatusCode, manblob} return nil, errFetchManifest{res.StatusCode, manblob}
} }
return manblob, res.Header.Get("Docker-Content-Digest"), nil // We might validate manblob against the Docker-Content-Digest header here to protect against transport errors.
return manblob, nil
} }
func (s *dockerImageSource) GetLayer(digest string) (io.ReadCloser, error) { func (s *dockerImageSource) GetLayer(digest string) (io.ReadCloser, error) {

View File

@ -193,9 +193,9 @@ func (s *openshiftImageSource) IntendedDockerReference() string {
return s.client.canonicalDockerReference() return s.client.canonicalDockerReference()
} }
func (s *openshiftImageSource) GetManifest() (manifest []byte, unverifiedCanonicalDigest string, err error) { func (s *openshiftImageSource) GetManifest() ([]byte, error) {
if err := s.ensureImageIsResolved(); err != nil { if err := s.ensureImageIsResolved(); err != nil {
return nil, "", err return nil, err
} }
return s.docker.GetManifest() return s.docker.GetManifest()
} }

View File

@ -35,7 +35,7 @@ type ImageSource interface {
// May be "" if unknown. // May be "" if unknown.
IntendedDockerReference() string IntendedDockerReference() string
// GetManifest returns the image's manifest. It may use a remote (= slow) service. // GetManifest returns the image's manifest. It may use a remote (= slow) service.
GetManifest() (manifest []byte, unverifiedCanonicalDigest string, err error) GetManifest() ([]byte, error)
GetLayer(digest string) (io.ReadCloser, error) GetLayer(digest string) (io.ReadCloser, error)
// GetSignatures returns the image's signatures. It may use a remote (= slow) service. // GetSignatures returns the image's signatures. It may use a remote (= slow) service.
GetSignatures() ([][]byte, error) GetSignatures() ([][]byte, error)
@ -71,7 +71,6 @@ type Image interface {
type DockerImageManifest struct { type DockerImageManifest struct {
Name string Name string
Tag string Tag string
Digest string
RepoTags []string RepoTags []string
Created time.Time Created time.Time
DockerVersion string DockerVersion string