2017-07-10 23:09:10 +00:00
package storage
import (
2017-09-01 02:14:40 +00:00
"context"
2017-07-10 23:09:10 +00:00
"fmt"
2017-08-01 22:27:52 +00:00
"net/url"
2017-07-10 23:09:10 +00:00
2020-08-24 11:18:39 +00:00
"github.com/distribution/distribution/v3"
2023-10-24 17:16:58 +00:00
"github.com/distribution/distribution/v3/internal/dcontext"
2020-08-24 11:18:39 +00:00
"github.com/distribution/distribution/v3/manifest/ocischema"
2017-07-10 23:09:10 +00:00
"github.com/opencontainers/go-digest"
2019-10-09 12:02:21 +00:00
v1 "github.com/opencontainers/image-spec/specs-go/v1"
2017-07-10 23:09:10 +00:00
)
2022-11-02 21:05:45 +00:00
// ocischemaManifestHandler is a ManifestHandler that covers ocischema manifests.
2017-07-10 23:09:10 +00:00
type ocischemaManifestHandler struct {
repository distribution . Repository
blobStore distribution . BlobStore
ctx context . Context
manifestURLs manifestURLs
}
var _ ManifestHandler = & ocischemaManifestHandler { }
func ( ms * ocischemaManifestHandler ) Unmarshal ( ctx context . Context , dgst digest . Digest , content [ ] byte ) ( distribution . Manifest , error ) {
2017-09-01 02:14:40 +00:00
dcontext . GetLogger ( ms . ctx ) . Debug ( "(*ocischemaManifestHandler).Unmarshal" )
2017-07-10 23:09:10 +00:00
2018-09-05 20:40:42 +00:00
m := & ocischema . DeserializedManifest { }
if err := m . UnmarshalJSON ( content ) ; err != nil {
2017-07-10 23:09:10 +00:00
return nil , err
}
2018-09-05 20:40:42 +00:00
return m , nil
2017-07-10 23:09:10 +00:00
}
func ( ms * ocischemaManifestHandler ) Put ( ctx context . Context , manifest distribution . Manifest , skipDependencyVerification bool ) ( digest . Digest , error ) {
2017-09-01 02:14:40 +00:00
dcontext . GetLogger ( ms . ctx ) . Debug ( "(*ocischemaManifestHandler).Put" )
2017-07-10 23:09:10 +00:00
m , ok := manifest . ( * ocischema . DeserializedManifest )
if ! ok {
return "" , fmt . Errorf ( "non-ocischema manifest put to ocischemaManifestHandler: %T" , manifest )
}
if err := ms . verifyManifest ( ms . ctx , * m , skipDependencyVerification ) ; err != nil {
return "" , err
}
mt , payload , err := m . Payload ( )
if err != nil {
return "" , err
}
revision , err := ms . blobStore . Put ( ctx , mt , payload )
if err != nil {
2017-09-01 02:14:40 +00:00
dcontext . GetLogger ( ctx ) . Errorf ( "error putting payload into blobstore: %v" , err )
2017-07-10 23:09:10 +00:00
return "" , err
}
return revision . Digest , nil
}
// verifyManifest ensures that the manifest content is valid from the
// perspective of the registry. As a policy, the registry only tries to store
// valid content, leaving trust policies of that content up to consumers.
func ( ms * ocischemaManifestHandler ) verifyManifest ( ctx context . Context , mnfst ocischema . DeserializedManifest , skipDependencyVerification bool ) error {
var errs distribution . ErrManifestVerification
2018-07-23 22:03:17 +00:00
if mnfst . Manifest . SchemaVersion != 2 {
return fmt . Errorf ( "unrecognized manifest schema version %d" , mnfst . Manifest . SchemaVersion )
}
2017-07-10 23:09:10 +00:00
if skipDependencyVerification {
return nil
}
manifestService , err := ms . repository . Manifests ( ctx )
if err != nil {
return err
}
blobsService := ms . repository . Blobs ( ctx )
for _ , descriptor := range mnfst . References ( ) {
2021-04-15 06:44:04 +00:00
err := descriptor . Digest . Validate ( )
if err != nil {
errs = append ( errs , err , distribution . ErrManifestBlobUnknown { Digest : descriptor . Digest } )
continue
}
2017-07-10 23:09:10 +00:00
switch descriptor . MediaType {
2023-04-30 17:01:20 +00:00
case v1 . MediaTypeImageLayer , v1 . MediaTypeImageLayerGzip , v1 . MediaTypeImageLayerNonDistributable , v1 . MediaTypeImageLayerNonDistributableGzip : //nolint:staticcheck // ignore A1019: v1.MediaTypeImageLayerNonDistributable is deprecated: Non-distributable layers are deprecated, and not recommended for future use.
2017-08-01 22:27:52 +00:00
allow := ms . manifestURLs . allow
deny := ms . manifestURLs . deny
for _ , u := range descriptor . URLs {
var pu * url . URL
pu , err = url . Parse ( u )
if err != nil || ( pu . Scheme != "http" && pu . Scheme != "https" ) || pu . Fragment != "" || ( allow != nil && ! allow . MatchString ( u ) ) || ( deny != nil && deny . MatchString ( u ) ) {
err = errInvalidURL
break
}
}
2021-04-15 06:44:04 +00:00
if err == nil {
// check the presence if it is normal layer or
// there is no urls for non-distributable
if len ( descriptor . URLs ) == 0 ||
( descriptor . MediaType == v1 . MediaTypeImageLayer || descriptor . MediaType == v1 . MediaTypeImageLayerGzip ) {
_ , err = blobsService . Stat ( ctx , descriptor . Digest )
}
2017-08-01 22:27:52 +00:00
}
2017-07-11 19:19:47 +00:00
case v1 . MediaTypeImageManifest :
2017-07-10 23:09:10 +00:00
var exists bool
exists , err = manifestService . Exists ( ctx , descriptor . Digest )
if err != nil || ! exists {
err = distribution . ErrBlobUnknown // just coerce to unknown.
}
2021-04-15 06:44:04 +00:00
if err != nil {
dcontext . GetLogger ( ms . ctx ) . WithError ( err ) . Debugf ( "failed to ensure exists of %v in manifest service" , descriptor . Digest )
}
2017-07-10 23:09:10 +00:00
fallthrough // double check the blob store.
default :
2021-04-15 06:44:04 +00:00
// check the presence
_ , err = blobsService . Stat ( ctx , descriptor . Digest )
2017-07-10 23:09:10 +00:00
}
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 : descriptor . Digest } )
}
}
if len ( errs ) != 0 {
return errs
}
return nil
}