Merge lp:~soren/ubuntu/oneiric/nova/reconcile-with-security into lp:ubuntu/oneiric-proposed/nova

Proposed by Soren Hansen
Status: Rejected
Rejected by: Soren Hansen
Proposed branch: lp:~soren/ubuntu/oneiric/nova/reconcile-with-security
Merge into: lp:ubuntu/oneiric-proposed/nova
Diff against target: 1610 lines (+1514/-9)
10 files modified
.pc/applied-patches (+1/-0)
.pc/security-fix-lp868360.patch/Authors (+125/-0)
.pc/security-fix-lp868360.patch/nova/api/ec2/__init__.py (+440/-0)
.pc/security-fix-lp868360.patch/nova/auth/manager.py (+842/-0)
Authors (+1/-0)
debian/changelog (+30/-0)
debian/patches/security-fix-lp868360.patch (+70/-0)
debian/patches/series (+1/-0)
nova/api/ec2/__init__.py (+2/-1)
nova/auth/manager.py (+2/-8)
To merge this branch: bzr merge lp:~soren/ubuntu/oneiric/nova/reconcile-with-security
Reviewer Review Type Date Requested Status
Ubuntu Development Team Pending
Review via email: mp+80562@code.launchpad.net

Description of the change

Merged oneiric-security into oneiric-proposed, added changelog entry.

To post a comment you must log in.
Revision history for this message
Dave Walker (davewalker) wrote :

Hey Soren, did this package ever hit -proposed archive pocket? It's not clear to me why this is needed?

Revision history for this message
Soren Hansen (soren) wrote :

We were working on a package update for -proposed. I forget if it ever landed, but I don't think it did.

However, as this work was progressing, a security update landed in -security. We obviously want to fold that security updates into our -proposed branch. That's what this branch does.

Unmerged revisions

47. By Soren Hansen

Merge update from oneiric-security and add new changelog entry for -proposed:
* 2011.3-0ubuntu6.1 never made it past -proposed, due to 2011.3-
  0ubuntu6.2 which was a security fix (without the changes from
  2011.3-0ubuntu6.1). This upload brings the changes from 2011.3-
  0ubuntu6.1 back.
* SECURITY UPDATE: fix information leak via invalid key
  debina/patches/security-fix-lp868360.patch: adjust nova/auth/manager.py
  to not return access, secret or admin fields for User error and
  project_manager_id, description and member_ids for Project
  - LP: #868360
  - CVE-2011-XXXX

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.pc/applied-patches'
2--- .pc/applied-patches 2011-10-12 14:33:25 +0000
3+++ .pc/applied-patches 2011-10-27 13:05:25 +0000
4@@ -11,3 +11,4 @@
5 fix-lp863305-images-permission.patch
6 fix-lp838581-removed-db_pool-complexities.patch
7 fix-iscsi-target-path.patch
8+security-fix-lp868360.patch
9
10=== added directory '.pc/security-fix-lp868360.patch'
11=== added file '.pc/security-fix-lp868360.patch/Authors'
12--- .pc/security-fix-lp868360.patch/Authors 1970-01-01 00:00:00 +0000
13+++ .pc/security-fix-lp868360.patch/Authors 2011-10-27 13:05:25 +0000
14@@ -0,0 +1,125 @@
15+Adam Gandelman <adamg@canonical.com>
16+Adam Johnson <adjohn@gmail.com>
17+Alex Meade <alex.meade@rackspace.com>
18+Alexander Sakhnov <asakhnov@mirantis.com>
19+Andrey Brindeyev <abrindeyev@griddynamics.com>
20+Andy Smith <code@term.ie>
21+Andy Southgate <andy.southgate@citrix.com>
22+Anne Gentle <anne@openstack.org>
23+Anthony Young <sleepsonthefloor@gmail.com>
24+Antony Messerli <ant@openstack.org>
25+Armando Migliaccio <Armando.Migliaccio@eu.citrix.com>
26+Arvind Somya <asomya@cisco.com>
27+Bilal Akhtar <bilalakhtar@ubuntu.com>
28+Brad Hall <brad@nicira.com>
29+Brad McConnell <bmcconne@rackspace.com>
30+Brian Lamar <brian.lamar@rackspace.com>
31+Brian Schott <bschott@isi.edu>
32+Brian Waldon <brian.waldon@rackspace.com>
33+Chiradeep Vittal <chiradeep@cloud.com>
34+Chmouel Boudjnah <chmouel@chmouel.com>
35+Chris Behrens <cbehrens@codestud.com>
36+Christian Berendt <berendt@b1-systems.de>
37+Christopher MacGown <chris@pistoncloud.com>
38+Chuck Short <zulcss@ubuntu.com>
39+Cory Wright <corywright@gmail.com>
40+Dan Prince <dan.prince@rackspace.com>
41+Dan Wendlandt <dan@nicira.com>
42+Dave Walker <DaveWalker@ubuntu.com>
43+David Pravec <David.Pravec@danix.org>
44+David Subiros <david.perez5@hp.com>
45+Dean Troyer <dtroyer@gmail.com>
46+Devendra Modium <dmodium@isi.edu>
47+Devin Carlen <devin.carlen@gmail.com>
48+Donal Lafferty <donal.lafferty@citrix.com>
49+Ed Leafe <ed@leafe.com>
50+Edouard Thuleau <thuleau@gmail.com>
51+Eldar Nugaev <reldan@oscloud.ru>
52+Eric Day <eday@oddments.org>
53+Eric Windisch <eric@cloudscaling.com>
54+Ewan Mellor <ewan.mellor@citrix.com>
55+Gabe Westmaas <gabe.westmaas@rackspace.com>
56+Hisaharu Ishii <ishii.hisaharu@lab.ntt.co.jp>
57+Hisaki Ohara <hisaki.ohara@intel.com>
58+Ilya Alekseyev <ilyaalekseyev@acm.org>
59+Isaku Yamahata <yamahata@valinux.co.jp>
60+Jake Dahn <jake@ansolabs.com>
61+Jason Cannavale <jason.cannavale@rackspace.com>
62+Jason Koelker <jason@koelker.net>
63+Jay Pipes <jaypipes@gmail.com>
64+Jesse Andrews <anotherjesse@gmail.com>
65+Jimmy Bergman <jimmy@sigint.se>
66+Joe Heck <heckj@mac.com>
67+Joel Moore <joelbm24@gmail.com>
68+Johannes Erdfelt <johannes.erdfelt@rackspace.com>
69+John Dewey <john@dewey.ws>
70+John Tran <jtran@attinteractive.com>
71+Jonathan Bryce <jbryce@jbryce.com>
72+Jordan Rinke <jordan@openstack.org>
73+Joseph Suh <jsuh@isi.edu>
74+Josh Durgin <joshd@hq.newdream.net>
75+Josh Kearney <josh@jk0.org>
76+Josh Kleinpeter <josh@kleinpeter.org>
77+Joshua McKenty <jmckenty@gmail.com>
78+Justin Santa Barbara <justin@fathomdb.com>
79+Justin Shepherd <jshepher@rackspace.com>
80+Kei Masumoto <masumotok@nttdata.co.jp>
81+Keisuke Tagami <tagami.keisuke@lab.ntt.co.jp>
82+masumoto<masumotok@nttdata.co.jp>
83+Ken Pepple <ken.pepple@gmail.com>
84+Kevin Bringard <kbringard@attinteractive.com>
85+Kevin L. Mitchell <kevin.mitchell@rackspace.com>
86+Kirill Shileev <kshileev@gmail.com>
87+Koji Iida <iida.koji@lab.ntt.co.jp>
88+Loganathan Parthipan <parthipan@hp.com>
89+Lorin Hochstein <lorin@isi.edu>
90+Lvov Maxim <usrleon@gmail.com>
91+Mandell Degerness <mdegerne@gmail.com>
92+Mark McLoughlin <markmc@redhat.com>
93+Mark Washenberger <mark.washenberger@rackspace.com>
94+Masanori Itoh <itoumsn@nttdata.co.jp>
95+Matt Dietz <matt.dietz@rackspace.com>
96+Matthew Hooker <matt@cloudscaling.com>
97+Michael Gundlach <michael.gundlach@rackspace.com>
98+Mike Scherbakov <mihgen@gmail.com>
99+Mohammed Naser <mnaser@vexxhost.com>
100+Monsyne Dragon <mdragon@rackspace.com>
101+Monty Taylor <mordred@inaugust.com>
102+MORITA Kazutaka <morita.kazutaka@gmail.com>
103+Muneyuki Noguchi <noguchimn@nttdata.co.jp>
104+Nachi Ueno <ueno.nachi@lab.ntt.co.jp>
105+Naveed Massjouni <naveedm9@gmail.com>
106+Nikolay Sokolov <nsokolov@griddynamics.com>
107+Nirmal Ranganathan <nirmal.ranganathan@rackspace.com>
108+Paul Voccio <paul@openstack.org>
109+Renuka Apte <renuka.apte@citrix.com>
110+Ricardo Carrillo Cruz <emaildericky@gmail.com>
111+Rick Clark <rick@openstack.org>
112+Rick Harris <rconradharris@gmail.com>
113+Rob Kost <kost@isi.edu>
114+Robie Basak <robie.basak@canonical.com>
115+Ryan Lane <rlane@wikimedia.org>
116+Ryan Lucio <rlucio@internap.com>
117+Ryu Ishimoto <ryu@midokura.jp>
118+Salvatore Orlando <salvatore.orlando@eu.citrix.com>
119+Sandy Walsh <sandy.walsh@rackspace.com>
120+Sateesh Chodapuneedi <sateesh.chodapuneedi@citrix.com>
121+Scott Moser <smoser@ubuntu.com>
122+Soren Hansen <soren.hansen@rackspace.com>
123+Stephanie Reese <reese.sm@gmail.com>
124+Thierry Carrez <thierry@openstack.org>
125+Tim Simpson <tim.simpson@rackspace.com>
126+Todd Willey <todd@ansolabs.com>
127+Trey Morris <trey.morris@rackspace.com>
128+Troy Toman <troy.toman@rackspace.com>
129+Tushar Patil <tushar.vitthal.patil@gmail.com>
130+Vasiliy Shlykov <vash@vasiliyshlykov.org>
131+Vishvananda Ishaya <vishvananda@gmail.com>
132+Vivek Y S <vivek.ys@gmail.com>
133+Vladimir Popovski <vladimir@zadarastorage.com>
134+William Wolf <throughnothing@gmail.com>
135+Yoshiaki Tamura <yoshi@midokura.jp>
136+Youcef Laribi <Youcef.Laribi@eu.citrix.com>
137+Yuriy Taraday <yorik.sar@gmail.com>
138+Zhixue Wu <Zhixue.Wu@citrix.com>
139+Zed Shaw <zedshaw@zedshaw.com>
140
141=== added directory '.pc/security-fix-lp868360.patch/nova'
142=== added directory '.pc/security-fix-lp868360.patch/nova/api'
143=== added directory '.pc/security-fix-lp868360.patch/nova/api/ec2'
144=== added file '.pc/security-fix-lp868360.patch/nova/api/ec2/__init__.py'
145--- .pc/security-fix-lp868360.patch/nova/api/ec2/__init__.py 1970-01-01 00:00:00 +0000
146+++ .pc/security-fix-lp868360.patch/nova/api/ec2/__init__.py 2011-10-27 13:05:25 +0000
147@@ -0,0 +1,440 @@
148+# vim: tabstop=4 shiftwidth=4 softtabstop=4
149+
150+# Copyright 2010 United States Government as represented by the
151+# Administrator of the National Aeronautics and Space Administration.
152+# All Rights Reserved.
153+#
154+# Licensed under the Apache License, Version 2.0 (the "License"); you may
155+# not use this file except in compliance with the License. You may obtain
156+# a copy of the License at
157+#
158+# http://www.apache.org/licenses/LICENSE-2.0
159+#
160+# Unless required by applicable law or agreed to in writing, software
161+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
162+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
163+# License for the specific language governing permissions and limitations
164+# under the License.
165+"""
166+Starting point for routing EC2 requests.
167+
168+"""
169+
170+from urlparse import urlparse
171+
172+import eventlet
173+from eventlet.green import httplib
174+import webob
175+import webob.dec
176+import webob.exc
177+
178+from nova import context
179+from nova import exception
180+from nova import flags
181+from nova import log as logging
182+from nova import utils
183+from nova import wsgi
184+from nova.api.ec2 import apirequest
185+from nova.api.ec2 import ec2utils
186+from nova.auth import manager
187+
188+FLAGS = flags.FLAGS
189+LOG = logging.getLogger("nova.api")
190+flags.DEFINE_integer('lockout_attempts', 5,
191+ 'Number of failed auths before lockout.')
192+flags.DEFINE_integer('lockout_minutes', 15,
193+ 'Number of minutes to lockout if triggered.')
194+flags.DEFINE_integer('lockout_window', 15,
195+ 'Number of minutes for lockout window.')
196+flags.DECLARE('use_forwarded_for', 'nova.api.auth')
197+
198+
199+class RequestLogging(wsgi.Middleware):
200+ """Access-Log akin logging for all EC2 API requests."""
201+
202+ @webob.dec.wsgify(RequestClass=wsgi.Request)
203+ def __call__(self, req):
204+ start = utils.utcnow()
205+ rv = req.get_response(self.application)
206+ self.log_request_completion(rv, req, start)
207+ return rv
208+
209+ def log_request_completion(self, response, request, start):
210+ apireq = request.environ.get('ec2.request', None)
211+ if apireq:
212+ controller = apireq.controller
213+ action = apireq.action
214+ else:
215+ controller = None
216+ action = None
217+ ctxt = request.environ.get('nova.context', None)
218+ delta = utils.utcnow() - start
219+ seconds = delta.seconds
220+ microseconds = delta.microseconds
221+ LOG.info(
222+ "%s.%ss %s %s %s %s:%s %s [%s] %s %s",
223+ seconds,
224+ microseconds,
225+ request.remote_addr,
226+ request.method,
227+ "%s%s" % (request.script_name, request.path_info),
228+ controller,
229+ action,
230+ response.status_int,
231+ request.user_agent,
232+ request.content_type,
233+ response.content_type,
234+ context=ctxt)
235+
236+
237+class Lockout(wsgi.Middleware):
238+ """Lockout for x minutes on y failed auths in a z minute period.
239+
240+ x = lockout_timeout flag
241+ y = lockout_window flag
242+ z = lockout_attempts flag
243+
244+ Uses memcached if lockout_memcached_servers flag is set, otherwise it
245+ uses a very simple in-proccess cache. Due to the simplicity of
246+ the implementation, the timeout window is started with the first
247+ failed request, so it will block if there are x failed logins within
248+ that period.
249+
250+ There is a possible race condition where simultaneous requests could
251+ sneak in before the lockout hits, but this is extremely rare and would
252+ only result in a couple of extra failed attempts."""
253+
254+ def __init__(self, application):
255+ """middleware can use fake for testing."""
256+ if FLAGS.memcached_servers:
257+ import memcache
258+ else:
259+ from nova import fakememcache as memcache
260+ self.mc = memcache.Client(FLAGS.memcached_servers,
261+ debug=0)
262+ super(Lockout, self).__init__(application)
263+
264+ @webob.dec.wsgify(RequestClass=wsgi.Request)
265+ def __call__(self, req):
266+ access_key = str(req.params['AWSAccessKeyId'])
267+ failures_key = "authfailures-%s" % access_key
268+ failures = int(self.mc.get(failures_key) or 0)
269+ if failures >= FLAGS.lockout_attempts:
270+ detail = _("Too many failed authentications.")
271+ raise webob.exc.HTTPForbidden(detail=detail)
272+ res = req.get_response(self.application)
273+ if res.status_int == 403:
274+ failures = self.mc.incr(failures_key)
275+ if failures is None:
276+ # NOTE(vish): To use incr, failures has to be a string.
277+ self.mc.set(failures_key, '1', time=FLAGS.lockout_window * 60)
278+ elif failures >= FLAGS.lockout_attempts:
279+ lock_mins = FLAGS.lockout_minutes
280+ msg = _('Access key %(access_key)s has had %(failures)d'
281+ ' failed authentications and will be locked out'
282+ ' for %(lock_mins)d minutes.') % locals()
283+ LOG.warn(msg)
284+ self.mc.set(failures_key, str(failures),
285+ time=FLAGS.lockout_minutes * 60)
286+ return res
287+
288+
289+class NoAuth(wsgi.Middleware):
290+ """Add user:project as 'nova.context' to WSGI environ."""
291+
292+ @webob.dec.wsgify(RequestClass=wsgi.Request)
293+ def __call__(self, req):
294+ if 'AWSAccessKeyId' not in req.params:
295+ raise webob.exc.HTTPBadRequest()
296+ user_id, _sep, project_id = req.params['AWSAccessKeyId'].partition(':')
297+ project_id = project_id or user_id
298+ remote_address = getattr(req, 'remote_address', '127.0.0.1')
299+ if FLAGS.use_forwarded_for:
300+ remote_address = req.headers.get('X-Forwarded-For', remote_address)
301+ ctx = context.RequestContext(user_id,
302+ project_id,
303+ is_admin=True,
304+ remote_address=remote_address)
305+
306+ req.environ['nova.context'] = ctx
307+ return self.application
308+
309+
310+class Authenticate(wsgi.Middleware):
311+ """Authenticate an EC2 request and add 'nova.context' to WSGI environ."""
312+
313+ @webob.dec.wsgify(RequestClass=wsgi.Request)
314+ def __call__(self, req):
315+ # Read request signature and access id.
316+ try:
317+ signature = req.params['Signature']
318+ access = req.params['AWSAccessKeyId']
319+ except KeyError:
320+ raise webob.exc.HTTPBadRequest()
321+
322+ # Make a copy of args for authentication and signature verification.
323+ auth_params = dict(req.params)
324+ # Not part of authentication args
325+ auth_params.pop('Signature')
326+
327+ # Authenticate the request.
328+ authman = manager.AuthManager()
329+ try:
330+ (user, project) = authman.authenticate(
331+ access,
332+ signature,
333+ auth_params,
334+ req.method,
335+ req.host,
336+ req.path)
337+ # Be explicit for what exceptions are 403, the rest bubble as 500
338+ except (exception.NotFound, exception.NotAuthorized) as ex:
339+ LOG.audit(_("Authentication Failure: %s"), unicode(ex))
340+ raise webob.exc.HTTPForbidden()
341+
342+ # Authenticated!
343+ remote_address = req.remote_addr
344+ if FLAGS.use_forwarded_for:
345+ remote_address = req.headers.get('X-Forwarded-For', remote_address)
346+ roles = authman.get_active_roles(user, project)
347+ ctxt = context.RequestContext(user_id=user.id,
348+ project_id=project.id,
349+ is_admin=user.is_admin(),
350+ roles=roles,
351+ remote_address=remote_address)
352+ req.environ['nova.context'] = ctxt
353+ uname = user.name
354+ pname = project.name
355+ msg = _('Authenticated Request For %(uname)s:%(pname)s)') % locals()
356+ LOG.audit(msg, context=req.environ['nova.context'])
357+ return self.application
358+
359+
360+class Requestify(wsgi.Middleware):
361+
362+ def __init__(self, app, controller):
363+ super(Requestify, self).__init__(app)
364+ self.controller = utils.import_class(controller)()
365+
366+ @webob.dec.wsgify(RequestClass=wsgi.Request)
367+ def __call__(self, req):
368+ non_args = ['Action', 'Signature', 'AWSAccessKeyId', 'SignatureMethod',
369+ 'SignatureVersion', 'Version', 'Timestamp']
370+ args = dict(req.params)
371+ try:
372+ # Raise KeyError if omitted
373+ action = req.params['Action']
374+ # Fix bug lp:720157 for older (version 1) clients
375+ version = req.params['SignatureVersion']
376+ if int(version) == 1:
377+ non_args.remove('SignatureMethod')
378+ if 'SignatureMethod' in args:
379+ args.pop('SignatureMethod')
380+ for non_arg in non_args:
381+ # Remove, but raise KeyError if omitted
382+ args.pop(non_arg)
383+ except KeyError, e:
384+ raise webob.exc.HTTPBadRequest()
385+
386+ LOG.debug(_('action: %s'), action)
387+ for key, value in args.items():
388+ LOG.debug(_('arg: %(key)s\t\tval: %(value)s') % locals())
389+
390+ # Success!
391+ api_request = apirequest.APIRequest(self.controller, action,
392+ req.params['Version'], args)
393+ req.environ['ec2.request'] = api_request
394+ req.environ['ec2.action_args'] = args
395+ return self.application
396+
397+
398+class Authorizer(wsgi.Middleware):
399+
400+ """Authorize an EC2 API request.
401+
402+ Return a 401 if ec2.controller and ec2.action in WSGI environ may not be
403+ executed in nova.context.
404+ """
405+
406+ def __init__(self, application):
407+ super(Authorizer, self).__init__(application)
408+ self.action_roles = {
409+ 'CloudController': {
410+ 'DescribeAvailabilityZones': ['all'],
411+ 'DescribeRegions': ['all'],
412+ 'DescribeSnapshots': ['all'],
413+ 'DescribeKeyPairs': ['all'],
414+ 'CreateKeyPair': ['all'],
415+ 'DeleteKeyPair': ['all'],
416+ 'DescribeSecurityGroups': ['all'],
417+ 'ImportPublicKey': ['all'],
418+ 'AuthorizeSecurityGroupIngress': ['netadmin'],
419+ 'RevokeSecurityGroupIngress': ['netadmin'],
420+ 'CreateSecurityGroup': ['netadmin'],
421+ 'DeleteSecurityGroup': ['netadmin'],
422+ 'GetConsoleOutput': ['projectmanager', 'sysadmin'],
423+ 'DescribeVolumes': ['projectmanager', 'sysadmin'],
424+ 'CreateVolume': ['projectmanager', 'sysadmin'],
425+ 'AttachVolume': ['projectmanager', 'sysadmin'],
426+ 'DetachVolume': ['projectmanager', 'sysadmin'],
427+ 'DescribeInstances': ['all'],
428+ 'DescribeAddresses': ['all'],
429+ 'AllocateAddress': ['netadmin'],
430+ 'ReleaseAddress': ['netadmin'],
431+ 'AssociateAddress': ['netadmin'],
432+ 'DisassociateAddress': ['netadmin'],
433+ 'RunInstances': ['projectmanager', 'sysadmin'],
434+ 'TerminateInstances': ['projectmanager', 'sysadmin'],
435+ 'RebootInstances': ['projectmanager', 'sysadmin'],
436+ 'UpdateInstance': ['projectmanager', 'sysadmin'],
437+ 'StartInstances': ['projectmanager', 'sysadmin'],
438+ 'StopInstances': ['projectmanager', 'sysadmin'],
439+ 'DeleteVolume': ['projectmanager', 'sysadmin'],
440+ 'DescribeImages': ['all'],
441+ 'DeregisterImage': ['projectmanager', 'sysadmin'],
442+ 'RegisterImage': ['projectmanager', 'sysadmin'],
443+ 'DescribeImageAttribute': ['all'],
444+ 'ModifyImageAttribute': ['projectmanager', 'sysadmin'],
445+ 'UpdateImage': ['projectmanager', 'sysadmin'],
446+ 'CreateImage': ['projectmanager', 'sysadmin'],
447+ },
448+ 'AdminController': {
449+ # All actions have the same permission: ['none'] (the default)
450+ # superusers will be allowed to run them
451+ # all others will get HTTPUnauthorized.
452+ },
453+ }
454+
455+ @webob.dec.wsgify(RequestClass=wsgi.Request)
456+ def __call__(self, req):
457+ context = req.environ['nova.context']
458+ controller = req.environ['ec2.request'].controller.__class__.__name__
459+ action = req.environ['ec2.request'].action
460+ allowed_roles = self.action_roles[controller].get(action, ['none'])
461+ if self._matches_any_role(context, allowed_roles):
462+ return self.application
463+ else:
464+ LOG.audit(_('Unauthorized request for controller=%(controller)s '
465+ 'and action=%(action)s') % locals(), context=context)
466+ raise webob.exc.HTTPUnauthorized()
467+
468+ def _matches_any_role(self, context, roles):
469+ """Return True if any role in roles is allowed in context."""
470+ if context.is_admin:
471+ return True
472+ if 'all' in roles:
473+ return True
474+ if 'none' in roles:
475+ return False
476+ return any(role in context.roles for role in roles)
477+
478+
479+class Executor(wsgi.Application):
480+
481+ """Execute an EC2 API request.
482+
483+ Executes 'ec2.action' upon 'ec2.controller', passing 'nova.context' and
484+ 'ec2.action_args' (all variables in WSGI environ.) Returns an XML
485+ response, or a 400 upon failure.
486+ """
487+
488+ @webob.dec.wsgify(RequestClass=wsgi.Request)
489+ def __call__(self, req):
490+ context = req.environ['nova.context']
491+ api_request = req.environ['ec2.request']
492+ result = None
493+ try:
494+ result = api_request.invoke(context)
495+ except exception.InstanceNotFound as ex:
496+ LOG.info(_('InstanceNotFound raised: %s'), unicode(ex),
497+ context=context)
498+ ec2_id = ec2utils.id_to_ec2_id(ex.kwargs['instance_id'])
499+ message = ex.message % {'instance_id': ec2_id}
500+ return self._error(req, context, type(ex).__name__, message)
501+ except exception.VolumeNotFound as ex:
502+ LOG.info(_('VolumeNotFound raised: %s'), unicode(ex),
503+ context=context)
504+ ec2_id = ec2utils.id_to_ec2_vol_id(ex.kwargs['volume_id'])
505+ message = ex.message % {'volume_id': ec2_id}
506+ return self._error(req, context, type(ex).__name__, message)
507+ except exception.SnapshotNotFound as ex:
508+ LOG.info(_('SnapshotNotFound raised: %s'), unicode(ex),
509+ context=context)
510+ ec2_id = ec2utils.id_to_ec2_snap_id(ex.kwargs['snapshot_id'])
511+ message = ex.message % {'snapshot_id': ec2_id}
512+ return self._error(req, context, type(ex).__name__, message)
513+ except exception.NotFound as ex:
514+ LOG.info(_('NotFound raised: %s'), unicode(ex), context=context)
515+ return self._error(req, context, type(ex).__name__, unicode(ex))
516+ except exception.ApiError as ex:
517+ LOG.exception(_('ApiError raised: %s'), unicode(ex),
518+ context=context)
519+ if ex.code:
520+ return self._error(req, context, ex.code, unicode(ex))
521+ else:
522+ return self._error(req, context, type(ex).__name__,
523+ unicode(ex))
524+ except exception.KeyPairExists as ex:
525+ LOG.debug(_('KeyPairExists raised: %s'), unicode(ex),
526+ context=context)
527+ return self._error(req, context, type(ex).__name__, unicode(ex))
528+ except exception.InvalidParameterValue as ex:
529+ LOG.debug(_('InvalidParameterValue raised: %s'), unicode(ex),
530+ context=context)
531+ return self._error(req, context, type(ex).__name__, unicode(ex))
532+ except exception.InvalidPortRange as ex:
533+ LOG.debug(_('InvalidPortRange raised: %s'), unicode(ex),
534+ context=context)
535+ return self._error(req, context, type(ex).__name__, unicode(ex))
536+ except exception.NotAuthorized as ex:
537+ LOG.info(_('NotAuthorized raised: %s'), unicode(ex),
538+ context=context)
539+ return self._error(req, context, type(ex).__name__, unicode(ex))
540+ except Exception as ex:
541+ extra = {'environment': req.environ}
542+ LOG.exception(_('Unexpected error raised: %s'), unicode(ex),
543+ extra=extra, context=context)
544+ return self._error(req,
545+ context,
546+ 'UnknownError',
547+ _('An unknown error has occurred. '
548+ 'Please try your request again.'))
549+ else:
550+ resp = webob.Response()
551+ resp.status = 200
552+ resp.headers['Content-Type'] = 'text/xml'
553+ resp.body = str(result)
554+ return resp
555+
556+ def _error(self, req, context, code, message):
557+ LOG.error("%s: %s", code, message, context=context)
558+ resp = webob.Response()
559+ resp.status = 400
560+ resp.headers['Content-Type'] = 'text/xml'
561+ resp.body = str('<?xml version="1.0"?>\n'
562+ '<Response><Errors><Error><Code>%s</Code>'
563+ '<Message>%s</Message></Error></Errors>'
564+ '<RequestID>%s</RequestID></Response>' %
565+ (utils.utf8(code), utils.utf8(message),
566+ utils.utf8(context.request_id)))
567+ return resp
568+
569+
570+class Versions(wsgi.Application):
571+
572+ @webob.dec.wsgify(RequestClass=wsgi.Request)
573+ def __call__(self, req):
574+ """Respond to a request for all EC2 versions."""
575+ # available api versions
576+ versions = [
577+ '1.0',
578+ '2007-01-19',
579+ '2007-03-01',
580+ '2007-08-29',
581+ '2007-10-10',
582+ '2007-12-15',
583+ '2008-02-01',
584+ '2008-09-01',
585+ '2009-04-04',
586+ ]
587+ return ''.join('%s\n' % v for v in versions)
588
589=== added directory '.pc/security-fix-lp868360.patch/nova/auth'
590=== added file '.pc/security-fix-lp868360.patch/nova/auth/manager.py'
591--- .pc/security-fix-lp868360.patch/nova/auth/manager.py 1970-01-01 00:00:00 +0000
592+++ .pc/security-fix-lp868360.patch/nova/auth/manager.py 2011-10-27 13:05:25 +0000
593@@ -0,0 +1,842 @@
594+# vim: tabstop=4 shiftwidth=4 softtabstop=4
595+
596+# Copyright 2010 United States Government as represented by the
597+# Administrator of the National Aeronautics and Space Administration.
598+# All Rights Reserved.
599+#
600+# Licensed under the Apache License, Version 2.0 (the "License"); you may
601+# not use this file except in compliance with the License. You may obtain
602+# a copy of the License at
603+#
604+# http://www.apache.org/licenses/LICENSE-2.0
605+#
606+# Unless required by applicable law or agreed to in writing, software
607+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
608+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
609+# License for the specific language governing permissions and limitations
610+# under the License.
611+
612+"""
613+WARNING: This code is deprecated and will be removed.
614+Keystone is the recommended solution for auth management.
615+
616+Nova authentication management
617+"""
618+
619+import os
620+import shutil
621+import string # pylint: disable=W0402
622+import tempfile
623+import uuid
624+import zipfile
625+
626+from nova import context
627+from nova import crypto
628+from nova import db
629+from nova import exception
630+from nova import flags
631+from nova import log as logging
632+from nova import utils
633+from nova.auth import signer
634+
635+
636+FLAGS = flags.FLAGS
637+flags.DEFINE_bool('use_deprecated_auth',
638+ False,
639+ 'This flag must be set to use old style auth')
640+
641+flags.DEFINE_list('allowed_roles',
642+ ['cloudadmin', 'itsec', 'sysadmin', 'netadmin', 'developer'],
643+ 'Allowed roles for project')
644+# NOTE(vish): a user with one of these roles will be a superuser and
645+# have access to all api commands
646+flags.DEFINE_list('superuser_roles', ['cloudadmin'],
647+ 'Roles that ignore authorization checking completely')
648+
649+# NOTE(vish): a user with one of these roles will have it for every
650+# project, even if he or she is not a member of the project
651+flags.DEFINE_list('global_roles', ['cloudadmin', 'itsec'],
652+ 'Roles that apply to all projects')
653+
654+flags.DEFINE_string('credentials_template',
655+ utils.abspath('auth/novarc.template'),
656+ 'Template for creating users rc file')
657+flags.DEFINE_string('vpn_client_template',
658+ utils.abspath('cloudpipe/client.ovpn.template'),
659+ 'Template for creating users vpn file')
660+flags.DEFINE_string('credential_vpn_file', 'nova-vpn.conf',
661+ 'Filename of certificate in credentials zip')
662+flags.DEFINE_string('credential_key_file', 'pk.pem',
663+ 'Filename of private key in credentials zip')
664+flags.DEFINE_string('credential_cert_file', 'cert.pem',
665+ 'Filename of certificate in credentials zip')
666+flags.DEFINE_string('credential_rc_file', '%src',
667+ 'Filename of rc in credentials zip, %s will be '
668+ 'replaced by name of the region (nova by default)')
669+flags.DEFINE_string('auth_driver', 'nova.auth.dbdriver.DbDriver',
670+ 'Driver that auth manager uses')
671+
672+LOG = logging.getLogger('nova.auth.manager')
673+
674+
675+if FLAGS.memcached_servers:
676+ import memcache
677+else:
678+ from nova import fakememcache as memcache
679+
680+
681+class AuthBase(object):
682+ """Base class for objects relating to auth
683+
684+ Objects derived from this class should be stupid data objects with
685+ an id member. They may optionally contain methods that delegate to
686+ AuthManager, but should not implement logic themselves.
687+ """
688+
689+ @classmethod
690+ def safe_id(cls, obj):
691+ """Safely get object id.
692+
693+ This method will return the id of the object if the object
694+ is of this class, otherwise it will return the original object.
695+ This allows methods to accept objects or ids as paramaters.
696+ """
697+ if isinstance(obj, cls):
698+ return obj.id
699+ else:
700+ return obj
701+
702+
703+class User(AuthBase):
704+ """Object representing a user
705+
706+ The following attributes are defined:
707+ :id: A system identifier for the user. A string (for LDAP)
708+ :name: The user name, potentially in some more friendly format
709+ :access: The 'username' for EC2 authentication
710+ :secret: The 'password' for EC2 authenticatoin
711+ :admin: ???
712+ """
713+
714+ def __init__(self, id, name, access, secret, admin):
715+ AuthBase.__init__(self)
716+ assert isinstance(id, basestring)
717+ self.id = id
718+ self.name = name
719+ self.access = access
720+ self.secret = secret
721+ self.admin = admin
722+
723+ def is_superuser(self):
724+ return AuthManager().is_superuser(self)
725+
726+ def is_admin(self):
727+ return AuthManager().is_admin(self)
728+
729+ def has_role(self, role):
730+ return AuthManager().has_role(self, role)
731+
732+ def add_role(self, role):
733+ return AuthManager().add_role(self, role)
734+
735+ def remove_role(self, role):
736+ return AuthManager().remove_role(self, role)
737+
738+ def is_project_member(self, project):
739+ return AuthManager().is_project_member(self, project)
740+
741+ def is_project_manager(self, project):
742+ return AuthManager().is_project_manager(self, project)
743+
744+ def __repr__(self):
745+ return "User('%s', '%s', '%s', '%s', %s)" % (self.id,
746+ self.name,
747+ self.access,
748+ self.secret,
749+ self.admin)
750+
751+
752+class Project(AuthBase):
753+ """Represents a Project returned from the datastore"""
754+
755+ def __init__(self, id, name, project_manager_id, description, member_ids):
756+ AuthBase.__init__(self)
757+ self.id = id
758+ self.name = name
759+ self.project_manager_id = project_manager_id
760+ self.description = description
761+ self.member_ids = member_ids
762+
763+ @property
764+ def project_manager(self):
765+ return AuthManager().get_user(self.project_manager_id)
766+
767+ @property
768+ def vpn_ip(self):
769+ ip, _port = AuthManager().get_project_vpn_data(self)
770+ return ip
771+
772+ @property
773+ def vpn_port(self):
774+ _ip, port = AuthManager().get_project_vpn_data(self)
775+ return port
776+
777+ def has_manager(self, user):
778+ return AuthManager().is_project_manager(user, self)
779+
780+ def has_member(self, user):
781+ return AuthManager().is_project_member(user, self)
782+
783+ def add_role(self, user, role):
784+ return AuthManager().add_role(user, role, self)
785+
786+ def remove_role(self, user, role):
787+ return AuthManager().remove_role(user, role, self)
788+
789+ def has_role(self, user, role):
790+ return AuthManager().has_role(user, role, self)
791+
792+ def get_credentials(self, user):
793+ return AuthManager().get_credentials(user, self)
794+
795+ def __repr__(self):
796+ return "Project('%s', '%s', '%s', '%s', %s)" % \
797+ (self.id, self.name, self.project_manager_id, self.description,
798+ self.member_ids)
799+
800+
801+class AuthManager(object):
802+ """Manager Singleton for dealing with Users, Projects, and Keypairs
803+
804+ Methods accept objects or ids.
805+
806+ AuthManager uses a driver object to make requests to the data backend.
807+ See ldapdriver for reference.
808+
809+ AuthManager also manages associated data related to Auth objects that
810+ need to be more accessible, such as vpn ips and ports.
811+ """
812+
813+ _instance = None
814+ mc = None
815+
816+ def __new__(cls, *args, **kwargs):
817+ """Returns the AuthManager singleton"""
818+ if not cls._instance or ('new' in kwargs and kwargs['new']):
819+ cls._instance = super(AuthManager, cls).__new__(cls)
820+ return cls._instance
821+
822+ def __init__(self, driver=None, *args, **kwargs):
823+ """Inits the driver from parameter or flag
824+
825+ __init__ is run every time AuthManager() is called, so we only
826+ reset the driver if it is not set or a new driver is specified.
827+ """
828+ self.network_manager = utils.import_object(FLAGS.network_manager)
829+ if driver or not getattr(self, 'driver', None):
830+ self.driver = utils.import_class(driver or FLAGS.auth_driver)
831+ if AuthManager.mc is None:
832+ AuthManager.mc = memcache.Client(FLAGS.memcached_servers, debug=0)
833+
834+ def authenticate(self, access, signature, params, verb='GET',
835+ server_string='127.0.0.1:8773', path='/',
836+ check_type='ec2', headers=None):
837+ """Authenticates AWS request using access key and signature
838+
839+ If the project is not specified, attempts to authenticate to
840+ a project with the same name as the user. This way, older tools
841+ that have no project knowledge will still work.
842+
843+ @type access: str
844+ @param access: Access key for user in the form "access:project".
845+
846+ @type signature: str
847+ @param signature: Signature of the request.
848+
849+ @type params: list of str
850+ @param params: Web paramaters used for the signature.
851+
852+ @type verb: str
853+ @param verb: Web request verb ('GET' or 'POST').
854+
855+ @type server_string: str
856+ @param server_string: Web request server string.
857+
858+ @type path: str
859+ @param path: Web request path.
860+
861+ @type check_type: str
862+ @param check_type: Type of signature to check. 'ec2' for EC2, 's3' for
863+ S3. Any other value will cause signature not to be
864+ checked.
865+
866+ @type headers: list
867+ @param headers: HTTP headers passed with the request (only needed for
868+ s3 signature checks)
869+
870+ @rtype: tuple (User, Project)
871+ @return: User and project that the request represents.
872+ """
873+ # TODO(vish): check for valid timestamp
874+ (access_key, _sep, project_id) = access.partition(':')
875+
876+ LOG.debug(_('Looking up user: %r'), access_key)
877+ user = self.get_user_from_access_key(access_key)
878+ LOG.debug('user: %r', user)
879+ if user is None:
880+ LOG.audit(_("Failed authorization for access key %s"), access_key)
881+ raise exception.AccessKeyNotFound(access_key=access_key)
882+
883+ # NOTE(vish): if we stop using project name as id we need better
884+ # logic to find a default project for user
885+ if project_id == '':
886+ LOG.debug(_("Using project name = user name (%s)"), user.name)
887+ project_id = user.name
888+
889+ project = self.get_project(project_id)
890+ if project is None:
891+ pjid = project_id
892+ uname = user.name
893+ LOG.audit(_("failed authorization: no project named %(pjid)s"
894+ " (user=%(uname)s)") % locals())
895+ raise exception.ProjectNotFound(project_id=project_id)
896+ if not self.is_admin(user) and not self.is_project_member(user,
897+ project):
898+ uname = user.name
899+ uid = user.id
900+ pjname = project.name
901+ pjid = project.id
902+ LOG.audit(_("Failed authorization: user %(uname)s not admin"
903+ " and not member of project %(pjname)s") % locals())
904+ raise exception.ProjectMembershipNotFound(project_id=pjid,
905+ user_id=uid)
906+ if check_type == 's3':
907+ sign = signer.Signer(user.secret.encode())
908+ expected_signature = sign.s3_authorization(headers, verb, path)
909+ LOG.debug(_('user.secret: %s'), user.secret)
910+ LOG.debug(_('expected_signature: %s'), expected_signature)
911+ LOG.debug(_('signature: %s'), signature)
912+ if signature != expected_signature:
913+ LOG.audit(_("Invalid signature for user %s"), user.name)
914+ raise exception.InvalidSignature(signature=signature,
915+ user=user)
916+ elif check_type == 'ec2':
917+ # NOTE(vish): hmac can't handle unicode, so encode ensures that
918+ # secret isn't unicode
919+ expected_signature = signer.Signer(user.secret.encode()).generate(
920+ params, verb, server_string, path)
921+ LOG.debug(_('user.secret: %s'), user.secret)
922+ LOG.debug(_('expected_signature: %s'), expected_signature)
923+ LOG.debug(_('signature: %s'), signature)
924+ if signature != expected_signature:
925+ (addr_str, port_str) = utils.parse_server_string(server_string)
926+ # If the given server_string contains port num, try without it.
927+ if port_str != '':
928+ host_only_signature = signer.Signer(
929+ user.secret.encode()).generate(params, verb,
930+ addr_str, path)
931+ LOG.debug(_('host_only_signature: %s'),
932+ host_only_signature)
933+ if signature == host_only_signature:
934+ return (user, project)
935+ LOG.audit(_("Invalid signature for user %s"), user.name)
936+ raise exception.InvalidSignature(signature=signature,
937+ user=user)
938+ return (user, project)
939+
940+ def get_access_key(self, user, project):
941+ """Get an access key that includes user and project"""
942+ if not isinstance(user, User):
943+ user = self.get_user(user)
944+ return "%s:%s" % (user.access, Project.safe_id(project))
945+
946+ def is_superuser(self, user):
947+ """Checks for superuser status, allowing user to bypass authorization
948+
949+ @type user: User or uid
950+ @param user: User to check.
951+
952+ @rtype: bool
953+ @return: True for superuser.
954+ """
955+ if not isinstance(user, User):
956+ user = self.get_user(user)
957+ # NOTE(vish): admin flag on user represents superuser
958+ if user.admin:
959+ return True
960+ for role in FLAGS.superuser_roles:
961+ if self.has_role(user, role):
962+ return True
963+
964+ def is_admin(self, user):
965+ """Checks for admin status, allowing user to access all projects
966+
967+ @type user: User or uid
968+ @param user: User to check.
969+
970+ @rtype: bool
971+ @return: True for admin.
972+ """
973+ if not isinstance(user, User):
974+ user = self.get_user(user)
975+ if self.is_superuser(user):
976+ return True
977+ for role in FLAGS.global_roles:
978+ if self.has_role(user, role):
979+ return True
980+
981+ def _build_mc_key(self, user, role, project=None):
982+ key_parts = ['rolecache', User.safe_id(user), str(role)]
983+ if project:
984+ key_parts.append(Project.safe_id(project))
985+ return '-'.join(key_parts)
986+
987+ def _clear_mc_key(self, user, role, project=None):
988+ # NOTE(anthony): it would be better to delete the key
989+ self.mc.set(self._build_mc_key(user, role, project), None)
990+
991+ def _has_role(self, user, role, project=None):
992+ mc_key = self._build_mc_key(user, role, project)
993+ rslt = self.mc.get(mc_key)
994+ if rslt is None:
995+ with self.driver() as drv:
996+ rslt = drv.has_role(user, role, project)
997+ self.mc.set(mc_key, rslt)
998+ return rslt
999+ else:
1000+ return rslt
1001+
1002+ def has_role(self, user, role, project=None):
1003+ """Checks existence of role for user
1004+
1005+ If project is not specified, checks for a global role. If project
1006+ is specified, checks for the union of the global role and the
1007+ project role.
1008+
1009+ Role 'projectmanager' only works for projects and simply checks to
1010+ see if the user is the project_manager of the specified project. It
1011+ is the same as calling is_project_manager(user, project).
1012+
1013+ @type user: User or uid
1014+ @param user: User to check.
1015+
1016+ @type role: str
1017+ @param role: Role to check.
1018+
1019+ @type project: Project or project_id
1020+ @param project: Project in which to look for local role.
1021+
1022+ @rtype: bool
1023+ @return: True if the user has the role.
1024+ """
1025+ if role == 'projectmanager':
1026+ if not project:
1027+ raise exception.Error(_("Must specify project"))
1028+ return self.is_project_manager(user, project)
1029+
1030+ global_role = self._has_role(User.safe_id(user),
1031+ role,
1032+ None)
1033+
1034+ if not global_role:
1035+ return global_role
1036+
1037+ if not project or role in FLAGS.global_roles:
1038+ return global_role
1039+
1040+ return self._has_role(User.safe_id(user),
1041+ role,
1042+ Project.safe_id(project))
1043+
1044+ def add_role(self, user, role, project=None):
1045+ """Adds role for user
1046+
1047+ If project is not specified, adds a global role. If project
1048+ is specified, adds a local role.
1049+
1050+ The 'projectmanager' role is special and can't be added or removed.
1051+
1052+ @type user: User or uid
1053+ @param user: User to which to add role.
1054+
1055+ @type role: str
1056+ @param role: Role to add.
1057+
1058+ @type project: Project or project_id
1059+ @param project: Project in which to add local role.
1060+ """
1061+ if role not in FLAGS.allowed_roles:
1062+ raise exception.UserRoleNotFound(role_id=role)
1063+ if project is not None and role in FLAGS.global_roles:
1064+ raise exception.GlobalRoleNotAllowed(role_id=role)
1065+ uid = User.safe_id(user)
1066+ pid = Project.safe_id(project)
1067+ if project:
1068+ LOG.audit(_("Adding role %(role)s to user %(uid)s"
1069+ " in project %(pid)s") % locals())
1070+ else:
1071+ LOG.audit(_("Adding sitewide role %(role)s to user %(uid)s")
1072+ % locals())
1073+ with self.driver() as drv:
1074+ self._clear_mc_key(uid, role, pid)
1075+ drv.add_role(uid, role, pid)
1076+
1077+ def remove_role(self, user, role, project=None):
1078+ """Removes role for user
1079+
1080+ If project is not specified, removes a global role. If project
1081+ is specified, removes a local role.
1082+
1083+ The 'projectmanager' role is special and can't be added or removed.
1084+
1085+ @type user: User or uid
1086+ @param user: User from which to remove role.
1087+
1088+ @type role: str
1089+ @param role: Role to remove.
1090+
1091+ @type project: Project or project_id
1092+ @param project: Project in which to remove local role.
1093+ """
1094+ uid = User.safe_id(user)
1095+ pid = Project.safe_id(project)
1096+ if project:
1097+ LOG.audit(_("Removing role %(role)s from user %(uid)s"
1098+ " on project %(pid)s") % locals())
1099+ else:
1100+ LOG.audit(_("Removing sitewide role %(role)s"
1101+ " from user %(uid)s") % locals())
1102+ with self.driver() as drv:
1103+ self._clear_mc_key(uid, role, pid)
1104+ drv.remove_role(uid, role, pid)
1105+
1106+ @staticmethod
1107+ def get_roles(project_roles=True):
1108+ """Get list of allowed roles"""
1109+ if project_roles:
1110+ return list(set(FLAGS.allowed_roles) - set(FLAGS.global_roles))
1111+ else:
1112+ return FLAGS.allowed_roles
1113+
1114+ def get_user_roles(self, user, project=None):
1115+ """Get user global or per-project roles"""
1116+ with self.driver() as drv:
1117+ return drv.get_user_roles(User.safe_id(user),
1118+ Project.safe_id(project))
1119+
1120+ def get_active_roles(self, user, project=None):
1121+ """Get all active roles for context"""
1122+ if project:
1123+ roles = FLAGS.allowed_roles + ['projectmanager']
1124+ else:
1125+ roles = FLAGS.global_roles
1126+ return [role for role in roles if self.has_role(user, role, project)]
1127+
1128+ def get_project(self, pid):
1129+ """Get project object by id"""
1130+ with self.driver() as drv:
1131+ project_dict = drv.get_project(pid)
1132+ if project_dict:
1133+ return Project(**project_dict)
1134+
1135+ def get_projects(self, user=None):
1136+ """Retrieves list of projects, optionally filtered by user"""
1137+ with self.driver() as drv:
1138+ project_list = drv.get_projects(User.safe_id(user))
1139+ if not project_list:
1140+ return []
1141+ return [Project(**project_dict) for project_dict in project_list]
1142+
1143+ def create_project(self, name, manager_user, description=None,
1144+ member_users=None):
1145+ """Create a project
1146+
1147+ @type name: str
1148+ @param name: Name of the project to create. The name will also be
1149+ used as the project id.
1150+
1151+ @type manager_user: User or uid
1152+ @param manager_user: This user will be the project manager.
1153+
1154+ @type description: str
1155+ @param project: Description of the project. If no description is
1156+ specified, the name of the project will be used.
1157+
1158+ @type member_users: list of User or uid
1159+ @param: Initial project members. The project manager will always be
1160+ added as a member, even if he isn't specified in this list.
1161+
1162+ @rtype: Project
1163+ @return: The new project.
1164+ """
1165+ if member_users:
1166+ member_users = [User.safe_id(u) for u in member_users]
1167+ with self.driver() as drv:
1168+ project_dict = drv.create_project(name,
1169+ User.safe_id(manager_user),
1170+ description,
1171+ member_users)
1172+ if project_dict:
1173+ LOG.audit(_("Created project %(name)s with"
1174+ " manager %(manager_user)s") % locals())
1175+ project = Project(**project_dict)
1176+ return project
1177+
1178+ def modify_project(self, project, manager_user=None, description=None):
1179+ """Modify a project
1180+
1181+ @type name: Project or project_id
1182+ @param project: The project to modify.
1183+
1184+ @type manager_user: User or uid
1185+ @param manager_user: This user will be the new project manager.
1186+
1187+ @type description: str
1188+ @param project: This will be the new description of the project.
1189+
1190+ """
1191+ LOG.audit(_("modifying project %s"), Project.safe_id(project))
1192+ if manager_user:
1193+ manager_user = User.safe_id(manager_user)
1194+ with self.driver() as drv:
1195+ drv.modify_project(Project.safe_id(project),
1196+ manager_user,
1197+ description)
1198+
1199+ def add_to_project(self, user, project):
1200+ """Add user to project"""
1201+ uid = User.safe_id(user)
1202+ pid = Project.safe_id(project)
1203+ LOG.audit(_("Adding user %(uid)s to project %(pid)s") % locals())
1204+ with self.driver() as drv:
1205+ return drv.add_to_project(User.safe_id(user),
1206+ Project.safe_id(project))
1207+
1208+ def is_project_manager(self, user, project):
1209+ """Checks if user is project manager"""
1210+ if not isinstance(project, Project):
1211+ project = self.get_project(project)
1212+ return User.safe_id(user) == project.project_manager_id
1213+
1214+ def is_project_member(self, user, project):
1215+ """Checks to see if user is a member of project"""
1216+ if not isinstance(project, Project):
1217+ project = self.get_project(project)
1218+ return User.safe_id(user) in project.member_ids
1219+
1220+ def remove_from_project(self, user, project):
1221+ """Removes a user from a project"""
1222+ uid = User.safe_id(user)
1223+ pid = Project.safe_id(project)
1224+ LOG.audit(_("Remove user %(uid)s from project %(pid)s") % locals())
1225+ with self.driver() as drv:
1226+ return drv.remove_from_project(uid, pid)
1227+
1228+ @staticmethod
1229+ def get_project_vpn_data(project):
1230+ """Gets vpn ip and port for project
1231+
1232+ @type project: Project or project_id
1233+ @param project: Project from which to get associated vpn data
1234+
1235+ @rvalue: tuple of (str, str)
1236+ @return: A tuple containing (ip, port) or None, None if vpn has
1237+ not been allocated for user.
1238+ """
1239+
1240+ networks = db.project_get_networks(context.get_admin_context(),
1241+ Project.safe_id(project), False)
1242+ if not networks:
1243+ return (None, None)
1244+
1245+ # TODO(tr3buchet): not sure what you guys plan on doing with this
1246+ # but it's possible for a project to have multiple sets of vpn data
1247+ # for now I'm just returning the first one
1248+ network = networks[0]
1249+ return (network['vpn_public_address'],
1250+ network['vpn_public_port'])
1251+
1252+ def delete_project(self, project):
1253+ """Deletes a project"""
1254+ LOG.audit(_("Deleting project %s"), Project.safe_id(project))
1255+ with self.driver() as drv:
1256+ drv.delete_project(Project.safe_id(project))
1257+
1258+ def get_user(self, uid):
1259+ """Retrieves a user by id"""
1260+ with self.driver() as drv:
1261+ user_dict = drv.get_user(uid)
1262+ if user_dict:
1263+ return User(**user_dict)
1264+
1265+ def get_user_from_access_key(self, access_key):
1266+ """Retrieves a user by access key"""
1267+ with self.driver() as drv:
1268+ user_dict = drv.get_user_from_access_key(access_key)
1269+ if user_dict:
1270+ return User(**user_dict)
1271+
1272+ def get_users(self):
1273+ """Retrieves a list of all users"""
1274+ with self.driver() as drv:
1275+ user_list = drv.get_users()
1276+ if not user_list:
1277+ return []
1278+ return [User(**user_dict) for user_dict in user_list]
1279+
1280+ def create_user(self, name, access=None, secret=None, admin=False):
1281+ """Creates a user
1282+
1283+ @type name: str
1284+ @param name: Name of the user to create.
1285+
1286+ @type access: str
1287+ @param access: Access Key (defaults to a random uuid)
1288+
1289+ @type secret: str
1290+ @param secret: Secret Key (defaults to a random uuid)
1291+
1292+ @type admin: bool
1293+ @param admin: Whether to set the admin flag. The admin flag gives
1294+ superuser status regardless of roles specifed for the user.
1295+
1296+ @type create_project: bool
1297+ @param: Whether to create a project for the user with the same name.
1298+
1299+ @rtype: User
1300+ @return: The new user.
1301+ """
1302+ if access is None:
1303+ access = str(uuid.uuid4())
1304+ if secret is None:
1305+ secret = str(uuid.uuid4())
1306+ with self.driver() as drv:
1307+ user_dict = drv.create_user(name, access, secret, admin)
1308+ if user_dict:
1309+ rv = User(**user_dict)
1310+ rvname = rv.name
1311+ rvadmin = rv.admin
1312+ LOG.audit(_("Created user %(rvname)s"
1313+ " (admin: %(rvadmin)r)") % locals())
1314+ return rv
1315+
1316+ def delete_user(self, user):
1317+ """Deletes a user
1318+
1319+ Additionally deletes all users key_pairs"""
1320+ uid = User.safe_id(user)
1321+ LOG.audit(_("Deleting user %s"), uid)
1322+ db.key_pair_destroy_all_by_user(context.get_admin_context(),
1323+ uid)
1324+ with self.driver() as drv:
1325+ drv.delete_user(uid)
1326+
1327+ def modify_user(self, user, access_key=None, secret_key=None, admin=None):
1328+ """Modify credentials for a user"""
1329+ uid = User.safe_id(user)
1330+ if access_key:
1331+ LOG.audit(_("Access Key change for user %s"), uid)
1332+ if secret_key:
1333+ LOG.audit(_("Secret Key change for user %s"), uid)
1334+ if admin is not None:
1335+ LOG.audit(_("Admin status set to %(admin)r"
1336+ " for user %(uid)s") % locals())
1337+ with self.driver() as drv:
1338+ drv.modify_user(uid, access_key, secret_key, admin)
1339+
1340+ def get_credentials(self, user, project=None, use_dmz=True):
1341+ """Get credential zip for user in project"""
1342+ if not isinstance(user, User):
1343+ user = self.get_user(user)
1344+ if project is None:
1345+ project = user.id
1346+ pid = Project.safe_id(project)
1347+ private_key, signed_cert = crypto.generate_x509_cert(user.id, pid)
1348+
1349+ tmpdir = tempfile.mkdtemp()
1350+ zf = os.path.join(tmpdir, "temp.zip")
1351+ zippy = zipfile.ZipFile(zf, 'w')
1352+ if use_dmz and FLAGS.region_list:
1353+ regions = {}
1354+ for item in FLAGS.region_list:
1355+ region, _sep, region_host = item.partition("=")
1356+ regions[region] = region_host
1357+ else:
1358+ regions = {'nova': FLAGS.ec2_host}
1359+ for region, host in regions.iteritems():
1360+ rc = self.__generate_rc(user,
1361+ pid,
1362+ use_dmz,
1363+ host)
1364+ zippy.writestr(FLAGS.credential_rc_file % region, rc)
1365+
1366+ zippy.writestr(FLAGS.credential_key_file, private_key)
1367+ zippy.writestr(FLAGS.credential_cert_file, signed_cert)
1368+
1369+ (vpn_ip, vpn_port) = self.get_project_vpn_data(project)
1370+ if vpn_ip:
1371+ configfile = open(FLAGS.vpn_client_template, "r")
1372+ s = string.Template(configfile.read())
1373+ configfile.close()
1374+ config = s.substitute(keyfile=FLAGS.credential_key_file,
1375+ certfile=FLAGS.credential_cert_file,
1376+ ip=vpn_ip,
1377+ port=vpn_port)
1378+ zippy.writestr(FLAGS.credential_vpn_file, config)
1379+ else:
1380+ LOG.warn(_("No vpn data for project %s"), pid)
1381+
1382+ zippy.writestr(FLAGS.ca_file, crypto.fetch_ca(pid))
1383+ zippy.close()
1384+ with open(zf, 'rb') as f:
1385+ read_buffer = f.read()
1386+
1387+ shutil.rmtree(tmpdir)
1388+ return read_buffer
1389+
1390+ def get_environment_rc(self, user, project=None, use_dmz=True):
1391+ """Get environment rc for user in project"""
1392+ if not isinstance(user, User):
1393+ user = self.get_user(user)
1394+ if project is None:
1395+ project = user.id
1396+ pid = Project.safe_id(project)
1397+ return self.__generate_rc(user, pid, use_dmz)
1398+
1399+ @staticmethod
1400+ def __generate_rc(user, pid, use_dmz=True, host=None):
1401+ """Generate rc file for user"""
1402+ if use_dmz:
1403+ ec2_host = FLAGS.ec2_dmz_host
1404+ else:
1405+ ec2_host = FLAGS.ec2_host
1406+ # NOTE(vish): Always use the dmz since it is used from inside the
1407+ # instance
1408+ s3_host = FLAGS.s3_dmz
1409+ if host:
1410+ s3_host = host
1411+ ec2_host = host
1412+ rc = open(FLAGS.credentials_template).read()
1413+ # NOTE(vish): Deprecated auth uses an access key, no auth uses a
1414+ # the user_id in place of it.
1415+ if FLAGS.use_deprecated_auth:
1416+ access = user.access
1417+ else:
1418+ access = user.id
1419+ rc = rc % {'access': access,
1420+ 'project': pid,
1421+ 'secret': user.secret,
1422+ 'ec2': '%s://%s:%s%s' % (FLAGS.ec2_scheme,
1423+ ec2_host,
1424+ FLAGS.ec2_port,
1425+ FLAGS.ec2_path),
1426+ 's3': 'http://%s:%s' % (s3_host, FLAGS.s3_port),
1427+ 'os': '%s://%s:%s%s' % (FLAGS.osapi_scheme,
1428+ ec2_host,
1429+ FLAGS.osapi_port,
1430+ FLAGS.osapi_path),
1431+ 'user': user.name,
1432+ 'nova': FLAGS.ca_file,
1433+ 'cert': FLAGS.credential_cert_file,
1434+ 'key': FLAGS.credential_key_file}
1435+ return rc
1436
1437=== modified file 'Authors'
1438--- Authors 2011-10-04 09:43:55 +0000
1439+++ Authors 2011-10-27 13:05:25 +0000
1440@@ -1,5 +1,6 @@
1441 Adam Gandelman <adamg@canonical.com>
1442 Adam Johnson <adjohn@gmail.com>
1443+Ahmad Hassan <ahmad.hassan@hp.com>
1444 Alex Meade <alex.meade@rackspace.com>
1445 Alexander Sakhnov <asakhnov@mirantis.com>
1446 Andrey Brindeyev <abrindeyev@griddynamics.com>
1447
1448=== modified file 'debian/changelog'
1449--- debian/changelog 2011-10-12 14:33:25 +0000
1450+++ debian/changelog 2011-10-27 13:05:25 +0000
1451@@ -1,3 +1,33 @@
1452+nova (2011.3-0ubuntu6.3) UNRELEASED; urgency=low
1453+
1454+ [ Scott Moser ]
1455+ * Removed db_pool complexities from nova.db.sqlalchemy.session (LP: #838581)
1456+
1457+ [ Chuck Short ]
1458+ * debian/patches/fix-iscsi-target-path.patch: Fix ISCSI target path patch.
1459+ (LP: #871278)
1460+ * debian/control: Either install xen-hypervisor-4.1-amd64 or
1461+ xen-hypervisor-4.1-i386 for nova-compute-xen. (LP: #873243)
1462+
1463+ [ Soren Hansen ]
1464+ * 2011.3-0ubuntu6.1 never made it past -proposed, due to 2011.3-
1465+ 0ubuntu6.2 which was a security fix (without the changes from
1466+ 2011.3-0ubuntu6.1). This upload brings the changes from 2011.3-
1467+ 0ubuntu6.1 back.
1468+
1469+ -- Chuck Short <zulcss@ubuntu.com> Wed, 12 Oct 2011 14:33:25 -0400
1470+
1471+nova (2011.3-0ubuntu6.2) oneiric-security; urgency=low
1472+
1473+ * SECURITY UPDATE: fix information leak via invalid key
1474+ debina/patches/security-fix-lp868360.patch: adjust nova/auth/manager.py
1475+ to not return access, secret or admin fields for User error and
1476+ project_manager_id, description and member_ids for Project
1477+ - LP: #868360
1478+ - CVE-2011-XXXX
1479+
1480+ -- Jamie Strandboge <jamie@ubuntu.com> Tue, 25 Oct 2011 08:57:02 -0500
1481+
1482 nova (2011.3-0ubuntu6.1) oneiric-proposed; urgency=low
1483
1484 [Scott Moser]
1485
1486=== added file 'debian/patches/security-fix-lp868360.patch'
1487--- debian/patches/security-fix-lp868360.patch 1970-01-01 00:00:00 +0000
1488+++ debian/patches/security-fix-lp868360.patch 2011-10-27 13:05:25 +0000
1489@@ -0,0 +1,70 @@
1490+From beee11edbfdd82cd81bc9c0fd75912c167892c2b Mon Sep 17 00:00:00 2001
1491+From: Ahmad Hassan <ahmad.hassan@hp.com>
1492+Date: Mon, 1 Aug 2011 17:16:49 +0100
1493+Subject: [PATCH] Stop returning correct password on api calls
1494+
1495+Captured invalid signature exception in authentication step, so that
1496+the problem is not returning exception to user, revealing the real
1497+password.
1498+Fixes bug 868360.
1499+
1500+Change-Id: Idb31f076a7b14309f0fda698261de816924da354
1501+---
1502+ Authors | 1 +
1503+ nova/api/ec2/__init__.py | 3 ++-
1504+ nova/auth/manager.py | 10 ++--------
1505+ 3 files changed, 5 insertions(+), 9 deletions(-)
1506+
1507+Index: nova-2011.3/Authors
1508+===================================================================
1509+--- nova-2011.3.orig/Authors 2011-10-25 08:55:16.000000000 -0500
1510++++ nova-2011.3/Authors 2011-10-25 08:56:17.000000000 -0500
1511+@@ -1,5 +1,6 @@
1512+ Adam Gandelman <adamg@canonical.com>
1513+ Adam Johnson <adjohn@gmail.com>
1514++Ahmad Hassan <ahmad.hassan@hp.com>
1515+ Alex Meade <alex.meade@rackspace.com>
1516+ Alexander Sakhnov <asakhnov@mirantis.com>
1517+ Andrey Brindeyev <abrindeyev@griddynamics.com>
1518+Index: nova-2011.3/nova/api/ec2/__init__.py
1519+===================================================================
1520+--- nova-2011.3.orig/nova/api/ec2/__init__.py 2011-10-25 08:55:16.000000000 -0500
1521++++ nova-2011.3/nova/api/ec2/__init__.py 2011-10-25 08:55:22.000000000 -0500
1522+@@ -188,7 +188,8 @@
1523+ req.host,
1524+ req.path)
1525+ # Be explicit for what exceptions are 403, the rest bubble as 500
1526+- except (exception.NotFound, exception.NotAuthorized) as ex:
1527++ except (exception.NotFound, exception.NotAuthorized,
1528++ exception.InvalidSignature) as ex:
1529+ LOG.audit(_("Authentication Failure: %s"), unicode(ex))
1530+ raise webob.exc.HTTPForbidden()
1531+
1532+Index: nova-2011.3/nova/auth/manager.py
1533+===================================================================
1534+--- nova-2011.3.orig/nova/auth/manager.py 2011-10-25 08:55:16.000000000 -0500
1535++++ nova-2011.3/nova/auth/manager.py 2011-10-25 08:55:22.000000000 -0500
1536+@@ -149,11 +149,7 @@
1537+ return AuthManager().is_project_manager(self, project)
1538+
1539+ def __repr__(self):
1540+- return "User('%s', '%s', '%s', '%s', %s)" % (self.id,
1541+- self.name,
1542+- self.access,
1543+- self.secret,
1544+- self.admin)
1545++ return "User('%s', '%s')" % (self.id, self.name)
1546+
1547+
1548+ class Project(AuthBase):
1549+@@ -200,9 +196,7 @@
1550+ return AuthManager().get_credentials(user, self)
1551+
1552+ def __repr__(self):
1553+- return "Project('%s', '%s', '%s', '%s', %s)" % \
1554+- (self.id, self.name, self.project_manager_id, self.description,
1555+- self.member_ids)
1556++ return "Project('%s', '%s')" % (self.id, self.name)
1557+
1558+
1559+ class AuthManager(object):
1560
1561=== modified file 'debian/patches/series'
1562--- debian/patches/series 2011-10-12 14:33:25 +0000
1563+++ debian/patches/series 2011-10-27 13:05:25 +0000
1564@@ -12,3 +12,4 @@
1565 fix-lp863305-images-permission.patch
1566 fix-lp838581-removed-db_pool-complexities.patch
1567 fix-iscsi-target-path.patch
1568+security-fix-lp868360.patch
1569
1570=== modified file 'nova/api/ec2/__init__.py'
1571--- nova/api/ec2/__init__.py 2011-09-22 09:33:49 +0000
1572+++ nova/api/ec2/__init__.py 2011-10-27 13:05:25 +0000
1573@@ -188,7 +188,8 @@
1574 req.host,
1575 req.path)
1576 # Be explicit for what exceptions are 403, the rest bubble as 500
1577- except (exception.NotFound, exception.NotAuthorized) as ex:
1578+ except (exception.NotFound, exception.NotAuthorized,
1579+ exception.InvalidSignature) as ex:
1580 LOG.audit(_("Authentication Failure: %s"), unicode(ex))
1581 raise webob.exc.HTTPForbidden()
1582
1583
1584=== modified file 'nova/auth/manager.py'
1585--- nova/auth/manager.py 2011-08-26 13:31:14 +0000
1586+++ nova/auth/manager.py 2011-10-27 13:05:25 +0000
1587@@ -149,11 +149,7 @@
1588 return AuthManager().is_project_manager(self, project)
1589
1590 def __repr__(self):
1591- return "User('%s', '%s', '%s', '%s', %s)" % (self.id,
1592- self.name,
1593- self.access,
1594- self.secret,
1595- self.admin)
1596+ return "User('%s', '%s')" % (self.id, self.name)
1597
1598
1599 class Project(AuthBase):
1600@@ -200,9 +196,7 @@
1601 return AuthManager().get_credentials(user, self)
1602
1603 def __repr__(self):
1604- return "Project('%s', '%s', '%s', '%s', %s)" % \
1605- (self.id, self.name, self.project_manager_id, self.description,
1606- self.member_ids)
1607+ return "Project('%s', '%s')" % (self.id, self.name)
1608
1609
1610 class AuthManager(object):

Subscribers

People subscribed via source and target branches

to all changes: