diff --git a/image/docker/schema1.py b/image/docker/schema1.py index d93cdc62b..d6d2c673e 100644 --- a/image/docker/schema1.py +++ b/image/docker/schema1.py @@ -11,6 +11,8 @@ import logging from collections import namedtuple, OrderedDict from datetime import datetime +from jsonschema import validate as validate_schema, ValidationError + from jwkest.jws import SIGNER_ALGS, keyrep from jwt.utils import base64url_encode, base64url_decode @@ -84,6 +86,73 @@ class Schema1V1Metadata(namedtuple('Schema1V1Metadata', ['image_id', 'parent_ima class DockerSchema1Manifest(object): + METASCHEMA = { + 'type': 'object', + 'properties': { + DOCKER_SCHEMA1_SIGNATURES_KEY: { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + DOCKER_SCHEMA1_PROTECTED_KEY: { + 'type': 'string', + }, + DOCKER_SCHEMA1_HEADER_KEY: { + 'type': 'object', + 'properties': { + 'alg': { + 'type': 'string', + }, + 'jwk': { + 'type': 'object', + }, + }, + 'required': ['alg', 'jwk'], + }, + DOCKER_SCHEMA1_SIGNATURE_KEY: { + 'type': 'string', + }, + }, + 'required': [DOCKER_SCHEMA1_PROTECTED_KEY, DOCKER_SCHEMA1_HEADER_KEY, + DOCKER_SCHEMA1_SIGNATURE_KEY], + }, + }, + DOCKER_SCHEMA1_REPO_TAG_KEY: { + 'type': 'string', + }, + DOCKER_SCHEMA1_REPO_NAME_KEY: { + 'type': 'string', + }, + DOCKER_SCHEMA1_HISTORY_KEY: { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + DOCKER_SCHEMA1_V1_COMPAT_KEY: { + 'type': 'string', + }, + }, + 'required': [DOCKER_SCHEMA1_V1_COMPAT_KEY], + }, + }, + DOCKER_SCHEMA1_FS_LAYERS_KEY: { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + DOCKER_SCHEMA1_BLOB_SUM_KEY: { + 'type': 'string', + }, + }, + 'required': [DOCKER_SCHEMA1_BLOB_SUM_KEY], + }, + }, + }, + 'required': [DOCKER_SCHEMA1_SIGNATURES_KEY, DOCKER_SCHEMA1_REPO_TAG_KEY, + DOCKER_SCHEMA1_REPO_NAME_KEY, DOCKER_SCHEMA1_FS_LAYERS_KEY, + DOCKER_SCHEMA1_HISTORY_KEY], + } + def __init__(self, manifest_bytes, validate=True): self._layers = None self._bytes = manifest_bytes @@ -93,6 +162,11 @@ class DockerSchema1Manifest(object): except ValueError as ve: raise MalformedSchema1Manifest('malformed manifest data: %s' % ve) + try: + validate_schema(self._parsed, DockerSchema1Manifest.METASCHEMA) + except ValidationError as ve: + raise MalformedSchema1Manifest('malformed manifest data: %s' % ve) + self._signatures = self._parsed[DOCKER_SCHEMA1_SIGNATURES_KEY] self._tag = self._parsed[DOCKER_SCHEMA1_REPO_TAG_KEY] diff --git a/test/registry_tests.py b/test/registry_tests.py index 933062a6d..c7eabc76a 100644 --- a/test/registry_tests.py +++ b/test/registry_tests.py @@ -1371,8 +1371,8 @@ class V1RegistryTests(V1RegistryPullMixin, V1RegistryPushMixin, RegistryTestsMix # Try some logins. self.conduct('POST', '/v1/users', json_data={'username': 'freshuser'}, expected_code=400) resp = self.conduct('POST', '/v1/users', - json_data={'username': 'devtable', 'password': 'password'}, - expected_code=400) + json_data={'username': 'devtable', 'password': 'password'}, + expected_code=400) # Because Docker self.assertEquals('"Username or email already exists"', resp.text) @@ -1623,6 +1623,20 @@ class V2RegistryTests(V2RegistryPullMixin, V2RegistryPushMixin, RegistryTestsMix headers={'Content-Type': 'application/vnd.docker.distribution.manifest.v2+json'}, auth='jwt') + def test_invalid_manifest(self): + namespace = 'devtable' + repository = 'somerepo' + tag_name = 'sometag' + + repo_name = _get_repo_name(namespace, repository) + + self.v2_ping() + self.do_auth('devtable', 'password', namespace, repository, scopes=['push', 'pull']) + + self.conduct('PUT', '/v2/%s/manifests/%s' % (repo_name, tag_name), + data='{}', expected_code=400, + auth='jwt') + def test_oci_manifest_type(self): namespace = 'devtable' repository = 'somerepo'