Fix Docker Auth and our V2 registry paths to support library (i.e. namespace-less) repositories.

This support is placed behind a feature flag.
This commit is contained in:
Joseph Schorr 2016-01-21 15:40:51 -05:00
parent 06b0f756bd
commit e4ffaff869
37 changed files with 270 additions and 148 deletions

View file

@ -1,6 +1,7 @@
import logging
import jwt.utils
import json
import features
from peewee import IntegrityError
from flask import make_response, request, url_for
@ -8,25 +9,25 @@ from collections import namedtuple, OrderedDict
from jwkest.jws import SIGNER_ALGS, keyrep
from datetime import datetime
from app import docker_v2_signing_key
from app import docker_v2_signing_key, app
from auth.registry_jwt_auth import process_registry_jwt_auth
from endpoints.decorators import anon_protect
from endpoints.v2 import v2_bp, require_repo_read, require_repo_write
from endpoints.v2.errors import (BlobUnknown, ManifestInvalid, ManifestUnverified,
ManifestUnknown, TagInvalid, NameInvalid)
from endpoints.v2.errors import (BlobUnknown, ManifestInvalid, ManifestUnknown, TagInvalid,
NameInvalid)
from endpoints.trackhelper import track_and_log
from endpoints.notificationhelper import spawn_notification
from digest import digest_tools
from data import model
from data.database import RepositoryTag
from endpoints.common import parse_repository_name
logger = logging.getLogger(__name__)
VALID_TAG_PATTERN = r'[\w][\w.-]{0,127}'
BASE_MANIFEST_ROUTE = '/<namespace>/<repo_name>/manifests/<regex("{0}"):manifest_ref>'
BASE_MANIFEST_ROUTE = '/<repopath:repository>/manifests/<regex("{0}"):manifest_ref>'
MANIFEST_DIGEST_ROUTE = BASE_MANIFEST_ROUTE.format(digest_tools.DIGEST_PATTERN)
MANIFEST_TAGNAME_ROUTE = BASE_MANIFEST_ROUTE.format(VALID_TAG_PATTERN)
@ -61,9 +62,17 @@ class SignedManifest(object):
self._parsed = json.loads(manifest_bytes)
self._signatures = self._parsed[_SIGNATURES_KEY]
self._namespace, self._repo_name = self._parsed[_REPO_NAME_KEY].split('/')
self._tag = self._parsed[_REPO_TAG_KEY]
repo_name_tuple = self._parsed[_REPO_NAME_KEY].split('/')
if len(repo_name_tuple) > 1:
self._namespace, self._repo_name = repo_name_tuple
elif len(repo_name_tuple) == 1:
self._namespace = ''
self._repo_name = repo_name_tuple[0]
else:
raise ValueError('repo_name has too many or too few pieces')
self._validate()
def _validate(self):
@ -144,9 +153,13 @@ class SignedManifestBuilder(object):
""" Class which represents a manifest which is currently being built.
"""
def __init__(self, namespace, repo_name, tag, architecture='amd64', schema_ver=1):
repo_name_key = '{0}/{1}'.format(namespace, repo_name)
if namespace == '':
repo_name_key = repo_name
self._base_payload = {
_REPO_TAG_KEY: tag,
_REPO_NAME_KEY: '{0}/{1}'.format(namespace, repo_name),
_REPO_NAME_KEY: repo_name_key,
_ARCH_KEY: architecture,
_SCHEMA_VER: schema_ver,
}
@ -213,6 +226,7 @@ class SignedManifestBuilder(object):
@v2_bp.route(MANIFEST_TAGNAME_ROUTE, methods=['GET'])
@process_registry_jwt_auth
@parse_repository_name
@require_repo_read
@anon_protect
def fetch_manifest_by_tagname(namespace, repo_name, manifest_ref):
@ -242,6 +256,7 @@ def fetch_manifest_by_tagname(namespace, repo_name, manifest_ref):
@v2_bp.route(MANIFEST_DIGEST_ROUTE, methods=['GET'])
@process_registry_jwt_auth
@parse_repository_name
@require_repo_read
@anon_protect
def fetch_manifest_by_digest(namespace, repo_name, manifest_ref):
@ -262,12 +277,13 @@ def fetch_manifest_by_digest(namespace, repo_name, manifest_ref):
@v2_bp.route(MANIFEST_TAGNAME_ROUTE, methods=['PUT'])
@process_registry_jwt_auth
@parse_repository_name
@require_repo_write
@anon_protect
def write_manifest_by_tagname(namespace, repo_name, manifest_ref):
try:
manifest = SignedManifest(request.data)
except ValueError:
except ValueError as ve:
raise ManifestInvalid()
if manifest.tag != manifest_ref:
@ -278,6 +294,7 @@ def write_manifest_by_tagname(namespace, repo_name, manifest_ref):
@v2_bp.route(MANIFEST_DIGEST_ROUTE, methods=['PUT'])
@process_registry_jwt_auth
@parse_repository_name
@require_repo_write
@anon_protect
def write_manifest_by_digest(namespace, repo_name, manifest_ref):
@ -293,8 +310,16 @@ def write_manifest_by_digest(namespace, repo_name, manifest_ref):
def _write_manifest(namespace, repo_name, manifest):
# Ensure that the manifest is for this repository.
if manifest.namespace != namespace or manifest.repo_name != repo_name:
# Ensure that the manifest is for this repository. If the manifest's namespace is empty, then
# it is for the library namespace and we need an extra check.
if (manifest.namespace == '' and features.LIBRARY_SUPPORT and
namespace == app.config['LIBRARY_NAMESPACE']):
# This is a library manifest. All good.
pass
elif manifest.namespace != namespace:
raise NameInvalid()
if manifest.repo_name != repo_name:
raise NameInvalid()
# Ensure that the repository exists.
@ -369,13 +394,15 @@ def _write_manifest(namespace, repo_name, manifest):
response = make_response('OK', 202)
response.headers['Docker-Content-Digest'] = manifest_digest
response.headers['Location'] = url_for('v2.fetch_manifest_by_digest', namespace=namespace,
repo_name=repo_name, manifest_ref=manifest_digest)
response.headers['Location'] = url_for('v2.fetch_manifest_by_digest',
repository='%s/%s' % (namespace, repo_name),
manifest_ref=manifest_digest)
return response
@v2_bp.route(MANIFEST_DIGEST_ROUTE, methods=['DELETE'])
@process_registry_jwt_auth
@parse_repository_name
@require_repo_write
@anon_protect
def delete_manifest_by_digest(namespace, repo_name, manifest_ref):
@ -406,8 +433,14 @@ def _generate_and_store_manifest(namespace, repo_name, tag_name):
image = model.tag.get_tag_image(namespace, repo_name, tag_name)
parents = model.image.get_parent_images(namespace, repo_name, image)
# If the manifest is being generated under the library namespace, then we make its namespace
# empty.
manifest_namespace = namespace
if features.LIBRARY_SUPPORT and namespace == app.config['LIBRARY_NAMESPACE']:
manifest_namespace = ''
# Create and populate the manifest builder
builder = SignedManifestBuilder(namespace, repo_name, tag_name)
builder = SignedManifestBuilder(manifest_namespace, repo_name, tag_name)
# Add the leaf layer
builder.add_layer(image.storage.content_checksum, image.v1_json_metadata)