~ubuntu-branches/ubuntu/quantal/keystone/quantal-security

« back to all changes in this revision

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

  • Committer: Package Import Robot
  • Author(s): Jamie Strandboge
  • Date: 2013-06-13 13:42:44 UTC
  • mfrom: (35.1.6 quantal-proposed)
  • Revision ID: package-import@ubuntu.com-20130613134244-mle4ueu39urzeknr
Tags: 2012.2.4-0ubuntu3.1
* SECURITY UPDATE: fix auth_token middleware neglects to check expiry of
  signed token when using PKI
  - debian/patches/CVE-2013-2104.patch: explicitly check the expiry on the
    tokens, and reject tokens that have expired. Also update test data
  - CVE-2013-2104
  - LP: #1179615
* debian/patches/fix-testsuite-for-2038-problem.patch: Adjust json example
  cert data to use 2037 instead of 2112 and regenerate the certs. Also
  adjust token expiry data to use 2037 instead of 2999.
* SECURITY UPDATE: fix authentication bypass when using LDAP backend
  - debian/patches/CVE-2013-2157.patch: identity/backends/ldap/core.py is
    adjusted to raise an assertion for invalid password when using LDAP and
    an empty password is submitted
  - CVE-2013-2157
  - LP: #1187305

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.common import manager
41
 
from keystone.common import utils
42
 
from keystone.common import wsgi
43
 
from keystone import config
44
 
from keystone import exception
45
 
from keystone import identity
46
 
from keystone import policy
47
 
from keystone import service
48
 
from keystone import token
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(
71
 
            '/ec2tokens',
72
 
            controller=ec2_controller,
73
 
            action='authenticate',
74
 
            conditions=dict(method=['POST']))
75
 
 
76
 
        # crud
77
 
        mapper.connect(
78
 
            '/users/{user_id}/credentials/OS-EC2',
79
 
            controller=ec2_controller,
80
 
            action='create_credential',
81
 
            conditions=dict(method=['POST']))
82
 
        mapper.connect(
83
 
            '/users/{user_id}/credentials/OS-EC2',
84
 
            controller=ec2_controller,
85
 
            action='get_credentials',
86
 
            conditions=dict(method=['GET']))
87
 
        mapper.connect(
88
 
            '/users/{user_id}/credentials/OS-EC2/{credential_id}',
89
 
            controller=ec2_controller,
90
 
            action='get_credential',
91
 
            conditions=dict(method=['GET']))
92
 
        mapper.connect(
93
 
            '/users/{user_id}/credentials/OS-EC2/{credential_id}',
94
 
            controller=ec2_controller,
95
 
            action='delete_credential',
96
 
            conditions=dict(method=['DELETE']))
97
 
 
98
 
 
99
 
class Ec2Controller(wsgi.Application):
100
 
    def __init__(self):
101
 
        self.catalog_api = catalog.Manager()
102
 
        self.identity_api = identity.Manager()
103
 
        self.token_api = token.Manager()
104
 
        self.policy_api = policy.Manager()
105
 
        self.ec2_api = Manager()
106
 
        super(Ec2Controller, self).__init__()
107
 
 
108
 
    def check_signature(self, creds_ref, credentials):
109
 
        signer = utils.Ec2Signer(creds_ref['secret'])
110
 
        signature = signer.generate(credentials)
111
 
        if utils.auth_str_equal(credentials['signature'], signature):
112
 
            return
113
 
        # NOTE(vish): Some libraries don't use the port when signing
114
 
        #             requests, so try again without port.
115
 
        elif ':' in credentials['signature']:
116
 
            hostname, _port = credentials['host'].split(':')
117
 
            credentials['host'] = hostname
118
 
            signature = signer.generate(credentials)
119
 
            if not utils.auth_str_equal(credentials.signature, signature):
120
 
                raise exception.Unauthorized(message='Invalid EC2 signature.')
121
 
        else:
122
 
            raise exception.Unauthorized(message='EC2 signature not supplied.')
123
 
 
124
 
    def authenticate(self, context, credentials=None, ec2Credentials=None):
125
 
        """Validate a signed EC2 request and provide a token.
126
 
 
127
 
        Other services (such as Nova) use this **admin** call to determine
128
 
        if a request they signed received is from a valid user.
129
 
 
130
 
        If it is a valid signature, an openstack token that maps
131
 
        to the user/tenant is returned to the caller, along with
132
 
        all the other details returned from a normal token validation
133
 
        call.
134
 
 
135
 
        The returned token is useful for making calls to other
136
 
        OpenStack services within the context of the request.
137
 
 
138
 
        :param context: standard context
139
 
        :param credentials: dict of ec2 signature
140
 
        :param ec2Credentials: DEPRECATED dict of ec2 signature
141
 
        :returns: token: openstack token equivalent to access key along
142
 
                         with the corresponding service catalog and roles
143
 
        """
144
 
 
145
 
        # FIXME(ja): validate that a service token was used!
146
 
 
147
 
        # NOTE(termie): backwards compat hack
148
 
        if not credentials and ec2Credentials:
149
 
            credentials = ec2Credentials
150
 
 
151
 
        if not 'access' in credentials:
152
 
            raise exception.Unauthorized(message='EC2 signature not supplied.')
153
 
 
154
 
        creds_ref = self._get_credentials(context,
155
 
                                          credentials['access'])
156
 
        self.check_signature(creds_ref, credentials)
157
 
 
158
 
        # TODO(termie): don't create new tokens every time
159
 
        # TODO(termie): this is copied from TokenController.authenticate
160
 
        token_id = uuid.uuid4().hex
161
 
        tenant_ref = self.identity_api.get_tenant(
162
 
            context=context,
163
 
            tenant_id=creds_ref['tenant_id'])
164
 
        user_ref = self.identity_api.get_user(
165
 
            context=context,
166
 
            user_id=creds_ref['user_id'])
167
 
        metadata_ref = self.identity_api.get_metadata(
168
 
            context=context,
169
 
            user_id=user_ref['id'],
170
 
            tenant_id=tenant_ref['id'])
171
 
 
172
 
        # TODO(termie): optimize this call at some point and put it into the
173
 
        #               the return for metadata
174
 
        # fill out the roles in the metadata
175
 
        roles = metadata_ref.get('roles', [])
176
 
        if not roles:
177
 
            raise exception.Unauthorized(message='User not valid for tenant.')
178
 
        roles_ref = [self.identity_api.get_role(context, role_id)
179
 
                     for role_id in roles]
180
 
 
181
 
        catalog_ref = self.catalog_api.get_catalog(
182
 
            context=context,
183
 
            user_id=user_ref['id'],
184
 
            tenant_id=tenant_ref['id'],
185
 
            metadata=metadata_ref)
186
 
 
187
 
        token_ref = self.token_api.create_token(
188
 
            context, token_id, dict(id=token_id,
189
 
                                    user=user_ref,
190
 
                                    tenant=tenant_ref,
191
 
                                    metadata=metadata_ref))
192
 
 
193
 
        # TODO(termie): make this a util function or something
194
 
        # TODO(termie): i don't think the ec2 middleware currently expects a
195
 
        #               full return, but it contains a note saying that it
196
 
        #               would be better to expect a full return
197
 
        token_controller = service.TokenController()
198
 
        return token_controller._format_authenticate(
199
 
            token_ref, roles_ref, catalog_ref)
200
 
 
201
 
    def create_credential(self, context, user_id, tenant_id):
202
 
        """Create a secret/access pair for use with ec2 style auth.
203
 
 
204
 
        Generates a new set of credentials that map the the user/tenant
205
 
        pair.
206
 
 
207
 
        :param context: standard context
208
 
        :param user_id: id of user
209
 
        :param tenant_id: id of tenant
210
 
        :returns: credential: dict of ec2 credential
211
 
        """
212
 
        if not self._is_admin(context):
213
 
            self._assert_identity(context, user_id)
214
 
 
215
 
        self._assert_valid_user_id(context, user_id)
216
 
        self._assert_valid_tenant_id(context, tenant_id)
217
 
 
218
 
        cred_ref = {'user_id': user_id,
219
 
                    'tenant_id': tenant_id,
220
 
                    'access': uuid.uuid4().hex,
221
 
                    'secret': uuid.uuid4().hex}
222
 
        self.ec2_api.create_credential(context, cred_ref['access'], cred_ref)
223
 
        return {'credential': cred_ref}
224
 
 
225
 
    def get_credentials(self, context, user_id):
226
 
        """List all credentials for a user.
227
 
 
228
 
        :param context: standard context
229
 
        :param user_id: id of user
230
 
        :returns: credentials: list of ec2 credential dicts
231
 
        """
232
 
        if not self._is_admin(context):
233
 
            self._assert_identity(context, user_id)
234
 
        self._assert_valid_user_id(context, user_id)
235
 
        return {'credentials': self.ec2_api.list_credentials(context, user_id)}
236
 
 
237
 
    def get_credential(self, context, user_id, credential_id):
238
 
        """Retreive a user's access/secret pair by the access key.
239
 
 
240
 
        Grab the full access/secret pair for a given access key.
241
 
 
242
 
        :param context: standard context
243
 
        :param user_id: id of user
244
 
        :param credential_id: access key for credentials
245
 
        :returns: credential: dict of ec2 credential
246
 
        """
247
 
        if not self._is_admin(context):
248
 
            self._assert_identity(context, user_id)
249
 
        self._assert_valid_user_id(context, user_id)
250
 
        creds = self._get_credentials(context, credential_id)
251
 
        return {'credential': creds}
252
 
 
253
 
    def delete_credential(self, context, user_id, credential_id):
254
 
        """Delete a user's access/secret pair.
255
 
 
256
 
        Used to revoke a user's access/secret pair
257
 
 
258
 
        :param context: standard context
259
 
        :param user_id: id of user
260
 
        :param credential_id: access key for credentials
261
 
        :returns: bool: success
262
 
        """
263
 
        if not self._is_admin(context):
264
 
            self._assert_identity(context, user_id)
265
 
            self._assert_owner(context, user_id, credential_id)
266
 
 
267
 
        self._assert_valid_user_id(context, user_id)
268
 
        self._get_credentials(context, credential_id)
269
 
        return self.ec2_api.delete_credential(context, credential_id)
270
 
 
271
 
    def _get_credentials(self, context, credential_id):
272
 
        """Return credentials from an ID.
273
 
 
274
 
        :param context: standard context
275
 
        :param credential_id: id of credential
276
 
        :raises exception.Unauthorized: when credential id is invalid
277
 
        :returns: credential: dict of ec2 credential.
278
 
        """
279
 
        creds = self.ec2_api.get_credential(context,
280
 
                                            credential_id)
281
 
        if not creds:
282
 
            raise exception.Unauthorized(message='EC2 access key not found.')
283
 
        return creds
284
 
 
285
 
    def _assert_identity(self, context, user_id):
286
 
        """Check that the provided token belongs to the user.
287
 
 
288
 
        :param context: standard context
289
 
        :param user_id: id of user
290
 
        :raises exception.Forbidden: when token is invalid
291
 
 
292
 
        """
293
 
        try:
294
 
            token_ref = self.token_api.get_token(
295
 
                context=context,
296
 
                token_id=context['token_id'])
297
 
        except exception.TokenNotFound:
298
 
            raise exception.Unauthorized()
299
 
        token_user_id = token_ref['user'].get('id')
300
 
        if not token_user_id == user_id:
301
 
            raise exception.Forbidden()
302
 
 
303
 
    def _is_admin(self, context):
304
 
        """Wrap admin assertion error return statement.
305
 
 
306
 
        :param context: standard context
307
 
        :returns: bool: success
308
 
 
309
 
        """
310
 
        try:
311
 
            self.assert_admin(context)
312
 
            return True
313
 
        except exception.Forbidden:
314
 
            return False
315
 
 
316
 
    def _assert_owner(self, context, user_id, credential_id):
317
 
        """Ensure the provided user owns the credential.
318
 
 
319
 
        :param context: standard context
320
 
        :param user_id: expected credential owner
321
 
        :param credential_id: id of credential object
322
 
        :raises exception.Forbidden: on failure
323
 
 
324
 
        """
325
 
        cred_ref = self.ec2_api.get_credential(context, credential_id)
326
 
        if not user_id == cred_ref['user_id']:
327
 
            raise exception.Forbidden()
328
 
 
329
 
    def _assert_valid_user_id(self, context, user_id):
330
 
        """Ensure a valid user id.
331
 
 
332
 
        :param context: standard context
333
 
        :param user_id: expected credential owner
334
 
        :raises exception.UserNotFound: on failure
335
 
 
336
 
        """
337
 
        user_ref = self.identity_api.get_user(
338
 
            context=context,
339
 
            user_id=user_id)
340
 
        if not user_ref:
341
 
            raise exception.UserNotFound(user_id=user_id)
342
 
 
343
 
    def _assert_valid_tenant_id(self, context, tenant_id):
344
 
        """Ensure a valid tenant id.
345
 
 
346
 
        :param context: standard context
347
 
        :param user_id: expected credential owner
348
 
        :raises exception.UserNotFound: on failure
349
 
 
350
 
        """
351
 
        tenant_ref = self.identity_api.get_tenant(
352
 
            context=context,
353
 
            tenant_id=tenant_id)
354
 
        if not tenant_ref:
355
 
            raise exception.TenantNotFound(tenant_id=tenant_id)