Add architecture validation to manifest lists that contain schema 1 manifests
Fixes https://jira.coreos.com/browse/QUAY-1266
This commit is contained in:
parent
b5a5ce7c43
commit
f0f2d9cdf4
9 changed files with 110 additions and 6 deletions
|
@ -38,6 +38,13 @@ class ManifestInterface(object):
|
|||
cannot be computed locally.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def validate(self, content_retriever):
|
||||
""" Performs validation of required assertions about the manifest. Raises a ManifestException
|
||||
on failure.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_layers(self, content_retriever):
|
||||
""" Returns the layers of this manifest, from base to leaf or None if this kind of manifest
|
||||
|
|
|
@ -213,6 +213,16 @@ class DockerSchema1Manifest(ManifestInterface):
|
|||
if not verified:
|
||||
raise InvalidSchema1Signature()
|
||||
|
||||
def validate(self, content_retriever):
|
||||
""" Performs validation of required assertions about the manifest. Raises a ManifestException
|
||||
on failure.
|
||||
"""
|
||||
# Already validated.
|
||||
|
||||
@property
|
||||
def architecture(self):
|
||||
return self._architecture
|
||||
|
||||
@property
|
||||
def is_manifest_list(self):
|
||||
return False
|
||||
|
|
|
@ -40,6 +40,13 @@ class MalformedSchema2ManifestList(ManifestException):
|
|||
pass
|
||||
|
||||
|
||||
class MismatchManifestException(MalformedSchema2ManifestList):
|
||||
""" Raised when a manifest list contains a schema 1 manifest with a differing architecture
|
||||
from that specified in the manifest list for the manifest.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class LazyManifestLoader(object):
|
||||
def __init__(self, manifest_data, content_retriever):
|
||||
self._manifest_data = manifest_data
|
||||
|
@ -239,6 +246,21 @@ class DockerSchema2ManifestList(ManifestInterface):
|
|||
manifests = self._parsed[DOCKER_SCHEMA2_MANIFESTLIST_MANIFESTS_KEY]
|
||||
return [LazyManifestLoader(m, content_retriever) for m in manifests]
|
||||
|
||||
def validate(self, content_retriever):
|
||||
""" Performs validation of required assertions about the manifest. Raises a ManifestException
|
||||
on failure.
|
||||
"""
|
||||
for index, m in enumerate(self._parsed[DOCKER_SCHEMA2_MANIFESTLIST_MANIFESTS_KEY]):
|
||||
if m[DOCKER_SCHEMA2_MANIFESTLIST_MEDIATYPE_KEY] == DOCKER_SCHEMA1_MANIFEST_CONTENT_TYPE:
|
||||
platform = m[DOCKER_SCHEMA2_MANIFESTLIST_PLATFORM_KEY]
|
||||
|
||||
# Validate the architecture against the schema 1 architecture defined.
|
||||
parsed = self.manifests(content_retriever)[index].manifest_obj
|
||||
assert isinstance(parsed, DockerSchema1Manifest)
|
||||
if (parsed.architecture and
|
||||
parsed.architecture != platform[DOCKER_SCHEMA2_MANIFESTLIST_ARCHITECTURE_KEY]):
|
||||
raise MismatchManifestException('Mismatch in arch for manifest `%s`' % parsed.digest)
|
||||
|
||||
def child_manifests(self, content_retriever):
|
||||
return self.manifests(content_retriever)
|
||||
|
||||
|
|
|
@ -150,6 +150,12 @@ class DockerSchema2Manifest(ManifestInterface):
|
|||
if layer.is_remote and not layer.urls:
|
||||
raise MalformedSchema2Manifest('missing `urls` for remote layer')
|
||||
|
||||
def validate(self, content_retriever):
|
||||
""" Performs validation of required assertions about the manifest. Raises a ManifestException
|
||||
on failure.
|
||||
"""
|
||||
# Nothing to validate.
|
||||
|
||||
@property
|
||||
def is_manifest_list(self):
|
||||
return False
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import json
|
||||
import pytest
|
||||
|
||||
from image.docker.schema1 import DockerSchema1Manifest, DOCKER_SCHEMA1_CONTENT_TYPES
|
||||
from image.docker.schema1 import (DockerSchema1Manifest, DOCKER_SCHEMA1_CONTENT_TYPES,
|
||||
DockerSchema1ManifestBuilder)
|
||||
from image.docker.schema2 import DOCKER_SCHEMA2_MANIFEST_CONTENT_TYPE
|
||||
from image.docker.schema2.manifest import DockerSchema2Manifest
|
||||
from image.docker.schema2.list import (MalformedSchema2ManifestList, DockerSchema2ManifestList,
|
||||
DockerSchema2ManifestListBuilder)
|
||||
DockerSchema2ManifestListBuilder, MismatchManifestException)
|
||||
from image.docker.schema2.test.test_manifest import MANIFEST_BYTES as v22_bytes
|
||||
from image.docker.schemautil import ContentRetrieverForTesting
|
||||
from image.docker.test.test_schema1 import MANIFEST_BYTES as v21_bytes
|
||||
|
@ -108,6 +109,9 @@ def test_valid_manifestlist():
|
|||
assert schema1_manifest.schema_version == 1
|
||||
assert schema1_manifest.digest == compatible_manifest.digest
|
||||
|
||||
# Ensure it validates.
|
||||
manifestlist.validate(retriever)
|
||||
|
||||
|
||||
def test_get_schema1_manifest_no_matching_list():
|
||||
manifestlist = DockerSchema2ManifestList(Bytes.for_string_or_unicode(NO_AMD_MANIFESTLIST_BYTES))
|
||||
|
@ -128,3 +132,20 @@ def test_builder():
|
|||
|
||||
built = builder.build()
|
||||
assert len(built.manifests(retriever)) == 2
|
||||
|
||||
|
||||
def test_invalid_manifestlist():
|
||||
# Build a manifest list with a schema 1 manifest of the wrong architecture.
|
||||
builder = DockerSchema1ManifestBuilder('foo', 'bar', 'baz')
|
||||
builder.add_layer('sha:2356', '{"id": "foo"}')
|
||||
manifest = builder.build().unsigned()
|
||||
|
||||
listbuilder = DockerSchema2ManifestListBuilder()
|
||||
listbuilder.add_manifest(manifest, 'amd32', 'linux')
|
||||
manifestlist = listbuilder.build()
|
||||
|
||||
retriever = ContentRetrieverForTesting()
|
||||
retriever.add_digest(manifest.digest, manifest.bytes.as_encoded_str())
|
||||
|
||||
with pytest.raises(MismatchManifestException):
|
||||
manifestlist.validate(retriever)
|
||||
|
|
Reference in a new issue