forked from TrueCloudLab/distribution
9c88801a12
Back in the before time, the best practices surrounding usage of Context weren't quite worked out. We defined our own type to make usage easier. As this packaged was used elsewhere, it make it more and more challenging to integrate with the forked `Context` type. Now that it is available in the standard library, we can just use that one directly. To make usage more consistent, we now use `dcontext` when referring to the distribution context package. Signed-off-by: Stephen J Day <stephen.day@docker.com>
142 lines
4.1 KiB
Go
142 lines
4.1 KiB
Go
package storage
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/docker/distribution"
|
|
dcontext "github.com/docker/distribution/context"
|
|
"github.com/docker/distribution/manifest/schema1"
|
|
"github.com/docker/distribution/reference"
|
|
"github.com/docker/libtrust"
|
|
"github.com/opencontainers/go-digest"
|
|
)
|
|
|
|
// signedManifestHandler is a ManifestHandler that covers schema1 manifests. It
|
|
// can unmarshal and put schema1 manifests that have been signed by libtrust.
|
|
type signedManifestHandler struct {
|
|
repository distribution.Repository
|
|
schema1SigningKey libtrust.PrivateKey
|
|
blobStore distribution.BlobStore
|
|
ctx context.Context
|
|
}
|
|
|
|
var _ ManifestHandler = &signedManifestHandler{}
|
|
|
|
func (ms *signedManifestHandler) Unmarshal(ctx context.Context, dgst digest.Digest, content []byte) (distribution.Manifest, error) {
|
|
dcontext.GetLogger(ms.ctx).Debug("(*signedManifestHandler).Unmarshal")
|
|
|
|
var (
|
|
signatures [][]byte
|
|
err error
|
|
)
|
|
|
|
jsig, err := libtrust.NewJSONSignature(content, signatures...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if ms.schema1SigningKey != nil {
|
|
if err := jsig.Sign(ms.schema1SigningKey); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Extract the pretty JWS
|
|
raw, err := jsig.PrettySignature("signatures")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var sm schema1.SignedManifest
|
|
if err := json.Unmarshal(raw, &sm); err != nil {
|
|
return nil, err
|
|
}
|
|
return &sm, nil
|
|
}
|
|
|
|
func (ms *signedManifestHandler) Put(ctx context.Context, manifest distribution.Manifest, skipDependencyVerification bool) (digest.Digest, error) {
|
|
dcontext.GetLogger(ms.ctx).Debug("(*signedManifestHandler).Put")
|
|
|
|
sm, ok := manifest.(*schema1.SignedManifest)
|
|
if !ok {
|
|
return "", fmt.Errorf("non-schema1 manifest put to signedManifestHandler: %T", manifest)
|
|
}
|
|
|
|
if err := ms.verifyManifest(ms.ctx, *sm, skipDependencyVerification); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
mt := schema1.MediaTypeManifest
|
|
payload := sm.Canonical
|
|
|
|
revision, err := ms.blobStore.Put(ctx, mt, payload)
|
|
if err != nil {
|
|
dcontext.GetLogger(ctx).Errorf("error putting payload into blobstore: %v", err)
|
|
return "", err
|
|
}
|
|
|
|
return revision.Digest, nil
|
|
}
|
|
|
|
// verifyManifest ensures that the manifest content is valid from the
|
|
// perspective of the registry. It ensures that the signature is valid for the
|
|
// enclosed payload. As a policy, the registry only tries to store valid
|
|
// content, leaving trust policies of that content up to consumers.
|
|
func (ms *signedManifestHandler) verifyManifest(ctx context.Context, mnfst schema1.SignedManifest, skipDependencyVerification bool) error {
|
|
var errs distribution.ErrManifestVerification
|
|
|
|
if len(mnfst.Name) > reference.NameTotalLengthMax {
|
|
errs = append(errs,
|
|
distribution.ErrManifestNameInvalid{
|
|
Name: mnfst.Name,
|
|
Reason: fmt.Errorf("manifest name must not be more than %v characters", reference.NameTotalLengthMax),
|
|
})
|
|
}
|
|
|
|
if !reference.NameRegexp.MatchString(mnfst.Name) {
|
|
errs = append(errs,
|
|
distribution.ErrManifestNameInvalid{
|
|
Name: mnfst.Name,
|
|
Reason: fmt.Errorf("invalid manifest name format"),
|
|
})
|
|
}
|
|
|
|
if len(mnfst.History) != len(mnfst.FSLayers) {
|
|
errs = append(errs, fmt.Errorf("mismatched history and fslayer cardinality %d != %d",
|
|
len(mnfst.History), len(mnfst.FSLayers)))
|
|
}
|
|
|
|
if _, err := schema1.Verify(&mnfst); err != nil {
|
|
switch err {
|
|
case libtrust.ErrMissingSignatureKey, libtrust.ErrInvalidJSONContent, libtrust.ErrMissingSignatureKey:
|
|
errs = append(errs, distribution.ErrManifestUnverified{})
|
|
default:
|
|
if err.Error() == "invalid signature" { // TODO(stevvooe): This should be exported by libtrust
|
|
errs = append(errs, distribution.ErrManifestUnverified{})
|
|
} else {
|
|
errs = append(errs, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
if !skipDependencyVerification {
|
|
for _, fsLayer := range mnfst.References() {
|
|
_, err := ms.repository.Blobs(ctx).Stat(ctx, fsLayer.Digest)
|
|
if err != nil {
|
|
if err != distribution.ErrBlobUnknown {
|
|
errs = append(errs, err)
|
|
}
|
|
|
|
// On error here, we always append unknown blob errors.
|
|
errs = append(errs, distribution.ErrManifestBlobUnknown{Digest: fsLayer.Digest})
|
|
}
|
|
}
|
|
}
|
|
if len(errs) != 0 {
|
|
return errs
|
|
}
|
|
|
|
return nil
|
|
}
|