From 892cc82b6a6dc3ae692c0b911370e7acc34c93c4 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Tue, 19 Jun 2018 17:32:17 -0400 Subject: [PATCH 1/3] Ensure that verbs cannot be performed on disabled namespaces or by disabled users --- endpoints/verbs/__init__.py | 4 ++++ endpoints/verbs/models_interface.py | 5 +++++ endpoints/verbs/models_pre_oci.py | 4 ++++ test/registry/registry_tests.py | 34 +++++++++++++++++++++++++++++ 4 files changed, 47 insertions(+) diff --git a/endpoints/verbs/__init__.py b/endpoints/verbs/__init__.py index 2a0a190ed..46142a893 100644 --- a/endpoints/verbs/__init__.py +++ b/endpoints/verbs/__init__.py @@ -204,6 +204,10 @@ def _verify_repo_verb(_, namespace, repo_name, tag, verb, checker=None): get_authenticated_user()) abort(405) + # Make sure the repo's namespace isn't disabled. + if not model.is_namespace_enabled(namespace): + abort(400) + # If there is a data checker, call it first. if checker is not None: if not checker(tag_image): diff --git a/endpoints/verbs/models_interface.py b/endpoints/verbs/models_interface.py index 0bb8fccac..87559b110 100644 --- a/endpoints/verbs/models_interface.py +++ b/endpoints/verbs/models_interface.py @@ -152,3 +152,8 @@ class VerbsDataInterface(object): or None if none. """ pass + + @abstractmethod + def is_namespace_enabled(self, namespace_name): + """ Returns whether the given namespace exists and is enabled. """ + pass diff --git a/endpoints/verbs/models_pre_oci.py b/endpoints/verbs/models_pre_oci.py index 4d233bfbe..6dc894769 100644 --- a/endpoints/verbs/models_pre_oci.py +++ b/endpoints/verbs/models_pre_oci.py @@ -141,6 +141,10 @@ class PreOCIModel(VerbsDataInterface): v1_metadata=_docker_v1_metadata(namespace_name, repo_name, found), internal_db_id=found.id,) + def is_namespace_enabled(self, namespace_name): + namespace = model.user.get_namespace_user(namespace_name) + return namespace is not None and namespace.enabled + pre_oci_model = PreOCIModel() diff --git a/test/registry/registry_tests.py b/test/registry/registry_tests.py index 9eb74fa53..e63e7bffa 100644 --- a/test/registry/registry_tests.py +++ b/test/registry/registry_tests.py @@ -799,6 +799,40 @@ def test_pull_torrent(pusher, basic_images, liveserver_session, liveserver, assert expected == found +def test_squashed_image_disabled_namespace(pusher, sized_images, liveserver_session, + liveserver, registry_server_executor, app_reloader): + """ Test: Attempting to pull a squashed image from a disabled namespace. """ + credentials = ('devtable', 'password') + + # Push an image to download. + pusher.push(liveserver_session, 'buynlarge', 'newrepo', 'latest', sized_images, + credentials=credentials) + + # Disable the buynlarge namespace. + registry_server_executor.on(liveserver).disable_namespace('buynlarge') + + # Attempt to pull the squashed version. + response = liveserver_session.get('/c1/squash/buynlarge/newrepo/latest', auth=credentials) + assert response.status_code == 400 + + +def test_squashed_image_disabled_user(pusher, sized_images, liveserver_session, + liveserver, registry_server_executor, app_reloader): + """ Test: Attempting to pull a squashed image via a disabled user. """ + credentials = ('devtable', 'password') + + # Push an image to download. + pusher.push(liveserver_session, 'buynlarge', 'newrepo', 'latest', sized_images, + credentials=credentials) + + # Disable the devtable namespace. + registry_server_executor.on(liveserver).disable_namespace('devtable') + + # Attempt to pull the squashed version. + response = liveserver_session.get('/c1/squash/buynlarge/newrepo/latest', auth=credentials) + assert response.status_code == 403 + + @pytest.mark.parametrize('use_estimates', [ False, True, From fdd0db7a7f8d1654c38c11bcc681b0da6753ab95 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Wed, 20 Jun 2018 18:21:59 -0400 Subject: [PATCH 2/3] Add test for trying to pull the catalog under a disabled namespace --- test/registry/protocol_v2.py | 4 +++- test/registry/registry_tests.py | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/test/registry/protocol_v2.py b/test/registry/protocol_v2.py index ec0b24923..938b77b54 100644 --- a/test/registry/protocol_v2.py +++ b/test/registry/protocol_v2.py @@ -16,6 +16,7 @@ class V2ProtocolSteps(Enum): GET_MANIFEST = 'get-manifest' PUT_MANIFEST = 'put-manifest' MOUNT_BLOB = 'mount-blob' + CATALOG = 'catalog' class V2Protocol(RegistryProtocol): @@ -421,7 +422,8 @@ class V2Protocol(RegistryProtocol): params['n'] = page_size while True: - response = self.conduct(session, 'GET', url, headers=headers, params=params) + response = self.conduct(session, 'GET', url, headers=headers, params=params, + expected_status=(200, expected_failure, V2ProtocolSteps.CATALOG)) data = response.json() assert len(data['repositories']) <= page_size diff --git a/test/registry/registry_tests.py b/test/registry/registry_tests.py index e63e7bffa..0accbde17 100644 --- a/test/registry/registry_tests.py +++ b/test/registry/registry_tests.py @@ -739,6 +739,21 @@ def test_catalog_caching(v2_protocol, basic_images, liveserver_session, app_relo assert set(cached_results) == set(results) +def test_catalog_disabled_namespace(v2_protocol, basic_images, liveserver_session, app_reloader, + liveserver, registry_server_executor): + credentials = ('devtable', 'password') + + # Get a valid token. + token, _ = v2_protocol.auth(liveserver_session, credentials, 'devtable', 'simple') + + # Disable the devtable namespace. + registry_server_executor.on(liveserver).disable_namespace('devtable') + + # Try to retrieve the catalog and ensure it fails to return any results. + results = v2_protocol.catalog(liveserver_session, bearer_token=token) + assert len(results) == 0 + + @pytest.mark.parametrize('username, namespace, repository', [ ('devtable', 'devtable', 'simple'), ('devtable', 'devtable', 'gargantuan'), From 2b34ae74fe50bcb6ee240ab977d530f150bf98cb Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Wed, 20 Jun 2018 18:26:15 -0400 Subject: [PATCH 3/3] Add test for trying to pull the tags of a repository under a disabled namespace --- test/registry/protocol_v2.py | 4 +++- test/registry/registry_tests.py | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/test/registry/protocol_v2.py b/test/registry/protocol_v2.py index 938b77b54..6f5fb0049 100644 --- a/test/registry/protocol_v2.py +++ b/test/registry/protocol_v2.py @@ -17,6 +17,7 @@ class V2ProtocolSteps(Enum): PUT_MANIFEST = 'put-manifest' MOUNT_BLOB = 'mount-blob' CATALOG = 'catalog' + LIST_TAGS = 'list-tags' class V2Protocol(RegistryProtocol): @@ -375,7 +376,8 @@ class V2Protocol(RegistryProtocol): params['n'] = page_size while True: - response = self.conduct(session, 'GET', url, headers=headers, params=params) + response = self.conduct(session, 'GET', url, headers=headers, params=params, + expected_status=(200, expected_failure, V2ProtocolSteps.LIST_TAGS)) data = response.json() assert len(data['tags']) <= page_size diff --git a/test/registry/registry_tests.py b/test/registry/registry_tests.py index 0accbde17..9a7e6a41e 100644 --- a/test/registry/registry_tests.py +++ b/test/registry/registry_tests.py @@ -779,6 +779,18 @@ def test_tags(username, namespace, repository, page_size, v2_protocol, liveserve assert set([r for r in results]) == set(expected_tags) +def test_tags_disabled_namespace(v2_protocol, basic_images, liveserver_session, app_reloader, + liveserver, registry_server_executor): + credentials = ('devtable', 'password') + + # Disable the buynlarge namespace. + registry_server_executor.on(liveserver).disable_namespace('buynlarge') + + # Try to retrieve the tags and ensure it fails. + v2_protocol.tags(liveserver_session, credentials=credentials, namespace='buynlarge', + repo_name='orgrepo', expected_failure=Failures.NAMESPACE_DISABLED) + + def test_pull_torrent(pusher, basic_images, liveserver_session, liveserver, registry_server_executor, app_reloader): """ Test: Retrieve a torrent for pulling the image via the Quay CLI. """