Gentoo Archives: gentoo-commits

From: Matt Thode <prometheanfire@g.o>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] repo/gentoo:master commit in: app-admin/glance/files/, app-admin/glance/
Date: Tue, 22 Sep 2015 17:46:32
Message-Id: 1442943959.4cb4bc39e1d1f115d6db596e0a7e6f23db3f924e.prometheanfire@gentoo
1 commit: 4cb4bc39e1d1f115d6db596e0a7e6f23db3f924e
2 Author: Matthew Thode <mthode <AT> mthode <DOT> org>
3 AuthorDate: Tue Sep 22 17:45:22 2015 +0000
4 Commit: Matt Thode <prometheanfire <AT> gentoo <DOT> org>
5 CommitDate: Tue Sep 22 17:45:59 2015 +0000
6 URL: https://gitweb.gentoo.org/repo/gentoo.git/commit/?id=4cb4bc39
7
8 app-admin/glance: fixing CVE-2015-5251
9
10 Package-Manager: portage-2.2.20.1
11
12 .../glance/files/cve-2015-5251-stable-kilo.patch | 192 +++++++++++++++++++++
13 app-admin/glance/glance-2015.1.1-r2.ebuild | 190 ++++++++++++++++++++
14 2 files changed, 382 insertions(+)
15
16 diff --git a/app-admin/glance/files/cve-2015-5251-stable-kilo.patch b/app-admin/glance/files/cve-2015-5251-stable-kilo.patch
17 new file mode 100644
18 index 0000000..f868645
19 --- /dev/null
20 +++ b/app-admin/glance/files/cve-2015-5251-stable-kilo.patch
21 @@ -0,0 +1,192 @@
22 +From 9beca533f42ae1fc87418de0c360e19bc59b24b5 Mon Sep 17 00:00:00 2001
23 +From: Stuart McLaren <stuart.mclaren@××.com>
24 +Date: Tue, 11 Aug 2015 10:37:09 +0000
25 +Subject: [PATCH] Prevent image status being directly modified via v1
26 +
27 +Users shouldn't be able to change an image's status directly via the
28 +v1 API.
29 +
30 +Some existing consumers of Glance set the x-image-meta-status header in
31 +requests to the Glance API, eg:
32 +
33 +https://github.com/openstack/nova/blob/master/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance#L184
34 +
35 +We should try to prevent users setting 'status' via v1, but without breaking
36 +existing benign API calls such as these.
37 +
38 +I've adopted the following approach (which has some prior art in 'protected properties').
39 +
40 +If a PUT request is received which contains an x-image-meta-status header:
41 +
42 +* The user provided status is ignored if it matches the current image
43 + status (this prevents benign calls such as the nova one above from
44 + breaking). The usual code (eg 200) will be returned.
45 +
46 +* If the user provided status doesn't match the current image status (ie
47 + there is a real attempt to change the value) 403 will be returned. This
48 + will break any calls which currently intentionally change the status.
49 +
50 +APIImpact
51 +
52 +Closes-bug: 1482371
53 +
54 +Change-Id: I44fadf32abb57c962b67467091c3f51c1ccc25e6
55 +(cherry picked from commit 4d08db5b6d42323ac1958ef3b7417d875e7bea8c)
56 +---
57 + glance/api/v1/__init__.py | 3 +
58 + glance/api/v1/images.py | 9 +++
59 + glance/tests/functional/v1/test_api.py | 89 ++++++++++++++++++++++
60 + .../integration/legacy_functional/test_v1_api.py | 2 +
61 + 4 files changed, 103 insertions(+)
62 +
63 +diff --git a/glance/api/v1/__init__.py b/glance/api/v1/__init__.py
64 +index 74de9aa1411d8e926770b67f7d851cf14e794414..9306bbb4fe78f77a26bb539c717fdfd2b38767c8 100644
65 +--- a/glance/api/v1/__init__.py
66 ++++ b/glance/api/v1/__init__.py
67 +@@ -21,3 +21,6 @@ SUPPORTED_PARAMS = ('limit', 'marker', 'sort_key', 'sort_dir')
68 +
69 + # Metadata which only an admin can change once the image is active
70 + ACTIVE_IMMUTABLE = ('size', 'checksum')
71 ++
72 ++# Metadata which cannot be changed (irrespective of the current image state)
73 ++IMMUTABLE = ('status',)
74 +diff --git a/glance/api/v1/images.py b/glance/api/v1/images.py
75 +index e33b91fbca79377e78ccfd329fa542ad422f5ffc..95e32949d958d0f57a3b60c141b91784a5801f5a 100644
76 +--- a/glance/api/v1/images.py
77 ++++ b/glance/api/v1/images.py
78 +@@ -57,6 +57,7 @@ _LW = i18n._LW
79 + SUPPORTED_PARAMS = glance.api.v1.SUPPORTED_PARAMS
80 + SUPPORTED_FILTERS = glance.api.v1.SUPPORTED_FILTERS
81 + ACTIVE_IMMUTABLE = glance.api.v1.ACTIVE_IMMUTABLE
82 ++IMMUTABLE = glance.api.v1.IMMUTABLE
83 +
84 + CONF = cfg.CONF
85 + CONF.import_opt('disk_formats', 'glance.common.config', group='image_format')
86 +@@ -912,6 +913,14 @@ class Controller(controller.BaseController):
87 + request=req,
88 + content_type="text/plain")
89 +
90 ++ for key in IMMUTABLE:
91 ++ if (image_meta.get(key) is not None and
92 ++ image_meta.get(key) != orig_image_meta.get(key)):
93 ++ msg = _("Forbidden to modify '%s' of image.") % key
94 ++ raise HTTPForbidden(explanation=msg,
95 ++ request=req,
96 ++ content_type="text/plain")
97 ++
98 + # The default behaviour for a PUT /images/<IMAGE_ID> is to
99 + # override any properties that were previously set. This, however,
100 + # leads to a number of issues for the common use case where a caller
101 +diff --git a/glance/tests/functional/v1/test_api.py b/glance/tests/functional/v1/test_api.py
102 +index 9fba3bb5e40c8742530691228c7b436b385fc2ca..6b3bfbb4270f1eb0f50418504e65be30ea23d10b 100644
103 +--- a/glance/tests/functional/v1/test_api.py
104 ++++ b/glance/tests/functional/v1/test_api.py
105 +@@ -715,3 +715,92 @@ class TestApi(functional.FunctionalTest):
106 + self.assertEqual(404, response.status)
107 +
108 + self.stop_servers()
109 ++
110 ++ def test_status_cannot_be_manipulated_directly(self):
111 ++ self.cleanup()
112 ++ self.start_servers(**self.__dict__.copy())
113 ++ headers = minimal_headers('Image1')
114 ++
115 ++ # Create a 'queued' image
116 ++ http = httplib2.Http()
117 ++ headers = {'Content-Type': 'application/octet-stream',
118 ++ 'X-Image-Meta-Disk-Format': 'raw',
119 ++ 'X-Image-Meta-Container-Format': 'bare'}
120 ++ path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
121 ++ response, content = http.request(path, 'POST', headers=headers,
122 ++ body=None)
123 ++ self.assertEqual(201, response.status)
124 ++ image = jsonutils.loads(content)['image']
125 ++ self.assertEqual('queued', image['status'])
126 ++
127 ++ # Ensure status of 'queued' image can't be changed
128 ++ path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
129 ++ image['id'])
130 ++ http = httplib2.Http()
131 ++ headers = {'X-Image-Meta-Status': 'active'}
132 ++ response, content = http.request(path, 'PUT', headers=headers)
133 ++ self.assertEqual(403, response.status)
134 ++ response, content = http.request(path, 'HEAD')
135 ++ self.assertEqual(200, response.status)
136 ++ self.assertEqual('queued', response['x-image-meta-status'])
137 ++
138 ++ # We allow 'setting' to the same status
139 ++ http = httplib2.Http()
140 ++ headers = {'X-Image-Meta-Status': 'queued'}
141 ++ response, content = http.request(path, 'PUT', headers=headers)
142 ++ self.assertEqual(200, response.status)
143 ++ response, content = http.request(path, 'HEAD')
144 ++ self.assertEqual(200, response.status)
145 ++ self.assertEqual('queued', response['x-image-meta-status'])
146 ++
147 ++ # Make image active
148 ++ http = httplib2.Http()
149 ++ headers = {'Content-Type': 'application/octet-stream'}
150 ++ response, content = http.request(path, 'PUT', headers=headers,
151 ++ body='data')
152 ++ self.assertEqual(200, response.status)
153 ++ image = jsonutils.loads(content)['image']
154 ++ self.assertEqual('active', image['status'])
155 ++
156 ++ # Ensure status of 'active' image can't be changed
157 ++ http = httplib2.Http()
158 ++ headers = {'X-Image-Meta-Status': 'queued'}
159 ++ response, content = http.request(path, 'PUT', headers=headers)
160 ++ self.assertEqual(403, response.status)
161 ++ response, content = http.request(path, 'HEAD')
162 ++ self.assertEqual(200, response.status)
163 ++ self.assertEqual('active', response['x-image-meta-status'])
164 ++
165 ++ # We allow 'setting' to the same status
166 ++ http = httplib2.Http()
167 ++ headers = {'X-Image-Meta-Status': 'active'}
168 ++ response, content = http.request(path, 'PUT', headers=headers)
169 ++ self.assertEqual(200, response.status)
170 ++ response, content = http.request(path, 'HEAD')
171 ++ self.assertEqual(200, response.status)
172 ++ self.assertEqual('active', response['x-image-meta-status'])
173 ++
174 ++ # Create a 'queued' image, ensure 'status' header is ignored
175 ++ http = httplib2.Http()
176 ++ path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
177 ++ headers = {'Content-Type': 'application/octet-stream',
178 ++ 'X-Image-Meta-Status': 'active'}
179 ++ response, content = http.request(path, 'POST', headers=headers,
180 ++ body=None)
181 ++ self.assertEqual(201, response.status)
182 ++ image = jsonutils.loads(content)['image']
183 ++ self.assertEqual('queued', image['status'])
184 ++
185 ++ # Create an 'active' image, ensure 'status' header is ignored
186 ++ http = httplib2.Http()
187 ++ path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
188 ++ headers = {'Content-Type': 'application/octet-stream',
189 ++ 'X-Image-Meta-Disk-Format': 'raw',
190 ++ 'X-Image-Meta-Status': 'queued',
191 ++ 'X-Image-Meta-Container-Format': 'bare'}
192 ++ response, content = http.request(path, 'POST', headers=headers,
193 ++ body='data')
194 ++ self.assertEqual(201, response.status)
195 ++ image = jsonutils.loads(content)['image']
196 ++ self.assertEqual('active', image['status'])
197 ++ self.stop_servers()
198 +diff --git a/glance/tests/integration/legacy_functional/test_v1_api.py b/glance/tests/integration/legacy_functional/test_v1_api.py
199 +index dff436465919569480bdbac537d20a6d61c98f46..511d46dfe18028bb430504784cc9d24c58736c3b 100644
200 +--- a/glance/tests/integration/legacy_functional/test_v1_api.py
201 ++++ b/glance/tests/integration/legacy_functional/test_v1_api.py
202 +@@ -358,6 +358,8 @@ class TestApi(base.ApiTest):
203 + path = "/v1/images"
204 + response, content = self.http.request(path, 'POST', headers=headers)
205 + self.assertEqual(201, response.status)
206 ++ image = jsonutils.loads(content)['image']
207 ++ self.assertEqual('active', image['status'])
208 +
209 + # 2. HEAD image-location
210 + # Verify image size is zero and the status is active
211 +--
212 +2.5.0
213 +
214
215 diff --git a/app-admin/glance/glance-2015.1.1-r2.ebuild b/app-admin/glance/glance-2015.1.1-r2.ebuild
216 new file mode 100644
217 index 0000000..2083b07
218 --- /dev/null
219 +++ b/app-admin/glance/glance-2015.1.1-r2.ebuild
220 @@ -0,0 +1,190 @@
221 +# Copyright 1999-2015 Gentoo Foundation
222 +# Distributed under the terms of the GNU General Public License v2
223 +# $Id$
224 +
225 +EAPI=5
226 +PYTHON_COMPAT=( python2_7 )
227 +
228 +inherit distutils-r1 user
229 +
230 +DESCRIPTION="Provides services for discovering, registering, and retrieving
231 +virtual machine images"
232 +HOMEPAGE="https://launchpad.net/glance"
233 +SRC_URI="https://launchpad.net/${PN}/kilo/${PV}/+download/${P}.tar.gz"
234 +
235 +LICENSE="Apache-2.0"
236 +SLOT="0"
237 +KEYWORDS="~amd64 ~x86"
238 +IUSE="doc mysql postgres +sqlite +swift test"
239 +REQUIRED_USE="|| ( mysql postgres sqlite )"
240 +
241 +DEPEND="
242 + dev-python/setuptools[${PYTHON_USEDEP}]
243 + >=dev-python/pbr-0.8.0[${PYTHON_USEDEP}]
244 + <dev-python/pbr-1.0[${PYTHON_USEDEP}]
245 + test? (
246 + ${RDEPEND}
247 + >=dev-python/hacking-0.10.0[${PYTHON_USEDEP}]
248 + <dev-python/hacking-0.11[${PYTHON_USEDEP}]
249 + ~dev-python/Babel-1.3[${PYTHON_USEDEP}]
250 + >=dev-python/coverage-3.6[${PYTHON_USEDEP}]
251 + >=dev-python/fixtures-0.3.14[${PYTHON_USEDEP}]
252 + <dev-python/fixtures-1.3.0[${PYTHON_USEDEP}]
253 + >=dev-python/mock-1.0[${PYTHON_USEDEP}]
254 + <dev-python/mock-1.1.0[${PYTHON_USEDEP}]
255 + >=dev-python/sphinx-1.1.2[${PYTHON_USEDEP}]
256 + !~dev-python/sphinx-1.2.0[${PYTHON_USEDEP}]
257 + <dev-python/sphinx-1.3[${PYTHON_USEDEP}]
258 + >=dev-python/requests-2.2.0[${PYTHON_USEDEP}]
259 + !~dev-python/requests-2.4.0[${PYTHON_USEDEP}]
260 + >=dev-python/testrepository-0.0.18[${PYTHON_USEDEP}]
261 + >=dev-python/testtools-0.9.36[${PYTHON_USEDEP}]
262 + !~dev-python/testtools-1.2.0[${PYTHON_USEDEP}]
263 + >=dev-python/psutil-1.1.1[${PYTHON_USEDEP}]
264 + <dev-python/psutil-2.0.0[${PYTHON_USEDEP}]
265 + >=dev-python/oslotest-1.5.1[${PYTHON_USEDEP}]
266 + <dev-python/oslotest-1.6.0[${PYTHON_USEDEP}]
267 + dev-python/mysql-python[${PYTHON_USEDEP}]
268 + dev-python/psycopg[${PYTHON_USEDEP}]
269 + ~dev-python/pysendfile-2.0.1[${PYTHON_USEDEP}]
270 + dev-python/qpid-python[${PYTHON_USEDEP}]
271 + >=dev-python/pyxattr-0.5.0[${PYTHON_USEDEP}]
272 + >=dev-python/oslo-sphinx-2.5.0[${PYTHON_USEDEP}]
273 + <dev-python/oslo-sphinx-2.6.0[${PYTHON_USEDEP}]
274 + >=dev-python/elasticsearch-py-1.3.0[${PYTHON_USEDEP}]
275 + )"
276 +
277 +#note to self, wsgiref is a python builtin, no need to package it
278 +#>=dev-python/wsgiref-0.1.2[${PYTHON_USEDEP}]
279 +
280 +RDEPEND="
281 + >=dev-python/greenlet-0.3.2[${PYTHON_USEDEP}]
282 + sqlite? (
283 + >=dev-python/sqlalchemy-0.9.7[sqlite,${PYTHON_USEDEP}]
284 + <=dev-python/sqlalchemy-0.9.99[sqlite,${PYTHON_USEDEP}]
285 + )
286 + mysql? (
287 + dev-python/mysql-python
288 + >=dev-python/sqlalchemy-0.9.7[${PYTHON_USEDEP}]
289 + <=dev-python/sqlalchemy-0.9.99[${PYTHON_USEDEP}]
290 + )
291 + postgres? (
292 + dev-python/psycopg:2
293 + >=dev-python/sqlalchemy-0.9.7[${PYTHON_USEDEP}]
294 + <=dev-python/sqlalchemy-0.9.99[${PYTHON_USEDEP}]
295 + )
296 + >=dev-python/anyjson-0.3.3[${PYTHON_USEDEP}]
297 + >=dev-python/eventlet-0.16.1[${PYTHON_USEDEP}]
298 + !~dev-python/eventlet-0.17.0[${PYTHON_USEDEP}]
299 + >=dev-python/pastedeploy-1.5.0[${PYTHON_USEDEP}]
300 + >=dev-python/routes-1.12.3[${PYTHON_USEDEP}]
301 + !~dev-python/routes-2.0[${PYTHON_USEDEP}]
302 + >=dev-python/webob-1.2.3[${PYTHON_USEDEP}]
303 + >=dev-python/sqlalchemy-migrate-0.9.5[${PYTHON_USEDEP}]
304 + >=dev-python/httplib2-0.7.5[${PYTHON_USEDEP}]
305 + >=dev-python/kombu-2.5.0[${PYTHON_USEDEP}]
306 + >=dev-python/pycrypto-2.6[${PYTHON_USEDEP}]
307 + >=dev-python/iso8601-0.1.9[${PYTHON_USEDEP}]
308 + dev-python/ordereddict[${PYTHON_USEDEP}]
309 + >=dev-python/oslo-config-1.9.3[${PYTHON_USEDEP}]
310 + <dev-python/oslo-config-1.10.0[${PYTHON_USEDEP}]
311 + >=dev-python/oslo-concurrency-1.8.0[${PYTHON_USEDEP}]
312 + <dev-python/oslo-concurrency-1.9.0[${PYTHON_USEDEP}]
313 + >=dev-python/oslo-context-0.2.0[${PYTHON_USEDEP}]
314 + <dev-python/oslo-context-0.3.0[${PYTHON_USEDEP}]
315 + >=dev-python/oslo-utils-1.4.0[${PYTHON_USEDEP}]
316 + <dev-python/oslo-utils-1.5.0[${PYTHON_USEDEP}]
317 + >=dev-python/stevedore-1.3.0[${PYTHON_USEDEP}]
318 + <dev-python/stevedore-1.4.0[${PYTHON_USEDEP}]
319 + >=dev-python/taskflow-0.7.1[${PYTHON_USEDEP}]
320 + <dev-python/taskflow-0.8.0[${PYTHON_USEDEP}]
321 + >=dev-python/keystonemiddleware-1.5.0[${PYTHON_USEDEP}]
322 + <dev-python/keystonemiddleware-1.6.0[${PYTHON_USEDEP}]
323 + >=dev-python/WSME-0.6[${PYTHON_USEDEP}]
324 + <dev-python/WSME-0.7[${PYTHON_USEDEP}]
325 + dev-python/posix_ipc[${PYTHON_USEDEP}]
326 + swift? (
327 + >=dev-python/python-swiftclient-2.2.0[${PYTHON_USEDEP}]
328 + <dev-python/python-swiftclient-2.5.0[${PYTHON_USEDEP}]
329 + )
330 + >=dev-python/oslo-vmware-0.11.1[${PYTHON_USEDEP}]
331 + <dev-python/oslo-vmware-0.12.0[${PYTHON_USEDEP}]
332 + dev-python/paste[${PYTHON_USEDEP}]
333 + >=dev-python/jsonschema-2.0.0[${PYTHON_USEDEP}]
334 + <dev-python/jsonschema-3.0.0[${PYTHON_USEDEP}]
335 + >=dev-python/python-keystoneclient-1.2.0[${PYTHON_USEDEP}]
336 + <dev-python/python-keystoneclient-1.4.0[${PYTHON_USEDEP}]
337 + >=dev-python/pyopenssl-0.11[${PYTHON_USEDEP}]
338 + >=dev-python/six-1.9.0[${PYTHON_USEDEP}]
339 + >=dev-python/oslo-db-1.7.0[${PYTHON_USEDEP}]
340 + <dev-python/oslo-db-1.8.0[${PYTHON_USEDEP}]
341 + >=dev-python/oslo-i18n-1.5.0[${PYTHON_USEDEP}]
342 + <dev-python/oslo-i18n-1.6.0[${PYTHON_USEDEP}]
343 + >=dev-python/oslo-log-1.0.0[${PYTHON_USEDEP}]
344 + <dev-python/oslo-log-1.1.0[${PYTHON_USEDEP}]
345 + >=dev-python/oslo-messaging-1.8.0[${PYTHON_USEDEP}]
346 + <dev-python/oslo-messaging-1.9.0[${PYTHON_USEDEP}]
347 + >=dev-python/oslo-policy-0.3.1[${PYTHON_USEDEP}]
348 + <dev-python/oslo-policy-0.4.0[${PYTHON_USEDEP}]
349 + >=dev-python/oslo-serialization-1.4.0[${PYTHON_USEDEP}]
350 + <dev-python/oslo-serialization-1.5.0[${PYTHON_USEDEP}]
351 + >=dev-python/retrying-1.2.3[${PYTHON_USEDEP}]
352 + !~dev-python/retrying-1.3.0[${PYTHON_USEDEP}]
353 + >=dev-python/osprofiler-0.3.0[${PYTHON_USEDEP}]
354 + >=dev-python/glance_store-0.3.0[${PYTHON_USEDEP}]
355 + <dev-python/glance_store-0.5.0[${PYTHON_USEDEP}]
356 + >=dev-python/semantic_version-2.3.1[${PYTHON_USEDEP}]
357 +"
358 +
359 +PATCHES=(
360 + "${FILESDIR}/cve-2015-5163-stable-kilo.patch"
361 + "${FILESDIR}/cve-2015-5251-stable-kilo.patch"
362 +)
363 +
364 +pkg_setup() {
365 + enewgroup glance
366 + enewuser glance -1 -1 /var/lib/glance glance
367 +}
368 +
369 +python_prepare_all() {
370 + sed -i '/xattr/d' test-requirements.txt || die
371 + sed -i '/pysendfile/d' test-requirements.txt || die
372 + distutils-r1_python_prepare_all
373 +}
374 +
375 +python_compile_all() {
376 + use doc && "${PYTHON}" setup.py build_sphinx
377 +}
378 +
379 +python_test() {
380 + # https://bugs.launchpad.net/glance/+bug/1251105
381 + # https://bugs.launchpad.net/glance/+bug/1242501
382 + testr init
383 + testr run --parallel || die "failed testsuite under python2.7"
384 +}
385 +
386 +python_install() {
387 + distutils-r1_python_install
388 +
389 + for svc in api registry scrubber; do
390 + newinitd "${FILESDIR}/glance.initd" glance-${svc}
391 + done
392 +
393 + diropts -m 0750 -o glance -g glance
394 + dodir /var/log/glance /var/lib/glance/images /var/lib/glance/scrubber
395 + keepdir /etc/glance
396 + keepdir /var/log/glance
397 + keepdir /var/lib/glance/images
398 + keepdir /var/lib/glance/scrubber
399 +
400 + insinto /etc/glance
401 + insopts -m 0640 -o glance -g glance
402 + doins etc/*.ini
403 + doins etc/*.conf
404 + doins etc/*.sample
405 +}
406 +
407 +python_install_all() {
408 + use doc && local HTML_DOCS=( doc/build/html/. )
409 + distutils-r1_python_install_all
410 +}