Merge pull request #21 from stevvooe/manifest-package

Move manifest to discrete package
This commit is contained in:
Olivier Gambier 2015-01-05 11:30:56 -08:00
commit 89cd694e51
17 changed files with 297 additions and 168 deletions

View file

@ -17,7 +17,7 @@ import (
"github.com/docker/distribution/common/testutil" "github.com/docker/distribution/common/testutil"
"github.com/docker/distribution/configuration" "github.com/docker/distribution/configuration"
"github.com/docker/distribution/digest" "github.com/docker/distribution/digest"
"github.com/docker/distribution/storage" "github.com/docker/distribution/manifest"
_ "github.com/docker/distribution/storagedriver/inmemory" _ "github.com/docker/distribution/storagedriver/inmemory"
"github.com/docker/libtrust" "github.com/docker/libtrust"
"github.com/gorilla/handlers" "github.com/gorilla/handlers"
@ -268,10 +268,10 @@ func TestManifestAPI(t *testing.T) {
// -------------------------------- // --------------------------------
// Attempt to push unsigned manifest with missing layers // Attempt to push unsigned manifest with missing layers
unsignedManifest := &storage.Manifest{ unsignedManifest := &manifest.Manifest{
Name: imageName, Name: imageName,
Tag: tag, Tag: tag,
FSLayers: []storage.FSLayer{ FSLayers: []manifest.FSLayer{
{ {
BlobSum: "asdf", BlobSum: "asdf",
}, },
@ -345,7 +345,7 @@ func TestManifestAPI(t *testing.T) {
// ------------------- // -------------------
// Push the signed manifest with all layers pushed. // Push the signed manifest with all layers pushed.
signedManifest, err := unsignedManifest.Sign(pk) signedManifest, err := manifest.Sign(unsignedManifest, pk)
if err != nil { if err != nil {
t.Fatalf("unexpected error signing manifest: %v", err) t.Fatalf("unexpected error signing manifest: %v", err)
} }
@ -362,7 +362,7 @@ func TestManifestAPI(t *testing.T) {
checkResponse(t, "fetching uploaded manifest", resp, http.StatusOK) checkResponse(t, "fetching uploaded manifest", resp, http.StatusOK)
var fetchedManifest storage.SignedManifest var fetchedManifest manifest.SignedManifest
dec = json.NewDecoder(resp.Body) dec = json.NewDecoder(resp.Body)
if err := dec.Decode(&fetchedManifest); err != nil { if err := dec.Decode(&fetchedManifest); err != nil {
t.Fatalf("error decoding fetched manifest: %v", err) t.Fatalf("error decoding fetched manifest: %v", err)
@ -404,7 +404,7 @@ func TestManifestAPI(t *testing.T) {
func putManifest(t *testing.T, msg, url string, v interface{}) *http.Response { func putManifest(t *testing.T, msg, url string, v interface{}) *http.Response {
var body []byte var body []byte
if sm, ok := v.(*storage.SignedManifest); ok { if sm, ok := v.(*manifest.SignedManifest); ok {
body = sm.Raw body = sm.Raw
} else { } else {
var err error var err error

View file

@ -12,18 +12,18 @@ import (
"github.com/docker/distribution/api/v2" "github.com/docker/distribution/api/v2"
"github.com/docker/distribution/digest" "github.com/docker/distribution/digest"
"github.com/docker/distribution/storage" "github.com/docker/distribution/manifest"
) )
// Client implements the client interface to the registry http api // Client implements the client interface to the registry http api
type Client interface { type Client interface {
// GetImageManifest returns an image manifest for the image at the given // GetImageManifest returns an image manifest for the image at the given
// name, tag pair. // name, tag pair.
GetImageManifest(name, tag string) (*storage.SignedManifest, error) GetImageManifest(name, tag string) (*manifest.SignedManifest, error)
// PutImageManifest uploads an image manifest for the image at the given // PutImageManifest uploads an image manifest for the image at the given
// name, tag pair. // name, tag pair.
PutImageManifest(name, tag string, imageManifest *storage.SignedManifest) error PutImageManifest(name, tag string, imageManifest *manifest.SignedManifest) error
// DeleteImage removes the image at the given name, tag pair. // DeleteImage removes the image at the given name, tag pair.
DeleteImage(name, tag string) error DeleteImage(name, tag string) error
@ -91,7 +91,7 @@ type clientImpl struct {
// TODO(bbland): use consistent route generation between server and client // TODO(bbland): use consistent route generation between server and client
func (r *clientImpl) GetImageManifest(name, tag string) (*storage.SignedManifest, error) { func (r *clientImpl) GetImageManifest(name, tag string) (*manifest.SignedManifest, error) {
manifestURL, err := r.ub.BuildManifestURL(name, tag) manifestURL, err := r.ub.BuildManifestURL(name, tag)
if err != nil { if err != nil {
return nil, err return nil, err
@ -124,7 +124,7 @@ func (r *clientImpl) GetImageManifest(name, tag string) (*storage.SignedManifest
decoder := json.NewDecoder(response.Body) decoder := json.NewDecoder(response.Body)
manifest := new(storage.SignedManifest) manifest := new(manifest.SignedManifest)
err = decoder.Decode(manifest) err = decoder.Decode(manifest)
if err != nil { if err != nil {
return nil, err return nil, err
@ -132,7 +132,7 @@ func (r *clientImpl) GetImageManifest(name, tag string) (*storage.SignedManifest
return manifest, nil return manifest, nil
} }
func (r *clientImpl) PutImageManifest(name, tag string, manifest *storage.SignedManifest) error { func (r *clientImpl) PutImageManifest(name, tag string, manifest *manifest.SignedManifest) error {
manifestURL, err := r.ub.BuildManifestURL(name, tag) manifestURL, err := r.ub.BuildManifestURL(name, tag)
if err != nil { if err != nil {
return err return err

View file

@ -11,7 +11,7 @@ import (
"github.com/docker/distribution/common/testutil" "github.com/docker/distribution/common/testutil"
"github.com/docker/distribution/digest" "github.com/docker/distribution/digest"
"github.com/docker/distribution/storage" "github.com/docker/distribution/manifest"
) )
type testBlob struct { type testBlob struct {
@ -33,8 +33,8 @@ func TestPush(t *testing.T) {
}, },
} }
uploadLocations := make([]string, len(testBlobs)) uploadLocations := make([]string, len(testBlobs))
blobs := make([]storage.FSLayer, len(testBlobs)) blobs := make([]manifest.FSLayer, len(testBlobs))
history := make([]storage.ManifestHistory, len(testBlobs)) history := make([]manifest.History, len(testBlobs))
for i, blob := range testBlobs { for i, blob := range testBlobs {
// TODO(bbland): this is returning the same location for all uploads, // TODO(bbland): this is returning the same location for all uploads,
@ -42,24 +42,24 @@ func TestPush(t *testing.T) {
// It's sort of okay because we're using unique digests, but this needs // It's sort of okay because we're using unique digests, but this needs
// to change at some point. // to change at some point.
uploadLocations[i] = fmt.Sprintf("/v2/%s/blobs/test-uuid", name) uploadLocations[i] = fmt.Sprintf("/v2/%s/blobs/test-uuid", name)
blobs[i] = storage.FSLayer{BlobSum: blob.digest} blobs[i] = manifest.FSLayer{BlobSum: blob.digest}
history[i] = storage.ManifestHistory{V1Compatibility: blob.digest.String()} history[i] = manifest.History{V1Compatibility: blob.digest.String()}
} }
manifest := &storage.SignedManifest{ m := &manifest.SignedManifest{
Manifest: storage.Manifest{ Manifest: manifest.Manifest{
Name: name, Name: name,
Tag: tag, Tag: tag,
Architecture: "x86", Architecture: "x86",
FSLayers: blobs, FSLayers: blobs,
History: history, History: history,
Versioned: storage.Versioned{ Versioned: manifest.Versioned{
SchemaVersion: 1, SchemaVersion: 1,
}, },
}, },
} }
var err error var err error
manifest.Raw, err = json.Marshal(manifest) m.Raw, err = json.Marshal(m)
blobRequestResponseMappings := make([]testutil.RequestResponseMapping, 2*len(testBlobs)) blobRequestResponseMappings := make([]testutil.RequestResponseMapping, 2*len(testBlobs))
for i, blob := range testBlobs { for i, blob := range testBlobs {
@ -94,7 +94,7 @@ func TestPush(t *testing.T) {
Request: testutil.Request{ Request: testutil.Request{
Method: "PUT", Method: "PUT",
Route: "/v2/" + name + "/manifests/" + tag, Route: "/v2/" + name + "/manifests/" + tag,
Body: manifest.Raw, Body: m.Raw,
}, },
Response: testutil.Response{ Response: testutil.Response{
StatusCode: http.StatusOK, StatusCode: http.StatusOK,
@ -119,7 +119,7 @@ func TestPush(t *testing.T) {
} }
objectStore := &memoryObjectStore{ objectStore := &memoryObjectStore{
mutex: new(sync.Mutex), mutex: new(sync.Mutex),
manifestStorage: make(map[string]*storage.SignedManifest), manifestStorage: make(map[string]*manifest.SignedManifest),
layerStorage: make(map[digest.Digest]Layer), layerStorage: make(map[digest.Digest]Layer),
} }
@ -139,7 +139,7 @@ func TestPush(t *testing.T) {
writer.Close() writer.Close()
} }
objectStore.WriteManifest(name, tag, manifest) objectStore.WriteManifest(name, tag, m)
err = Push(client, objectStore, name, tag) err = Push(client, objectStore, name, tag)
if err != nil { if err != nil {
@ -160,27 +160,27 @@ func TestPull(t *testing.T) {
contents: []byte("some other contents"), contents: []byte("some other contents"),
}, },
} }
blobs := make([]storage.FSLayer, len(testBlobs)) blobs := make([]manifest.FSLayer, len(testBlobs))
history := make([]storage.ManifestHistory, len(testBlobs)) history := make([]manifest.History, len(testBlobs))
for i, blob := range testBlobs { for i, blob := range testBlobs {
blobs[i] = storage.FSLayer{BlobSum: blob.digest} blobs[i] = manifest.FSLayer{BlobSum: blob.digest}
history[i] = storage.ManifestHistory{V1Compatibility: blob.digest.String()} history[i] = manifest.History{V1Compatibility: blob.digest.String()}
} }
manifest := &storage.SignedManifest{ m := &manifest.SignedManifest{
Manifest: storage.Manifest{ Manifest: manifest.Manifest{
Name: name, Name: name,
Tag: tag, Tag: tag,
Architecture: "x86", Architecture: "x86",
FSLayers: blobs, FSLayers: blobs,
History: history, History: history,
Versioned: storage.Versioned{ Versioned: manifest.Versioned{
SchemaVersion: 1, SchemaVersion: 1,
}, },
}, },
} }
manifestBytes, err := json.Marshal(manifest) manifestBytes, err := json.Marshal(m)
blobRequestResponseMappings := make([]testutil.RequestResponseMapping, len(testBlobs)) blobRequestResponseMappings := make([]testutil.RequestResponseMapping, len(testBlobs))
for i, blob := range testBlobs { for i, blob := range testBlobs {
@ -213,7 +213,7 @@ func TestPull(t *testing.T) {
} }
objectStore := &memoryObjectStore{ objectStore := &memoryObjectStore{
mutex: new(sync.Mutex), mutex: new(sync.Mutex),
manifestStorage: make(map[string]*storage.SignedManifest), manifestStorage: make(map[string]*manifest.SignedManifest),
layerStorage: make(map[digest.Digest]Layer), layerStorage: make(map[digest.Digest]Layer),
} }
@ -222,7 +222,7 @@ func TestPull(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
m, err := objectStore.Manifest(name, tag) m, err = objectStore.Manifest(name, tag)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -272,25 +272,25 @@ func TestPullResume(t *testing.T) {
contents: []byte("some other contents"), contents: []byte("some other contents"),
}, },
} }
layers := make([]storage.FSLayer, len(testBlobs)) layers := make([]manifest.FSLayer, len(testBlobs))
history := make([]storage.ManifestHistory, len(testBlobs)) history := make([]manifest.History, len(testBlobs))
for i, layer := range testBlobs { for i, layer := range testBlobs {
layers[i] = storage.FSLayer{BlobSum: layer.digest} layers[i] = manifest.FSLayer{BlobSum: layer.digest}
history[i] = storage.ManifestHistory{V1Compatibility: layer.digest.String()} history[i] = manifest.History{V1Compatibility: layer.digest.String()}
} }
manifest := &storage.Manifest{ m := &manifest.Manifest{
Name: name, Name: name,
Tag: tag, Tag: tag,
Architecture: "x86", Architecture: "x86",
FSLayers: layers, FSLayers: layers,
History: history, History: history,
Versioned: storage.Versioned{ Versioned: manifest.Versioned{
SchemaVersion: 1, SchemaVersion: 1,
}, },
} }
manifestBytes, err := json.Marshal(manifest) manifestBytes, err := json.Marshal(m)
layerRequestResponseMappings := make([]testutil.RequestResponseMapping, 2*len(testBlobs)) layerRequestResponseMappings := make([]testutil.RequestResponseMapping, 2*len(testBlobs))
for i, blob := range testBlobs { for i, blob := range testBlobs {
@ -340,7 +340,7 @@ func TestPullResume(t *testing.T) {
} }
objectStore := &memoryObjectStore{ objectStore := &memoryObjectStore{
mutex: new(sync.Mutex), mutex: new(sync.Mutex),
manifestStorage: make(map[string]*storage.SignedManifest), manifestStorage: make(map[string]*manifest.SignedManifest),
layerStorage: make(map[digest.Digest]Layer), layerStorage: make(map[digest.Digest]Layer),
} }
@ -355,12 +355,12 @@ func TestPullResume(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
m, err := objectStore.Manifest(name, tag) sm, err := objectStore.Manifest(name, tag)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
mBytes, err := json.Marshal(m) mBytes, err := json.Marshal(sm)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -7,7 +7,7 @@ import (
"sync" "sync"
"github.com/docker/distribution/digest" "github.com/docker/distribution/digest"
"github.com/docker/distribution/storage" "github.com/docker/distribution/manifest"
) )
var ( var (
@ -26,11 +26,11 @@ var (
type ObjectStore interface { type ObjectStore interface {
// Manifest retrieves the image manifest stored at the given repository name // Manifest retrieves the image manifest stored at the given repository name
// and tag // and tag
Manifest(name, tag string) (*storage.SignedManifest, error) Manifest(name, tag string) (*manifest.SignedManifest, error)
// WriteManifest stores an image manifest at the given repository name and // WriteManifest stores an image manifest at the given repository name and
// tag // tag
WriteManifest(name, tag string, manifest *storage.SignedManifest) error WriteManifest(name, tag string, manifest *manifest.SignedManifest) error
// Layer returns a handle to a layer for reading and writing // Layer returns a handle to a layer for reading and writing
Layer(dgst digest.Digest) (Layer, error) Layer(dgst digest.Digest) (Layer, error)
@ -83,11 +83,11 @@ type LayerWriter interface {
// memoryObjectStore is an in-memory implementation of the ObjectStore interface // memoryObjectStore is an in-memory implementation of the ObjectStore interface
type memoryObjectStore struct { type memoryObjectStore struct {
mutex *sync.Mutex mutex *sync.Mutex
manifestStorage map[string]*storage.SignedManifest manifestStorage map[string]*manifest.SignedManifest
layerStorage map[digest.Digest]Layer layerStorage map[digest.Digest]Layer
} }
func (objStore *memoryObjectStore) Manifest(name, tag string) (*storage.SignedManifest, error) { func (objStore *memoryObjectStore) Manifest(name, tag string) (*manifest.SignedManifest, error) {
objStore.mutex.Lock() objStore.mutex.Lock()
defer objStore.mutex.Unlock() defer objStore.mutex.Unlock()
@ -98,7 +98,7 @@ func (objStore *memoryObjectStore) Manifest(name, tag string) (*storage.SignedMa
return manifest, nil return manifest, nil
} }
func (objStore *memoryObjectStore) WriteManifest(name, tag string, manifest *storage.SignedManifest) error { func (objStore *memoryObjectStore) WriteManifest(name, tag string, manifest *manifest.SignedManifest) error {
objStore.mutex.Lock() objStore.mutex.Lock()
defer objStore.mutex.Unlock() defer objStore.mutex.Unlock()

View file

@ -4,9 +4,9 @@ import (
"fmt" "fmt"
"io" "io"
"github.com/docker/distribution/storage"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/docker/distribution/manifest"
) )
// simultaneousLayerPullWindow is the size of the parallel layer pull window. // simultaneousLayerPullWindow is the size of the parallel layer pull window.
@ -77,7 +77,7 @@ func Pull(c Client, objectStore ObjectStore, name, tag string) error {
return nil return nil
} }
func pullLayer(c Client, objectStore ObjectStore, name string, fsLayer storage.FSLayer) error { func pullLayer(c Client, objectStore ObjectStore, name string, fsLayer manifest.FSLayer) error {
log.WithField("layer", fsLayer).Info("Pulling layer") log.WithField("layer", fsLayer).Info("Pulling layer")
layer, err := objectStore.Layer(fsLayer.BlobSum) layer, err := objectStore.Layer(fsLayer.BlobSum)

View file

@ -4,7 +4,7 @@ import (
"fmt" "fmt"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/docker/distribution/storage" "github.com/docker/distribution/manifest"
) )
// simultaneousLayerPushWindow is the size of the parallel layer push window. // simultaneousLayerPushWindow is the size of the parallel layer push window.
@ -12,7 +12,7 @@ import (
// push window has been successfully pushed. // push window has been successfully pushed.
const simultaneousLayerPushWindow = 4 const simultaneousLayerPushWindow = 4
type pushFunction func(fsLayer storage.FSLayer) error type pushFunction func(fsLayer manifest.FSLayer) error
// Push implements a client push workflow for the image defined by the given // Push implements a client push workflow for the image defined by the given
// name and tag pair, using the given ObjectStore for local manifest and layer // name and tag pair, using the given ObjectStore for local manifest and layer
@ -71,7 +71,7 @@ func Push(c Client, objectStore ObjectStore, name, tag string) error {
return nil return nil
} }
func pushLayer(c Client, objectStore ObjectStore, name string, fsLayer storage.FSLayer) error { func pushLayer(c Client, objectStore ObjectStore, name string, fsLayer manifest.FSLayer) error {
log.WithField("layer", fsLayer).Info("Pushing layer") log.WithField("layer", fsLayer).Info("Pushing layer")
layer, err := objectStore.Layer(fsLayer.BlobSum) layer, err := objectStore.Layer(fsLayer.BlobSum)

View file

@ -7,6 +7,7 @@ import (
"github.com/docker/distribution/api/v2" "github.com/docker/distribution/api/v2"
"github.com/docker/distribution/digest" "github.com/docker/distribution/digest"
"github.com/docker/distribution/manifest"
"github.com/docker/distribution/storage" "github.com/docker/distribution/storage"
"github.com/gorilla/handlers" "github.com/gorilla/handlers"
) )
@ -56,7 +57,7 @@ func (imh *imageManifestHandler) PutImageManifest(w http.ResponseWriter, r *http
manifests := imh.services.Manifests() manifests := imh.services.Manifests()
dec := json.NewDecoder(r.Body) dec := json.NewDecoder(r.Body)
var manifest storage.SignedManifest var manifest manifest.SignedManifest
if err := dec.Decode(&manifest); err != nil { if err := dec.Decode(&manifest); err != nil {
imh.Errors.Push(v2.ErrorCodeManifestInvalid, err) imh.Errors.Push(v2.ErrorCodeManifestInvalid, err)
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)

View file

@ -1,12 +1,9 @@
package storage package manifest
import ( import (
"crypto/x509"
"encoding/json" "encoding/json"
"github.com/Sirupsen/logrus"
"github.com/docker/distribution/digest" "github.com/docker/distribution/digest"
"github.com/docker/libtrust"
) )
// Versioned provides a struct with just the manifest schemaVersion. Incoming // Versioned provides a struct with just the manifest schemaVersion. Incoming
@ -36,65 +33,7 @@ type Manifest struct {
FSLayers []FSLayer `json:"fsLayers"` FSLayers []FSLayer `json:"fsLayers"`
// History is a list of unstructured historical data for v1 compatibility // History is a list of unstructured historical data for v1 compatibility
History []ManifestHistory `json:"history"` History []History `json:"history"`
}
// Sign signs the manifest with the provided private key, returning a
// SignedManifest. This typically won't be used within the registry, except
// for testing.
func (m *Manifest) Sign(pk libtrust.PrivateKey) (*SignedManifest, error) {
p, err := json.MarshalIndent(m, "", " ")
if err != nil {
return nil, err
}
js, err := libtrust.NewJSONSignature(p)
if err != nil {
return nil, err
}
if err := js.Sign(pk); err != nil {
return nil, err
}
pretty, err := js.PrettySignature("signatures")
if err != nil {
return nil, err
}
return &SignedManifest{
Manifest: *m,
Raw: pretty,
}, nil
}
// SignWithChain signs the manifest with the given private key and x509 chain.
// The public key of the first element in the chain must be the public key
// corresponding with the sign key.
func (m *Manifest) SignWithChain(key libtrust.PrivateKey, chain []*x509.Certificate) (*SignedManifest, error) {
p, err := json.MarshalIndent(m, "", " ")
if err != nil {
return nil, err
}
js, err := libtrust.NewJSONSignature(p)
if err != nil {
return nil, err
}
if err := js.SignWithChain(key, chain); err != nil {
return nil, err
}
pretty, err := js.PrettySignature("signatures")
if err != nil {
return nil, err
}
return &SignedManifest{
Manifest: *m,
Raw: pretty,
}, nil
} }
// SignedManifest provides an envelope for a signed image manifest, including // SignedManifest provides an envelope for a signed image manifest, including
@ -109,30 +48,6 @@ type SignedManifest struct {
Raw []byte `json:"-"` Raw []byte `json:"-"`
} }
// Verify verifies the signature of the signed manifest returning the public
// keys used during signing.
func (sm *SignedManifest) Verify() ([]libtrust.PublicKey, error) {
js, err := libtrust.ParsePrettySignature(sm.Raw, "signatures")
if err != nil {
logrus.WithField("err", err).Debugf("(*SignedManifest).Verify")
return nil, err
}
return js.Verify()
}
// VerifyChains verifies the signature of the signed manifest against the
// certificate pool returning the list of verified chains. Signatures without
// an x509 chain are not checked.
func (sm *SignedManifest) VerifyChains(ca *x509.CertPool) ([][]*x509.Certificate, error) {
js, err := libtrust.ParsePrettySignature(sm.Raw, "signatures")
if err != nil {
return nil, err
}
return js.VerifyChains(ca)
}
// UnmarshalJSON populates a new ImageManifest struct from JSON data. // UnmarshalJSON populates a new ImageManifest struct from JSON data.
func (sm *SignedManifest) UnmarshalJSON(b []byte) error { func (sm *SignedManifest) UnmarshalJSON(b []byte) error {
var manifest Manifest var manifest Manifest
@ -149,7 +64,7 @@ func (sm *SignedManifest) UnmarshalJSON(b []byte) error {
// MarshalJSON returns the contents of raw. If Raw is nil, marshals the inner // MarshalJSON returns the contents of raw. If Raw is nil, marshals the inner
// contents. Applications requiring a marshaled signed manifest should simply // contents. Applications requiring a marshaled signed manifest should simply
// use Raw directly, since the the content produced by json.Marshal will // use Raw directly, since the the content produced by json.Marshal will be
// compacted and will fail signature checks. // compacted and will fail signature checks.
func (sm *SignedManifest) MarshalJSON() ([]byte, error) { func (sm *SignedManifest) MarshalJSON() ([]byte, error) {
if len(sm.Raw) > 0 { if len(sm.Raw) > 0 {
@ -166,8 +81,8 @@ type FSLayer struct {
BlobSum digest.Digest `json:"blobSum"` BlobSum digest.Digest `json:"blobSum"`
} }
// ManifestHistory stores unstructured v1 compatibility information // History stores unstructured v1 compatibility information
type ManifestHistory struct { type History struct {
// V1Compatibility is the raw v1 compatibility information // V1Compatibility is the raw v1 compatibility information
V1Compatibility string `json:"v1Compatibility"` V1Compatibility string `json:"v1Compatibility"`
} }

110
manifest/manifest_test.go Normal file
View file

@ -0,0 +1,110 @@
package manifest
import (
"bytes"
"encoding/json"
"reflect"
"testing"
"github.com/docker/libtrust"
)
type testEnv struct {
name, tag string
manifest *Manifest
signed *SignedManifest
pk libtrust.PrivateKey
}
func TestManifestMarshaling(t *testing.T) {
env := genEnv(t)
// Check that the Raw field is the same as json.MarshalIndent with these
// parameters.
p, err := json.MarshalIndent(env.signed, "", " ")
if err != nil {
t.Fatalf("error marshaling manifest: %v", err)
}
if !bytes.Equal(p, env.signed.Raw) {
t.Fatalf("manifest bytes not equal: %q != %q", string(env.signed.Raw), string(p))
}
}
func TestManifestUnmarshaling(t *testing.T) {
env := genEnv(t)
var signed SignedManifest
if err := json.Unmarshal(env.signed.Raw, &signed); err != nil {
t.Fatalf("error unmarshaling signed manifest: %v", err)
}
if !reflect.DeepEqual(&signed, env.signed) {
t.Fatalf("manifests are different after unmarshaling: %v != %v", signed, env.signed)
}
}
func TestManifestVerification(t *testing.T) {
env := genEnv(t)
publicKeys, err := Verify(env.signed)
if err != nil {
t.Fatalf("error verifying manifest: %v", err)
}
if len(publicKeys) == 0 {
t.Fatalf("no public keys found in signature")
}
var found bool
publicKey := env.pk.PublicKey()
// ensure that one of the extracted public keys matches the private key.
for _, candidate := range publicKeys {
if candidate.KeyID() == publicKey.KeyID() {
found = true
break
}
}
if !found {
t.Fatalf("expected public key, %v, not found in verified keys: %v", publicKey, publicKeys)
}
}
func genEnv(t *testing.T) *testEnv {
pk, err := libtrust.GenerateECP256PrivateKey()
if err != nil {
t.Fatalf("error generating test key: %v", err)
}
name, tag := "foo/bar", "test"
m := Manifest{
Versioned: Versioned{
SchemaVersion: 1,
},
Name: name,
Tag: tag,
FSLayers: []FSLayer{
{
BlobSum: "asdf",
},
{
BlobSum: "qwer",
},
},
}
sm, err := Sign(&m, pk)
if err != nil {
t.Fatalf("error signing manifest: %v", err)
}
return &testEnv{
name: name,
tag: tag,
manifest: &m,
signed: sm,
pk: pk,
}
}

66
manifest/sign.go Normal file
View file

@ -0,0 +1,66 @@
package manifest
import (
"crypto/x509"
"encoding/json"
"github.com/docker/libtrust"
)
// Sign signs the manifest with the provided private key, returning a
// SignedManifest. This typically won't be used within the registry, except
// for testing.
func Sign(m *Manifest, pk libtrust.PrivateKey) (*SignedManifest, error) {
p, err := json.MarshalIndent(m, "", " ")
if err != nil {
return nil, err
}
js, err := libtrust.NewJSONSignature(p)
if err != nil {
return nil, err
}
if err := js.Sign(pk); err != nil {
return nil, err
}
pretty, err := js.PrettySignature("signatures")
if err != nil {
return nil, err
}
return &SignedManifest{
Manifest: *m,
Raw: pretty,
}, nil
}
// SignWithChain signs the manifest with the given private key and x509 chain.
// The public key of the first element in the chain must be the public key
// corresponding with the sign key.
func SignWithChain(m *Manifest, key libtrust.PrivateKey, chain []*x509.Certificate) (*SignedManifest, error) {
p, err := json.MarshalIndent(m, "", " ")
if err != nil {
return nil, err
}
js, err := libtrust.NewJSONSignature(p)
if err != nil {
return nil, err
}
if err := js.SignWithChain(key, chain); err != nil {
return nil, err
}
pretty, err := js.PrettySignature("signatures")
if err != nil {
return nil, err
}
return &SignedManifest{
Manifest: *m,
Raw: pretty,
}, nil
}

32
manifest/verify.go Normal file
View file

@ -0,0 +1,32 @@
package manifest
import (
"crypto/x509"
"github.com/Sirupsen/logrus"
"github.com/docker/libtrust"
)
// Verify verifies the signature of the signed manifest returning the public
// keys used during signing.
func Verify(sm *SignedManifest) ([]libtrust.PublicKey, error) {
js, err := libtrust.ParsePrettySignature(sm.Raw, "signatures")
if err != nil {
logrus.WithField("err", err).Debugf("(*SignedManifest).Verify")
return nil, err
}
return js.Verify()
}
// VerifyChains verifies the signature of the signed manifest against the
// certificate pool returning the list of verified chains. Signatures without
// an x509 chain are not checked.
func VerifyChains(sm *SignedManifest, ca *x509.CertPool) ([][]*x509.Certificate, error) {
js, err := libtrust.ParsePrettySignature(sm.Raw, "signatures")
if err != nil {
return nil, err
}
return js.VerifyChains(ca)
}

View file

@ -6,6 +6,7 @@ import (
"time" "time"
"github.com/docker/distribution/digest" "github.com/docker/distribution/digest"
"github.com/docker/distribution/manifest"
) )
// Layer provides a readable and seekable layer object. Typically, // Layer provides a readable and seekable layer object. Typically,
@ -74,7 +75,7 @@ var (
// ErrUnknownLayer returned when layer cannot be found. // ErrUnknownLayer returned when layer cannot be found.
type ErrUnknownLayer struct { type ErrUnknownLayer struct {
FSLayer FSLayer FSLayer manifest.FSLayer
} }
func (err ErrUnknownLayer) Error() string { func (err ErrUnknownLayer) Error() string {
@ -83,7 +84,7 @@ func (err ErrUnknownLayer) Error() string {
// ErrLayerInvalidDigest returned when tarsum check fails. // ErrLayerInvalidDigest returned when tarsum check fails.
type ErrLayerInvalidDigest struct { type ErrLayerInvalidDigest struct {
FSLayer FSLayer FSLayer manifest.FSLayer
} }
func (err ErrLayerInvalidDigest) Error() string { func (err ErrLayerInvalidDigest) Error() string {

View file

@ -2,6 +2,7 @@ package storage
import ( import (
"github.com/docker/distribution/digest" "github.com/docker/distribution/digest"
"github.com/docker/distribution/manifest"
"github.com/docker/distribution/storagedriver" "github.com/docker/distribution/storagedriver"
) )
@ -33,7 +34,7 @@ func (ls *layerStore) Fetch(name string, digest digest.Digest) (Layer, error) {
if err != nil { if err != nil {
switch err := err.(type) { switch err := err.(type) {
case storagedriver.PathNotFoundError, *storagedriver.PathNotFoundError: case storagedriver.PathNotFoundError, *storagedriver.PathNotFoundError:
return nil, ErrUnknownLayer{FSLayer{BlobSum: digest}} return nil, ErrUnknownLayer{manifest.FSLayer{BlobSum: digest}}
default: default:
return nil, err return nil, err
} }
@ -43,7 +44,7 @@ func (ls *layerStore) Fetch(name string, digest digest.Digest) (Layer, error) {
if err != nil { if err != nil {
switch err := err.(type) { switch err := err.(type) {
case storagedriver.PathNotFoundError, *storagedriver.PathNotFoundError: case storagedriver.PathNotFoundError, *storagedriver.PathNotFoundError:
return nil, ErrUnknownLayer{FSLayer{BlobSum: digest}} return nil, ErrUnknownLayer{manifest.FSLayer{BlobSum: digest}}
default: default:
return nil, err return nil, err
} }

View file

@ -10,6 +10,7 @@ import (
"code.google.com/p/go-uuid/uuid" "code.google.com/p/go-uuid/uuid"
"github.com/docker/distribution/digest" "github.com/docker/distribution/digest"
"github.com/docker/distribution/manifest"
"github.com/docker/distribution/storagedriver" "github.com/docker/distribution/storagedriver"
"github.com/docker/docker/pkg/tarsum" "github.com/docker/docker/pkg/tarsum"
@ -284,7 +285,7 @@ func (luc *layerUploadController) validateLayer(fp layerFile, size int64, dgst d
} }
if !digestVerifier.Verified() { if !digestVerifier.Verified() {
return "", ErrLayerInvalidDigest{FSLayer{BlobSum: dgst}} return "", ErrLayerInvalidDigest{manifest.FSLayer{BlobSum: dgst}}
} }
return dgst, nil return dgst, nil

View file

@ -6,6 +6,7 @@ import (
"path" "path"
"strings" "strings"
"github.com/docker/distribution/manifest"
"github.com/docker/distribution/storagedriver" "github.com/docker/distribution/storagedriver"
"github.com/docker/libtrust" "github.com/docker/libtrust"
) )
@ -116,7 +117,7 @@ func (ms *manifestStore) Exists(name, tag string) (bool, error) {
return true, nil return true, nil
} }
func (ms *manifestStore) Get(name, tag string) (*SignedManifest, error) { func (ms *manifestStore) Get(name, tag string) (*manifest.SignedManifest, error) {
p, err := ms.path(name, tag) p, err := ms.path(name, tag)
if err != nil { if err != nil {
return nil, err return nil, err
@ -132,7 +133,7 @@ func (ms *manifestStore) Get(name, tag string) (*SignedManifest, error) {
} }
} }
var manifest SignedManifest var manifest manifest.SignedManifest
if err := json.Unmarshal(content, &manifest); err != nil { if err := json.Unmarshal(content, &manifest); err != nil {
// TODO(stevvooe): Corrupted manifest error? // TODO(stevvooe): Corrupted manifest error?
@ -144,7 +145,7 @@ func (ms *manifestStore) Get(name, tag string) (*SignedManifest, error) {
return &manifest, nil return &manifest, nil
} }
func (ms *manifestStore) Put(name, tag string, manifest *SignedManifest) error { func (ms *manifestStore) Put(name, tag string, manifest *manifest.SignedManifest) error {
p, err := ms.path(name, tag) p, err := ms.path(name, tag)
if err != nil { if err != nil {
return err return err
@ -185,19 +186,19 @@ func (ms *manifestStore) path(name, tag string) (string, error) {
}) })
} }
func (ms *manifestStore) verifyManifest(name, tag string, manifest *SignedManifest) error { func (ms *manifestStore) verifyManifest(name, tag string, mnfst *manifest.SignedManifest) error {
// TODO(stevvooe): This verification is present here, but this needs to be // TODO(stevvooe): This verification is present here, but this needs to be
// lifted out of the storage infrastructure and moved into a package // lifted out of the storage infrastructure and moved into a package
// oriented towards defining verifiers and reporting them with // oriented towards defining verifiers and reporting them with
// granularity. // granularity.
var errs ErrManifestVerification var errs ErrManifestVerification
if manifest.Name != name { if mnfst.Name != name {
// TODO(stevvooe): This needs to be an exported error // TODO(stevvooe): This needs to be an exported error
errs = append(errs, fmt.Errorf("name does not match manifest name")) errs = append(errs, fmt.Errorf("name does not match manifest name"))
} }
if manifest.Tag != tag { if mnfst.Tag != tag {
// TODO(stevvooe): This needs to be an exported error. // TODO(stevvooe): This needs to be an exported error.
errs = append(errs, fmt.Errorf("tag does not match manifest tag")) errs = append(errs, fmt.Errorf("tag does not match manifest tag"))
} }
@ -206,7 +207,7 @@ func (ms *manifestStore) verifyManifest(name, tag string, manifest *SignedManife
// VerifyWithChains. We need to define the exact source of the CA. // VerifyWithChains. We need to define the exact source of the CA.
// Perhaps, its a configuration value injected into manifest store. // Perhaps, its a configuration value injected into manifest store.
if _, err := manifest.Verify(); err != nil { if _, err := manifest.Verify(mnfst); err != nil {
switch err { switch err {
case libtrust.ErrMissingSignatureKey, libtrust.ErrInvalidJSONContent, libtrust.ErrMissingSignatureKey: case libtrust.ErrMissingSignatureKey, libtrust.ErrInvalidJSONContent, libtrust.ErrMissingSignatureKey:
errs = append(errs, ErrManifestUnverified{}) errs = append(errs, ErrManifestUnverified{})
@ -219,7 +220,7 @@ func (ms *manifestStore) verifyManifest(name, tag string, manifest *SignedManife
} }
} }
for _, fsLayer := range manifest.FSLayers { for _, fsLayer := range mnfst.FSLayers {
exists, err := ms.layerService.Exists(name, fsLayer.BlobSum) exists, err := ms.layerService.Exists(name, fsLayer.BlobSum)
if err != nil { if err != nil {
errs = append(errs, err) errs = append(errs, err)

View file

@ -4,10 +4,10 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/docker/libtrust"
"github.com/docker/distribution/digest" "github.com/docker/distribution/digest"
"github.com/docker/distribution/manifest"
"github.com/docker/distribution/storagedriver/inmemory" "github.com/docker/distribution/storagedriver/inmemory"
"github.com/docker/libtrust"
) )
func TestManifestStorage(t *testing.T) { func TestManifestStorage(t *testing.T) {
@ -42,13 +42,13 @@ func TestManifestStorage(t *testing.T) {
} }
} }
manifest := Manifest{ m := manifest.Manifest{
Versioned: Versioned{ Versioned: manifest.Versioned{
SchemaVersion: 1, SchemaVersion: 1,
}, },
Name: name, Name: name,
Tag: tag, Tag: tag,
FSLayers: []FSLayer{ FSLayers: []manifest.FSLayer{
{ {
BlobSum: "asdf", BlobSum: "asdf",
}, },
@ -63,7 +63,7 @@ func TestManifestStorage(t *testing.T) {
t.Fatalf("unexpected error generating private key: %v", err) t.Fatalf("unexpected error generating private key: %v", err)
} }
sm, err := manifest.Sign(pk) sm, err := manifest.Sign(&m, pk)
if err != nil { if err != nil {
t.Fatalf("error signing manifest: %v", err) t.Fatalf("error signing manifest: %v", err)
} }

View file

@ -2,6 +2,7 @@ package storage
import ( import (
"github.com/docker/distribution/digest" "github.com/docker/distribution/digest"
"github.com/docker/distribution/manifest"
"github.com/docker/distribution/storagedriver" "github.com/docker/distribution/storagedriver"
) )
@ -59,10 +60,10 @@ type ManifestService interface {
Exists(name, tag string) (bool, error) Exists(name, tag string) (bool, error)
// Get retrieves the named manifest, if it exists. // Get retrieves the named manifest, if it exists.
Get(name, tag string) (*SignedManifest, error) Get(name, tag string) (*manifest.SignedManifest, error)
// Put creates or updates the named manifest. // Put creates or updates the named manifest.
Put(name, tag string, manifest *SignedManifest) error Put(name, tag string, manifest *manifest.SignedManifest) error
// Delete removes the named manifest, if it exists. // Delete removes the named manifest, if it exists.
Delete(name, tag string) error Delete(name, tag string) error