2015-01-30 05:28:20 +00:00
|
|
|
package graph
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
2015-03-04 20:05:17 +00:00
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
2015-01-30 05:28:20 +00:00
|
|
|
"os"
|
|
|
|
"testing"
|
|
|
|
|
2015-05-29 23:28:06 +00:00
|
|
|
"github.com/docker/distribution/digest"
|
2015-01-30 05:28:20 +00:00
|
|
|
"github.com/docker/docker/image"
|
2015-03-04 20:05:17 +00:00
|
|
|
"github.com/docker/docker/pkg/tarsum"
|
2015-01-30 05:28:20 +00:00
|
|
|
"github.com/docker/docker/registry"
|
2015-03-04 20:05:17 +00:00
|
|
|
"github.com/docker/docker/runconfig"
|
2015-01-30 05:28:20 +00:00
|
|
|
"github.com/docker/docker/utils"
|
2015-05-29 23:28:06 +00:00
|
|
|
"github.com/docker/libtrust"
|
2015-01-30 05:28:20 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
testManifestImageName = "testapp"
|
|
|
|
testManifestImageID = "d821b739e8834ec89ac4469266c3d11515da88fdcbcbdddcbcddb636f54fdde9"
|
|
|
|
testManifestImageIDShort = "d821b739e883"
|
|
|
|
testManifestTag = "manifesttest"
|
|
|
|
)
|
|
|
|
|
2015-03-04 20:05:17 +00:00
|
|
|
func (s *TagStore) newManifest(localName, remoteName, tag string) ([]byte, error) {
|
|
|
|
manifest := ®istry.ManifestData{
|
|
|
|
Name: remoteName,
|
|
|
|
Tag: tag,
|
|
|
|
SchemaVersion: 1,
|
|
|
|
}
|
|
|
|
localRepo, err := s.Get(localName)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if localRepo == nil {
|
|
|
|
return nil, fmt.Errorf("Repo does not exist: %s", localName)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the top-most layer id which the tag points to
|
|
|
|
layerId, exists := localRepo[tag]
|
|
|
|
if !exists {
|
|
|
|
return nil, fmt.Errorf("Tag does not exist for %s: %s", localName, tag)
|
|
|
|
}
|
|
|
|
layersSeen := make(map[string]bool)
|
|
|
|
|
|
|
|
layer, err := s.graph.Get(layerId)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
manifest.Architecture = layer.Architecture
|
|
|
|
manifest.FSLayers = make([]*registry.FSLayer, 0, 4)
|
|
|
|
manifest.History = make([]*registry.ManifestHistory, 0, 4)
|
|
|
|
var metadata runconfig.Config
|
|
|
|
if layer.Config != nil {
|
|
|
|
metadata = *layer.Config
|
|
|
|
}
|
|
|
|
|
|
|
|
for ; layer != nil; layer, err = layer.GetParent() {
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if layersSeen[layer.ID] {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if layer.Config != nil && metadata.Image != layer.ID {
|
|
|
|
err = runconfig.Merge(&metadata, layer.Config)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
checksum, err := layer.GetCheckSum(s.graph.ImageRoot(layer.ID))
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Error getting image checksum: %s", err)
|
|
|
|
}
|
|
|
|
if tarsum.VersionLabelForChecksum(checksum) != tarsum.Version1.String() {
|
|
|
|
archive, err := layer.TarLayer()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
defer archive.Close()
|
|
|
|
|
|
|
|
tarSum, err := tarsum.NewTarSum(archive, true, tarsum.Version1)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if _, err := io.Copy(ioutil.Discard, tarSum); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
checksum = tarSum.Sum(nil)
|
|
|
|
|
|
|
|
// Save checksum value
|
|
|
|
if err := layer.SaveCheckSum(s.graph.ImageRoot(layer.ID), checksum); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
jsonData, err := layer.RawJson()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Cannot retrieve the path for {%s}: %s", layer.ID, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
manifest.FSLayers = append(manifest.FSLayers, ®istry.FSLayer{BlobSum: checksum})
|
|
|
|
|
|
|
|
layersSeen[layer.ID] = true
|
|
|
|
|
|
|
|
manifest.History = append(manifest.History, ®istry.ManifestHistory{V1Compatibility: string(jsonData)})
|
|
|
|
}
|
|
|
|
|
|
|
|
manifestBytes, err := json.MarshalIndent(manifest, "", " ")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return manifestBytes, nil
|
|
|
|
}
|
|
|
|
|
2015-01-30 05:28:20 +00:00
|
|
|
func TestManifestTarsumCache(t *testing.T) {
|
|
|
|
tmp, err := utils.TestDirectory("")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(tmp)
|
|
|
|
store := mkTestTagStore(tmp, t)
|
|
|
|
defer store.graph.driver.Cleanup()
|
|
|
|
|
|
|
|
archive, err := fakeTar()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
img := &image.Image{ID: testManifestImageID}
|
|
|
|
if err := store.graph.Register(img, archive); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2015-04-14 02:46:29 +00:00
|
|
|
if err := store.Tag(testManifestImageName, testManifestTag, testManifestImageID, false); err != nil {
|
2015-01-30 05:28:20 +00:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if cs, err := img.GetCheckSum(store.graph.ImageRoot(testManifestImageID)); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
} else if cs != "" {
|
|
|
|
t.Fatalf("Non-empty checksum file after register")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate manifest
|
|
|
|
payload, err := store.newManifest(testManifestImageName, testManifestImageName, testManifestTag)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
manifestChecksum, err := img.GetCheckSum(store.graph.ImageRoot(testManifestImageID))
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var manifest registry.ManifestData
|
|
|
|
if err := json.Unmarshal(payload, &manifest); err != nil {
|
|
|
|
t.Fatalf("error unmarshalling manifest: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(manifest.FSLayers) != 1 {
|
|
|
|
t.Fatalf("Unexpected number of layers, expecting 1: %d", len(manifest.FSLayers))
|
|
|
|
}
|
|
|
|
|
|
|
|
if manifest.FSLayers[0].BlobSum != manifestChecksum {
|
|
|
|
t.Fatalf("Unexpected blob sum, expecting %q, got %q", manifestChecksum, manifest.FSLayers[0].BlobSum)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(manifest.History) != 1 {
|
|
|
|
t.Fatalf("Unexpected number of layer history, expecting 1: %d", len(manifest.History))
|
|
|
|
}
|
|
|
|
|
|
|
|
v1compat, err := img.RawJson()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if manifest.History[0].V1Compatibility != string(v1compat) {
|
|
|
|
t.Fatalf("Unexpected json value\nExpected:\n%s\nActual:\n%s", v1compat, manifest.History[0].V1Compatibility)
|
|
|
|
}
|
|
|
|
}
|
2015-05-29 23:28:06 +00:00
|
|
|
|
|
|
|
// TestManifestDigestCheck ensures that loadManifest properly verifies the
|
|
|
|
// remote and local digest.
|
|
|
|
func TestManifestDigestCheck(t *testing.T) {
|
|
|
|
tmp, err := utils.TestDirectory("")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(tmp)
|
|
|
|
store := mkTestTagStore(tmp, t)
|
|
|
|
defer store.graph.driver.Cleanup()
|
|
|
|
|
|
|
|
archive, err := fakeTar()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
img := &image.Image{ID: testManifestImageID}
|
|
|
|
if err := store.graph.Register(img, archive); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if err := store.Tag(testManifestImageName, testManifestTag, testManifestImageID, false); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if cs, err := img.GetCheckSum(store.graph.ImageRoot(testManifestImageID)); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
} else if cs != "" {
|
|
|
|
t.Fatalf("Non-empty checksum file after register")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate manifest
|
|
|
|
payload, err := store.newManifest(testManifestImageName, testManifestImageName, testManifestTag)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected error generating test manifest: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
pk, err := libtrust.GenerateECP256PrivateKey()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected error generating private key: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
sig, err := libtrust.NewJSONSignature(payload)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error creating signature: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := sig.Sign(pk); err != nil {
|
|
|
|
t.Fatalf("error signing manifest bytes: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
signedBytes, err := sig.PrettySignature("signatures")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error getting signed bytes: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
dgst, err := digest.FromBytes(payload)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error getting digest of manifest: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// use this as the "bad" digest
|
|
|
|
zeroDigest, err := digest.FromBytes([]byte{})
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error making zero digest: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remote and local match, everything should look good
|
|
|
|
local, _, _, err := store.loadManifest(signedBytes, dgst.String(), dgst)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected error verifying local and remote digest: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if local != dgst {
|
|
|
|
t.Fatalf("local digest not correctly calculated: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// remote and no local, since pulling by tag
|
|
|
|
local, _, _, err = store.loadManifest(signedBytes, "tag", dgst)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected error verifying tag pull and remote digest: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if local != dgst {
|
|
|
|
t.Fatalf("local digest not correctly calculated: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// remote and differing local, this is the most important to fail
|
|
|
|
local, _, _, err = store.loadManifest(signedBytes, zeroDigest.String(), dgst)
|
|
|
|
if err == nil {
|
|
|
|
t.Fatalf("error expected when verifying with differing local digest")
|
|
|
|
}
|
|
|
|
|
|
|
|
// no remote, no local (by tag)
|
|
|
|
local, _, _, err = store.loadManifest(signedBytes, "tag", "")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected error verifying manifest without remote digest: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if local != dgst {
|
|
|
|
t.Fatalf("local digest not correctly calculated: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// no remote, with local
|
|
|
|
local, _, _, err = store.loadManifest(signedBytes, dgst.String(), "")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected error verifying manifest without remote digest: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if local != dgst {
|
|
|
|
t.Fatalf("local digest not correctly calculated: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// bad remote, we fail the check.
|
|
|
|
local, _, _, err = store.loadManifest(signedBytes, dgst.String(), zeroDigest)
|
|
|
|
if err == nil {
|
|
|
|
t.Fatalf("error expected when verifying with differing remote digest")
|
|
|
|
}
|
|
|
|
}
|