Merge pull request #633 from RichardScothern/manifest-verification

External manifest verification
This commit is contained in:
Stephen Day 2015-07-15 13:00:05 -07:00
commit 7c5c26b341
10 changed files with 118 additions and 48 deletions

View file

@ -51,11 +51,15 @@ func Listen(repo distribution.Repository, listener Listener) distribution.Reposi
} }
} }
func (rl *repositoryListener) Manifests() distribution.ManifestService { func (rl *repositoryListener) Manifests(ctx context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) {
return &manifestServiceListener{ manifests, err := rl.Repository.Manifests(ctx, options...)
ManifestService: rl.Repository.Manifests(), if err != nil {
parent: rl, return nil, err
} }
return &manifestServiceListener{
ManifestService: manifests,
parent: rl,
}, nil
} }
func (rl *repositoryListener) Blobs(ctx context.Context) distribution.BlobStore { func (rl *repositoryListener) Blobs(ctx context.Context) distribution.BlobStore {

View file

@ -146,9 +146,12 @@ func checkExerciseRepository(t *testing.T, repository distribution.Repository) {
t.Fatalf("unexpected error signing manifest: %v", err) t.Fatalf("unexpected error signing manifest: %v", err)
} }
manifests := repository.Manifests() manifests, err := repository.Manifests(ctx)
if err != nil {
t.Fatal(err.Error())
}
if err := manifests.Put(sm); err != nil { if err = manifests.Put(sm); err != nil {
t.Fatalf("unexpected error putting the manifest: %v", err) t.Fatalf("unexpected error putting the manifest: %v", err)
} }

View file

@ -46,7 +46,8 @@ type Repository interface {
Name() string Name() string
// Manifests returns a reference to this repository's manifest service. // Manifests returns a reference to this repository's manifest service.
Manifests() ManifestService // with the supplied options applied.
Manifests(ctx context.Context, options ...ManifestServiceOption) (ManifestService, error)
// Blobs returns a reference to this repository's blob service. // Blobs returns a reference to this repository's blob service.
Blobs(ctx context.Context) BlobStore Blobs(ctx context.Context) BlobStore

View file

@ -70,18 +70,20 @@ func (r *repository) Blobs(ctx context.Context) distribution.BlobStore {
} }
} }
func (r *repository) Manifests() distribution.ManifestService { func (r *repository) Manifests(ctx context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) {
// todo(richardscothern): options should be sent over the wire
return &manifests{ return &manifests{
name: r.Name(), name: r.Name(),
ub: r.ub, ub: r.ub,
client: r.client, client: r.client,
etags: make(map[string]string), etags: make(map[string]string),
} }, nil
} }
func (r *repository) Signatures() distribution.SignatureService { func (r *repository) Signatures() distribution.SignatureService {
ms, _ := r.Manifests(r.context)
return &signatures{ return &signatures{
manifests: r.Manifests(), manifests: ms,
} }
} }
@ -236,6 +238,8 @@ func (ms *manifests) Put(m *manifest.SignedManifest) error {
return err return err
} }
// todo(richardscothern): do something with options here when they become applicable
putRequest, err := http.NewRequest("PUT", manifestURL, bytes.NewReader(m.Raw)) putRequest, err := http.NewRequest("PUT", manifestURL, bytes.NewReader(m.Raw))
if err != nil { if err != nil {
return err return err

View file

@ -492,6 +492,7 @@ func checkEqualManifest(m1, m2 *manifest.SignedManifest) error {
} }
func TestManifestFetch(t *testing.T) { func TestManifestFetch(t *testing.T) {
ctx := context.Background()
repo := "test.example.com/repo" repo := "test.example.com/repo"
m1, dgst := newRandomSchemaV1Manifest(repo, "latest", 6) m1, dgst := newRandomSchemaV1Manifest(repo, "latest", 6)
var m testutil.RequestResponseMap var m testutil.RequestResponseMap
@ -504,7 +505,10 @@ func TestManifestFetch(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
ms := r.Manifests() ms, err := r.Manifests(ctx)
if err != nil {
t.Fatal(err)
}
ok, err := ms.Exists(dgst) ok, err := ms.Exists(dgst)
if err != nil { if err != nil {
@ -536,8 +540,12 @@ func TestManifestFetchWithEtag(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
ctx := context.Background()
ms, err := r.Manifests(ctx)
if err != nil {
t.Fatal(err)
}
ms := r.Manifests()
m2, err := ms.GetByTag("latest", AddEtagToTag("latest", d1.String())) m2, err := ms.GetByTag("latest", AddEtagToTag("latest", d1.String()))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -572,8 +580,12 @@ func TestManifestDelete(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
ctx := context.Background()
ms, err := r.Manifests(ctx)
if err != nil {
t.Fatal(err)
}
ms := r.Manifests()
if err := ms.Delete(dgst1); err != nil { if err := ms.Delete(dgst1); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -609,8 +621,12 @@ func TestManifestPut(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
ctx := context.Background()
ms, err := r.Manifests(ctx)
if err != nil {
t.Fatal(err)
}
ms := r.Manifests()
if err := ms.Put(m1); err != nil { if err := ms.Put(m1); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -653,8 +669,12 @@ func TestManifestTags(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
ctx := context.Background()
ms, err := r.Manifests(ctx)
if err != nil {
t.Fatal(err)
}
ms := r.Manifests()
tags, err := ms.Tags() tags, err := ms.Tags()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -691,7 +711,11 @@ func TestManifestUnauthorized(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
ms := r.Manifests() ctx := context.Background()
ms, err := r.Manifests(ctx)
if err != nil {
t.Fatal(err)
}
_, err = ms.Get(dgst) _, err = ms.Get(dgst)
if err == nil { if err == nil {

View file

@ -50,13 +50,13 @@ type imageManifestHandler struct {
// GetImageManifest fetches the image manifest from the storage backend, if it exists. // GetImageManifest fetches the image manifest from the storage backend, if it exists.
func (imh *imageManifestHandler) GetImageManifest(w http.ResponseWriter, r *http.Request) { func (imh *imageManifestHandler) GetImageManifest(w http.ResponseWriter, r *http.Request) {
ctxu.GetLogger(imh).Debug("GetImageManifest") ctxu.GetLogger(imh).Debug("GetImageManifest")
manifests := imh.Repository.Manifests() manifests, err := imh.Repository.Manifests(imh)
if err != nil {
var ( imh.Errors = append(imh.Errors, err)
sm *manifest.SignedManifest return
err error }
)
var sm *manifest.SignedManifest
if imh.Tag != "" { if imh.Tag != "" {
sm, err = manifests.GetByTag(imh.Tag) sm, err = manifests.GetByTag(imh.Tag)
} else { } else {
@ -106,7 +106,12 @@ func etagMatch(r *http.Request, etag string) bool {
// PutImageManifest validates and stores and image in the registry. // PutImageManifest validates and stores and image in the registry.
func (imh *imageManifestHandler) PutImageManifest(w http.ResponseWriter, r *http.Request) { func (imh *imageManifestHandler) PutImageManifest(w http.ResponseWriter, r *http.Request) {
ctxu.GetLogger(imh).Debug("PutImageManifest") ctxu.GetLogger(imh).Debug("PutImageManifest")
manifests := imh.Repository.Manifests() manifests, err := imh.Repository.Manifests(imh)
if err != nil {
imh.Errors = append(imh.Errors, err)
return
}
dec := json.NewDecoder(r.Body) dec := json.NewDecoder(r.Body)
var manifest manifest.SignedManifest var manifest manifest.SignedManifest

View file

@ -34,7 +34,11 @@ type tagsAPIResponse struct {
// GetTags returns a json list of tags for a specific image name. // GetTags returns a json list of tags for a specific image name.
func (th *tagsHandler) GetTags(w http.ResponseWriter, r *http.Request) { func (th *tagsHandler) GetTags(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close() defer r.Body.Close()
manifests := th.Repository.Manifests() manifests, err := th.Repository.Manifests(th)
if err != nil {
th.Errors = append(th.Errors, err)
return
}
tags, err := manifests.Tags() tags, err := manifests.Tags()
if err != nil { if err != nil {

View file

@ -11,10 +11,11 @@ import (
) )
type manifestStore struct { type manifestStore struct {
repository *repository repository *repository
revisionStore *revisionStore revisionStore *revisionStore
tagStore *tagStore tagStore *tagStore
ctx context.Context ctx context.Context
skipDependencyVerification bool
} }
var _ distribution.ManifestService = &manifestStore{} var _ distribution.ManifestService = &manifestStore{}
@ -39,10 +40,19 @@ func (ms *manifestStore) Get(dgst digest.Digest) (*manifest.SignedManifest, erro
return ms.revisionStore.get(ms.ctx, dgst) return ms.revisionStore.get(ms.ctx, dgst)
} }
// SkipLayerVerification allows a manifest to be Put before it's
// layers are on the filesystem
func SkipLayerVerification(ms distribution.ManifestService) error {
if ms, ok := ms.(*manifestStore); ok {
ms.skipDependencyVerification = true
return nil
}
return fmt.Errorf("skip layer verification only valid for manifeststore")
}
func (ms *manifestStore) Put(manifest *manifest.SignedManifest) error { func (ms *manifestStore) Put(manifest *manifest.SignedManifest) error {
context.GetLogger(ms.ctx).Debug("(*manifestStore).Put") context.GetLogger(ms.ctx).Debug("(*manifestStore).Put")
// Verify the manifest.
if err := ms.verifyManifest(ms.ctx, manifest); err != nil { if err := ms.verifyManifest(ms.ctx, manifest); err != nil {
return err return err
} }
@ -113,18 +123,19 @@ func (ms *manifestStore) verifyManifest(ctx context.Context, mnfst *manifest.Sig
} }
} }
for _, fsLayer := range mnfst.FSLayers { if !ms.skipDependencyVerification {
_, err := ms.repository.Blobs(ctx).Stat(ctx, fsLayer.BlobSum) for _, fsLayer := range mnfst.FSLayers {
if err != nil { _, err := ms.repository.Blobs(ctx).Stat(ctx, fsLayer.BlobSum)
if err != distribution.ErrBlobUnknown { if err != nil {
errs = append(errs, err) if err != distribution.ErrBlobUnknown {
} errs = append(errs, err)
}
// On error here, we always append unknown blob errors. // On error here, we always append unknown blob errors.
errs = append(errs, distribution.ErrManifestBlobUnknown{Digest: fsLayer.BlobSum}) errs = append(errs, distribution.ErrManifestBlobUnknown{Digest: fsLayer.BlobSum})
}
} }
} }
if len(errs) != 0 { if len(errs) != 0 {
return errs return errs
} }

View file

@ -48,7 +48,11 @@ func newManifestStoreTestEnv(t *testing.T, name, tag string) *manifestStoreTestE
func TestManifestStorage(t *testing.T) { func TestManifestStorage(t *testing.T) {
env := newManifestStoreTestEnv(t, "foo/bar", "thetag") env := newManifestStoreTestEnv(t, "foo/bar", "thetag")
ms := env.repository.Manifests() ctx := context.Background()
ms, err := env.repository.Manifests(ctx)
if err != nil {
t.Fatal(err)
}
exists, err := ms.ExistsByTag(env.tag) exists, err := ms.ExistsByTag(env.tag)
if err != nil { if err != nil {
@ -97,14 +101,14 @@ 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(&m, pk) sm, merr := manifest.Sign(&m, pk)
if err != nil { if merr != nil {
t.Fatalf("error signing manifest: %v", err) t.Fatalf("error signing manifest: %v", err)
} }
err = ms.Put(sm) err = ms.Put(sm)
if err == nil { if err == nil {
t.Fatalf("expected errors putting manifest") t.Fatalf("expected errors putting manifest with full verification")
} }
switch err := err.(type) { switch err := err.(type) {

View file

@ -99,15 +99,15 @@ func (repo *repository) Name() string {
// Manifests returns an instance of ManifestService. Instantiation is cheap and // Manifests returns an instance of ManifestService. Instantiation is cheap and
// may be context sensitive in the future. The instance should be used similar // may be context sensitive in the future. The instance should be used similar
// to a request local. // to a request local.
func (repo *repository) Manifests() distribution.ManifestService { func (repo *repository) Manifests(ctx context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) {
return &manifestStore{ ms := &manifestStore{
ctx: repo.ctx, ctx: ctx,
repository: repo, repository: repo,
revisionStore: &revisionStore{ revisionStore: &revisionStore{
ctx: repo.ctx, ctx: ctx,
repository: repo, repository: repo,
blobStore: &linkedBlobStore{ blobStore: &linkedBlobStore{
ctx: repo.ctx, ctx: ctx,
blobStore: repo.blobStore, blobStore: repo.blobStore,
repository: repo, repository: repo,
statter: &linkedBlobStatter{ statter: &linkedBlobStatter{
@ -122,11 +122,21 @@ func (repo *repository) Manifests() distribution.ManifestService {
}, },
}, },
tagStore: &tagStore{ tagStore: &tagStore{
ctx: repo.ctx, ctx: ctx,
repository: repo, repository: repo,
blobStore: repo.registry.blobStore, blobStore: repo.registry.blobStore,
}, },
} }
// Apply options
for _, option := range options {
err := option(ms)
if err != nil {
return nil, err
}
}
return ms, nil
} }
// Blobs returns an instance of the BlobStore. Instantiation is cheap and // Blobs returns an instance of the BlobStore. Instantiation is cheap and