Add the ability to blacklist v2 for specific versions
This commit is contained in:
parent
4a84388f15
commit
766d60493f
7 changed files with 97 additions and 2 deletions
|
@ -193,6 +193,10 @@ class DefaultConfig(object):
|
|||
# Feature Flag: Whether the v2/ endpoint is visible
|
||||
FEATURE_ADVERTISE_V2 = True
|
||||
|
||||
# Semver spec for which Docker versions we will blacklist
|
||||
# Documentation: http://pythonhosted.org/semantic_version/reference.html#semantic_version.Spec
|
||||
BLACKLIST_V2_SPEC = '<1.6.0'
|
||||
|
||||
BUILD_MANAGER = ('enterprise', {})
|
||||
|
||||
DISTRIBUTED_STORAGE_CONFIG = {
|
||||
|
|
|
@ -3,6 +3,7 @@ import logging
|
|||
from flask import Blueprint, make_response, url_for, request, jsonify
|
||||
from functools import wraps
|
||||
from urlparse import urlparse
|
||||
from semantic_version import Spec
|
||||
|
||||
import features
|
||||
|
||||
|
@ -13,8 +14,10 @@ from auth.auth_context import get_grant_context
|
|||
from auth.permissions import (ReadRepositoryPermission, ModifyRepositoryPermission,
|
||||
AdministerRepositoryPermission)
|
||||
from data import model
|
||||
from app import app
|
||||
from util.http import abort
|
||||
from util.saas.metricqueue import time_blueprint
|
||||
from util.registry.dockerver import docker_version
|
||||
from auth.registry_jwt_auth import process_registry_jwt_auth, get_auth_headers
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -75,6 +78,14 @@ def route_show_if(value):
|
|||
@process_registry_jwt_auth
|
||||
@anon_allowed
|
||||
def v2_support_enabled():
|
||||
docker_ver = docker_version(request.user_agent.string)
|
||||
|
||||
# Check if our version is one of the blacklisted versions, if we can't
|
||||
# identify the version (None) we will fail open and assume that it is
|
||||
# newer and therefore should not be blacklisted.
|
||||
if Spec(app.config['BLACKLIST_V2_SPEC']).match(docker_ver) and docker_ver is not None:
|
||||
abort(404)
|
||||
|
||||
response = make_response('true', 200)
|
||||
|
||||
if get_grant_context() is None:
|
||||
|
|
|
@ -57,4 +57,5 @@ pyjwkest
|
|||
rfc3987
|
||||
jsonpath-rw
|
||||
bintrees
|
||||
redlock
|
||||
redlock
|
||||
semantic-version
|
||||
|
|
|
@ -78,6 +78,7 @@ reportlab==2.7
|
|||
requests==2.7.0
|
||||
requests-oauthlib==0.5.0
|
||||
rfc3987==1.3.4
|
||||
semantic-version==2.4.2
|
||||
simplejson==3.7.3
|
||||
six==1.9.0
|
||||
SQLAlchemy==1.0.6
|
||||
|
|
|
@ -181,13 +181,18 @@ class RegistryTestCaseMixin(LiveServerTestCase):
|
|||
|
||||
class BaseRegistryMixin(object):
|
||||
def conduct(self, method, url, headers=None, data=None, auth=None, params=None, expected_code=200,
|
||||
json_data=None):
|
||||
json_data=None, user_agent=None):
|
||||
params = params or {}
|
||||
params['_csrf_token'] = self.csrf_token
|
||||
|
||||
headers = headers or {}
|
||||
auth_tuple = None
|
||||
|
||||
if user_agent is not None:
|
||||
headers['User-Agent'] = user_agent
|
||||
else:
|
||||
headers['User-Agent'] = 'docker/1.9.1'
|
||||
|
||||
if self.docker_token:
|
||||
headers['X-Docker-Token'] = self.docker_token
|
||||
|
||||
|
@ -1026,6 +1031,9 @@ class V2RegistryTests(V2RegistryPullMixin, V2RegistryPushMixin, RegistryTestsMix
|
|||
# Try to get tags before a repo exists.
|
||||
self.conduct('GET', '/v2/devtable/doesnotexist/tags/list', auth='jwt', expected_code=401)
|
||||
|
||||
def test_one_five_blacklist(self):
|
||||
self.conduct('GET', '/v2/', expected_code=404, user_agent='Go 1.1 package http')
|
||||
|
||||
|
||||
class V1PushV2PullRegistryTests(V2RegistryPullMixin, V1RegistryPushMixin, RegistryTestsMixin,
|
||||
RegistryTestCaseMixin, LiveServerTestCase):
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import unittest
|
||||
|
||||
from itertools import islice
|
||||
from semantic_version import Version, Spec
|
||||
|
||||
from util.validation import generate_valid_usernames
|
||||
from util.registry.generatorfile import GeneratorFile
|
||||
from util.registry.dockerver import docker_version
|
||||
|
||||
class TestGeneratorFile(unittest.TestCase):
|
||||
def sample_generator(self):
|
||||
|
@ -131,6 +133,47 @@ class TestUsernameGenerator(unittest.TestCase):
|
|||
self.assertEquals('a003', generated_output[3])
|
||||
|
||||
|
||||
class TestDockerVersionParsing(unittest.TestCase):
|
||||
def test_parsing(self):
|
||||
tests_cases = [
|
||||
('docker/1.6.0 go/go1.4.2 git-commit/1234567 kernel/4.2.0-18-generic os/linux arch/amd64',
|
||||
Version('1.6.0')),
|
||||
('docker/1.7.1 go/go1.4.2 kernel/4.1.7-15.23.amzn1.x86_64 os/linux arch/amd64',
|
||||
Version('1.7.1')),
|
||||
('docker/1.6.2 go/go1.4.2 git-commit/7c8fca2-dirty kernel/4.0.5 os/linux arch/amd64',
|
||||
Version('1.6.2')),
|
||||
('docker/1.9.0 go/go1.4.2 git-commit/76d6bc9 kernel/3.16.0-4-amd64 os/linux arch/amd64',
|
||||
Version('1.9.0')),
|
||||
('docker/1.9.1 go/go1.4.2 git-commit/a34a1d5 kernel/3.10.0-229.20.1.el7.x86_64 os/linux arch/amd64',
|
||||
Version('1.9.1')),
|
||||
('docker/1.8.2-circleci go/go1.4.2 git-commit/a8b52f5 kernel/3.13.0-71-generic os/linux arch/amd64',
|
||||
Version('1.8.2')),
|
||||
('Go 1.1 package http', Version('1.5.0')),
|
||||
('curl', None),
|
||||
('docker/1.8 stuff', Version('1.8.0')),
|
||||
]
|
||||
|
||||
for ua_string, ver_info in tests_cases:
|
||||
parsed_ver = docker_version(ua_string)
|
||||
self.assertEquals(ver_info, parsed_ver)
|
||||
|
||||
def test_specs(self):
|
||||
test_cases = [
|
||||
# (Spec, no_match_case_list, matching_case_list)
|
||||
(Spec('<1.6.0'), ['1.6.0', '1.6.1', '1.9.0', '100.5.2'], ['0.0.0', '1.5.99']),
|
||||
(Spec('<1.9.0'), ['1.9.0', '100.5.2'], ['0.0.0', '1.5.99', '1.6.0', '1.6.1']),
|
||||
(Spec('<1.6.0,>0.0.1'), ['1.6.0', '1.6.1', '1.9.0', '0.0.0'], ['1.5.99']),
|
||||
]
|
||||
|
||||
for spec, no_match_cases, match_cases in test_cases:
|
||||
for no_match_case in no_match_cases:
|
||||
self.assertFalse(spec.match(Version(no_match_case)),
|
||||
'Spec: %s Case: %s' % (spec, no_match_case))
|
||||
|
||||
for match_case in match_cases:
|
||||
self.assertTrue(spec.match(Version(match_case)),
|
||||
'Spec: %s Case: %s' % (spec, match_case))
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
|
|
27
util/registry/dockerver.py
Normal file
27
util/registry/dockerver.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
import re
|
||||
|
||||
from semantic_version import Version
|
||||
|
||||
_USER_AGENT_SEARCH_REGEX = re.compile(r'docker\/([0-9]+(?:\.[0-9]+){1,2})')
|
||||
_EXACT_1_5_USER_AGENT = re.compile(r'^Go 1\.1 package http$')
|
||||
_ONE_FIVE_ZERO = '1.5.0'
|
||||
|
||||
def docker_version(user_agent_string):
|
||||
""" Extract the Docker version from the user agent, taking special care to
|
||||
handle the case of a 1.5 client requesting an auth token, which sends
|
||||
a broken user agent. If we can not positively identify a version, return
|
||||
None.
|
||||
"""
|
||||
|
||||
# First search for a well defined semver portion in the UA header.
|
||||
found_semver = _USER_AGENT_SEARCH_REGEX.search(user_agent_string)
|
||||
if found_semver:
|
||||
return Version(found_semver.group(1), partial=True)
|
||||
|
||||
# Check if we received the very specific header which represents a 1.5 request
|
||||
# to the auth endpoints.
|
||||
elif _EXACT_1_5_USER_AGENT.match(user_agent_string):
|
||||
return Version(_ONE_FIVE_ZERO)
|
||||
|
||||
else:
|
||||
return None
|
Reference in a new issue