Enhancements for Docker schema implementations in preparing for supporting schema 2 in the OCI model

This adds additional required properties and methods to the Docker schema interface to allow us to treat both schema1 and schema2 manifests and lists logically equivalent from the OCI mode perspective
This commit is contained in:
Joseph Schorr 2018-11-12 23:27:01 +02:00
parent 6b86b87a16
commit e344d4a5cf
12 changed files with 447 additions and 22 deletions

View file

@ -151,7 +151,7 @@ class DockerSchema1Manifest(ManifestInterface):
},
},
},
'required': [DOCKER_SCHEMA1_SIGNATURES_KEY, DOCKER_SCHEMA1_REPO_TAG_KEY,
'required': [DOCKER_SCHEMA1_REPO_TAG_KEY,
DOCKER_SCHEMA1_REPO_NAME_KEY, DOCKER_SCHEMA1_FS_LAYERS_KEY,
DOCKER_SCHEMA1_HISTORY_KEY],
}
@ -170,8 +170,9 @@ class DockerSchema1Manifest(ManifestInterface):
except ValidationError as ve:
raise MalformedSchema1Manifest('manifest data does not match schema: %s' % ve)
self._signatures = self._parsed[DOCKER_SCHEMA1_SIGNATURES_KEY]
self._signatures = self._parsed.get(DOCKER_SCHEMA1_SIGNATURES_KEY)
self._tag = self._parsed[DOCKER_SCHEMA1_REPO_TAG_KEY]
self._architecture = self._parsed[DOCKER_SCHEMA1_ARCH_KEY]
repo_name = self._parsed[DOCKER_SCHEMA1_REPO_NAME_KEY]
repo_name_tuple = repo_name.split('/')
@ -191,6 +192,9 @@ class DockerSchema1Manifest(ManifestInterface):
return DockerSchema1Manifest(encoded_bytes.encode('utf-8'), validate)
def _validate(self):
if not self._signatures:
return
for signature in self._signatures:
bytes_to_verify = '{0}.{1}'.format(signature['protected'],
base64url_encode(self._payload))
@ -208,11 +212,12 @@ class DockerSchema1Manifest(ManifestInterface):
@property
def content_type(self):
return DOCKER_SCHEMA1_SIGNED_MANIFEST_CONTENT_TYPE
return (DOCKER_SCHEMA1_SIGNED_MANIFEST_CONTENT_TYPE
if self._signatures else DOCKER_SCHEMA1_MANIFEST_CONTENT_TYPE)
@property
def media_type(self):
return DOCKER_SCHEMA1_SIGNED_MANIFEST_CONTENT_TYPE
return self.content_type
@property
def signatures(self):
@ -297,6 +302,24 @@ class DockerSchema1Manifest(ManifestInterface):
def blob_digests(self):
return [str(layer.digest) for layer in self.layers]
def child_manifests(self, lookup_manifest_fn):
return None
def get_manifest_labels(self, lookup_config_fn):
return self.layers[-1].v1_metadata.labels
def unsigned(self):
if self.media_type == DOCKER_SCHEMA1_MANIFEST_CONTENT_TYPE:
return self
# Create an unsigned version of the manifest.
builder = DockerSchema1ManifestBuilder(self._namespace, self._repo_name, self._tag,
self._architecture)
for layer in reversed(self.layers):
builder.add_layer(str(layer.digest), layer.raw_v1_metadata)
return builder.build()
def _generate_layers(self):
"""
Returns a generator of objects that have the blobSum and v1Compatibility keys in them,
@ -330,12 +353,18 @@ class DockerSchema1Manifest(ManifestInterface):
@property
def _payload(self):
if self._signatures is None:
return self._bytes
protected = str(self._signatures[0][DOCKER_SCHEMA1_PROTECTED_KEY])
parsed_protected = json.loads(base64url_decode(protected))
signed_content_head = self._bytes[:parsed_protected[DOCKER_SCHEMA1_FORMAT_LENGTH_KEY]]
signed_content_tail = base64url_decode(str(parsed_protected[DOCKER_SCHEMA1_FORMAT_TAIL_KEY]))
return signed_content_head + signed_content_tail
def generate_legacy_layers(self, images_map, lookup_config_fn):
return self.rewrite_invalid_image_ids(images_map)
def rewrite_invalid_image_ids(self, images_map):
"""
Rewrites Docker v1 image IDs and returns a generator of DockerV1Metadata.
@ -428,9 +457,9 @@ class DockerSchema1ManifestBuilder(object):
return self
def build(self, json_web_key):
def build(self, json_web_key=None):
"""
Builds a DockerSchema1Manifest object complete with signature.
Builds a DockerSchema1Manifest object, with optional signature.
"""
payload = OrderedDict(self._base_payload)
payload.update({
@ -439,6 +468,8 @@ class DockerSchema1ManifestBuilder(object):
})
payload_str = json.dumps(payload, indent=3)
if json_web_key is None:
return DockerSchema1Manifest(payload_str)
split_point = payload_str.rfind('\n}')