[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#773914: marked as done (unblock: glance/2014.1.3-6)



Your message dated Thu, 25 Dec 2014 16:50:34 +0000
with message-id <3a542f512f7528f4357b94fd3623daa9@mail.adsl.funky-badger.org>
and subject line Re: Bug#773914: unblock: glance/2014.1.3-6
has caused the Debian Bug report #773914,
regarding unblock: glance/2014.1.3-6
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact owner@bugs.debian.org
immediately.)


-- 
773914: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=773914
Debian Bug Tracking System
Contact owner@bugs.debian.org with problems
--- Begin Message ---
Package: release.debian.org
Severity: normal
User: release.debian.org@packages.debian.org
Usertags: unblock

A great Christmas to all the release team!

The last upload of Glance fixes a nasty bug where users of Glance basically
have access to all of the filesystem of the Glance server.

Please unblock glance/2014.1.3-6.

Cheers,

Thomas Goirand (zigo)
diff -Nru glance-2014.1.3/debian/changelog glance-2014.1.3/debian/changelog
--- glance-2014.1.3/debian/changelog	2014-10-08 17:02:24.000000000 +0000
+++ glance-2014.1.3/debian/changelog	2014-12-25 09:29:14.000000000 +0000
@@ -1,3 +1,12 @@
+glance (2014.1.3-6) unstable; urgency=high
+
+  * Added restrict_client_download_and_delete_files_in_glance-api_juno.patch
+    from upstream (Closes: #773836).
+  * Build-depends on openstack-pkg-tools (>= 20~) to ensure we have the
+    systemd fixes.
+
+ -- Thomas Goirand <zigo@debian.org>  Thu, 25 Dec 2014 17:28:05 +0800
+
 glance (2014.1.3-5) unstable; urgency=medium
 
   * Fixed glance-api and glance-registry config file path.
diff -Nru glance-2014.1.3/debian/control glance-2014.1.3/debian/control
--- glance-2014.1.3/debian/control	2014-10-08 17:02:24.000000000 +0000
+++ glance-2014.1.3/debian/control	2014-12-25 09:29:14.000000000 +0000
@@ -5,7 +5,7 @@
 Uploaders: Thomas Goirand <zigo@debian.org>
 Build-Depends: debhelper (>= 9),
                dh-systemd,
-               openstack-pkg-tools (>= 14~),
+               openstack-pkg-tools (>= 20~),
                po-debconf,
                python-all (>= 2.6.6-3~),
                python-pbr (>= 0.6),
diff -Nru glance-2014.1.3/debian/patches/restrict_client_download_and_delete_files_in_glance-api.patch glance-2014.1.3/debian/patches/restrict_client_download_and_delete_files_in_glance-api.patch
--- glance-2014.1.3/debian/patches/restrict_client_download_and_delete_files_in_glance-api.patch	1970-01-01 00:00:00.000000000 +0000
+++ glance-2014.1.3/debian/patches/restrict_client_download_and_delete_files_in_glance-api.patch	2014-12-25 09:29:14.000000000 +0000
@@ -0,0 +1,611 @@
+Subject: To prevent client use v2 patch api to handle file and swift location
+ The change will be used to restrict client to download and delete any file in
+ glance-api server. The same resone and logic as what we did in v1:
+ https://github.com/openstack/glance/blob/master/glance/api/v1/images.py#L429
+Author: Zhi Yan Liu <zhiyanl@cn.ibm.com>
+Date: Mon, 15 Dec 2014 04:29:55 +0000 (+0800)
+X-Git-Url: https://review.openstack.org/gitweb?p=openstack%2Fglance.git;a=commitdiff_plain;h=8bdb7ed9f5beaf816e7abba726904646bf3680dd
+Bug-Ubuntu: https://bugs.launchpad.net/glance/+bug/1400966
+Bug-Debian: https://bugs.debian.org/773836
+Change-Id: I72dbead3cb2dcb87f52658ddb880e26880cc229b
+Origin: upstream, https://review.openstack.org/#/c/142788/
+Last-Update: 2014-12-25
+
+diff --git a/glance/api/v1/images.py b/glance/api/v1/images.py
+index dd6ec06..9aff3f1 100644
+--- a/glance/api/v1/images.py
++++ b/glance/api/v1/images.py
+@@ -21,7 +21,6 @@ import copy
+ 
+ import eventlet
+ from oslo.config import cfg
+-import six.moves.urllib.parse as urlparse
+ from webob.exc import HTTPBadRequest
+ from webob.exc import HTTPConflict
+ from webob.exc import HTTPForbidden
+@@ -48,6 +47,7 @@ from glance.store import get_known_schemes
+ from glance.store import get_size_from_backend
+ from glance.store import get_store_from_location
+ from glance.store import get_store_from_scheme
++from glance.store import validate_external_location
+ 
+ LOG = logging.getLogger(__name__)
+ SUPPORTED_PARAMS = glance.api.v1.SUPPORTED_PARAMS
+@@ -404,23 +404,19 @@ class Controller(controller.BaseController):
+     @staticmethod
+     def _validate_source(source, req):
+         """
+-        External sources (as specified via the location or copy-from headers)
+-        are supported only over non-local store types, i.e. S3, Swift, HTTP.
+-        Note the absence of file:// for security reasons, see LP bug #942118.
+-        If the above constraint is violated, we reject with 400 "Bad Request".
++        To validate if external sources (as specified via the location
++        or copy-from headers) are supported. Otherwise we reject
++        with 400 "Bad Request".
+         """
+         if source:
+-            pieces = urlparse.urlparse(source)
+-            schemes = [scheme for scheme in get_known_schemes()
+-                       if scheme != 'file']
+-            for scheme in schemes:
+-                if pieces.scheme == scheme:
+-                    return source
+-            msg = _("External sourcing not supported for store %s") % source
+-            LOG.debug(msg)
+-            raise HTTPBadRequest(explanation=msg,
+-                                 request=req,
+-                                 content_type="text/plain")
++            if validate_external_location(source):
++                return source
++            else:
++                msg = _("External source are not supported: '%s'") % source
++                LOG.debug(msg)
++                raise HTTPBadRequest(explanation=msg,
++                                     request=req,
++                                     content_type="text/plain")
+ 
+     @staticmethod
+     def _copy_from(req):
+diff --git a/glance/store/__init__.py b/glance/store/__init__.py
+index 273b7c7..344311b 100644
+--- a/glance/store/__init__.py
++++ b/glance/store/__init__.py
+@@ -19,6 +19,7 @@ import sys
+ 
+ from oslo.config import cfg
+ import six
++import six.moves.urllib.parse as urlparse
+ 
+ from glance.common import exception
+ from glance.common import utils
+@@ -421,6 +422,24 @@ def set_acls(context, location_uri, public=False, read_tenants=[],
+         LOG.debug(_("Skipping store.set_acls... not implemented."))
+ 
+ 
++def validate_external_location(uri):
++    """
++    Validate if URI of external location are supported.
++
++    Only over non-local store types are OK, i.e. S3, Swift,
++    HTTP. Note the absence of 'file://' for security reasons,
++    see LP bug #942118, 1400966, 'swift+config://' is also
++    absent for security reasons, see LP bug #1334196.
++
++    :param uri: The URI of external image location.
++    :return: Whether given URI of external image location are OK.
++    """
++    pieces = urlparse.urlparse(uri)
++    valid_schemes = [scheme for scheme in get_known_schemes()
++                     if scheme != 'file' and scheme != 'swift+config']
++    return pieces.scheme in valid_schemes
++
++
+ class ImageRepoProxy(glance.domain.proxy.Repo):
+ 
+     def __init__(self, image_repo, context, store_api):
+@@ -453,22 +472,23 @@ class ImageRepoProxy(glance.domain.proxy.Repo):
+ 
+ 
+ def _check_location_uri(context, store_api, uri):
+-    """
+-    Check if an image location uri is valid.
++    """Check if an image location is valid.
+ 
+     :param context: Glance request context
+     :param store_api: store API module
+     :param uri: location's uri string
+     """
++
+     is_ok = True
+     try:
+-        size = store_api.get_size_from_backend(context, uri)
+         # NOTE(zhiyan): Some stores return zero when it catch exception
+-        is_ok = size > 0
++        is_ok = (store_api.validate_external_location(uri) and
++                 store_api.get_size_from_backend(context, uri) > 0)
+     except (exception.UnknownScheme, exception.NotFound):
+         is_ok = False
+     if not is_ok:
+-        raise exception.BadStoreUri(_('Invalid location: %s') % uri)
++        reason = _('Invalid location')
++        raise exception.BadStoreUri(message=reason)
+ 
+ 
+ def _check_image_location(context, store_api, location):
+diff --git a/glance/tests/functional/v1/test_copy_to_file.py b/glance/tests/functional/v1/test_copy_to_file.py
+index ae2c320..2c5d833 100644
+--- a/glance/tests/functional/v1/test_copy_to_file.py
++++ b/glance/tests/functional/v1/test_copy_to_file.py
+@@ -248,9 +248,35 @@ class TestCopyToFile(functional.FunctionalTest):
+         path = "http://%s:%d/v1/images"; % ("127.0.0.1", self.api_port)
+         http = httplib2.Http()
+         response, content = http.request(path, 'POST', headers=headers)
+-        self.assertEqual(response.status, 400, content)
++        self.assertEqual(400, response.status, content)
+ 
+-        expected = 'External sourcing not supported for store ' + copy_from
++        expected = 'External source are not supported: \'%s\'' % copy_from
++        msg = 'expected "%s" in "%s"' % (expected, content)
++        self.assertTrue(expected in content, msg)
++
++        self.stop_servers()
++
++    @skip_if_disabled
++    def test_copy_from_swift_config(self):
++        """
++        Ensure we can't copy from swift+config
++        """
++        self.cleanup()
++
++        self.start_servers(**self.__dict__.copy())
++
++        # POST /images with public image copied from file (to file)
++        headers = {'X-Image-Meta-Name': 'copied',
++                   'X-Image-Meta-disk_format': 'raw',
++                   'X-Image-Meta-container_format': 'ovf',
++                   'X-Image-Meta-Is-Public': 'True',
++                   'X-Glance-API-Copy-From': 'swift+config://xxx'}
++        path = "http://%s:%d/v1/images"; % ("127.0.0.1", self.api_port)
++        http = httplib2.Http()
++        response, content = http.request(path, 'POST', headers=headers)
++        self.assertEqual(400, response.status, content)
++
++        expected = 'External source are not supported: \'swift+config://xxx\''
+         msg = 'expected "%s" in "%s"' % (expected, content)
+         self.assertTrue(expected in content, msg)
+ 
+diff --git a/glance/tests/functional/v2/test_images.py b/glance/tests/functional/v2/test_images.py
+index 4247434..ef3f944 100644
+--- a/glance/tests/functional/v2/test_images.py
++++ b/glance/tests/functional/v2/test_images.py
+@@ -15,7 +15,6 @@
+ 
+ import os
+ import signal
+-import tempfile
+ import uuid
+ 
+ import requests
+@@ -38,6 +37,19 @@ class TestImages(functional.FunctionalTest):
+         self.cleanup()
+         self.api_server.deployment_flavor = 'noauth'
+         self.start_servers(**self.__dict__.copy())
++        for i in range(3):
++            ret = test_http.http_server("foo_image_id%d" % i,
++                                        "foo_image%d" % i)
++            setattr(self, 'http_server%d_pid' % i, ret[0])
++            setattr(self, 'http_port%d' % i, ret[1])
++
++    def tearDown(self):
++        for i in range(3):
++            pid = getattr(self, 'http_server%d_pid' % i, None)
++            if pid:
++                os.kill(pid, signal.SIGKILL)
++
++        super(TestImages, self).tearDown()
+ 
+     def _url(self, path):
+         return 'http://127.0.0.1:%d%s' % (self.api_port, path)
+@@ -282,21 +294,15 @@ class TestImages(functional.FunctionalTest):
+         self.assertEqual(413, response.status_code, response.text)
+ 
+         # Adding 3 image locations should fail since configured limit is 2
+-        for i in range(3):
+-            file_path = os.path.join(self.test_dir, 'fake_image_%i' % i)
+-            with open(file_path, 'w') as fap:
+-                fap.write('glance')
+-
+         path = self._url('/v2/images/%s' % image_id)
+         media_type = 'application/openstack-images-v2.1-json-patch'
+         headers = self._headers({'content-type': media_type})
+         changes = []
+         for i in range(3):
++            url = ('http://127.0.0.1:%s/foo_image' %
++                   getattr(self, 'http_port%d' % i))
+             changes.append({'op': 'add', 'path': '/locations/-',
+-                            'value': {'url': 'file://{0}'.format(
+-                                os.path.join(self.test_dir,
+-                                             'fake_image_%i' % i)),
+-                                      'metadata': {}},
++                            'value': {'url': url, 'metadata': {}},
+                             })
+ 
+         data = jsonutils.dumps(changes)
+@@ -1811,17 +1817,14 @@ class TestImages(functional.FunctionalTest):
+         self.assertNotIn('size', image)
+         self.assertNotIn('virtual_size', image)
+ 
+-        file_path = os.path.join(self.test_dir, 'fake_image')
+-        with open(file_path, 'w') as fap:
+-            fap.write('glance')
+-
+         # Update locations for the queued image
+         path = self._url('/v2/images/%s' % image_id)
+         media_type = 'application/openstack-images-v2.1-json-patch'
+         headers = self._headers({'content-type': media_type})
++        url = 'http://127.0.0.1:%s/foo_image' % self.http_port0
+         data = jsonutils.dumps([{'op': 'replace', 'path': '/locations',
+-                                 'value': [{'url': 'file://' + file_path,
+-                                            'metadata': {}}]}])
++                                 'value': [{'url': url, 'metadata': {}}]
++                                 }])
+         response = requests.patch(path, headers=headers, data=data)
+         self.assertEqual(200, response.status_code, response.text)
+ 
+@@ -1830,7 +1833,42 @@ class TestImages(functional.FunctionalTest):
+         response = requests.get(path, headers=headers)
+         self.assertEqual(200, response.status_code)
+         image = jsonutils.loads(response.text)
+-        self.assertEqual(image['size'], 6)
++        self.assertEqual(10, image['size'])
++
++    def test_update_locations_with_restricted_sources(self):
++        self.start_servers(**self.__dict__.copy())
++        # Create an image
++        path = self._url('/v2/images')
++        headers = self._headers({'content-type': 'application/json'})
++        data = jsonutils.dumps({'name': 'image-1', 'disk_format': 'aki',
++                                'container_format': 'aki'})
++        response = requests.post(path, headers=headers, data=data)
++        self.assertEqual(201, response.status_code)
++
++        # Returned image entity should have a generated id and status
++        image = jsonutils.loads(response.text)
++        image_id = image['id']
++        self.assertEqual('queued', image['status'])
++        self.assertNotIn('size', image)
++        self.assertNotIn('virtual_size', image)
++
++        # Update locations for the queued image
++        path = self._url('/v2/images/%s' % image_id)
++        media_type = 'application/openstack-images-v2.1-json-patch'
++        headers = self._headers({'content-type': media_type})
++        data = jsonutils.dumps([{'op': 'replace', 'path': '/locations',
++                                 'value': [{'url': 'file:///foo_image',
++                                            'metadata': {}}]
++                                 }])
++        response = requests.patch(path, headers=headers, data=data)
++        self.assertEqual(400, response.status_code, response.text)
++
++        data = jsonutils.dumps([{'op': 'replace', 'path': '/locations',
++                                 'value': [{'url': 'swift+config:///foo_image',
++                                            'metadata': {}}]
++                                 }])
++        response = requests.patch(path, headers=headers, data=data)
++        self.assertEqual(400, response.status_code, response.text)
+ 
+ 
+ class TestImageDirectURLVisibility(functional.FunctionalTest):
+@@ -2040,16 +2078,17 @@ class TestImageLocationSelectionStrategy(functional.FunctionalTest):
+         super(TestImageLocationSelectionStrategy, self).setUp()
+         self.cleanup()
+         self.api_server.deployment_flavor = 'noauth'
+-        self.foo_image_file = tempfile.NamedTemporaryFile()
+-        self.foo_image_file.write("foo image file")
+-        self.foo_image_file.flush()
+-        self.addCleanup(self.foo_image_file.close)
+-        ret = test_http.http_server("foo_image_id", "foo_image")
+-        self.http_server_pid, self.http_port = ret
++        for i in range(3):
++            ret = test_http.http_server("foo_image_id%d" % i,
++                                        "foo_image%d" % i)
++            setattr(self, 'http_server%d_pid' % i, ret[0])
++            setattr(self, 'http_port%d' % i, ret[1])
+ 
+     def tearDown(self):
+-        if self.http_server_pid is not None:
+-            os.kill(self.http_server_pid, signal.SIGKILL)
++        for i in range(3):
++            pid = getattr(self, 'http_server%d_pid' % i, None)
++            if pid:
++                os.kill(pid, signal.SIGKILL)
+ 
+         super(TestImageLocationSelectionStrategy, self).tearDown()
+ 
+@@ -2098,14 +2137,14 @@ class TestImageLocationSelectionStrategy(functional.FunctionalTest):
+         self.assertTrue('locations' in image)
+         self.assertTrue(image["locations"] == [])
+ 
+-       # Update image locations via PATCH
++        # Update image locations via PATCH
+         path = self._url('/v2/images/%s' % image_id)
+         media_type = 'application/openstack-images-v2.1-json-patch'
+         headers = self._headers({'content-type': media_type})
+-        values = [{'url': 'file://%s' % self.foo_image_file.name,
+-                   'metadata': {'idx': '1'}},
+-                  {'url': 'http://127.0.0.1:%s/foo_image' % self.http_port,
+-                   'metadata': {'idx': '0'}}]
++        values = [{'url': 'http://127.0.0.1:%s/foo_image' % self.http_port0,
++                   'metadata': {}},
++                  {'url': 'http://127.0.0.1:%s/foo_image' % self.http_port1,
++                   'metadata': {}}]
+         doc = [{'op': 'replace',
+                 'path': '/locations',
+                 'value': values}]
+@@ -2126,67 +2165,6 @@ class TestImageLocationSelectionStrategy(functional.FunctionalTest):
+ 
+         self.stop_servers()
+ 
+-    def test_image_locatons_with_store_type_strategy(self):
+-        self.api_server.show_image_direct_url = True
+-        self.api_server.show_multiple_locations = True
+-        self.image_location_quota = 10
+-        self.api_server.location_strategy = 'store_type'
+-        preference = "http, swift, filesystem"
+-        self.api_server.store_type_location_strategy_preference = preference
+-        self.start_servers(**self.__dict__.copy())
+-
+-        # Create an image
+-        path = self._url('/v2/images')
+-        headers = self._headers({'content-type': 'application/json'})
+-        data = jsonutils.dumps({'name': 'image-1', 'type': 'kernel',
+-                                'foo': 'bar', 'disk_format': 'aki',
+-                                'container_format': 'aki'})
+-        response = requests.post(path, headers=headers, data=data)
+-        self.assertEqual(201, response.status_code)
+-
+-        # Get the image id
+-        image = jsonutils.loads(response.text)
+-        image_id = image['id']
+-
+-        # Image locations should not be visible before location is set
+-        path = self._url('/v2/images/%s' % image_id)
+-        headers = self._headers({'Content-Type': 'application/json'})
+-        response = requests.get(path, headers=headers)
+-        self.assertEqual(200, response.status_code)
+-        image = jsonutils.loads(response.text)
+-        self.assertTrue('locations' in image)
+-        self.assertTrue(image["locations"] == [])
+-
+-       # Update image locations via PATCH
+-        path = self._url('/v2/images/%s' % image_id)
+-        media_type = 'application/openstack-images-v2.1-json-patch'
+-        headers = self._headers({'content-type': media_type})
+-        values = [{'url': 'file://%s' % self.foo_image_file.name,
+-                   'metadata': {'idx': '1'}},
+-                  {'url': 'http://127.0.0.1:%s/foo_image' % self.http_port,
+-                   'metadata': {'idx': '0'}}]
+-        doc = [{'op': 'replace',
+-                'path': '/locations',
+-                'value': values}]
+-        data = jsonutils.dumps(doc)
+-        response = requests.patch(path, headers=headers, data=data)
+-        self.assertEqual(200, response.status_code)
+-
+-        values.sort(key=lambda loc: int(loc['metadata']['idx']))
+-
+-        # Image locations should be visible
+-        path = self._url('/v2/images/%s' % image_id)
+-        headers = self._headers({'Content-Type': 'application/json'})
+-        response = requests.get(path, headers=headers)
+-        self.assertEqual(200, response.status_code)
+-        image = jsonutils.loads(response.text)
+-        self.assertTrue('locations' in image)
+-        self.assertEqual(image['locations'], values)
+-        self.assertTrue('direct_url' in image)
+-        self.assertEqual(image['direct_url'], values[0]['url'])
+-
+-        self.stop_servers()
+-
+ 
+ class TestImageMembers(functional.FunctionalTest):
+ 
+diff --git a/glance/tests/unit/test_store_image.py b/glance/tests/unit/test_store_image.py
+index 424915b..9d6cc4e 100644
+--- a/glance/tests/unit/test_store_image.py
++++ b/glance/tests/unit/test_store_image.py
+@@ -16,6 +16,7 @@ import mox
+ 
+ from glance.common import exception
+ import glance.store
++from glance.tests.unit import base as unit_test_base
+ from glance.tests.unit import utils as unit_test_utils
+ from glance.tests import utils
+ 
+@@ -731,7 +732,7 @@ class TestStoreImageRepo(utils.BaseTestCase):
+         self.assertEqual(acls['read'], [TENANT2])
+ 
+ 
+-class TestImageFactory(utils.BaseTestCase):
++class TestImageFactory(unit_test_base.StoreClearingUnitTest):
+ 
+     def setUp(self):
+         super(TestImageFactory, self).setUp()
+diff --git a/glance/tests/unit/test_store_location.py b/glance/tests/unit/test_store_location.py
+index df8d5d7..eac5590 100644
+--- a/glance/tests/unit/test_store_location.py
++++ b/glance/tests/unit/test_store_location.py
+@@ -24,6 +24,7 @@ import glance.store.s3
+ import glance.store.swift
+ import glance.store.vmware_datastore
+ from glance.tests.unit import base
++from glance.tests.unit import utils
+ 
+ 
+ class TestStoreLocation(base.StoreClearingUnitTest):
+@@ -488,11 +489,14 @@ class TestStoreLocation(base.StoreClearingUnitTest):
+                               ctx,
+                               store)
+ 
++    class FakeImageProxy(object):
++        size = None
++        context = None
++
++        def __init__(self, store_api):
++            self.store_api = store_api
++
+     def test_add_location_for_image_without_size(self):
+-        class FakeImageProxy():
+-            size = None
+-            context = None
+-            store_api = mock.Mock()
+ 
+         def fake_get_size_from_backend(context, uri):
+             return 1
+@@ -504,14 +508,31 @@ class TestStoreLocation(base.StoreClearingUnitTest):
+             loc2 = {'url': 'file:///fake2.img.tar.gz', 'metadata': {}}
+ 
+             # Test for insert location
+-            image1 = FakeImageProxy()
++            image1 = TestStoreLocation.FakeImageProxy(mock.Mock())
+             locations = glance.store.StoreLocations(image1, [])
+             locations.insert(0, loc2)
+             self.assertEqual(image1.size, 1)
+ 
+             # Test for set_attr of _locations_proxy
+-            image2 = FakeImageProxy()
++            image2 = TestStoreLocation.FakeImageProxy(mock.Mock())
+             locations = glance.store.StoreLocations(image2, [loc1])
+             locations[0] = loc2
+             self.assertTrue(loc2 in locations)
+             self.assertEqual(image2.size, 1)
++
++    def test_add_location_with_restricted_sources(self):
++
++        loc1 = {'url': 'file:///fake1.img.tar.gz', 'metadata': {}}
++        loc2 = {'url': 'swift+config:///xxx', 'metadata': {}}
++
++        # Test for insert location
++        image1 = TestStoreLocation.FakeImageProxy(utils.FakeStoreAPI())
++        locations = glance.store.StoreLocations(image1, [])
++        self.assertRaises(exception.BadStoreUri, locations.insert, 0, loc1)
++        self.assertNotIn(loc1, locations)
++
++        # Test for set_attr of _locations_proxy
++        image2 = TestStoreLocation.FakeImageProxy(utils.FakeStoreAPI())
++        locations = glance.store.StoreLocations(image2, [loc1])
++        self.assertRaises(exception.BadStoreUri, locations.insert, 0, loc2)
++        self.assertNotIn(loc2, locations)
+diff --git a/glance/tests/unit/utils.py b/glance/tests/unit/utils.py
+index 1c4e16a..bd6b8c4 100644
+--- a/glance/tests/unit/utils.py
++++ b/glance/tests/unit/utils.py
+@@ -14,9 +14,9 @@
+ #    under the License.
+ 
+ import urllib
+-import urlparse
+ 
+ from oslo.config import cfg
++import six.moves.urllib.parse as urlparse
+ 
+ from glance.common import exception
+ from glance.common import wsgi
+@@ -188,6 +188,12 @@ class FakeStoreAPI(object):
+     def check_location_metadata(self, val, key=''):
+         glance.store.check_location_metadata(val)
+ 
++    def validate_external_location(self, uri):
++        if uri and urlparse.urlparse(uri).scheme:
++            return glance.store.validate_external_location(uri)
++        else:
++            return True
++
+ 
+ class FakePolicyEnforcer(object):
+     def __init__(self, *_args, **kwargs):
+diff --git a/glance/tests/unit/v1/test_api.py b/glance/tests/unit/v1/test_api.py
+index 5618cb0..bea15c7 100644
+--- a/glance/tests/unit/v1/test_api.py
++++ b/glance/tests/unit/v1/test_api.py
+@@ -379,7 +379,7 @@ class TestGlanceAPI(base.IsolatedUnitTest):
+ 
+         res = req.get_response(self.api)
+         self.assertEqual(res.status_int, 400)
+-        self.assertTrue('External sourcing not supported' in res.body)
++        self.assertIn('External source are not supported', res.body)
+ 
+     def test_create_with_location_bad_store_uri(self):
+         fixture_headers = {
+@@ -962,6 +962,36 @@ class TestGlanceAPI(base.IsolatedUnitTest):
+             res = req.get_response(self.api)
+             self.assertEqual(res.status_int, 409)
+ 
++    def test_add_location_with_invalid_location_on_restricted_sources(self):
++        """Tests creates an image from location and restricted sources"""
++        fixture_headers = {'x-image-meta-store': 'file',
++                           'x-image-meta-disk-format': 'vhd',
++                           'x-image-meta-location': 'file:///etc/passwd',
++                           'x-image-meta-container-format': 'ovf',
++                           'x-image-meta-name': 'fake image #F'}
++
++        req = webob.Request.blank("/images")
++        req.headers['Content-Type'] = 'application/octet-stream'
++        req.method = 'POST'
++        for k, v in fixture_headers.iteritems():
++            req.headers[k] = v
++        res = req.get_response(self.api)
++        self.assertEqual(400, res.status_int)
++
++        fixture_headers = {'x-image-meta-store': 'file',
++                           'x-image-meta-disk-format': 'vhd',
++                           'x-image-meta-location': 'swift+config://xxx',
++                           'x-image-meta-container-format': 'ovf',
++                           'x-image-meta-name': 'fake image #F'}
++
++        req = webob.Request.blank("/images")
++        req.headers['Content-Type'] = 'application/octet-stream'
++        req.method = 'POST'
++        for k, v in fixture_headers.iteritems():
++            req.headers[k] = v
++        res = req.get_response(self.api)
++        self.assertEqual(400, res.status_int)
++
+     def test_add_copy_from_with_location(self):
+         """Tests creates an image from copy-from and location"""
+         fixture_headers = {'x-image-meta-store': 'file',
+@@ -978,6 +1008,34 @@ class TestGlanceAPI(base.IsolatedUnitTest):
+         res = req.get_response(self.api)
+         self.assertEqual(res.status_int, 400)
+ 
++    def test_add_copy_from_with_restricted_sources(self):
++        """Tests creates an image from copy-from with restricted sources"""
++        fixture_headers = {'x-image-meta-store': 'file',
++                           'x-image-meta-disk-format': 'vhd',
++                           'x-glance-api-copy-from': 'file:///etc/passwd',
++                           'x-image-meta-container-format': 'ovf',
++                           'x-image-meta-name': 'fake image #F'}
++
++        req = webob.Request.blank("/images")
++        req.method = 'POST'
++        for k, v in six.iteritems(fixture_headers):
++            req.headers[k] = v
++        res = req.get_response(self.api)
++        self.assertEqual(400, res.status_int)
++
++        fixture_headers = {'x-image-meta-store': 'file',
++                           'x-image-meta-disk-format': 'vhd',
++                           'x-glance-api-copy-from': 'swift+config://xxx',
++                           'x-image-meta-container-format': 'ovf',
++                           'x-image-meta-name': 'fake image #F'}
++
++        req = webob.Request.blank("/images")
++        req.method = 'POST'
++        for k, v in six.iteritems(fixture_headers):
++            req.headers[k] = v
++        res = req.get_response(self.api)
++        self.assertEqual(400, res.status_int)
++
+     def test_add_copy_from_upload_image_unauthorized_with_body(self):
+         rules = {"upload_image": '!', "modify_image": '@',
+                  "add_image": '@'}
diff -Nru glance-2014.1.3/debian/patches/series glance-2014.1.3/debian/patches/series
--- glance-2014.1.3/debian/patches/series	2014-10-08 17:02:24.000000000 +0000
+++ glance-2014.1.3/debian/patches/series	2014-12-25 09:29:14.000000000 +0000
@@ -1,3 +1,4 @@
 disable-network-for-docs.patch
 default-config.patch
 sql_conn-registry.patch
+restrict_client_download_and_delete_files_in_glance-api.patch

--- End Message ---
--- Begin Message ---
On 2014-12-25 15:34, Thomas Goirand wrote:
The last upload of Glance fixes a nasty bug where users of Glance basically
have access to all of the filesystem of the Glance server.

Please unblock glance/2014.1.3-6.

Unblocked.

Regards,

Adam

--- End Message ---

Reply to: