1 package handlers
2
3 import (
4 "bytes"
5 "fmt"
6 "net/http"
7 "strings"
8
9 "github.com/distribution/reference"
10 "github.com/docker/distribution"
11 dcontext "github.com/docker/distribution/context"
12 "github.com/docker/distribution/manifest/manifestlist"
13 "github.com/docker/distribution/manifest/ocischema"
14 "github.com/docker/distribution/manifest/schema1"
15 "github.com/docker/distribution/manifest/schema2"
16 "github.com/docker/distribution/registry/api/errcode"
17 v2 "github.com/docker/distribution/registry/api/v2"
18 "github.com/docker/distribution/registry/auth"
19 "github.com/gorilla/handlers"
20 "github.com/opencontainers/go-digest"
21 v1 "github.com/opencontainers/image-spec/specs-go/v1"
22 )
23
24
25
26 const (
27 defaultArch = "amd64"
28 defaultOS = "linux"
29 maxManifestBodySize = 4 << 20
30 imageClass = "image"
31 )
32
33 type storageType int
34
35 const (
36 manifestSchema1 storageType = iota
37 manifestSchema2
38 manifestlistSchema
39 ociSchema
40 ociImageIndexSchema
41 numStorageTypes
42 )
43
44
45
46 func manifestDispatcher(ctx *Context, r *http.Request) http.Handler {
47 manifestHandler := &manifestHandler{
48 Context: ctx,
49 }
50 reference := getReference(ctx)
51 dgst, err := digest.Parse(reference)
52 if err != nil {
53
54 manifestHandler.Tag = reference
55 } else {
56 manifestHandler.Digest = dgst
57 }
58
59 mhandler := handlers.MethodHandler{
60 "GET": http.HandlerFunc(manifestHandler.GetManifest),
61 "HEAD": http.HandlerFunc(manifestHandler.GetManifest),
62 }
63
64 if !ctx.readOnly {
65 mhandler["PUT"] = http.HandlerFunc(manifestHandler.PutManifest)
66 mhandler["DELETE"] = http.HandlerFunc(manifestHandler.DeleteManifest)
67 }
68
69 return mhandler
70 }
71
72
73 type manifestHandler struct {
74 *Context
75
76
77 Tag string
78 Digest digest.Digest
79 }
80
81
82 func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request) {
83 dcontext.GetLogger(imh).Debug("GetImageManifest")
84 manifests, err := imh.Repository.Manifests(imh)
85 if err != nil {
86 imh.Errors = append(imh.Errors, err)
87 return
88 }
89 var supports [numStorageTypes]bool
90
91
92
93 for _, acceptHeader := range r.Header["Accept"] {
94
95
96
97
98
99 for _, mediaType := range strings.Split(acceptHeader, ",") {
100
101 if i := strings.Index(mediaType, ";"); i >= 0 {
102 mediaType = mediaType[:i]
103 }
104
105
106 mediaType = strings.TrimSpace(mediaType)
107
108 if mediaType == schema2.MediaTypeManifest {
109 supports[manifestSchema2] = true
110 }
111 if mediaType == manifestlist.MediaTypeManifestList {
112 supports[manifestlistSchema] = true
113 }
114 if mediaType == v1.MediaTypeImageManifest {
115 supports[ociSchema] = true
116 }
117 if mediaType == v1.MediaTypeImageIndex {
118 supports[ociImageIndexSchema] = true
119 }
120 }
121 }
122
123 if imh.Tag != "" {
124 tags := imh.Repository.Tags(imh)
125 desc, err := tags.Get(imh, imh.Tag)
126 if err != nil {
127 if _, ok := err.(distribution.ErrTagUnknown); ok {
128 imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithDetail(err))
129 } else {
130 imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
131 }
132 return
133 }
134 imh.Digest = desc.Digest
135 }
136
137 if etagMatch(r, imh.Digest.String()) {
138 w.WriteHeader(http.StatusNotModified)
139 return
140 }
141
142 var options []distribution.ManifestServiceOption
143 if imh.Tag != "" {
144 options = append(options, distribution.WithTag(imh.Tag))
145 }
146 manifest, err := manifests.Get(imh, imh.Digest, options...)
147 if err != nil {
148 if _, ok := err.(distribution.ErrManifestUnknownRevision); ok {
149 imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithDetail(err))
150 } else {
151 imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
152 }
153 return
154 }
155
156 manifestType := manifestSchema1
157 schema2Manifest, isSchema2 := manifest.(*schema2.DeserializedManifest)
158 manifestList, isManifestList := manifest.(*manifestlist.DeserializedManifestList)
159 if isSchema2 {
160 manifestType = manifestSchema2
161 } else if _, isOCImanifest := manifest.(*ocischema.DeserializedManifest); isOCImanifest {
162 manifestType = ociSchema
163 } else if isManifestList {
164 if manifestList.MediaType == manifestlist.MediaTypeManifestList {
165 manifestType = manifestlistSchema
166 } else if manifestList.MediaType == v1.MediaTypeImageIndex {
167 manifestType = ociImageIndexSchema
168 }
169 }
170
171 if manifestType == ociSchema && !supports[ociSchema] {
172 imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithMessage("OCI manifest found, but accept header does not support OCI manifests"))
173 return
174 }
175 if manifestType == ociImageIndexSchema && !supports[ociImageIndexSchema] {
176 imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithMessage("OCI index found, but accept header does not support OCI indexes"))
177 return
178 }
179
180
181
182 if imh.Tag != "" && manifestType == manifestSchema2 && !supports[manifestSchema2] {
183
184 dcontext.GetLogger(imh).Infof("rewriting manifest %s in schema1 format to support old client", imh.Digest.String())
185
186 manifest, err = imh.convertSchema2Manifest(schema2Manifest)
187 if err != nil {
188 return
189 }
190 } else if imh.Tag != "" && manifestType == manifestlistSchema && !supports[manifestlistSchema] {
191
192 dcontext.GetLogger(imh).Infof("rewriting manifest list %s in schema1 format to support old client", imh.Digest.String())
193
194
195
196 var manifestDigest digest.Digest
197 for _, manifestDescriptor := range manifestList.Manifests {
198 if manifestDescriptor.Platform.Architecture == defaultArch && manifestDescriptor.Platform.OS == defaultOS {
199 manifestDigest = manifestDescriptor.Digest
200 break
201 }
202 }
203
204 if manifestDigest == "" {
205 imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown)
206 return
207 }
208
209 manifest, err = manifests.Get(imh, manifestDigest)
210 if err != nil {
211 if _, ok := err.(distribution.ErrManifestUnknownRevision); ok {
212 imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithDetail(err))
213 } else {
214 imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
215 }
216 return
217 }
218
219
220 if schema2Manifest, isSchema2 := manifest.(*schema2.DeserializedManifest); isSchema2 && !supports[manifestSchema2] {
221 manifest, err = imh.convertSchema2Manifest(schema2Manifest)
222 if err != nil {
223 return
224 }
225 } else {
226 imh.Digest = manifestDigest
227 }
228 }
229
230 ct, p, err := manifest.Payload()
231 if err != nil {
232 return
233 }
234
235 w.Header().Set("Content-Type", ct)
236 w.Header().Set("Content-Length", fmt.Sprint(len(p)))
237 w.Header().Set("Docker-Content-Digest", imh.Digest.String())
238 w.Header().Set("Etag", fmt.Sprintf(`"%s"`, imh.Digest))
239 w.Write(p)
240 }
241
242 func (imh *manifestHandler) convertSchema2Manifest(schema2Manifest *schema2.DeserializedManifest) (distribution.Manifest, error) {
243 targetDescriptor := schema2Manifest.Target()
244 blobs := imh.Repository.Blobs(imh)
245 configJSON, err := blobs.Get(imh, targetDescriptor.Digest)
246 if err != nil {
247 if err == distribution.ErrBlobUnknown {
248 imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err))
249 } else {
250 imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
251 }
252 return nil, err
253 }
254
255 ref := imh.Repository.Named()
256
257 if imh.Tag != "" {
258 ref, err = reference.WithTag(ref, imh.Tag)
259 if err != nil {
260 imh.Errors = append(imh.Errors, v2.ErrorCodeTagInvalid.WithDetail(err))
261 return nil, err
262 }
263 }
264
265 builder := schema1.NewConfigManifestBuilder(imh.Repository.Blobs(imh), imh.Context.App.trustKey, ref, configJSON)
266 for _, d := range schema2Manifest.Layers {
267 if err := builder.AppendReference(d); err != nil {
268 imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err))
269 return nil, err
270 }
271 }
272 manifest, err := builder.Build(imh)
273 if err != nil {
274 imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err))
275 return nil, err
276 }
277 imh.Digest = digest.FromBytes(manifest.(*schema1.SignedManifest).Canonical)
278
279 return manifest, nil
280 }
281
282 func etagMatch(r *http.Request, etag string) bool {
283 for _, headerVal := range r.Header["If-None-Match"] {
284 if headerVal == etag || headerVal == fmt.Sprintf(`"%s"`, etag) {
285 return true
286 }
287 }
288 return false
289 }
290
291
292 func (imh *manifestHandler) PutManifest(w http.ResponseWriter, r *http.Request) {
293 dcontext.GetLogger(imh).Debug("PutImageManifest")
294 manifests, err := imh.Repository.Manifests(imh)
295 if err != nil {
296 imh.Errors = append(imh.Errors, err)
297 return
298 }
299
300 var jsonBuf bytes.Buffer
301 if err := copyFullPayload(imh, w, r, &jsonBuf, maxManifestBodySize, "image manifest PUT"); err != nil {
302
303 imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err.Error()))
304 return
305 }
306
307 mediaType := r.Header.Get("Content-Type")
308 manifest, desc, err := distribution.UnmarshalManifest(mediaType, jsonBuf.Bytes())
309 if err != nil {
310 imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err))
311 return
312 }
313
314 if imh.Digest != "" {
315 if desc.Digest != imh.Digest {
316 dcontext.GetLogger(imh).Errorf("payload digest does match: %q != %q", desc.Digest, imh.Digest)
317 imh.Errors = append(imh.Errors, v2.ErrorCodeDigestInvalid)
318 return
319 }
320 } else if imh.Tag != "" {
321 imh.Digest = desc.Digest
322 } else {
323 imh.Errors = append(imh.Errors, v2.ErrorCodeTagInvalid.WithDetail("no tag or digest specified"))
324 return
325 }
326
327 isAnOCIManifest := mediaType == v1.MediaTypeImageManifest || mediaType == v1.MediaTypeImageIndex
328
329 if isAnOCIManifest {
330 dcontext.GetLogger(imh).Debug("Putting an OCI Manifest!")
331 } else {
332 dcontext.GetLogger(imh).Debug("Putting a Docker Manifest!")
333 }
334
335 var options []distribution.ManifestServiceOption
336 if imh.Tag != "" {
337 options = append(options, distribution.WithTag(imh.Tag))
338 }
339
340 if err := imh.applyResourcePolicy(manifest); err != nil {
341 imh.Errors = append(imh.Errors, err)
342 return
343 }
344
345 _, err = manifests.Put(imh, manifest, options...)
346 if err != nil {
347
348
349 if err == distribution.ErrUnsupported {
350 imh.Errors = append(imh.Errors, errcode.ErrorCodeUnsupported)
351 return
352 }
353 if err == distribution.ErrAccessDenied {
354 imh.Errors = append(imh.Errors, errcode.ErrorCodeDenied)
355 return
356 }
357 switch err := err.(type) {
358 case distribution.ErrManifestVerification:
359 for _, verificationError := range err {
360 switch verificationError := verificationError.(type) {
361 case distribution.ErrManifestBlobUnknown:
362 imh.Errors = append(imh.Errors, v2.ErrorCodeManifestBlobUnknown.WithDetail(verificationError.Digest))
363 case distribution.ErrManifestNameInvalid:
364 imh.Errors = append(imh.Errors, v2.ErrorCodeNameInvalid.WithDetail(err))
365 case distribution.ErrManifestUnverified:
366 imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnverified)
367 default:
368 if verificationError == digest.ErrDigestInvalidFormat {
369 imh.Errors = append(imh.Errors, v2.ErrorCodeDigestInvalid)
370 } else {
371 imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown, verificationError)
372 }
373 }
374 }
375 case errcode.Error:
376 imh.Errors = append(imh.Errors, err)
377 default:
378 imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
379 }
380 return
381 }
382
383
384 if imh.Tag != "" {
385 tags := imh.Repository.Tags(imh)
386 err = tags.Tag(imh, imh.Tag, desc)
387 if err != nil {
388 imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
389 return
390 }
391
392 }
393
394
395 ref, err := reference.WithDigest(imh.Repository.Named(), imh.Digest)
396 if err != nil {
397 imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
398 return
399 }
400
401 location, err := imh.urlBuilder.BuildManifestURL(ref)
402 if err != nil {
403
404
405
406 dcontext.GetLogger(imh).Errorf("error building manifest url from digest: %v", err)
407 }
408
409 w.Header().Set("Location", location)
410 w.Header().Set("Docker-Content-Digest", imh.Digest.String())
411 w.WriteHeader(http.StatusCreated)
412
413 dcontext.GetLogger(imh).Debug("Succeeded in putting manifest!")
414 }
415
416
417
418 func (imh *manifestHandler) applyResourcePolicy(manifest distribution.Manifest) error {
419 allowedClasses := imh.App.Config.Policy.Repository.Classes
420 if len(allowedClasses) == 0 {
421 return nil
422 }
423
424 var class string
425 switch m := manifest.(type) {
426 case *schema1.SignedManifest:
427 class = imageClass
428 case *schema2.DeserializedManifest:
429 switch m.Config.MediaType {
430 case schema2.MediaTypeImageConfig:
431 class = imageClass
432 case schema2.MediaTypePluginConfig:
433 class = "plugin"
434 default:
435 return errcode.ErrorCodeDenied.WithMessage("unknown manifest class for " + m.Config.MediaType)
436 }
437 case *ocischema.DeserializedManifest:
438 switch m.Config.MediaType {
439 case v1.MediaTypeImageConfig:
440 class = imageClass
441 default:
442 return errcode.ErrorCodeDenied.WithMessage("unknown manifest class for " + m.Config.MediaType)
443 }
444 }
445
446 if class == "" {
447 return nil
448 }
449
450
451 var allowedClass bool
452 for _, c := range allowedClasses {
453 if class == c {
454 allowedClass = true
455 break
456 }
457 }
458 if !allowedClass {
459 return errcode.ErrorCodeDenied.WithMessage(fmt.Sprintf("registry does not allow %s manifest", class))
460 }
461
462 resources := auth.AuthorizedResources(imh)
463 n := imh.Repository.Named().Name()
464
465 var foundResource bool
466 for _, r := range resources {
467 if r.Name == n {
468 if r.Class == "" {
469 r.Class = imageClass
470 }
471 if r.Class == class {
472 return nil
473 }
474 foundResource = true
475 }
476 }
477
478
479 if foundResource {
480 return errcode.ErrorCodeDenied.WithMessage(fmt.Sprintf("repository not authorized for %s manifest", class))
481 }
482
483 return nil
484
485 }
486
487
488 func (imh *manifestHandler) DeleteManifest(w http.ResponseWriter, r *http.Request) {
489 dcontext.GetLogger(imh).Debug("DeleteImageManifest")
490
491 manifests, err := imh.Repository.Manifests(imh)
492 if err != nil {
493 imh.Errors = append(imh.Errors, err)
494 return
495 }
496
497 err = manifests.Delete(imh, imh.Digest)
498 if err != nil {
499 switch err {
500 case digest.ErrDigestUnsupported:
501 case digest.ErrDigestInvalidFormat:
502 imh.Errors = append(imh.Errors, v2.ErrorCodeDigestInvalid)
503 return
504 case distribution.ErrBlobUnknown:
505 imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown)
506 return
507 case distribution.ErrUnsupported:
508 imh.Errors = append(imh.Errors, errcode.ErrorCodeUnsupported)
509 return
510 default:
511 imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown)
512 return
513 }
514 }
515
516 tagService := imh.Repository.Tags(imh)
517 referencedTags, err := tagService.Lookup(imh, distribution.Descriptor{Digest: imh.Digest})
518 if err != nil {
519 imh.Errors = append(imh.Errors, err)
520 return
521 }
522
523 for _, tag := range referencedTags {
524 if err := tagService.Untag(imh, tag); err != nil {
525 imh.Errors = append(imh.Errors, err)
526 return
527 }
528 }
529
530 w.WriteHeader(http.StatusAccepted)
531 }
532
View as plain text