diff options
author | Matthew Thode <prometheanfire@gentoo.org> | 2016-02-04 11:32:33 -0600 |
---|---|---|
committer | Matthew Thode <prometheanfire@gentoo.org> | 2016-02-04 11:33:45 -0600 |
commit | 9923cf35ae299d9643c1122dfea857e5f76667f8 (patch) | |
tree | 23c63668dced66f058f9a74c41580896460b8ec1 /app-admin/glance | |
parent | dev-tex/pgf: add alpha keyword (diff) | |
download | gentoo-9923cf35ae299d9643c1122dfea857e5f76667f8.tar.gz gentoo-9923cf35ae299d9643c1122dfea857e5f76667f8.tar.bz2 gentoo-9923cf35ae299d9643c1122dfea857e5f76667f8.zip |
app-admin/glance: fix bug 573844 CVE-2016-0757
Package-Manager: portage-2.2.26
Diffstat (limited to 'app-admin/glance')
-rw-r--r-- | app-admin/glance/files/CVE-2015-5286_2015.1.1.patch | 137 | ||||
-rw-r--r-- | app-admin/glance/files/cve-2015-5163-stable-kilo.patch | 260 | ||||
-rw-r--r-- | app-admin/glance/files/cve-2015-5251-stable-kilo.patch | 192 | ||||
-rw-r--r-- | app-admin/glance/files/cve-2016-0757-stable-liberty.patch | 332 | ||||
-rw-r--r-- | app-admin/glance/files/glance-2013.2-sphinx_mapping.patch | 12 | ||||
-rw-r--r-- | app-admin/glance/glance-11.0.1-r1.ebuild | 229 |
6 files changed, 561 insertions, 601 deletions
diff --git a/app-admin/glance/files/CVE-2015-5286_2015.1.1.patch b/app-admin/glance/files/CVE-2015-5286_2015.1.1.patch deleted file mode 100644 index 04781355e2ba..000000000000 --- a/app-admin/glance/files/CVE-2015-5286_2015.1.1.patch +++ /dev/null @@ -1,137 +0,0 @@ -From 5bebd513fa71edcdb84f7dec7b16f3523c0c1092 Mon Sep 17 00:00:00 2001 -From: Mike Fedosin <mfedosin@mirantis.com> -Date: Sun, 20 Sep 2015 17:01:22 +0300 -Subject: Cleanup chunks for deleted image if token expired - -In patch I47229b366c25367ec1bd48aec684e0880f3dfe60 it was -introduced the logic that if image was deleted during file -upload when we want to update image status from 'saving' -to 'active' it's expected to get Duplicate error and delete -stale chunks after that. But if user's token is expired -there will be Unathorized exception and chunks will stay -in store and clog it. -And when, the upload operation for such an image is -completed the operator configured quota can be exceeded. - -This patch fixes the issue of left over chunks for an image -which was deleted from saving status, by correctly handle -auth exceptions from registry server. - -Partial-bug: #1498163 - -Conflicts: - glance/api/v1/upload_utils.py - (Kilo catches NotFound instead of ImagenotFound) - -Change-Id: I17a66eca55bfb83107046910e69c4da01415deec -(cherry picked from commit 98a8832777a0639a4031e52c69f0d565b3f500c5) - -diff --git a/glance/api/v1/upload_utils.py b/glance/api/v1/upload_utils.py -index 7adb2dc..ad4f724 100644 ---- a/glance/api/v1/upload_utils.py -+++ b/glance/api/v1/upload_utils.py -@@ -171,6 +171,14 @@ def upload_data_to_store(req, image_meta, image_data, store, notifier): - raise exception.NotFound() - else: - raise -+ -+ except exception.NotAuthenticated as e: -+ # Delete image data due to possible token expiration. -+ LOG.debug("Authentication error - the token may have " -+ "expired during file upload. Deleting image data for " -+ " %s " % image_id) -+ initiate_deletion(req, location_data, image_id) -+ raise webob.exc.HTTPUnauthorized(explanation=e.msg, request=req) - except exception.NotFound: - msg = _LI("Image %s could not be found after upload. The image may" - " have been deleted during the upload.") % image_id -diff --git a/glance/api/v2/image_data.py b/glance/api/v2/image_data.py -index 4025eeb..9967662 100644 ---- a/glance/api/v2/image_data.py -+++ b/glance/api/v2/image_data.py -@@ -88,7 +88,19 @@ class ImageDataController(object): - raise webob.exc.HTTPGone(explanation=msg, - request=req, - content_type='text/plain') -- -+ except exception.NotAuthenticated: -+ msg = (_("Authentication error - the token may have " -+ "expired during file upload. Deleting image data for " -+ "%s.") % image_id) -+ LOG.debug(msg) -+ try: -+ image.delete() -+ except exception.NotAuthenticated: -+ # NOTE: Ignore this exception -+ pass -+ raise webob.exc.HTTPUnauthorized(explanation=msg, -+ request=req, -+ content_type='text/plain') - except ValueError as e: - LOG.debug("Cannot save data for image %(id)s: %(e)s", - {'id': image_id, 'e': utils.exception_to_str(e)}) -diff --git a/glance/tests/unit/v1/test_upload_utils.py b/glance/tests/unit/v1/test_upload_utils.py -index 1afaf00..8d05515 100644 ---- a/glance/tests/unit/v1/test_upload_utils.py -+++ b/glance/tests/unit/v1/test_upload_utils.py -@@ -323,3 +323,29 @@ class TestUploadUtils(base.StoreClearingUnitTest): - 'metadata': {}}, image_meta['id']) - mock_safe_kill.assert_called_once_with( - req, image_meta['id'], 'saving') -+ -+ @mock.patch.object(registry, 'update_image_metadata', -+ side_effect=exception.NotAuthenticated) -+ @mock.patch.object(upload_utils, 'initiate_deletion') -+ def test_activate_image_with_expired_token( -+ self, mocked_delete, mocked_update): -+ """Test token expiration during image upload. -+ -+ If users token expired before image was uploaded then if auth error -+ was caught from registry during changing image status from 'saving' -+ to 'active' then it's required to delete all image data. -+ """ -+ context = mock.Mock() -+ req = mock.Mock() -+ req.context = context -+ with self._get_store_and_notifier() as (location, checksum, image_meta, -+ image_data, store, notifier, -+ update_data): -+ self.assertRaises(webob.exc.HTTPUnauthorized, -+ upload_utils.upload_data_to_store, -+ req, image_meta, image_data, store, notifier) -+ self.assertEqual(2, mocked_update.call_count) -+ mocked_delete.assert_called_once_with( -+ req, -+ {'url': 'file://foo/bar', 'status': 'active', 'metadata': {}}, -+ 'c80a1a6c-bd1f-41c5-90ee-81afedb1d58d') -diff --git a/glance/tests/unit/v2/test_image_data_resource.py b/glance/tests/unit/v2/test_image_data_resource.py -index bc8891e..7458eda 100644 ---- a/glance/tests/unit/v2/test_image_data_resource.py -+++ b/glance/tests/unit/v2/test_image_data_resource.py -@@ -192,6 +192,23 @@ class TestImagesController(base.StoreClearingUnitTest): - self.assertRaises(webob.exc.HTTPBadRequest, self.controller.upload, - request, unit_test_utils.UUID1, 'YYYY', 4) - -+ def test_upload_with_expired_token(self): -+ def side_effect(image, from_state=None): -+ if from_state == 'saving': -+ raise exception.NotAuthenticated() -+ -+ mocked_save = mock.Mock(side_effect=side_effect) -+ mocked_delete = mock.Mock() -+ request = unit_test_utils.get_fake_request() -+ image = FakeImage('abcd') -+ image.delete = mocked_delete -+ self.image_repo.result = image -+ self.image_repo.save = mocked_save -+ self.assertRaises(webob.exc.HTTPUnauthorized, self.controller.upload, -+ request, unit_test_utils.UUID1, 'YYYY', 4) -+ self.assertEqual(3, mocked_save.call_count) -+ mocked_delete.assert_called_once_with() -+ - def test_upload_non_existent_image_during_save_initiates_deletion(self): - def fake_save_not_found(self): - raise exception.NotFound() --- -cgit v0.10.2 - diff --git a/app-admin/glance/files/cve-2015-5163-stable-kilo.patch b/app-admin/glance/files/cve-2015-5163-stable-kilo.patch deleted file mode 100644 index 91507c964f43..000000000000 --- a/app-admin/glance/files/cve-2015-5163-stable-kilo.patch +++ /dev/null @@ -1,260 +0,0 @@ -From eb99e45829a1b4c93db5692bdbf636a86faa56c4 Mon Sep 17 00:00:00 2001 -From: Flavio Percoco <flaper87@gmail.com> -Date: Thu, 9 Jul 2015 14:44:04 +0200 -Subject: Don't import files with backed files - -There's a security issue where it'd be possible to import images with -backed files using the task engine and then use/convert those to access -system files or any other file in the system. An example of an attack -would be to import an image with a backing file pointing to -`/etc/passwd`, then convert it to raw and download the generated image. - -This patch forbids importing files with baking files entirely. It does -that in the `_ImportToFS` task, which is the one that imports the image -locally to then execute other tasks on it. It's not necessary for the -`_ImportToStore` task because other tasks won't be executed when the -image is imported in the final store. - -Change-Id: I35f43c3b3f326942fb53b7dadb94700ac4513494 -Closes-bug: #1471912 -(cherry picked from commit d529863a1e8d2307526bdb395b4aebe97f81603d) - -diff --git a/glance/async/flows/base_import.py b/glance/async/flows/base_import.py -index 7656bde..d216aa8 100644 ---- a/glance/async/flows/base_import.py -+++ b/glance/async/flows/base_import.py -@@ -13,12 +13,15 @@ - # License for the specific language governing permissions and limitations - # under the License. - -+import json - import logging - import os - - import glance_store as store_api - from glance_store import backend -+from oslo_concurrency import processutils as putils - from oslo_config import cfg -+from oslo_utils import excutils - import six - from stevedore import named - from taskflow.patterns import linear_flow as lf -@@ -146,6 +149,29 @@ class _ImportToFS(task.Task): - data = script_utils.get_image_data_iter(self.uri) - - path = self.store.add(image_id, data, 0, context=None)[0] -+ -+ try: -+ # NOTE(flaper87): Consider moving this code to a common -+ # place that other tasks can consume as well. -+ stdout, stderr = putils.trycmd('qemu-img', 'info', -+ '--output=json', path, -+ log_errors=putils.LOG_ALL_ERRORS) -+ except OSError as exc: -+ with excutils.save_and_reraise_exception(): -+ msg = (_LE('Failed to execute security checks on the image ' -+ '%(task_id)s: %(exc)s') % -+ {'task_id': self.task_id, 'exc': exc.message}) -+ LOG.error(msg) -+ -+ metadata = json.loads(stdout) -+ -+ backing_file = metadata.get('backing-filename') -+ if backing_file is not None: -+ msg = _("File %(path)s has invalid backing file " -+ "%(bfile)s, aborting.") % {'path': path, -+ 'bfile': backing_file} -+ raise RuntimeError(msg) -+ - return path - - def revert(self, image_id, result=None, **kwargs): -diff --git a/glance/tests/unit/async/flows/test_import.py b/glance/tests/unit/async/flows/test_import.py -index 70f790c..4cf3d13 100644 ---- a/glance/tests/unit/async/flows/test_import.py -+++ b/glance/tests/unit/async/flows/test_import.py -@@ -13,14 +13,17 @@ - # License for the specific language governing permissions and limitations - # under the License. - -+import json - import mock - import os - import urllib2 - - import glance_store -+from oslo_concurrency import processutils as putils - from oslo_config import cfg - from six.moves import cStringIO - from taskflow import task -+from taskflow.types import failure - - import glance.async.flows.base_import as import_flow - from glance.async import taskflow_executor -@@ -106,16 +109,23 @@ class TestImportTask(test_utils.BaseTestCase): - - with mock.patch.object(script_utils, 'get_image_data_iter') as dmock: - dmock.return_value = cStringIO("TEST_IMAGE") -- executor.begin_processing(self.task.task_id) -- image_path = os.path.join(self.test_dir, self.image.image_id) -- tmp_image_path = os.path.join(self.work_dir, -- "%s.tasks_import" % image_path) -- self.assertFalse(os.path.exists(tmp_image_path)) -- self.assertTrue(os.path.exists(image_path)) -- self.assertEqual(1, len(list(self.image.locations))) -- self.assertEqual("file://%s/%s" % (self.test_dir, -- self.image.image_id), -- self.image.locations[0]['url']) -+ -+ with mock.patch.object(putils, 'trycmd') as tmock: -+ tmock.return_value = (json.dumps({ -+ 'format': 'qcow2', -+ }), None) -+ -+ executor.begin_processing(self.task.task_id) -+ image_path = os.path.join(self.test_dir, self.image.image_id) -+ tmp_image_path = os.path.join(self.work_dir, -+ "%s.tasks_import" % image_path) -+ -+ self.assertFalse(os.path.exists(tmp_image_path)) -+ self.assertTrue(os.path.exists(image_path)) -+ self.assertEqual(1, len(list(self.image.locations))) -+ self.assertEqual("file://%s/%s" % (self.test_dir, -+ self.image.image_id), -+ self.image.locations[0]['url']) - - def test_import_flow_missing_work_dir(self): - self.config(engine_mode='serial', group='taskflow_executor') -@@ -151,6 +161,54 @@ class TestImportTask(test_utils.BaseTestCase): - self.assertFalse(os.path.exists(tmp_image_path)) - self.assertTrue(os.path.exists(image_path)) - -+ def test_import_flow_backed_file_import_to_fs(self): -+ self.config(engine_mode='serial', group='taskflow_executor') -+ -+ img_factory = mock.MagicMock() -+ -+ executor = taskflow_executor.TaskExecutor( -+ self.context, -+ self.task_repo, -+ self.img_repo, -+ img_factory) -+ -+ self.task_repo.get.return_value = self.task -+ -+ def create_image(*args, **kwargs): -+ kwargs['image_id'] = UUID1 -+ return self.img_factory.new_image(*args, **kwargs) -+ -+ self.img_repo.get.return_value = self.image -+ img_factory.new_image.side_effect = create_image -+ -+ with mock.patch.object(script_utils, 'get_image_data_iter') as dmock: -+ dmock.return_value = cStringIO("TEST_IMAGE") -+ -+ with mock.patch.object(putils, 'trycmd') as tmock: -+ tmock.return_value = (json.dumps({ -+ 'backing-filename': '/etc/password' -+ }), None) -+ -+ with mock.patch.object(import_flow._ImportToFS, -+ 'revert') as rmock: -+ self.assertRaises(RuntimeError, -+ executor.begin_processing, -+ self.task.task_id) -+ self.assertTrue(rmock.called) -+ self.assertIsInstance(rmock.call_args[1]['result'], -+ failure.Failure) -+ -+ image_path = os.path.join(self.test_dir, -+ self.image.image_id) -+ -+ fname = "%s.tasks_import" % image_path -+ tmp_image_path = os.path.join(self.work_dir, fname) -+ -+ self.assertFalse(os.path.exists(tmp_image_path)) -+ # Note(sabari): The image should not have been uploaded to -+ # the store as the flow failed before ImportToStore Task. -+ self.assertFalse(os.path.exists(image_path)) -+ - def test_import_flow_revert(self): - self.config(engine_mode='serial', - group='taskflow_executor') -@@ -175,20 +233,31 @@ class TestImportTask(test_utils.BaseTestCase): - with mock.patch.object(script_utils, 'get_image_data_iter') as dmock: - dmock.return_value = cStringIO("TEST_IMAGE") - -- with mock.patch.object(import_flow, "_get_import_flows") as imock: -- imock.return_value = (x for x in [_ErrorTask()]) -- self.assertRaises(RuntimeError, -- executor.begin_processing, self.task.task_id) -- image_path = os.path.join(self.test_dir, self.image.image_id) -- tmp_image_path = os.path.join(self.work_dir, -- "%s.tasks_import" % image_path) -- self.assertFalse(os.path.exists(tmp_image_path)) -- -- # NOTE(flaper87): Eventually, we want this to be assertTrue. -- # The current issue is there's no way to tell taskflow to -- # continue on failures. That is, revert the subflow but keep -- # executing the parent flow. Under discussion/development. -- self.assertFalse(os.path.exists(image_path)) -+ with mock.patch.object(putils, 'trycmd') as tmock: -+ tmock.return_value = (json.dumps({ -+ 'format': 'qcow2', -+ }), None) -+ -+ with mock.patch.object(import_flow, -+ "_get_import_flows") as imock: -+ imock.return_value = (x for x in [_ErrorTask()]) -+ self.assertRaises(RuntimeError, -+ executor.begin_processing, -+ self.task.task_id) -+ -+ image_path = os.path.join(self.test_dir, -+ self.image.image_id) -+ tmp_image_path = os.path.join(self.work_dir, -+ ("%s.tasks_import" % -+ image_path)) -+ self.assertFalse(os.path.exists(tmp_image_path)) -+ -+ # NOTE(flaper87): Eventually, we want this to be assertTrue -+ # The current issue is there's no way to tell taskflow to -+ # continue on failures. That is, revert the subflow but -+ # keep executing the parent flow. Under -+ # discussion/development. -+ self.assertFalse(os.path.exists(image_path)) - - def test_import_flow_no_import_flows(self): - self.config(engine_mode='serial', -@@ -271,15 +340,20 @@ class TestImportTask(test_utils.BaseTestCase): - with mock.patch.object(script_utils, 'get_image_data_iter') as dmock: - dmock.return_value = "test" - -- image_id = UUID1 -- path = import_fs.execute(image_id) -- reader, size = glance_store.get_from_backend(path) -- self.assertEqual(4, size) -- self.assertEqual(dmock.return_value, "".join(reader)) -+ with mock.patch.object(putils, 'trycmd') as tmock: -+ tmock.return_value = (json.dumps({ -+ 'format': 'qcow2', -+ }), None) -+ -+ image_id = UUID1 -+ path = import_fs.execute(image_id) -+ reader, size = glance_store.get_from_backend(path) -+ self.assertEqual(4, size) -+ self.assertEqual(dmock.return_value, "".join(reader)) - -- image_path = os.path.join(self.work_dir, image_id) -- tmp_image_path = os.path.join(self.work_dir, image_path) -- self.assertTrue(os.path.exists(tmp_image_path)) -+ image_path = os.path.join(self.work_dir, image_id) -+ tmp_image_path = os.path.join(self.work_dir, image_path) -+ self.assertTrue(os.path.exists(tmp_image_path)) - - def test_delete_from_fs(self): - delete_fs = import_flow._DeleteFromFS(self.task.task_id, --- -cgit v0.10.2 - diff --git a/app-admin/glance/files/cve-2015-5251-stable-kilo.patch b/app-admin/glance/files/cve-2015-5251-stable-kilo.patch deleted file mode 100644 index f86864575dc0..000000000000 --- a/app-admin/glance/files/cve-2015-5251-stable-kilo.patch +++ /dev/null @@ -1,192 +0,0 @@ -From 9beca533f42ae1fc87418de0c360e19bc59b24b5 Mon Sep 17 00:00:00 2001 -From: Stuart McLaren <stuart.mclaren@hp.com> -Date: Tue, 11 Aug 2015 10:37:09 +0000 -Subject: [PATCH] Prevent image status being directly modified via v1 - -Users shouldn't be able to change an image's status directly via the -v1 API. - -Some existing consumers of Glance set the x-image-meta-status header in -requests to the Glance API, eg: - -https://github.com/openstack/nova/blob/master/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance#L184 - -We should try to prevent users setting 'status' via v1, but without breaking -existing benign API calls such as these. - -I've adopted the following approach (which has some prior art in 'protected properties'). - -If a PUT request is received which contains an x-image-meta-status header: - -* The user provided status is ignored if it matches the current image - status (this prevents benign calls such as the nova one above from - breaking). The usual code (eg 200) will be returned. - -* If the user provided status doesn't match the current image status (ie - there is a real attempt to change the value) 403 will be returned. This - will break any calls which currently intentionally change the status. - -APIImpact - -Closes-bug: 1482371 - -Change-Id: I44fadf32abb57c962b67467091c3f51c1ccc25e6 -(cherry picked from commit 4d08db5b6d42323ac1958ef3b7417d875e7bea8c) ---- - glance/api/v1/__init__.py | 3 + - glance/api/v1/images.py | 9 +++ - glance/tests/functional/v1/test_api.py | 89 ++++++++++++++++++++++ - .../integration/legacy_functional/test_v1_api.py | 2 + - 4 files changed, 103 insertions(+) - -diff --git a/glance/api/v1/__init__.py b/glance/api/v1/__init__.py -index 74de9aa1411d8e926770b67f7d851cf14e794414..9306bbb4fe78f77a26bb539c717fdfd2b38767c8 100644 ---- a/glance/api/v1/__init__.py -+++ b/glance/api/v1/__init__.py -@@ -21,3 +21,6 @@ SUPPORTED_PARAMS = ('limit', 'marker', 'sort_key', 'sort_dir') - - # Metadata which only an admin can change once the image is active - ACTIVE_IMMUTABLE = ('size', 'checksum') -+ -+# Metadata which cannot be changed (irrespective of the current image state) -+IMMUTABLE = ('status',) -diff --git a/glance/api/v1/images.py b/glance/api/v1/images.py -index e33b91fbca79377e78ccfd329fa542ad422f5ffc..95e32949d958d0f57a3b60c141b91784a5801f5a 100644 ---- a/glance/api/v1/images.py -+++ b/glance/api/v1/images.py -@@ -57,6 +57,7 @@ _LW = i18n._LW - SUPPORTED_PARAMS = glance.api.v1.SUPPORTED_PARAMS - SUPPORTED_FILTERS = glance.api.v1.SUPPORTED_FILTERS - ACTIVE_IMMUTABLE = glance.api.v1.ACTIVE_IMMUTABLE -+IMMUTABLE = glance.api.v1.IMMUTABLE - - CONF = cfg.CONF - CONF.import_opt('disk_formats', 'glance.common.config', group='image_format') -@@ -912,6 +913,14 @@ class Controller(controller.BaseController): - request=req, - content_type="text/plain") - -+ for key in IMMUTABLE: -+ if (image_meta.get(key) is not None and -+ image_meta.get(key) != orig_image_meta.get(key)): -+ msg = _("Forbidden to modify '%s' of image.") % key -+ raise HTTPForbidden(explanation=msg, -+ request=req, -+ content_type="text/plain") -+ - # The default behaviour for a PUT /images/<IMAGE_ID> is to - # override any properties that were previously set. This, however, - # leads to a number of issues for the common use case where a caller -diff --git a/glance/tests/functional/v1/test_api.py b/glance/tests/functional/v1/test_api.py -index 9fba3bb5e40c8742530691228c7b436b385fc2ca..6b3bfbb4270f1eb0f50418504e65be30ea23d10b 100644 ---- a/glance/tests/functional/v1/test_api.py -+++ b/glance/tests/functional/v1/test_api.py -@@ -715,3 +715,92 @@ class TestApi(functional.FunctionalTest): - self.assertEqual(404, response.status) - - self.stop_servers() -+ -+ def test_status_cannot_be_manipulated_directly(self): -+ self.cleanup() -+ self.start_servers(**self.__dict__.copy()) -+ headers = minimal_headers('Image1') -+ -+ # Create a 'queued' image -+ http = httplib2.Http() -+ headers = {'Content-Type': 'application/octet-stream', -+ 'X-Image-Meta-Disk-Format': 'raw', -+ 'X-Image-Meta-Container-Format': 'bare'} -+ path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port) -+ response, content = http.request(path, 'POST', headers=headers, -+ body=None) -+ self.assertEqual(201, response.status) -+ image = jsonutils.loads(content)['image'] -+ self.assertEqual('queued', image['status']) -+ -+ # Ensure status of 'queued' image can't be changed -+ path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port, -+ image['id']) -+ http = httplib2.Http() -+ headers = {'X-Image-Meta-Status': 'active'} -+ response, content = http.request(path, 'PUT', headers=headers) -+ self.assertEqual(403, response.status) -+ response, content = http.request(path, 'HEAD') -+ self.assertEqual(200, response.status) -+ self.assertEqual('queued', response['x-image-meta-status']) -+ -+ # We allow 'setting' to the same status -+ http = httplib2.Http() -+ headers = {'X-Image-Meta-Status': 'queued'} -+ response, content = http.request(path, 'PUT', headers=headers) -+ self.assertEqual(200, response.status) -+ response, content = http.request(path, 'HEAD') -+ self.assertEqual(200, response.status) -+ self.assertEqual('queued', response['x-image-meta-status']) -+ -+ # Make image active -+ http = httplib2.Http() -+ headers = {'Content-Type': 'application/octet-stream'} -+ response, content = http.request(path, 'PUT', headers=headers, -+ body='data') -+ self.assertEqual(200, response.status) -+ image = jsonutils.loads(content)['image'] -+ self.assertEqual('active', image['status']) -+ -+ # Ensure status of 'active' image can't be changed -+ http = httplib2.Http() -+ headers = {'X-Image-Meta-Status': 'queued'} -+ response, content = http.request(path, 'PUT', headers=headers) -+ self.assertEqual(403, response.status) -+ response, content = http.request(path, 'HEAD') -+ self.assertEqual(200, response.status) -+ self.assertEqual('active', response['x-image-meta-status']) -+ -+ # We allow 'setting' to the same status -+ http = httplib2.Http() -+ headers = {'X-Image-Meta-Status': 'active'} -+ response, content = http.request(path, 'PUT', headers=headers) -+ self.assertEqual(200, response.status) -+ response, content = http.request(path, 'HEAD') -+ self.assertEqual(200, response.status) -+ self.assertEqual('active', response['x-image-meta-status']) -+ -+ # Create a 'queued' image, ensure 'status' header is ignored -+ http = httplib2.Http() -+ path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port) -+ headers = {'Content-Type': 'application/octet-stream', -+ 'X-Image-Meta-Status': 'active'} -+ response, content = http.request(path, 'POST', headers=headers, -+ body=None) -+ self.assertEqual(201, response.status) -+ image = jsonutils.loads(content)['image'] -+ self.assertEqual('queued', image['status']) -+ -+ # Create an 'active' image, ensure 'status' header is ignored -+ http = httplib2.Http() -+ path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port) -+ headers = {'Content-Type': 'application/octet-stream', -+ 'X-Image-Meta-Disk-Format': 'raw', -+ 'X-Image-Meta-Status': 'queued', -+ 'X-Image-Meta-Container-Format': 'bare'} -+ response, content = http.request(path, 'POST', headers=headers, -+ body='data') -+ self.assertEqual(201, response.status) -+ image = jsonutils.loads(content)['image'] -+ self.assertEqual('active', image['status']) -+ self.stop_servers() -diff --git a/glance/tests/integration/legacy_functional/test_v1_api.py b/glance/tests/integration/legacy_functional/test_v1_api.py -index dff436465919569480bdbac537d20a6d61c98f46..511d46dfe18028bb430504784cc9d24c58736c3b 100644 ---- a/glance/tests/integration/legacy_functional/test_v1_api.py -+++ b/glance/tests/integration/legacy_functional/test_v1_api.py -@@ -358,6 +358,8 @@ class TestApi(base.ApiTest): - path = "/v1/images" - response, content = self.http.request(path, 'POST', headers=headers) - self.assertEqual(201, response.status) -+ image = jsonutils.loads(content)['image'] -+ self.assertEqual('active', image['status']) - - # 2. HEAD image-location - # Verify image size is zero and the status is active --- -2.5.0 - diff --git a/app-admin/glance/files/cve-2016-0757-stable-liberty.patch b/app-admin/glance/files/cve-2016-0757-stable-liberty.patch new file mode 100644 index 000000000000..19c8365eb018 --- /dev/null +++ b/app-admin/glance/files/cve-2016-0757-stable-liberty.patch @@ -0,0 +1,332 @@ +From c6021e9b3642340036347026a3f251e066e53094 Mon Sep 17 00:00:00 2001 +From: Erno Kuvaja <jokke@usr.fi> +Date: Tue, 19 Jan 2016 13:37:05 +0000 +Subject: [PATCH] Prevent user to remove last location of the image + +If the last location of the image is removed, image transitions back to queued. +This allows user to upload new data into the existing image record. By +preventing removal of the last location we prevent the image transition back to +queued. + +This change also prevents doing the same operation via replacing the locations +with empty list. + +SecurityImpact +DocImpact +APIImpact + +Conflicts: + glance/tests/unit/v2/test_images_resource.py + +Change-Id: Ieb03aaba887492819f9c58aa67f7acfcea81720e +Closes-Bug: #1525915 +(cherry picked from commit 2f4504da2149697bcdb93ed855e15025d2a08f8c) +--- + glance/api/v2/images.py | 19 +++- + glance/tests/functional/v2/test_images.py | 14 --- + glance/tests/unit/v2/test_images_resource.py | 122 ++++----------------- + ...oving-last-image-location-d5ee3e00efe14f34.yaml | 10 ++ + 4 files changed, 44 insertions(+), 121 deletions(-) + create mode 100644 releasenotes/notes/Prevent-removing-last-image-location-d5ee3e00efe14f34.yaml + +diff --git a/glance/api/v2/images.py b/glance/api/v2/images.py +index 17678f2..cf667bf 100644 +--- a/glance/api/v2/images.py ++++ b/glance/api/v2/images.py +@@ -181,7 +181,10 @@ class ImagesController(object): + path = change['path'] + path_root = path[0] + value = change['value'] +- if path_root == 'locations': ++ if path_root == 'locations' and value == []: ++ msg = _("Cannot set locations to empty list.") ++ raise webob.exc.HTTPForbidden(message=msg) ++ elif path_root == 'locations' and value != []: + self._do_replace_locations(image, value) + elif path_root == 'owner' and req.context.is_admin == False: + msg = _("Owner can't be updated by non admin.") +@@ -217,7 +220,10 @@ class ImagesController(object): + path = change['path'] + path_root = path[0] + if path_root == 'locations': +- self._do_remove_locations(image, path[1]) ++ try: ++ self._do_remove_locations(image, path[1]) ++ except exception.Forbidden as e: ++ raise webob.exc.HTTPForbidden(e.msg) + else: + if hasattr(image, path_root): + msg = _("Property %s may not be removed.") +@@ -306,6 +312,11 @@ class ImagesController(object): + explanation=encodeutils.exception_to_unicode(ve)) + + def _do_remove_locations(self, image, path_pos): ++ if len(image.locations) == 1: ++ LOG.debug("User forbidden to remove last location of image %s", ++ image.image_id) ++ msg = _("Cannot remove last location in the image.") ++ raise exception.Forbidden(message=msg) + pos = self._get_locations_op_pos(path_pos, + len(image.locations), False) + if pos is None: +@@ -315,11 +326,11 @@ class ImagesController(object): + # NOTE(zhiyan): this actually deletes the location + # from the backend store. + image.locations.pop(pos) ++ # TODO(jokke): Fix this, we should catch what store throws and ++ # provide definitely something else than IternalServerError to user. + except Exception as e: + raise webob.exc.HTTPInternalServerError( + explanation=encodeutils.exception_to_unicode(e)) +- if len(image.locations) == 0 and image.status == 'active': +- image.status = 'queued' + + + class RequestDeserializer(wsgi.JSONRequestDeserializer): +diff --git a/glance/tests/functional/v2/test_images.py b/glance/tests/functional/v2/test_images.py +index aabc567..f199787 100644 +--- a/glance/tests/functional/v2/test_images.py ++++ b/glance/tests/functional/v2/test_images.py +@@ -522,20 +522,6 @@ class TestImages(functional.FunctionalTest): + response = requests.patch(path, headers=headers, data=data) + self.assertEqual(200, response.status_code, response.text) + +- # Remove all locations of the image then the image size shouldn't be +- # able to access +- path = self._url('/v2/images/%s' % image2_id) +- media_type = 'application/openstack-images-v2.1-json-patch' +- headers = self._headers({'content-type': media_type}) +- doc = [{'op': 'replace', 'path': '/locations', 'value': []}] +- data = jsonutils.dumps(doc) +- response = requests.patch(path, headers=headers, data=data) +- self.assertEqual(200, response.status_code, response.text) +- image = jsonutils.loads(response.text) +- self.assertIsNone(image['size']) +- self.assertIsNone(image['virtual_size']) +- self.assertEqual('queued', image['status']) +- + # Deletion should work. Deleting image-1 + path = self._url('/v2/images/%s' % image_id) + response = requests.delete(path, headers=self._headers()) +diff --git a/glance/tests/unit/v2/test_images_resource.py b/glance/tests/unit/v2/test_images_resource.py +index 698c284..ee09ee7 100644 +--- a/glance/tests/unit/v2/test_images_resource.py ++++ b/glance/tests/unit/v2/test_images_resource.py +@@ -1417,26 +1417,6 @@ class TestImagesController(base.IsolatedUnitTest): + self.assertRaises(webob.exc.HTTPConflict, self.controller.update, + another_request, created_image.image_id, changes) + +- def test_update_replace_locations(self): +- self.stubs.Set(store, 'get_size_from_backend', +- unit_test_utils.fake_get_size_from_backend) +- request = unit_test_utils.get_fake_request() +- changes = [{'op': 'replace', 'path': ['locations'], 'value': []}] +- output = self.controller.update(request, UUID1, changes) +- self.assertEqual(UUID1, output.image_id) +- self.assertEqual(0, len(output.locations)) +- self.assertEqual('queued', output.status) +- self.assertIsNone(output.size) +- +- new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}} +- changes = [{'op': 'replace', 'path': ['locations'], +- 'value': [new_location]}] +- output = self.controller.update(request, UUID1, changes) +- self.assertEqual(UUID1, output.image_id) +- self.assertEqual(1, len(output.locations)) +- self.assertEqual(new_location, output.locations[0]) +- self.assertEqual('active', output.status) +- + def test_update_replace_locations_non_empty(self): + new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}} + request = unit_test_utils.get_fake_request() +@@ -1448,35 +1428,9 @@ class TestImagesController(base.IsolatedUnitTest): + def test_update_replace_locations_invalid(self): + request = unit_test_utils.get_fake_request() + changes = [{'op': 'replace', 'path': ['locations'], 'value': []}] +- output = self.controller.update(request, UUID1, changes) +- self.assertEqual(UUID1, output.image_id) +- self.assertEqual(0, len(output.locations)) +- self.assertEqual('queued', output.status) +- +- request = unit_test_utils.get_fake_request() +- changes = [{'op': 'replace', 'path': ['locations'], +- 'value': [{'url': 'unknow://foo', 'metadata': {}}]}] +- self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, ++ self.assertRaises(webob.exc.HTTPForbidden, self.controller.update, + request, UUID1, changes) + +- def test_update_replace_locations_status_exception(self): +- self.stubs.Set(store, 'get_size_from_backend', +- unit_test_utils.fake_get_size_from_backend) +- request = unit_test_utils.get_fake_request() +- changes = [{'op': 'replace', 'path': ['locations'], 'value': []}] +- output = self.controller.update(request, UUID2, changes) +- self.assertEqual(UUID2, output.image_id) +- self.assertEqual(0, len(output.locations)) +- self.assertEqual('queued', output.status) +- +- self.db.image_update(None, UUID2, {'disk_format': None}) +- +- new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}} +- changes = [{'op': 'replace', 'path': ['locations'], +- 'value': [new_location]}] +- self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, +- request, UUID2, changes) +- + def test_update_add_property(self): + request = unit_test_utils.get_fake_request() + +@@ -1600,24 +1554,6 @@ class TestImagesController(base.IsolatedUnitTest): + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, + request, UUID1, changes) + +- def test_update_add_locations_status_exception(self): +- self.stubs.Set(store, 'get_size_from_backend', +- unit_test_utils.fake_get_size_from_backend) +- request = unit_test_utils.get_fake_request() +- changes = [{'op': 'replace', 'path': ['locations'], 'value': []}] +- output = self.controller.update(request, UUID2, changes) +- self.assertEqual(UUID2, output.image_id) +- self.assertEqual(0, len(output.locations)) +- self.assertEqual('queued', output.status) +- +- self.db.image_update(None, UUID2, {'disk_format': None}) +- +- new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}} +- changes = [{'op': 'add', 'path': ['locations', '-'], +- 'value': new_location}] +- self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, +- request, UUID2, changes) +- + def test_update_add_duplicate_locations(self): + new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}} + request = unit_test_utils.get_fake_request() +@@ -1631,23 +1567,6 @@ class TestImagesController(base.IsolatedUnitTest): + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, + request, UUID1, changes) + +- def test_update_replace_duplicate_locations(self): +- self.stubs.Set(store, 'get_size_from_backend', +- unit_test_utils.fake_get_size_from_backend) +- request = unit_test_utils.get_fake_request() +- changes = [{'op': 'replace', 'path': ['locations'], 'value': []}] +- output = self.controller.update(request, UUID1, changes) +- self.assertEqual(UUID1, output.image_id) +- self.assertEqual(0, len(output.locations)) +- self.assertEqual('queued', output.status) +- +- new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}} +- changes = [{'op': 'replace', 'path': ['locations'], +- 'value': [new_location, new_location]}] +- +- self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, +- request, UUID1, changes) +- + def test_update_add_too_many_locations(self): + self.config(image_location_quota=1) + request = unit_test_utils.get_fake_request() +@@ -1748,9 +1667,12 @@ class TestImagesController(base.IsolatedUnitTest): + {'op': 'add', 'path': ['locations', '-'], + 'value': {'url': '%s/fake_location_1' % BASE_URI, + 'metadata': {}}}, ++ {'op': 'add', 'path': ['locations', '-'], ++ 'value': {'url': '%s/fake_location_2' % BASE_URI, ++ 'metadata': {}}}, + ] + self.controller.update(request, UUID1, changes) +- self.config(image_location_quota=1) ++ self.config(image_location_quota=2) + + # We must remove two properties to avoid being + # over the limit of 1 property +@@ -1763,8 +1685,8 @@ class TestImagesController(base.IsolatedUnitTest): + ] + output = self.controller.update(request, UUID1, changes) + self.assertEqual(UUID1, output.image_id) +- self.assertEqual(1, len(output.locations)) +- self.assertIn('fake_location_3', output.locations[0]['url']) ++ self.assertEqual(2, len(output.locations)) ++ self.assertIn('fake_location_3', output.locations[1]['url']) + self.assertNotEqual(output.created_at, output.updated_at) + + def test_update_remove_base_property(self): +@@ -1805,24 +1727,23 @@ class TestImagesController(base.IsolatedUnitTest): + unit_test_utils.fake_get_size_from_backend) + + request = unit_test_utils.get_fake_request() +- changes = [{'op': 'remove', 'path': ['locations', '0']}] +- output = self.controller.update(request, UUID1, changes) +- self.assertEqual(output.image_id, UUID1) +- self.assertEqual(0, len(output.locations)) +- self.assertEqual('queued', output.status) +- self.assertIsNone(output.size) +- + new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}} + changes = [{'op': 'add', 'path': ['locations', '-'], + 'value': new_location}] ++ self.controller.update(request, UUID1, changes) ++ changes = [{'op': 'remove', 'path': ['locations', '0']}] + output = self.controller.update(request, UUID1, changes) + self.assertEqual(UUID1, output.image_id) + self.assertEqual(1, len(output.locations)) +- self.assertEqual(new_location, output.locations[0]) + self.assertEqual('active', output.status) + + def test_update_remove_location_invalid_pos(self): + request = unit_test_utils.get_fake_request() ++ changes = [ ++ {'op': 'add', 'path': ['locations', '-'], ++ 'value': {'url': '%s/fake_location' % BASE_URI, ++ 'metadata': {}}}] ++ self.controller.update(request, UUID1, changes) + changes = [{'op': 'remove', 'path': ['locations', None]}] + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, + request, UUID1, changes) +@@ -1844,6 +1765,11 @@ class TestImagesController(base.IsolatedUnitTest): + fake_delete_image_location_from_backend) + + request = unit_test_utils.get_fake_request() ++ changes = [ ++ {'op': 'add', 'path': ['locations', '-'], ++ 'value': {'url': '%s/fake_location' % BASE_URI, ++ 'metadata': {}}}] ++ self.controller.update(request, UUID1, changes) + changes = [{'op': 'remove', 'path': ['locations', '0']}] + self.assertRaises(webob.exc.HTTPInternalServerError, + self.controller.update, request, UUID1, changes) +@@ -2137,16 +2063,6 @@ class TestImagesControllerPolicies(base.IsolatedUnitTest): + self.assertRaises(webob.exc.HTTPForbidden, self.controller.update, + request, UUID1, changes) + +- self.stubs.Set(self.store_utils, 'delete_image_location_from_backend', +- fake_delete_image_location_from_backend) +- +- changes = [{'op': 'replace', 'path': ['locations'], 'value': []}] +- self.controller.update(request, UUID1, changes) +- changes = [{'op': 'replace', 'path': ['locations'], +- 'value': [new_location]}] +- self.assertRaises(webob.exc.HTTPForbidden, self.controller.update, +- request, UUID1, changes) +- + def test_update_delete_image_location_unauthorized(self): + rules = {"delete_image_location": False} + self.policy.set_rules(rules) +diff --git a/releasenotes/notes/Prevent-removing-last-image-location-d5ee3e00efe14f34.yaml b/releasenotes/notes/Prevent-removing-last-image-location-d5ee3e00efe14f34.yaml +new file mode 100644 +index 0000000..344e6e5 +--- /dev/null ++++ b/releasenotes/notes/Prevent-removing-last-image-location-d5ee3e00efe14f34.yaml +@@ -0,0 +1,10 @@ ++--- ++security: ++ - Fixing bug 1525915; image might be transitioning ++ from active to queued by regular user by removing ++ last location of image (or replacing locations ++ with empty list). This allows user to re-upload ++ data to the image breaking Glance's promise of ++ image data immutability. From now on, last ++ location cannot be removed and locations cannot ++ be replaced with empty list. +-- +1.9.1 + diff --git a/app-admin/glance/files/glance-2013.2-sphinx_mapping.patch b/app-admin/glance/files/glance-2013.2-sphinx_mapping.patch deleted file mode 100644 index 0a0f575ca0d3..000000000000 --- a/app-admin/glance/files/glance-2013.2-sphinx_mapping.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff -ur glance-2013.2.orig/doc/source/conf.py glance-2013.2/doc/source/conf.py ---- doc/source/conf.py 2013-10-17 21:39:46.000000000 +0800 -+++ doc/source/conf.py 2013-11-13 18:51:29.099839976 +0800 -@@ -250,8 +250,3 @@ - - # If false, no module index is generated. - #latex_use_modindex = True -- --# Example configuration for intersphinx: refer to the Python standard library. --intersphinx_mapping = {'python': ('http://docs.python.org/', None), -- 'nova': ('http://nova.openstack.org', None), -- 'swift': ('http://swift.openstack.org', None)} diff --git a/app-admin/glance/glance-11.0.1-r1.ebuild b/app-admin/glance/glance-11.0.1-r1.ebuild new file mode 100644 index 000000000000..1126febfd97f --- /dev/null +++ b/app-admin/glance/glance-11.0.1-r1.ebuild @@ -0,0 +1,229 @@ +# Copyright 1999-2016 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 +# $Id$ + +EAPI=5 +PYTHON_COMPAT=( python2_7 ) + +inherit distutils-r1 user + +DESCRIPTION="Services for discovering, registering, and retrieving VM images" +HOMEPAGE="https://launchpad.net/glance" +SRC_URI="https://tarballs.openstack.org/${PN}/${P}.tar.gz" + +LICENSE="Apache-2.0" +SLOT="0" +KEYWORDS="~amd64 ~x86" +IUSE="doc mysql postgres +sqlite +swift test" +REQUIRED_USE="|| ( mysql postgres sqlite )" + +CDEPEND=">=dev-python/pbr-1.6.0[${PYTHON_USEDEP}]" +DEPEND=" + dev-python/setuptools[${PYTHON_USEDEP}] + ${CDEPEND} + test? ( + ${RDEPEND} + >=dev-python/Babel-1.3[${PYTHON_USEDEP}] + <=dev-python/Babel-2.1.1[${PYTHON_USEDEP}] + >=dev-python/coverage-3.6[${PYTHON_USEDEP}] + <=dev-python/coverage-4.0.3[${PYTHON_USEDEP}] + >=dev-python/fixtures-1.3.1[${PYTHON_USEDEP}] + <=dev-python/fixtures-1.4.0-r9999[${PYTHON_USEDEP}] + >=dev-python/mox3-0.7.0[${PYTHON_USEDEP}] + <=dev-python/mox3-0.12.0[${PYTHON_USEDEP}] + >=dev-python/mock-1.2[${PYTHON_USEDEP}] + <=dev-python/mock-1.3.0[${PYTHON_USEDEP}] + >=dev-python/sphinx-1.1.2[${PYTHON_USEDEP}] + !~dev-python/sphinx-1.2.0[${PYTHON_USEDEP}] + <dev-python/sphinx-1.3[${PYTHON_USEDEP}] + >=dev-python/requests-2.5.2[${PYTHON_USEDEP}] + !~dev-python/requests-2.8.0[${PYTHON_USEDEP}] + <=dev-python/requests-2.8.1[${PYTHON_USEDEP}] + >=dev-python/testrepository-0.0.18[${PYTHON_USEDEP}] + <=dev-python/testrepository-0.0.20[${PYTHON_USEDEP}] + >=dev-python/testresources-0.2.4[${PYTHON_USEDEP}] + <=dev-python/testresources-1.0.0-r9999[${PYTHON_USEDEP}] + >=dev-python/testscenarios-0.4[${PYTHON_USEDEP}] + <=dev-python/testscenarios-0.5[${PYTHON_USEDEP}] + >=dev-python/testtools-1.4.0[${PYTHON_USEDEP}] + <=dev-python/testtools-1.8.1[${PYTHON_USEDEP}] + >=dev-python/psutil-1.1.1[${PYTHON_USEDEP}] + <dev-python/psutil-2.0.0[${PYTHON_USEDEP}] + >=dev-python/oslotest-1.10.0[${PYTHON_USEDEP}] + <=dev-python/oslotest-2.0.0[${PYTHON_USEDEP}] + >=dev-python/pymysql-0.6.2[${PYTHON_USEDEP}] + <=dev-python/pymysql-0.6.7[${PYTHON_USEDEP}] + >=dev-python/psycopg-2.5[${PYTHON_USEDEP}] + <=dev-python/psycopg-2.6.1[${PYTHON_USEDEP}] + >=dev-python/pysendfile-2.0.0[${PYTHON_USEDEP}] + <=dev-python/pysendfile-2.0.1[${PYTHON_USEDEP}] + <=dev-python/qpid-python-0.32[$(python_gen_usedep 'python2_7')] + >=dev-python/pyxattr-0.5.0[${PYTHON_USEDEP}] + >=dev-python/python-swiftclient-2.2.0[${PYTHON_USEDEP}] + <=dev-python/python-swiftclient-2.7.0[${PYTHON_USEDEP}] + >=dev-python/oslo-sphinx-2.5.0[${PYTHON_USEDEP}] + <=dev-python/oslo-sphinx-4.1.0[${PYTHON_USEDEP}] + >=dev-python/reno-0.1.1[${PYTHON_USEDEP}] + )" + +#note to self, wsgiref is a python builtin, no need to package it +#>=dev-python/wsgiref-0.1.2[${PYTHON_USEDEP}] + +RDEPEND=" + ${CDEPEND} + sqlite? ( + >=dev-python/sqlalchemy-0.9.9[sqlite,${PYTHON_USEDEP}] + <dev-python/sqlalchemy-1.0.10[sqlite,${PYTHON_USEDEP}] + ) + mysql? ( + dev-python/mysql-python + >=dev-python/sqlalchemy-0.9.9[${PYTHON_USEDEP}] + <dev-python/sqlalchemy-1.0.10[${PYTHON_USEDEP}] + ) + postgres? ( + dev-python/psycopg:2[${PYTHON_USEDEP}] + >=dev-python/sqlalchemy-0.9.9[${PYTHON_USEDEP}] + <dev-python/sqlalchemy-1.0.10[${PYTHON_USEDEP}] + ) + ~dev-python/anyjson-0.3.3[${PYTHON_USEDEP}] + ~dev-python/eventlet-0.17.4[${PYTHON_USEDEP}] + >=dev-python/pastedeploy-1.5.0[${PYTHON_USEDEP}] + <=dev-python/pastedeploy-1.5.2[${PYTHON_USEDEP}] + >=dev-python/routes-1.12.3[${PYTHON_USEDEP}] + !~dev-python/routes-2.0[${PYTHON_USEDEP}] + !~dev-python/routes-2.1[$(python_gen_usedep 'python2_7')] + <=dev-python/routes-2.2[${PYTHON_USEDEP}] + >=dev-python/webob-1.2.3[${PYTHON_USEDEP}] + <=dev-python/webob-1.5.1[${PYTHON_USEDEP}] + >=dev-python/sqlalchemy-migrate-0.9.6[${PYTHON_USEDEP}] + <=dev-python/sqlalchemy-migrate-0.10.0[${PYTHON_USEDEP}] + >=dev-python/httplib2-0.7.5[${PYTHON_USEDEP}] + <=dev-python/httplib2-0.9.2[${PYTHON_USEDEP}] + >=dev-python/pycrypto-2.6[${PYTHON_USEDEP}] + <=dev-python/pycrypto-2.6.1[${PYTHON_USEDEP}] + >=dev-python/iso8601-0.1.9[${PYTHON_USEDEP}] + <=dev-python/iso8601-0.1.11[${PYTHON_USEDEP}] + >=dev-python/oslo-config-2.3.0[${PYTHON_USEDEP}] + <=dev-python/oslo-config-3.1.0[${PYTHON_USEDEP}] + >=dev-python/oslo-concurrency-2.3.0[${PYTHON_USEDEP}] + <=dev-python/oslo-concurrency-3.1.0[${PYTHON_USEDEP}] + >=dev-python/oslo-context-0.2.0[${PYTHON_USEDEP}] + <=dev-python/oslo-context-1.0.0[${PYTHON_USEDEP}] + >=dev-python/oslo-service-0.7.0[${PYTHON_USEDEP}] + <=dev-python/oslo-service-1.1.0[${PYTHON_USEDEP}] + >=dev-python/oslo-utils-2.0.0[${PYTHON_USEDEP}] + !~dev-python/oslo-utils-2.6.0[${PYTHON_USEDEP}] + <=dev-python/oslo-utils-3.2.0[${PYTHON_USEDEP}] + >=dev-python/stevedore-1.5.0[${PYTHON_USEDEP}] + <=dev-python/stevedore-1.10.0[${PYTHON_USEDEP}] + >=dev-python/futurist-0.1.2[${PYTHON_USEDEP}] + <=dev-python/futurist-0.8.0[${PYTHON_USEDEP}] + >=dev-python/taskflow-1.16.0[${PYTHON_USEDEP}] + <=dev-python/taskflow-1.25.0[${PYTHON_USEDEP}] + >=dev-python/keystonemiddleware-2.0.0[${PYTHON_USEDEP}] + !~dev-python/keystonemiddleware-2.4.0[${PYTHON_USEDEP}] + <=dev-python/keystonemiddleware-4.0.0[${PYTHON_USEDEP}] + >=dev-python/WSME-0.7[${PYTHON_USEDEP}] + <=dev-python/WSME-0.8.0[${PYTHON_USEDEP}] + <=dev-python/paste-2.0.2[${PYTHON_USEDEP}] + >=dev-python/jsonschema-2.0.0[${PYTHON_USEDEP}] + !~dev-python/jsonschema-2.5.0[${PYTHON_USEDEP}] + <dev-python/jsonschema-3.0.0[${PYTHON_USEDEP}] + >=dev-python/python-keystoneclient-1.6.0[${PYTHON_USEDEP}] + !~dev-python/python-keystoneclient-1.8.0[${PYTHON_USEDEP}] + <=dev-python/python-keystoneclient-2.0.0-r9999[${PYTHON_USEDEP}] + >=dev-python/pyopenssl-0.14[${PYTHON_USEDEP}] + <=dev-python/pyopenssl-0.15.1-r9999[${PYTHON_USEDEP}] + >=dev-python/six-1.9.0[${PYTHON_USEDEP}] + <=dev-python/six-1.10.0[${PYTHON_USEDEP}] + >=dev-python/oslo-db-2.4.1[${PYTHON_USEDEP}] + <=dev-python/oslo-db-4.1.0[${PYTHON_USEDEP}] + >=dev-python/oslo-i18n-1.5.0[${PYTHON_USEDEP}] + <=dev-python/oslo-i18n-3.1.0[${PYTHON_USEDEP}] + >=dev-python/oslo-log-1.8.0[${PYTHON_USEDEP}] + <=dev-python/oslo-log-2.1.0[${PYTHON_USEDEP}] + >=dev-python/oslo-messaging-1.16.0[${PYTHON_USEDEP}] + !~dev-python/oslo-messaging-1.17.0[${PYTHON_USEDEP}] + !~dev-python/oslo-messaging-1.17.1[${PYTHON_USEDEP}] + !~dev-python/oslo-messaging-2.6.0[${PYTHON_USEDEP}] + !~dev-python/oslo-messaging-2.6.1[${PYTHON_USEDEP}] + !~dev-python/oslo-messaging-2.7.0[${PYTHON_USEDEP}] + !~dev-python/oslo-messaging-2.8.0[${PYTHON_USEDEP}] + !~dev-python/oslo-messaging-2.8.1[${PYTHON_USEDEP}] + !~dev-python/oslo-messaging-2.9.0[${PYTHON_USEDEP}] + !~dev-python/oslo-messaging-3.1.0[${PYTHON_USEDEP}] + <=dev-python/oslo-messaging-3.0.0[${PYTHON_USEDEP}] + >=dev-python/oslo-middleware-2.8.0[${PYTHON_USEDEP}] + <=dev-python/oslo-middleware-3.3.0[${PYTHON_USEDEP}] + >=dev-python/oslo-policy-0.5.0[${PYTHON_USEDEP}] + <=dev-python/oslo-policy-1.1.0[${PYTHON_USEDEP}] + >=dev-python/oslo-serialization-1.4.0[${PYTHON_USEDEP}] + <=dev-python/oslo-serialization-2.1.0[${PYTHON_USEDEP}] + >=dev-python/retrying-1.2.3[${PYTHON_USEDEP}] + !~dev-python/retrying-1.3.0[${PYTHON_USEDEP}] + <=dev-python/retrying-1.3.3[${PYTHON_USEDEP}] + >=dev-python/osprofiler-0.3.0[${PYTHON_USEDEP}] + <=dev-python/osprofiler-0.3.1[${PYTHON_USEDEP}] + >=dev-python/glance_store-0.7.1[${PYTHON_USEDEP}] + !~dev-python/glance_store-0.9.0[${PYTHON_USEDEP}] + <=dev-python/glance_store-0.9.1[${PYTHON_USEDEP}] + >=dev-python/semantic_version-2.3.1[${PYTHON_USEDEP}] + <=dev-python/semantic_version-2.4.2[${PYTHON_USEDEP}] + >=dev-python/castellan-0.2.0[${PYTHON_USEDEP}] + <=dev-python/castellan-0.3.1[${PYTHON_USEDEP}] + >=dev-python/cryptography-1.0[${PYTHON_USEDEP}] + <=dev-python/cryptography-1.1.2-r9999[${PYTHON_USEDEP}] +" + +PATCHES=( + "${FILESDIR}/cve-2016-0757-stable-liberty.patch" +) + +pkg_setup() { + enewgroup glance + enewuser glance -1 -1 /var/lib/glance glance +} + +python_prepare_all() { + sed -i '/xattr/d' test-requirements.txt || die + sed -i '/pysendfile/d' test-requirements.txt || die + sed -i '/^hacking/d' test-requirements.txt || die + distutils-r1_python_prepare_all +} + +python_compile_all() { + use doc && "${PYTHON}" setup.py build_sphinx +} + +python_test() { + # https://bugs.launchpad.net/glance/+bug/1251105 + # https://bugs.launchpad.net/glance/+bug/1242501 + testr init + testr run --parallel || die "failed testsuite under python2.7" +} + +python_install() { + distutils-r1_python_install + + for svc in api registry scrubber; do + newinitd "${FILESDIR}/glance.initd" glance-${svc} + done + + diropts -m 0750 -o glance -g glance + dodir /var/log/glance /var/lib/glance/images /var/lib/glance/scrubber + keepdir /etc/glance + keepdir /var/log/glance + keepdir /var/lib/glance/images + keepdir /var/lib/glance/scrubber + + insinto /etc/glance + insopts -m 0640 -o glance -g glance + doins etc/*.ini + doins etc/*.conf + doins etc/*.sample +} + +python_install_all() { + use doc && local HTML_DOCS=( doc/build/html/. ) + distutils-r1_python_install_all +} |