Merge pull request #21 from stevvooe/manifest-package
Move manifest to discrete package
This commit is contained in:
commit
89cd694e51
17 changed files with 297 additions and 168 deletions
12
api_test.go
12
api_test.go
|
@ -17,7 +17,7 @@ import (
|
|||
"github.com/docker/distribution/common/testutil"
|
||||
"github.com/docker/distribution/configuration"
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/distribution/storage"
|
||||
"github.com/docker/distribution/manifest"
|
||||
_ "github.com/docker/distribution/storagedriver/inmemory"
|
||||
"github.com/docker/libtrust"
|
||||
"github.com/gorilla/handlers"
|
||||
|
@ -268,10 +268,10 @@ func TestManifestAPI(t *testing.T) {
|
|||
|
||||
// --------------------------------
|
||||
// Attempt to push unsigned manifest with missing layers
|
||||
unsignedManifest := &storage.Manifest{
|
||||
unsignedManifest := &manifest.Manifest{
|
||||
Name: imageName,
|
||||
Tag: tag,
|
||||
FSLayers: []storage.FSLayer{
|
||||
FSLayers: []manifest.FSLayer{
|
||||
{
|
||||
BlobSum: "asdf",
|
||||
},
|
||||
|
@ -345,7 +345,7 @@ func TestManifestAPI(t *testing.T) {
|
|||
|
||||
// -------------------
|
||||
// Push the signed manifest with all layers pushed.
|
||||
signedManifest, err := unsignedManifest.Sign(pk)
|
||||
signedManifest, err := manifest.Sign(unsignedManifest, pk)
|
||||
if err != nil {
|
||||
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)
|
||||
|
||||
var fetchedManifest storage.SignedManifest
|
||||
var fetchedManifest manifest.SignedManifest
|
||||
dec = json.NewDecoder(resp.Body)
|
||||
if err := dec.Decode(&fetchedManifest); err != nil {
|
||||
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 {
|
||||
var body []byte
|
||||
if sm, ok := v.(*storage.SignedManifest); ok {
|
||||
if sm, ok := v.(*manifest.SignedManifest); ok {
|
||||
body = sm.Raw
|
||||
} else {
|
||||
var err error
|
||||
|
|
|
@ -12,18 +12,18 @@ import (
|
|||
|
||||
"github.com/docker/distribution/api/v2"
|
||||
"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
|
||||
type Client interface {
|
||||
// GetImageManifest returns an image manifest for the image at the given
|
||||
// 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
|
||||
// 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(name, tag string) error
|
||||
|
@ -91,7 +91,7 @@ type clientImpl struct {
|
|||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -124,7 +124,7 @@ func (r *clientImpl) GetImageManifest(name, tag string) (*storage.SignedManifest
|
|||
|
||||
decoder := json.NewDecoder(response.Body)
|
||||
|
||||
manifest := new(storage.SignedManifest)
|
||||
manifest := new(manifest.SignedManifest)
|
||||
err = decoder.Decode(manifest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -132,7 +132,7 @@ func (r *clientImpl) GetImageManifest(name, tag string) (*storage.SignedManifest
|
|||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
|
||||
"github.com/docker/distribution/common/testutil"
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/distribution/storage"
|
||||
"github.com/docker/distribution/manifest"
|
||||
)
|
||||
|
||||
type testBlob struct {
|
||||
|
@ -33,8 +33,8 @@ func TestPush(t *testing.T) {
|
|||
},
|
||||
}
|
||||
uploadLocations := make([]string, len(testBlobs))
|
||||
blobs := make([]storage.FSLayer, len(testBlobs))
|
||||
history := make([]storage.ManifestHistory, len(testBlobs))
|
||||
blobs := make([]manifest.FSLayer, len(testBlobs))
|
||||
history := make([]manifest.History, len(testBlobs))
|
||||
|
||||
for i, blob := range testBlobs {
|
||||
// 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
|
||||
// to change at some point.
|
||||
uploadLocations[i] = fmt.Sprintf("/v2/%s/blobs/test-uuid", name)
|
||||
blobs[i] = storage.FSLayer{BlobSum: blob.digest}
|
||||
history[i] = storage.ManifestHistory{V1Compatibility: blob.digest.String()}
|
||||
blobs[i] = manifest.FSLayer{BlobSum: blob.digest}
|
||||
history[i] = manifest.History{V1Compatibility: blob.digest.String()}
|
||||
}
|
||||
|
||||
manifest := &storage.SignedManifest{
|
||||
Manifest: storage.Manifest{
|
||||
m := &manifest.SignedManifest{
|
||||
Manifest: manifest.Manifest{
|
||||
Name: name,
|
||||
Tag: tag,
|
||||
Architecture: "x86",
|
||||
FSLayers: blobs,
|
||||
History: history,
|
||||
Versioned: storage.Versioned{
|
||||
Versioned: manifest.Versioned{
|
||||
SchemaVersion: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
var err error
|
||||
manifest.Raw, err = json.Marshal(manifest)
|
||||
m.Raw, err = json.Marshal(m)
|
||||
|
||||
blobRequestResponseMappings := make([]testutil.RequestResponseMapping, 2*len(testBlobs))
|
||||
for i, blob := range testBlobs {
|
||||
|
@ -94,7 +94,7 @@ func TestPush(t *testing.T) {
|
|||
Request: testutil.Request{
|
||||
Method: "PUT",
|
||||
Route: "/v2/" + name + "/manifests/" + tag,
|
||||
Body: manifest.Raw,
|
||||
Body: m.Raw,
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
|
@ -119,7 +119,7 @@ func TestPush(t *testing.T) {
|
|||
}
|
||||
objectStore := &memoryObjectStore{
|
||||
mutex: new(sync.Mutex),
|
||||
manifestStorage: make(map[string]*storage.SignedManifest),
|
||||
manifestStorage: make(map[string]*manifest.SignedManifest),
|
||||
layerStorage: make(map[digest.Digest]Layer),
|
||||
}
|
||||
|
||||
|
@ -139,7 +139,7 @@ func TestPush(t *testing.T) {
|
|||
writer.Close()
|
||||
}
|
||||
|
||||
objectStore.WriteManifest(name, tag, manifest)
|
||||
objectStore.WriteManifest(name, tag, m)
|
||||
|
||||
err = Push(client, objectStore, name, tag)
|
||||
if err != nil {
|
||||
|
@ -160,27 +160,27 @@ func TestPull(t *testing.T) {
|
|||
contents: []byte("some other contents"),
|
||||
},
|
||||
}
|
||||
blobs := make([]storage.FSLayer, len(testBlobs))
|
||||
history := make([]storage.ManifestHistory, len(testBlobs))
|
||||
blobs := make([]manifest.FSLayer, len(testBlobs))
|
||||
history := make([]manifest.History, len(testBlobs))
|
||||
|
||||
for i, blob := range testBlobs {
|
||||
blobs[i] = storage.FSLayer{BlobSum: blob.digest}
|
||||
history[i] = storage.ManifestHistory{V1Compatibility: blob.digest.String()}
|
||||
blobs[i] = manifest.FSLayer{BlobSum: blob.digest}
|
||||
history[i] = manifest.History{V1Compatibility: blob.digest.String()}
|
||||
}
|
||||
|
||||
manifest := &storage.SignedManifest{
|
||||
Manifest: storage.Manifest{
|
||||
m := &manifest.SignedManifest{
|
||||
Manifest: manifest.Manifest{
|
||||
Name: name,
|
||||
Tag: tag,
|
||||
Architecture: "x86",
|
||||
FSLayers: blobs,
|
||||
History: history,
|
||||
Versioned: storage.Versioned{
|
||||
Versioned: manifest.Versioned{
|
||||
SchemaVersion: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
manifestBytes, err := json.Marshal(manifest)
|
||||
manifestBytes, err := json.Marshal(m)
|
||||
|
||||
blobRequestResponseMappings := make([]testutil.RequestResponseMapping, len(testBlobs))
|
||||
for i, blob := range testBlobs {
|
||||
|
@ -213,7 +213,7 @@ func TestPull(t *testing.T) {
|
|||
}
|
||||
objectStore := &memoryObjectStore{
|
||||
mutex: new(sync.Mutex),
|
||||
manifestStorage: make(map[string]*storage.SignedManifest),
|
||||
manifestStorage: make(map[string]*manifest.SignedManifest),
|
||||
layerStorage: make(map[digest.Digest]Layer),
|
||||
}
|
||||
|
||||
|
@ -222,7 +222,7 @@ func TestPull(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
m, err := objectStore.Manifest(name, tag)
|
||||
m, err = objectStore.Manifest(name, tag)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -272,25 +272,25 @@ func TestPullResume(t *testing.T) {
|
|||
contents: []byte("some other contents"),
|
||||
},
|
||||
}
|
||||
layers := make([]storage.FSLayer, len(testBlobs))
|
||||
history := make([]storage.ManifestHistory, len(testBlobs))
|
||||
layers := make([]manifest.FSLayer, len(testBlobs))
|
||||
history := make([]manifest.History, len(testBlobs))
|
||||
|
||||
for i, layer := range testBlobs {
|
||||
layers[i] = storage.FSLayer{BlobSum: layer.digest}
|
||||
history[i] = storage.ManifestHistory{V1Compatibility: layer.digest.String()}
|
||||
layers[i] = manifest.FSLayer{BlobSum: layer.digest}
|
||||
history[i] = manifest.History{V1Compatibility: layer.digest.String()}
|
||||
}
|
||||
|
||||
manifest := &storage.Manifest{
|
||||
m := &manifest.Manifest{
|
||||
Name: name,
|
||||
Tag: tag,
|
||||
Architecture: "x86",
|
||||
FSLayers: layers,
|
||||
History: history,
|
||||
Versioned: storage.Versioned{
|
||||
Versioned: manifest.Versioned{
|
||||
SchemaVersion: 1,
|
||||
},
|
||||
}
|
||||
manifestBytes, err := json.Marshal(manifest)
|
||||
manifestBytes, err := json.Marshal(m)
|
||||
|
||||
layerRequestResponseMappings := make([]testutil.RequestResponseMapping, 2*len(testBlobs))
|
||||
for i, blob := range testBlobs {
|
||||
|
@ -340,7 +340,7 @@ func TestPullResume(t *testing.T) {
|
|||
}
|
||||
objectStore := &memoryObjectStore{
|
||||
mutex: new(sync.Mutex),
|
||||
manifestStorage: make(map[string]*storage.SignedManifest),
|
||||
manifestStorage: make(map[string]*manifest.SignedManifest),
|
||||
layerStorage: make(map[digest.Digest]Layer),
|
||||
}
|
||||
|
||||
|
@ -355,12 +355,12 @@ func TestPullResume(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
m, err := objectStore.Manifest(name, tag)
|
||||
sm, err := objectStore.Manifest(name, tag)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mBytes, err := json.Marshal(m)
|
||||
mBytes, err := json.Marshal(sm)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"sync"
|
||||
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/distribution/storage"
|
||||
"github.com/docker/distribution/manifest"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -26,11 +26,11 @@ var (
|
|||
type ObjectStore interface {
|
||||
// Manifest retrieves the image manifest stored at the given repository name
|
||||
// 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
|
||||
// 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(dgst digest.Digest) (Layer, error)
|
||||
|
@ -83,11 +83,11 @@ type LayerWriter interface {
|
|||
// memoryObjectStore is an in-memory implementation of the ObjectStore interface
|
||||
type memoryObjectStore struct {
|
||||
mutex *sync.Mutex
|
||||
manifestStorage map[string]*storage.SignedManifest
|
||||
manifestStorage map[string]*manifest.SignedManifest
|
||||
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()
|
||||
defer objStore.mutex.Unlock()
|
||||
|
||||
|
@ -98,7 +98,7 @@ func (objStore *memoryObjectStore) Manifest(name, tag string) (*storage.SignedMa
|
|||
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()
|
||||
defer objStore.mutex.Unlock()
|
||||
|
||||
|
|
|
@ -4,9 +4,9 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/docker/distribution/storage"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/docker/distribution/manifest"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
layer, err := objectStore.Layer(fsLayer.BlobSum)
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
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.
|
||||
|
@ -12,7 +12,7 @@ import (
|
|||
// push window has been successfully pushed.
|
||||
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
|
||||
// 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
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
layer, err := objectStore.Layer(fsLayer.BlobSum)
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"github.com/docker/distribution/api/v2"
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/distribution/manifest"
|
||||
"github.com/docker/distribution/storage"
|
||||
"github.com/gorilla/handlers"
|
||||
)
|
||||
|
@ -56,7 +57,7 @@ func (imh *imageManifestHandler) PutImageManifest(w http.ResponseWriter, r *http
|
|||
manifests := imh.services.Manifests()
|
||||
dec := json.NewDecoder(r.Body)
|
||||
|
||||
var manifest storage.SignedManifest
|
||||
var manifest manifest.SignedManifest
|
||||
if err := dec.Decode(&manifest); err != nil {
|
||||
imh.Errors.Push(v2.ErrorCodeManifestInvalid, err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
package storage
|
||||
package manifest
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/libtrust"
|
||||
)
|
||||
|
||||
// Versioned provides a struct with just the manifest schemaVersion. Incoming
|
||||
|
@ -36,65 +33,7 @@ type Manifest struct {
|
|||
FSLayers []FSLayer `json:"fsLayers"`
|
||||
|
||||
// History is a list of unstructured historical data for v1 compatibility
|
||||
History []ManifestHistory `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
|
||||
History []History `json:"history"`
|
||||
}
|
||||
|
||||
// SignedManifest provides an envelope for a signed image manifest, including
|
||||
|
@ -109,30 +48,6 @@ type SignedManifest struct {
|
|||
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.
|
||||
func (sm *SignedManifest) UnmarshalJSON(b []byte) error {
|
||||
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
|
||||
// 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.
|
||||
func (sm *SignedManifest) MarshalJSON() ([]byte, error) {
|
||||
if len(sm.Raw) > 0 {
|
||||
|
@ -166,8 +81,8 @@ type FSLayer struct {
|
|||
BlobSum digest.Digest `json:"blobSum"`
|
||||
}
|
||||
|
||||
// ManifestHistory stores unstructured v1 compatibility information
|
||||
type ManifestHistory struct {
|
||||
// History stores unstructured v1 compatibility information
|
||||
type History struct {
|
||||
// V1Compatibility is the raw v1 compatibility information
|
||||
V1Compatibility string `json:"v1Compatibility"`
|
||||
}
|
110
manifest/manifest_test.go
Normal file
110
manifest/manifest_test.go
Normal 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
66
manifest/sign.go
Normal 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
32
manifest/verify.go
Normal 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)
|
||||
}
|
|
@ -6,6 +6,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/distribution/manifest"
|
||||
)
|
||||
|
||||
// Layer provides a readable and seekable layer object. Typically,
|
||||
|
@ -74,7 +75,7 @@ var (
|
|||
|
||||
// ErrUnknownLayer returned when layer cannot be found.
|
||||
type ErrUnknownLayer struct {
|
||||
FSLayer FSLayer
|
||||
FSLayer manifest.FSLayer
|
||||
}
|
||||
|
||||
func (err ErrUnknownLayer) Error() string {
|
||||
|
@ -83,7 +84,7 @@ func (err ErrUnknownLayer) Error() string {
|
|||
|
||||
// ErrLayerInvalidDigest returned when tarsum check fails.
|
||||
type ErrLayerInvalidDigest struct {
|
||||
FSLayer FSLayer
|
||||
FSLayer manifest.FSLayer
|
||||
}
|
||||
|
||||
func (err ErrLayerInvalidDigest) Error() string {
|
||||
|
|
|
@ -2,6 +2,7 @@ package storage
|
|||
|
||||
import (
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/distribution/manifest"
|
||||
"github.com/docker/distribution/storagedriver"
|
||||
)
|
||||
|
||||
|
@ -33,7 +34,7 @@ func (ls *layerStore) Fetch(name string, digest digest.Digest) (Layer, error) {
|
|||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case storagedriver.PathNotFoundError, *storagedriver.PathNotFoundError:
|
||||
return nil, ErrUnknownLayer{FSLayer{BlobSum: digest}}
|
||||
return nil, ErrUnknownLayer{manifest.FSLayer{BlobSum: digest}}
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
|
@ -43,7 +44,7 @@ func (ls *layerStore) Fetch(name string, digest digest.Digest) (Layer, error) {
|
|||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case storagedriver.PathNotFoundError, *storagedriver.PathNotFoundError:
|
||||
return nil, ErrUnknownLayer{FSLayer{BlobSum: digest}}
|
||||
return nil, ErrUnknownLayer{manifest.FSLayer{BlobSum: digest}}
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"code.google.com/p/go-uuid/uuid"
|
||||
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/distribution/manifest"
|
||||
"github.com/docker/distribution/storagedriver"
|
||||
"github.com/docker/docker/pkg/tarsum"
|
||||
|
||||
|
@ -284,7 +285,7 @@ func (luc *layerUploadController) validateLayer(fp layerFile, size int64, dgst d
|
|||
}
|
||||
|
||||
if !digestVerifier.Verified() {
|
||||
return "", ErrLayerInvalidDigest{FSLayer{BlobSum: dgst}}
|
||||
return "", ErrLayerInvalidDigest{manifest.FSLayer{BlobSum: dgst}}
|
||||
}
|
||||
|
||||
return dgst, nil
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/distribution/manifest"
|
||||
"github.com/docker/distribution/storagedriver"
|
||||
"github.com/docker/libtrust"
|
||||
)
|
||||
|
@ -116,7 +117,7 @@ func (ms *manifestStore) Exists(name, tag string) (bool, error) {
|
|||
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)
|
||||
if err != nil {
|
||||
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 {
|
||||
// TODO(stevvooe): Corrupted manifest error?
|
||||
|
@ -144,7 +145,7 @@ func (ms *manifestStore) Get(name, tag string) (*SignedManifest, error) {
|
|||
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)
|
||||
if err != nil {
|
||||
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
|
||||
// lifted out of the storage infrastructure and moved into a package
|
||||
// oriented towards defining verifiers and reporting them with
|
||||
// granularity.
|
||||
|
||||
var errs ErrManifestVerification
|
||||
if manifest.Name != name {
|
||||
if mnfst.Name != name {
|
||||
// TODO(stevvooe): This needs to be an exported error
|
||||
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.
|
||||
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.
|
||||
// Perhaps, its a configuration value injected into manifest store.
|
||||
|
||||
if _, err := manifest.Verify(); err != nil {
|
||||
if _, err := manifest.Verify(mnfst); err != nil {
|
||||
switch err {
|
||||
case libtrust.ErrMissingSignatureKey, libtrust.ErrInvalidJSONContent, libtrust.ErrMissingSignatureKey:
|
||||
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)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
|
|
|
@ -4,10 +4,10 @@ import (
|
|||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/libtrust"
|
||||
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/distribution/manifest"
|
||||
"github.com/docker/distribution/storagedriver/inmemory"
|
||||
"github.com/docker/libtrust"
|
||||
)
|
||||
|
||||
func TestManifestStorage(t *testing.T) {
|
||||
|
@ -42,13 +42,13 @@ func TestManifestStorage(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
manifest := Manifest{
|
||||
Versioned: Versioned{
|
||||
m := manifest.Manifest{
|
||||
Versioned: manifest.Versioned{
|
||||
SchemaVersion: 1,
|
||||
},
|
||||
Name: name,
|
||||
Tag: tag,
|
||||
FSLayers: []FSLayer{
|
||||
FSLayers: []manifest.FSLayer{
|
||||
{
|
||||
BlobSum: "asdf",
|
||||
},
|
||||
|
@ -63,7 +63,7 @@ func TestManifestStorage(t *testing.T) {
|
|||
t.Fatalf("unexpected error generating private key: %v", err)
|
||||
}
|
||||
|
||||
sm, err := manifest.Sign(pk)
|
||||
sm, err := manifest.Sign(&m, pk)
|
||||
if err != nil {
|
||||
t.Fatalf("error signing manifest: %v", err)
|
||||
}
|
|
@ -2,6 +2,7 @@ package storage
|
|||
|
||||
import (
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/distribution/manifest"
|
||||
"github.com/docker/distribution/storagedriver"
|
||||
)
|
||||
|
||||
|
@ -59,10 +60,10 @@ type ManifestService interface {
|
|||
Exists(name, tag string) (bool, error)
|
||||
|
||||
// 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(name, tag string, manifest *SignedManifest) error
|
||||
Put(name, tag string, manifest *manifest.SignedManifest) error
|
||||
|
||||
// Delete removes the named manifest, if it exists.
|
||||
Delete(name, tag string) error
|
||||
|
|
Loading…
Reference in a new issue