Skip to content

Commit 8de83ca

Browse files
committed
proxy: Verify *either* toplevel or target
(Only compile tested locally) An issue was raised in that a current Red Hat internal build system was performing a signature just on the per-architecture manifest, but the current proxy code is expecting a signature on the manifest list. To quote Miloslav from that bug: > Podman signs both the per-platform items and the top level, > and enforces signatures only on per-platform items. cosign, > by default, signs only the top level (and has an option to > sign everything), I’m not sure which one it enforces. > I don’t immediately recall other platforms. We believe the current proxy code is secure since we always require signatures (if configured) on the manifest list, and the manifest list covers the individual manifests via sha256 digest. However, we want to support signatures only being present on the per-arch manifest too in order to be flexible. Yet, we can't hard switch to requiring signatures on the per-arch manifests as that would immediately break anyone relying on the current behavior of validating the toplevel. Change the logic here to: - Verify signature on the manifest list, and cache the error (if any) - Fetch the manifest - Verify signature on the manifest - Allow if *either* signature was accepted; conversely, only error if signature validation failed on *both* the manifest list and manifest This also switches things to cache the manifest upfront instead of doing it lazily on `GetManifest/GetConfig`; in practice callers were always immediately requesting those anyways. The use case of just fetching blobs exists (e.g. to resume an interrupted fetch), but is relatively obscure and in general I think it's good to re-verify signatures on each operation. Signed-off-by: Colin Walters <[email protected]>
1 parent 5e88bb0 commit 8de83ca

File tree

1 file changed

+49
-59
lines changed

1 file changed

+49
-59
lines changed

cmd/skopeo/proxy.go

Lines changed: 49 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ func (h *proxyHandler) OpenImage(args []any) (replyBuf, error) {
225225
}
226226

227227
func (h *proxyHandler) openImageImpl(args []any, allowNotFound bool) (retReplyBuf replyBuf, retErr error) {
228+
ctx := context.Background()
228229
h.lock.Lock()
229230
defer h.lock.Unlock()
230231
var ret replyBuf
@@ -254,20 +255,63 @@ func (h *proxyHandler) openImageImpl(args []any, allowNotFound bool) (retReplyBu
254255
}
255256

256257
unparsedTopLevel := image.UnparsedInstance(imgsrc, nil)
257-
allowed, err := h.policyctx.IsRunningImageAllowed(context.Background(), unparsedTopLevel)
258+
// Check the signature on the toplevel (possibly multi-arch) manifest, but we don't
259+
// yet propagate the error here.
260+
allowed, toplevelVerificationErr := h.policyctx.IsRunningImageAllowed(context.Background(), unparsedTopLevel)
261+
if toplevelVerificationErr == nil && !allowed {
262+
return ret, fmt.Errorf("internal inconsistency: policy verification failed without returning an error")
263+
}
264+
265+
mfest, manifestType, err := unparsedTopLevel.Manifest(ctx)
258266
if err != nil {
259267
return ret, err
260268
}
261-
if !allowed {
262-
return ret, fmt.Errorf("internal inconsistency: policy verification failed without returning an error")
269+
var target *image.UnparsedImage
270+
if manifest.MIMETypeIsMultiImage(manifestType) {
271+
manifestList, err := manifest.ListFromBlob(mfest, manifestType)
272+
if err != nil {
273+
return ret, err
274+
}
275+
instanceDigest, err := manifestList.ChooseInstance(h.sysctx)
276+
if err != nil {
277+
return ret, err
278+
}
279+
target = image.UnparsedInstance(imgsrc, &instanceDigest)
280+
281+
allowed, targetVerificationErr := h.policyctx.IsRunningImageAllowed(context.Background(), target)
282+
if targetVerificationErr == nil && !allowed {
283+
return ret, fmt.Errorf("internal inconsistency: policy verification failed without returning an error")
284+
}
285+
286+
// Now, we only error if *both* the toplevel and target verification failed.
287+
// If either succeeded, that's OK. We want to support a case where the manifest
288+
// list is signed, but the target is not (because we previously supported that behavior),
289+
// and we want to support the case where only the target is signed (as some signing
290+
// systems are known to do this).
291+
if targetVerificationErr != nil && toplevelVerificationErr != nil {
292+
return ret, toplevelVerificationErr
293+
}
294+
} else {
295+
target = unparsedTopLevel
296+
297+
// We're not using a manifest list, so require verification of the single arch manifest.
298+
if toplevelVerificationErr != nil {
299+
return ret, toplevelVerificationErr
300+
}
301+
}
302+
303+
cachedimg, err := image.FromUnparsedImage(ctx, h.sysctx, target)
304+
if err != nil {
305+
return ret, err
263306
}
264307

265308
// Note that we never return zero as an imageid; this code doesn't yet
266309
// handle overflow though.
267310
h.imageSerial++
268311
openimg := &openImage{
269-
id: h.imageSerial,
270-
src: imgsrc,
312+
id: h.imageSerial,
313+
src: imgsrc,
314+
cachedimg: cachedimg,
271315
}
272316
h.images[openimg.id] = openimg
273317
ret.value = openimg.id
@@ -367,44 +411,6 @@ func (h *proxyHandler) returnBytes(retval any, buf []byte) (replyBuf, error) {
367411
return ret, nil
368412
}
369413

370-
// cacheTargetManifest is invoked when GetManifest or GetConfig is invoked
371-
// the first time for a given image. If the requested image is a manifest
372-
// list, this function resolves it to the image matching the calling process'
373-
// operating system and architecture.
374-
//
375-
// TODO: Add GetRawManifest or so that exposes manifest lists
376-
func (h *proxyHandler) cacheTargetManifest(img *openImage) error {
377-
ctx := context.Background()
378-
if img.cachedimg != nil {
379-
return nil
380-
}
381-
unparsedToplevel := image.UnparsedInstance(img.src, nil)
382-
mfest, manifestType, err := unparsedToplevel.Manifest(ctx)
383-
if err != nil {
384-
return err
385-
}
386-
var target *image.UnparsedImage
387-
if manifest.MIMETypeIsMultiImage(manifestType) {
388-
manifestList, err := manifest.ListFromBlob(mfest, manifestType)
389-
if err != nil {
390-
return err
391-
}
392-
instanceDigest, err := manifestList.ChooseInstance(h.sysctx)
393-
if err != nil {
394-
return err
395-
}
396-
target = image.UnparsedInstance(img.src, &instanceDigest)
397-
} else {
398-
target = unparsedToplevel
399-
}
400-
cachedimg, err := image.FromUnparsedImage(ctx, h.sysctx, target)
401-
if err != nil {
402-
return err
403-
}
404-
img.cachedimg = cachedimg
405-
return nil
406-
}
407-
408414
// GetManifest returns a copy of the manifest, converted to OCI format, along with the original digest.
409415
// Manifest lists are resolved to the current operating system and architecture.
410416
func (h *proxyHandler) GetManifest(args []any) (replyBuf, error) {
@@ -424,10 +430,6 @@ func (h *proxyHandler) GetManifest(args []any) (replyBuf, error) {
424430
return ret, err
425431
}
426432

427-
err = h.cacheTargetManifest(imgref)
428-
if err != nil {
429-
return ret, err
430-
}
431433
img := imgref.cachedimg
432434

433435
ctx := context.Background()
@@ -494,10 +496,6 @@ func (h *proxyHandler) GetFullConfig(args []any) (replyBuf, error) {
494496
if err != nil {
495497
return ret, err
496498
}
497-
err = h.cacheTargetManifest(imgref)
498-
if err != nil {
499-
return ret, err
500-
}
501499
img := imgref.cachedimg
502500

503501
ctx := context.TODO()
@@ -531,10 +529,6 @@ func (h *proxyHandler) GetConfig(args []any) (replyBuf, error) {
531529
if err != nil {
532530
return ret, err
533531
}
534-
err = h.cacheTargetManifest(imgref)
535-
if err != nil {
536-
return ret, err
537-
}
538532
img := imgref.cachedimg
539533

540534
ctx := context.TODO()
@@ -641,10 +635,6 @@ func (h *proxyHandler) GetLayerInfo(args []any) (replyBuf, error) {
641635

642636
ctx := context.TODO()
643637

644-
err = h.cacheTargetManifest(imgref)
645-
if err != nil {
646-
return ret, err
647-
}
648638
img := imgref.cachedimg
649639

650640
layerInfos, err := img.LayerInfosForCopy(ctx)

0 commit comments

Comments
 (0)