components/openstack/keystone/patches/06-CVE-2014-3476.patch
branchs11-update
changeset 4072 db0cec748ec0
parent 4067 4be1f488dda8
child 4094 78203277f011
equal deleted inserted replaced
4067:4be1f488dda8 4072:db0cec748ec0
     1 Upstream patch for CVE-2014-3476.  This fix will be included in the
       
     2 Juno-2 development milestone and in future 2013.2.4 and 2014.1.2 releases
       
     3 
       
     4 From 9162837329665b4316afc2270a602dc8ae11f6d2 Mon Sep 17 00:00:00 2001
       
     5 From: Adam Young <[email protected]>
       
     6 Date: Thu, 29 May 2014 13:56:17 -0400
       
     7 Subject: [PATCH] Block delegation escalation of privilege
       
     8 
       
     9 Forbids doing the following with either a trust
       
    10   or oauth based token:
       
    11   creating a trust
       
    12   approving a request_token
       
    13   listing request tokens
       
    14 
       
    15 Change-Id: I1528f9dd003f5e03cbc50b78e1b32dbbf85ffcc2
       
    16 Closes-Bug:  1324592
       
    17 ---
       
    18  keystone/common/controller.py          |   36 +++++++++++-
       
    19  keystone/contrib/oauth1/controllers.py |   12 ++++
       
    20  keystone/tests/test_v3_auth.py         |   62 ++++++++++++++++++++
       
    21  keystone/tests/test_v3_oauth1.py       |   98 ++++++++++++++++++++++++++++++++
       
    22  keystone/trust/controllers.py          |    9 +++
       
    23  5 files changed, 216 insertions(+), 1 deletion(-)
       
    24 
       
    25 diff --git a/keystone/common/controller.py b/keystone/common/controller.py
       
    26 index faadc09..d6c33df 100644
       
    27 --- a/keystone/common/controller.py
       
    28 +++ b/keystone/common/controller.py
       
    29 @@ -44,7 +44,7 @@ def _build_policy_check_credentials(self, action, context, kwargs):
       
    30      # it would otherwise need to reload the token_ref from backing store.
       
    31      wsgi.validate_token_bind(context, token_ref)
       
    32  
       
    33 -    creds = {}
       
    34 +    creds = {'is_delegated_auth': False}
       
    35      if 'token_data' in token_ref and 'token' in token_ref['token_data']:
       
    36          #V3 Tokens
       
    37          token_data = token_ref['token_data']['token']
       
    38 @@ -66,9 +66,32 @@ def _build_policy_check_credentials(self, action, context, kwargs):
       
    39              creds['roles'] = []
       
    40              for role in token_data['roles']:
       
    41                  creds['roles'].append(role['name'])
       
    42 +
       
    43 +        trust = token_data.get('OS-TRUST:trust')
       
    44 +        if trust is None:
       
    45 +            creds['trust_id'] = None
       
    46 +            creds['trustor_id'] = None
       
    47 +            creds['trustee_id'] = None
       
    48 +        else:
       
    49 +            creds['trust_id'] = trust['id']
       
    50 +            creds['trustor_id'] = trust['trustor_user']['id']
       
    51 +            creds['trustee_id'] = trust['trustee_user']['id']
       
    52 +            creds['is_delegated_auth'] = True
       
    53 +
       
    54 +        oauth1 = token_data.get('OS-OAUTH1')
       
    55 +        if oauth1 is None:
       
    56 +            creds['consumer_id'] = None
       
    57 +            creds['access_token_id'] = None
       
    58 +        else:
       
    59 +            creds['consumer_id'] = oauth1['consumer_id']
       
    60 +            creds['access_token_id'] = oauth1['access_token_id']
       
    61 +            creds['is_delegated_auth'] = True
       
    62 +
       
    63      else:
       
    64          #v2 Tokens
       
    65          creds = token_ref.get('metadata', {}).copy()
       
    66 +        creds['is_delegated_auth'] = False
       
    67 +
       
    68          try:
       
    69              creds['user_id'] = token_ref['user'].get('id')
       
    70          except AttributeError:
       
    71 @@ -81,6 +104,16 @@ def _build_policy_check_credentials(self, action, context, kwargs):
       
    72          # NOTE(vish): this is pretty inefficient
       
    73          creds['roles'] = [self.identity_api.get_role(role)['name']
       
    74                            for role in creds.get('roles', [])]
       
    75 +        trust = token_ref.get('trust')
       
    76 +        if trust is None:
       
    77 +            creds['trust_id'] = None
       
    78 +            creds['trustor_id'] = None
       
    79 +            creds['trustee_id'] = None
       
    80 +        else:
       
    81 +            creds['trust_id'] = trust.get('id')
       
    82 +            creds['trustor_id'] = trust.get('trustor_id')
       
    83 +            creds['trustee_id'] = trust.get('trustee_id')
       
    84 +            creds['is_delegated_auth'] = True
       
    85  
       
    86      return creds
       
    87  
       
    88 @@ -155,6 +188,7 @@ def protected(callback=None):
       
    89                  policy_dict.update(kwargs)
       
    90                  self.policy_api.enforce(creds, action, flatten(policy_dict))
       
    91                  LOG.debug(_('RBAC: Authorization granted'))
       
    92 +                context['environment'] = {'KEYSTONE_AUTH_CONTEXT': creds}
       
    93              return f(self, context, *args, **kwargs)
       
    94          return inner
       
    95      return wrapper
       
    96 diff --git a/keystone/contrib/oauth1/controllers.py b/keystone/contrib/oauth1/controllers.py
       
    97 index b8c2441..d4024df 100644
       
    98 --- a/keystone/contrib/oauth1/controllers.py
       
    99 +++ b/keystone/contrib/oauth1/controllers.py
       
   100 @@ -86,6 +86,12 @@ class AccessTokenCrudV3(controller.V3Controller):
       
   101  
       
   102      @controller.protected()
       
   103      def list_access_tokens(self, context, user_id):
       
   104 +        auth_context = context.get('environment',
       
   105 +                                   {}).get('KEYSTONE_AUTH_CONTEXT', {})
       
   106 +        if auth_context.get('is_delegated_auth'):
       
   107 +            raise exception.Forbidden(
       
   108 +                _('Cannot list request tokens'
       
   109 +                  ' with a token issued via delegation.'))
       
   110          refs = self.oauth_api.list_access_tokens(user_id)
       
   111          formatted_refs = ([self._format_token_entity(x) for x in refs])
       
   112          return AccessTokenCrudV3.wrap_collection(context, formatted_refs)
       
   113 @@ -314,6 +320,12 @@ class OAuthControllerV3(controller.V3Controller):
       
   114          there is not another easy way to make sure the user knows which roles
       
   115          are being requested before authorizing.
       
   116          """
       
   117 +        auth_context = context.get('environment',
       
   118 +                                   {}).get('KEYSTONE_AUTH_CONTEXT', {})
       
   119 +        if auth_context.get('is_delegated_auth'):
       
   120 +            raise exception.Forbidden(
       
   121 +                _('Cannot authorize a request token'
       
   122 +                  ' with a token issued via delegation.'))
       
   123  
       
   124          req_token = self.oauth_api.get_request_token(request_token_id)
       
   125  
       
   126 diff --git a/keystone/tests/test_v3_auth.py b/keystone/tests/test_v3_auth.py
       
   127 index e89e29f..f3e3ace 100644
       
   128 --- a/keystone/tests/test_v3_auth.py
       
   129 +++ b/keystone/tests/test_v3_auth.py
       
   130 @@ -2150,6 +2150,68 @@ class TestTrustAuth(TestAuthInfo):
       
   131          self.assertEqual(r.result['token']['project']['name'],
       
   132                           self.project['name'])
       
   133  
       
   134 +    def test_impersonation_token_cannot_create_new_trust(self):
       
   135 +        ref = self.new_trust_ref(
       
   136 +            trustor_user_id=self.user_id,
       
   137 +            trustee_user_id=self.trustee_user_id,
       
   138 +            project_id=self.project_id,
       
   139 +            impersonation=True,
       
   140 +            expires=dict(minutes=1),
       
   141 +            role_ids=[self.role_id])
       
   142 +        del ref['id']
       
   143 +
       
   144 +        r = self.post('/OS-TRUST/trusts', body={'trust': ref})
       
   145 +        trust = self.assertValidTrustResponse(r)
       
   146 +
       
   147 +        auth_data = self.build_authentication_request(
       
   148 +            user_id=self.trustee_user['id'],
       
   149 +            password=self.trustee_user['password'],
       
   150 +            trust_id=trust['id'])
       
   151 +        r = self.post('/auth/tokens', body=auth_data)
       
   152 +
       
   153 +        trust_token = r.headers['X-Subject-Token']
       
   154 +
       
   155 +        # Build second trust
       
   156 +        ref = self.new_trust_ref(
       
   157 +            trustor_user_id=self.user_id,
       
   158 +            trustee_user_id=self.trustee_user_id,
       
   159 +            project_id=self.project_id,
       
   160 +            impersonation=True,
       
   161 +            expires=dict(minutes=1),
       
   162 +            role_ids=[self.role_id])
       
   163 +        del ref['id']
       
   164 +
       
   165 +        self.post('/OS-TRUST/trusts',
       
   166 +                  body={'trust': ref},
       
   167 +                  token=trust_token,
       
   168 +                  expected_status=403)
       
   169 +
       
   170 +    def test_delete_trust_revokes_tokens(self):
       
   171 +        ref = self.new_trust_ref(
       
   172 +            trustor_user_id=self.user_id,
       
   173 +            trustee_user_id=self.trustee_user_id,
       
   174 +            project_id=self.project_id,
       
   175 +            impersonation=False,
       
   176 +            expires=dict(minutes=1),
       
   177 +            role_ids=[self.role_id])
       
   178 +        del ref['id']
       
   179 +        r = self.post('/OS-TRUST/trusts', body={'trust': ref})
       
   180 +        trust = self.assertValidTrustResponse(r)
       
   181 +        trust_id = trust['id']
       
   182 +        auth_data = self.build_authentication_request(
       
   183 +            user_id=self.trustee_user['id'],
       
   184 +            password=self.trustee_user['password'],
       
   185 +            trust_id=trust_id)
       
   186 +        r = self.post('/auth/tokens', body=auth_data)
       
   187 +        self.assertValidProjectTrustScopedTokenResponse(
       
   188 +            r, self.trustee_user)
       
   189 +        trust_token = r.headers['X-Subject-Token']
       
   190 +        self.delete('/OS-TRUST/trusts/%(trust_id)s' % {
       
   191 +            'trust_id': trust_id},
       
   192 +            expected_status=204)
       
   193 +        headers = {'X-Subject-Token': trust_token}
       
   194 +        self.head('/auth/tokens', headers=headers, expected_status=404)
       
   195 +
       
   196      def test_delete_trust(self):
       
   197          ref = self.new_trust_ref(
       
   198              trustor_user_id=self.user_id,
       
   199 diff --git a/keystone/tests/test_v3_oauth1.py b/keystone/tests/test_v3_oauth1.py
       
   200 index 73a34d7..a83c86e 100644
       
   201 --- a/keystone/tests/test_v3_oauth1.py
       
   202 +++ b/keystone/tests/test_v3_oauth1.py
       
   203 @@ -16,6 +16,7 @@
       
   204  
       
   205  import copy
       
   206  import os
       
   207 +import tempfile
       
   208  import urlparse
       
   209  import uuid
       
   210  
       
   211 @@ -26,6 +27,8 @@ from keystone import contrib
       
   212  from keystone.contrib import oauth1
       
   213  from keystone.contrib.oauth1 import controllers
       
   214  from keystone.openstack.common import importutils
       
   215 +from keystone.openstack.common import jsonutils
       
   216 +from keystone.policy.backends import rules
       
   217  from keystone.tests import test_v3
       
   218  
       
   219  
       
   220 @@ -447,6 +450,101 @@ class AuthTokenTests(OAuthFlowTests):
       
   221          self.assertTrue(len(tokens) > 0)
       
   222          self.assertTrue(keystone_token_uuid in tokens)
       
   223  
       
   224 +    def _create_trust_get_token(self):
       
   225 +        ref = self.new_trust_ref(
       
   226 +            trustor_user_id=self.user_id,
       
   227 +            trustee_user_id=self.user_id,
       
   228 +            project_id=self.project_id,
       
   229 +            impersonation=True,
       
   230 +            expires=dict(minutes=1),
       
   231 +            role_ids=[self.role_id])
       
   232 +        del ref['id']
       
   233 +
       
   234 +        r = self.post('/OS-TRUST/trusts', body={'trust': ref})
       
   235 +        trust = self.assertValidTrustResponse(r)
       
   236 +
       
   237 +        auth_data = self.build_authentication_request(
       
   238 +            user_id=self.user['id'],
       
   239 +            password=self.user['password'],
       
   240 +            trust_id=trust['id'])
       
   241 +        r = self.post('/auth/tokens', body=auth_data)
       
   242 +
       
   243 +        trust_token = r.headers['X-Subject-Token']
       
   244 +        return trust_token
       
   245 +
       
   246 +    def _approve_request_token_url(self):
       
   247 +        consumer = self._create_single_consumer()
       
   248 +        consumer_id = consumer.get('id')
       
   249 +        consumer_secret = consumer.get('secret')
       
   250 +        self.consumer = oauth1.Consumer(consumer_id, consumer_secret)
       
   251 +        self.assertIsNotNone(self.consumer.key)
       
   252 +
       
   253 +        url, headers = self._create_request_token(self.consumer,
       
   254 +                                                  self.project_id)
       
   255 +        content = self.post(url, headers=headers)
       
   256 +        credentials = urlparse.parse_qs(content.result)
       
   257 +        request_key = credentials.get('oauth_token')[0]
       
   258 +        request_secret = credentials.get('oauth_token_secret')[0]
       
   259 +        self.request_token = oauth1.Token(request_key, request_secret)
       
   260 +        self.assertIsNotNone(self.request_token.key)
       
   261 +
       
   262 +        url = self._authorize_request_token(request_key)
       
   263 +
       
   264 +        return url
       
   265 +
       
   266 +    def test_oauth_token_cannot_create_new_trust(self):
       
   267 +        self.test_oauth_flow()
       
   268 +        ref = self.new_trust_ref(
       
   269 +            trustor_user_id=self.user_id,
       
   270 +            trustee_user_id=self.user_id,
       
   271 +            project_id=self.project_id,
       
   272 +            impersonation=True,
       
   273 +            expires=dict(minutes=1),
       
   274 +            role_ids=[self.role_id])
       
   275 +        del ref['id']
       
   276 +
       
   277 +        self.post('/OS-TRUST/trusts',
       
   278 +                  body={'trust': ref},
       
   279 +                  token=self.keystone_token_id,
       
   280 +                  expected_status=403)
       
   281 +
       
   282 +    def test_oauth_token_cannot_authorize_request_token(self):
       
   283 +        self.test_oauth_flow()
       
   284 +        url = self._approve_request_token_url()
       
   285 +        body = {'roles': [{'id': self.role_id}]}
       
   286 +        self.put(url, body=body, token=self.keystone_token_id,
       
   287 +                 expected_status=403)
       
   288 +
       
   289 +    def test_oauth_token_cannot_list_request_tokens(self):
       
   290 +        self._set_policy({"identity:list_access_tokens": [],
       
   291 +                          "identity:create_consumer": [],
       
   292 +                          "identity:authorize_request_token": []})
       
   293 +        self.test_oauth_flow()
       
   294 +        url = '/users/%s/OS-OAUTH1/access_tokens' % self.user_id
       
   295 +        self.get(url, token=self.keystone_token_id,
       
   296 +                 expected_status=403)
       
   297 +
       
   298 +    def _set_policy(self, new_policy):
       
   299 +        _unused, self.tmpfilename = tempfile.mkstemp()
       
   300 +        rules.reset()
       
   301 +        self.opt(policy_file=self.tmpfilename)
       
   302 +        with open(self.tmpfilename, "w") as policyfile:
       
   303 +            policyfile.write(jsonutils.dumps(new_policy))
       
   304 +        self.addCleanup(os.remove, self.tmpfilename)
       
   305 +
       
   306 +    def test_trust_token_cannot_authorize_request_token(self):
       
   307 +        trust_token = self._create_trust_get_token()
       
   308 +        url = self._approve_request_token_url()
       
   309 +        body = {'roles': [{'id': self.role_id}]}
       
   310 +        self.put(url, body=body, token=trust_token, expected_status=403)
       
   311 +
       
   312 +    def test_trust_token_cannot_list_request_tokens(self):
       
   313 +        self._set_policy({"identity:list_access_tokens": [],
       
   314 +                          "identity:create_trust": []})
       
   315 +        trust_token = self._create_trust_get_token()
       
   316 +        url = '/users/%s/OS-OAUTH1/access_tokens' % self.user_id
       
   317 +        self.get(url, token=trust_token, expected_status=403)
       
   318 +
       
   319  
       
   320  class MaliciousOAuth1Tests(OAuth1Tests):
       
   321  
       
   322 diff --git a/keystone/trust/controllers.py b/keystone/trust/controllers.py
       
   323 index 1d54f51..7fdc8c2 100644
       
   324 --- a/keystone/trust/controllers.py
       
   325 +++ b/keystone/trust/controllers.py
       
   326 @@ -144,6 +144,15 @@ class TrustV3(controller.V3Controller):
       
   327  
       
   328          # TODO(ayoung): instead of raising ValidationError on the first
       
   329          # problem, return a collection of all the problems.
       
   330 +
       
   331 +        # Explicitly prevent a trust token from creating a new trust.
       
   332 +        auth_context = context.get('environment',
       
   333 +                                   {}).get('KEYSTONE_AUTH_CONTEXT', {})
       
   334 +        if auth_context.get('is_delegated_auth'):
       
   335 +            raise exception.Forbidden(
       
   336 +                _('Cannot create a trust'
       
   337 +                  ' with a token issued via delegation.'))
       
   338 +
       
   339          if not trust:
       
   340              raise exception.ValidationError(attribute='trust',
       
   341                                              target='request')