~ubuntu-branches/debian/sid/keystone/sid

« back to all changes in this revision

Viewing changes to .pc/CVE-2013-0282_ensure-user-tenant-enabled-ec2.patch/keystone/contrib/ec2/core.py

  • Committer: Package Import Robot
  • Author(s): Thomas Goirand
  • Date: 2013-02-19 12:56:42 UTC
  • Revision ID: package-import@ubuntu.com-20130219125642-lkow6z7yq6b84ho3
Tags: 2012.1.1-13
* CVE-2013-0282: Ensure EC2 users and tenant are enabled (Closes: #700947).
* CVE-2013-0280: Information leak and Denial of Service using XML entities
  (Closes: #700948).

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 
2
 
 
3
# Copyright 2012 OpenStack LLC
 
4
#
 
5
# Licensed under the Apache License, Version 2.0 (the "License"); you may
 
6
# not use this file except in compliance with the License. You may obtain
 
7
# a copy of the License at
 
8
#
 
9
#      http://www.apache.org/licenses/LICENSE-2.0
 
10
#
 
11
# Unless required by applicable law or agreed to in writing, software
 
12
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 
13
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 
14
# License for the specific language governing permissions and limitations
 
15
# under the License.
 
16
 
 
17
"""Main entry point into the EC2 Credentials service.
 
18
 
 
19
This service allows the creation of access/secret credentials used for
 
20
the ec2 interop layer of OpenStack.
 
21
 
 
22
A user can create as many access/secret pairs, each of which map to a
 
23
specific tenant.  This is required because OpenStack supports a user
 
24
belonging to multiple tenants, whereas the signatures created on ec2-style
 
25
requests don't allow specification of which tenant the user wishs to act
 
26
upon.
 
27
 
 
28
To complete the cycle, we provide a method that OpenStack services can
 
29
use to validate a signature and get a corresponding openstack token.  This
 
30
token allows method calls to other services within the context the
 
31
access/secret was created.  As an example, nova requests keystone to validate
 
32
the signature of a request, receives a token, and then makes a request to
 
33
glance to list images needed to perform the requested task.
 
34
 
 
35
"""
 
36
 
 
37
import uuid
 
38
 
 
39
from keystone import catalog
 
40
from keystone import config
 
41
from keystone import exception
 
42
from keystone import identity
 
43
from keystone import policy
 
44
from keystone import service
 
45
from keystone import token
 
46
from keystone.common import manager
 
47
from keystone.common import utils
 
48
from keystone.common import wsgi
 
49
 
 
50
 
 
51
CONF = config.CONF
 
52
 
 
53
 
 
54
class Manager(manager.Manager):
 
55
    """Default pivot point for the EC2 Credentials backend.
 
56
 
 
57
    See :mod:`keystone.common.manager.Manager` for more details on how this
 
58
    dynamically calls the backend.
 
59
 
 
60
    """
 
61
 
 
62
    def __init__(self):
 
63
        super(Manager, self).__init__(CONF.ec2.driver)
 
64
 
 
65
 
 
66
class Ec2Extension(wsgi.ExtensionRouter):
 
67
    def add_routes(self, mapper):
 
68
        ec2_controller = Ec2Controller()
 
69
        # validation
 
70
        mapper.connect('/ec2tokens',
 
71
                       controller=ec2_controller,
 
72
                       action='authenticate',
 
73
                       conditions=dict(method=['POST']))
 
74
 
 
75
        # crud
 
76
        mapper.connect('/users/{user_id}/credentials/OS-EC2',
 
77
                       controller=ec2_controller,
 
78
                       action='create_credential',
 
79
                       conditions=dict(method=['POST']))
 
80
        mapper.connect('/users/{user_id}/credentials/OS-EC2',
 
81
                       controller=ec2_controller,
 
82
                       action='get_credentials',
 
83
                       conditions=dict(method=['GET']))
 
84
        mapper.connect('/users/{user_id}/credentials/OS-EC2/{credential_id}',
 
85
                       controller=ec2_controller,
 
86
                       action='get_credential',
 
87
                       conditions=dict(method=['GET']))
 
88
        mapper.connect('/users/{user_id}/credentials/OS-EC2/{credential_id}',
 
89
                       controller=ec2_controller,
 
90
                       action='delete_credential',
 
91
                       conditions=dict(method=['DELETE']))
 
92
 
 
93
 
 
94
class Ec2Controller(wsgi.Application):
 
95
    def __init__(self):
 
96
        self.catalog_api = catalog.Manager()
 
97
        self.identity_api = identity.Manager()
 
98
        self.token_api = token.Manager()
 
99
        self.policy_api = policy.Manager()
 
100
        self.ec2_api = Manager()
 
101
        super(Ec2Controller, self).__init__()
 
102
 
 
103
    def check_signature(self, creds_ref, credentials):
 
104
        signer = utils.Ec2Signer(creds_ref['secret'])
 
105
        signature = signer.generate(credentials)
 
106
        if utils.auth_str_equal(credentials['signature'], signature):
 
107
            return
 
108
        # NOTE(vish): Some libraries don't use the port when signing
 
109
        #             requests, so try again without port.
 
110
        elif ':' in credentials['signature']:
 
111
            hostname, _port = credentials['host'].split(':')
 
112
            credentials['host'] = hostname
 
113
            signature = signer.generate(credentials)
 
114
            if not utils.auth_str_equal(credentials.signature, signature):
 
115
                raise exception.Unauthorized(message='Invalid EC2 signature.')
 
116
        else:
 
117
            raise exception.Unauthorized(message='EC2 signature not supplied.')
 
118
 
 
119
    def authenticate(self, context, credentials=None,
 
120
                         ec2Credentials=None):
 
121
        """Validate a signed EC2 request and provide a token.
 
122
 
 
123
        Other services (such as Nova) use this **admin** call to determine
 
124
        if a request they signed received is from a valid user.
 
125
 
 
126
        If it is a valid signature, an openstack token that maps
 
127
        to the user/tenant is returned to the caller, along with
 
128
        all the other details returned from a normal token validation
 
129
        call.
 
130
 
 
131
        The returned token is useful for making calls to other
 
132
        OpenStack services within the context of the request.
 
133
 
 
134
        :param context: standard context
 
135
        :param credentials: dict of ec2 signature
 
136
        :param ec2Credentials: DEPRECATED dict of ec2 signature
 
137
        :returns: token: openstack token equivalent to access key along
 
138
                         with the corresponding service catalog and roles
 
139
        """
 
140
 
 
141
        # FIXME(ja): validate that a service token was used!
 
142
 
 
143
        # NOTE(termie): backwards compat hack
 
144
        if not credentials and ec2Credentials:
 
145
            credentials = ec2Credentials
 
146
 
 
147
        if not 'access' in credentials:
 
148
            raise exception.Unauthorized(message='EC2 signature not supplied.')
 
149
 
 
150
        creds_ref = self._get_credentials(context,
 
151
                                          credentials['access'])
 
152
        self.check_signature(creds_ref, credentials)
 
153
 
 
154
        # TODO(termie): don't create new tokens every time
 
155
        # TODO(termie): this is copied from TokenController.authenticate
 
156
        token_id = uuid.uuid4().hex
 
157
        tenant_ref = self.identity_api.get_tenant(
 
158
                context=context,
 
159
                tenant_id=creds_ref['tenant_id'])
 
160
        user_ref = self.identity_api.get_user(
 
161
                context=context,
 
162
                user_id=creds_ref['user_id'])
 
163
        metadata_ref = self.identity_api.get_metadata(
 
164
            context=context,
 
165
            user_id=user_ref['id'],
 
166
            tenant_id=tenant_ref['id'])
 
167
 
 
168
        # TODO(termie): optimize this call at some point and put it into the
 
169
        #               the return for metadata
 
170
        # fill out the roles in the metadata
 
171
        roles = metadata_ref.get('roles', [])
 
172
        if not roles:
 
173
            raise exception.Unauthorized(message='User not valid for tenant.')
 
174
        roles_ref = [self.identity_api.get_role(context, role_id)
 
175
                     for role_id in roles]
 
176
 
 
177
        catalog_ref = self.catalog_api.get_catalog(
 
178
                context=context,
 
179
                user_id=user_ref['id'],
 
180
                tenant_id=tenant_ref['id'],
 
181
                    metadata=metadata_ref)
 
182
 
 
183
        token_ref = self.token_api.create_token(
 
184
                context, token_id, dict(id=token_id,
 
185
                                        user=user_ref,
 
186
                                        tenant=tenant_ref,
 
187
                                        metadata=metadata_ref))
 
188
 
 
189
        # TODO(termie): make this a util function or something
 
190
        # TODO(termie): i don't think the ec2 middleware currently expects a
 
191
        #               full return, but it contains a note saying that it
 
192
        #               would be better to expect a full return
 
193
        token_controller = service.TokenController()
 
194
        return token_controller._format_authenticate(
 
195
                token_ref, roles_ref, catalog_ref)
 
196
 
 
197
    def create_credential(self, context, user_id, tenant_id):
 
198
        """Create a secret/access pair for use with ec2 style auth.
 
199
 
 
200
        Generates a new set of credentials that map the the user/tenant
 
201
        pair.
 
202
 
 
203
        :param context: standard context
 
204
        :param user_id: id of user
 
205
        :param tenant_id: id of tenant
 
206
        :returns: credential: dict of ec2 credential
 
207
        """
 
208
        if not self._is_admin(context):
 
209
            self._assert_identity(context, user_id)
 
210
 
 
211
        self._assert_valid_user_id(context, user_id)
 
212
        self._assert_valid_tenant_id(context, tenant_id)
 
213
 
 
214
        cred_ref = {'user_id': user_id,
 
215
                    'tenant_id': tenant_id,
 
216
                    'access': uuid.uuid4().hex,
 
217
                    'secret': uuid.uuid4().hex}
 
218
        self.ec2_api.create_credential(context, cred_ref['access'], cred_ref)
 
219
        return {'credential': cred_ref}
 
220
 
 
221
    def get_credentials(self, context, user_id):
 
222
        """List all credentials for a user.
 
223
 
 
224
        :param context: standard context
 
225
        :param user_id: id of user
 
226
        :returns: credentials: list of ec2 credential dicts
 
227
        """
 
228
        if not self._is_admin(context):
 
229
            self._assert_identity(context, user_id)
 
230
        self._assert_valid_user_id(context, user_id)
 
231
        return {'credentials': self.ec2_api.list_credentials(context, user_id)}
 
232
 
 
233
    def get_credential(self, context, user_id, credential_id):
 
234
        """Retreive a user's access/secret pair by the access key.
 
235
 
 
236
        Grab the full access/secret pair for a given access key.
 
237
 
 
238
        :param context: standard context
 
239
        :param user_id: id of user
 
240
        :param credential_id: access key for credentials
 
241
        :returns: credential: dict of ec2 credential
 
242
        """
 
243
        if not self._is_admin(context):
 
244
            self._assert_identity(context, user_id)
 
245
        self._assert_valid_user_id(context, user_id)
 
246
        creds = self._get_credentials(context, credential_id)
 
247
        return {'credential': creds}
 
248
 
 
249
    def delete_credential(self, context, user_id, credential_id):
 
250
        """Delete a user's access/secret pair.
 
251
 
 
252
        Used to revoke a user's access/secret pair
 
253
 
 
254
        :param context: standard context
 
255
        :param user_id: id of user
 
256
        :param credential_id: access key for credentials
 
257
        :returns: bool: success
 
258
        """
 
259
        if not self._is_admin(context):
 
260
            self._assert_identity(context, user_id)
 
261
            self._assert_owner(context, user_id, credential_id)
 
262
 
 
263
        self._assert_valid_user_id(context, user_id)
 
264
        self._get_credentials(context, credential_id)
 
265
        return self.ec2_api.delete_credential(context, credential_id)
 
266
 
 
267
    def _get_credentials(self, context, credential_id):
 
268
        """Return credentials from an ID.
 
269
 
 
270
        :param context: standard context
 
271
        :param credential_id: id of credential
 
272
        :raises exception.Unauthorized: when credential id is invalid
 
273
        :returns: credential: dict of ec2 credential.
 
274
        """
 
275
        creds = self.ec2_api.get_credential(context,
 
276
                                            credential_id)
 
277
        if not creds:
 
278
            raise exception.Unauthorized(message='EC2 access key not found.')
 
279
        return creds
 
280
 
 
281
    def _assert_identity(self, context, user_id):
 
282
        """Check that the provided token belongs to the user.
 
283
 
 
284
        :param context: standard context
 
285
        :param user_id: id of user
 
286
        :raises exception.Forbidden: when token is invalid
 
287
 
 
288
        """
 
289
        try:
 
290
            token_ref = self.token_api.get_token(context=context,
 
291
                    token_id=context['token_id'])
 
292
        except exception.TokenNotFound:
 
293
            raise exception.Unauthorized()
 
294
        token_user_id = token_ref['user'].get('id')
 
295
        if not token_user_id == user_id:
 
296
            raise exception.Forbidden()
 
297
 
 
298
    def _is_admin(self, context):
 
299
        """Wrap admin assertion error return statement.
 
300
 
 
301
        :param context: standard context
 
302
        :returns: bool: success
 
303
 
 
304
        """
 
305
        try:
 
306
            self.assert_admin(context)
 
307
            return True
 
308
        except exception.Forbidden:
 
309
            return False
 
310
 
 
311
    def _assert_owner(self, context, user_id, credential_id):
 
312
        """Ensure the provided user owns the credential.
 
313
 
 
314
        :param context: standard context
 
315
        :param user_id: expected credential owner
 
316
        :param credential_id: id of credential object
 
317
        :raises exception.Forbidden: on failure
 
318
 
 
319
        """
 
320
        cred_ref = self.ec2_api.get_credential(context, credential_id)
 
321
        if not user_id == cred_ref['user_id']:
 
322
            raise exception.Forbidden()
 
323
 
 
324
    def _assert_valid_user_id(self, context, user_id):
 
325
        """Ensure a valid user id.
 
326
 
 
327
        :param context: standard context
 
328
        :param user_id: expected credential owner
 
329
        :raises exception.UserNotFound: on failure
 
330
 
 
331
        """
 
332
        user_ref = self.identity_api.get_user(
 
333
            context=context,
 
334
            user_id=user_id)
 
335
        if not user_ref:
 
336
            raise exception.UserNotFound(user_id=user_id)
 
337
 
 
338
    def _assert_valid_tenant_id(self, context, tenant_id):
 
339
        """Ensure a valid tenant id.
 
340
 
 
341
        :param context: standard context
 
342
        :param user_id: expected credential owner
 
343
        :raises exception.UserNotFound: on failure
 
344
 
 
345
        """
 
346
        tenant_ref = self.identity_api.get_tenant(
 
347
            context=context,
 
348
            tenant_id=tenant_id)
 
349
        if not tenant_ref:
 
350
            raise exception.TenantNotFound(tenant_id=tenant_id)