github.com/mika/distribution@v2.2.2-0.20160108133430-a75790e3d8e0+incompatible/registry/storage/signedmanifesthandler.go (about)

     1  package storage
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  
     7  	"github.com/docker/distribution"
     8  	"github.com/docker/distribution/context"
     9  	"github.com/docker/distribution/digest"
    10  	"github.com/docker/distribution/manifest/schema1"
    11  	"github.com/docker/distribution/reference"
    12  	"github.com/docker/libtrust"
    13  )
    14  
    15  // signedManifestHandler is a ManifestHandler that covers schema1 manifests. It
    16  // can unmarshal and put schema1 manifests that have been signed by libtrust.
    17  type signedManifestHandler struct {
    18  	repository *repository
    19  	blobStore  *linkedBlobStore
    20  	ctx        context.Context
    21  	signatures *signatureStore
    22  }
    23  
    24  var _ ManifestHandler = &signedManifestHandler{}
    25  
    26  func (ms *signedManifestHandler) Unmarshal(ctx context.Context, dgst digest.Digest, content []byte) (distribution.Manifest, error) {
    27  	context.GetLogger(ms.ctx).Debug("(*signedManifestHandler).Unmarshal")
    28  	// Fetch the signatures for the manifest
    29  	signatures, err := ms.signatures.Get(dgst)
    30  	if err != nil {
    31  		return nil, err
    32  	}
    33  
    34  	jsig, err := libtrust.NewJSONSignature(content, signatures...)
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  
    39  	// Extract the pretty JWS
    40  	raw, err := jsig.PrettySignature("signatures")
    41  	if err != nil {
    42  		return nil, err
    43  	}
    44  
    45  	var sm schema1.SignedManifest
    46  	if err := json.Unmarshal(raw, &sm); err != nil {
    47  		return nil, err
    48  	}
    49  	return &sm, nil
    50  }
    51  
    52  func (ms *signedManifestHandler) Put(ctx context.Context, manifest distribution.Manifest, skipDependencyVerification bool) (digest.Digest, error) {
    53  	context.GetLogger(ms.ctx).Debug("(*signedManifestHandler).Put")
    54  
    55  	sm, ok := manifest.(*schema1.SignedManifest)
    56  	if !ok {
    57  		return "", fmt.Errorf("non-schema1 manifest put to signedManifestHandler: %T", manifest)
    58  	}
    59  
    60  	if err := ms.verifyManifest(ms.ctx, *sm, skipDependencyVerification); err != nil {
    61  		return "", err
    62  	}
    63  
    64  	mt := schema1.MediaTypeManifest
    65  	payload := sm.Canonical
    66  
    67  	revision, err := ms.blobStore.Put(ctx, mt, payload)
    68  	if err != nil {
    69  		context.GetLogger(ctx).Errorf("error putting payload into blobstore: %v", err)
    70  		return "", err
    71  	}
    72  
    73  	// Link the revision into the repository.
    74  	if err := ms.blobStore.linkBlob(ctx, revision); err != nil {
    75  		return "", err
    76  	}
    77  
    78  	// Grab each json signature and store them.
    79  	signatures, err := sm.Signatures()
    80  	if err != nil {
    81  		return "", err
    82  	}
    83  
    84  	if err := ms.signatures.Put(revision.Digest, signatures...); err != nil {
    85  		return "", err
    86  	}
    87  
    88  	return revision.Digest, nil
    89  }
    90  
    91  // verifyManifest ensures that the manifest content is valid from the
    92  // perspective of the registry. It ensures that the signature is valid for the
    93  // enclosed payload. As a policy, the registry only tries to store valid
    94  // content, leaving trust policies of that content up to consumers.
    95  func (ms *signedManifestHandler) verifyManifest(ctx context.Context, mnfst schema1.SignedManifest, skipDependencyVerification bool) error {
    96  	var errs distribution.ErrManifestVerification
    97  
    98  	if len(mnfst.Name) > reference.NameTotalLengthMax {
    99  		errs = append(errs,
   100  			distribution.ErrManifestNameInvalid{
   101  				Name:   mnfst.Name,
   102  				Reason: fmt.Errorf("manifest name must not be more than %v characters", reference.NameTotalLengthMax),
   103  			})
   104  	}
   105  
   106  	if !reference.NameRegexp.MatchString(mnfst.Name) {
   107  		errs = append(errs,
   108  			distribution.ErrManifestNameInvalid{
   109  				Name:   mnfst.Name,
   110  				Reason: fmt.Errorf("invalid manifest name format"),
   111  			})
   112  	}
   113  
   114  	if len(mnfst.History) != len(mnfst.FSLayers) {
   115  		errs = append(errs, fmt.Errorf("mismatched history and fslayer cardinality %d != %d",
   116  			len(mnfst.History), len(mnfst.FSLayers)))
   117  	}
   118  
   119  	if _, err := schema1.Verify(&mnfst); err != nil {
   120  		switch err {
   121  		case libtrust.ErrMissingSignatureKey, libtrust.ErrInvalidJSONContent, libtrust.ErrMissingSignatureKey:
   122  			errs = append(errs, distribution.ErrManifestUnverified{})
   123  		default:
   124  			if err.Error() == "invalid signature" { // TODO(stevvooe): This should be exported by libtrust
   125  				errs = append(errs, distribution.ErrManifestUnverified{})
   126  			} else {
   127  				errs = append(errs, err)
   128  			}
   129  		}
   130  	}
   131  
   132  	if !skipDependencyVerification {
   133  		for _, fsLayer := range mnfst.References() {
   134  			_, err := ms.repository.Blobs(ctx).Stat(ctx, fsLayer.Digest)
   135  			if err != nil {
   136  				if err != distribution.ErrBlobUnknown {
   137  					errs = append(errs, err)
   138  				}
   139  
   140  				// On error here, we always append unknown blob errors.
   141  				errs = append(errs, distribution.ErrManifestBlobUnknown{Digest: fsLayer.Digest})
   142  			}
   143  		}
   144  	}
   145  	if len(errs) != 0 {
   146  		return errs
   147  	}
   148  
   149  	return nil
   150  }