Alan Pevec 1a20c7b
From b954590b1c7676bd0fb0c848123060292f3cfa8e Mon Sep 17 00:00:00 2001
Alan Pevec 1a20c7b
From: Adam Young <ayoung@redhat.com>
Alan Pevec 1a20c7b
Date: Thu, 29 May 2014 13:56:17 -0400
Alan Pevec 1a20c7b
Subject: [PATCH] Block delegation escalation of privilege
Alan Pevec 1a20c7b
Alan Pevec 1a20c7b
Forbids doing the following with either a trust
Alan Pevec 1a20c7b
  or oauth based token:
Alan Pevec 1a20c7b
  creating a trust
Alan Pevec 1a20c7b
  approving a request_token
Alan Pevec 1a20c7b
  listing request tokens
Alan Pevec 1a20c7b
Alan Pevec 1a20c7b
Change-Id: I1528f9dd003f5e03cbc50b78e1b32dbbf85ffcc2
Alan Pevec 1a20c7b
Closes-Bug:  1324592
Alan Pevec 1a20c7b
(cherry picked from commit 73785122eefe523bb57c819e085c7f6ec97d779c)
Alan Pevec 1a20c7b
---
Alan Pevec 1a20c7b
 keystone/common/authorization.py       | 36 ++++++++++++-
Alan Pevec 1a20c7b
 keystone/contrib/oauth1/controllers.py | 12 +++++
Alan Pevec 1a20c7b
 keystone/tests/test_v3_auth.py         | 36 +++++++++++++
Alan Pevec 1a20c7b
 keystone/tests/test_v3_oauth1.py       | 97 ++++++++++++++++++++++++++++++++++
Alan Pevec 1a20c7b
 keystone/trust/controllers.py          |  9 ++++
Alan Pevec 1a20c7b
 5 files changed, 188 insertions(+), 2 deletions(-)
Alan Pevec 1a20c7b
Alan Pevec 1a20c7b
diff --git a/keystone/common/authorization.py b/keystone/common/authorization.py
Alan Pevec 1a20c7b
index 6dc7435..11d0d79 100644
Alan Pevec 1a20c7b
--- a/keystone/common/authorization.py
Alan Pevec 1a20c7b
+++ b/keystone/common/authorization.py
Alan Pevec 1a20c7b
@@ -67,7 +67,7 @@ def is_v3_token(token):
Alan Pevec 1a20c7b
 
Alan Pevec 1a20c7b
 
Alan Pevec 1a20c7b
 def v3_token_to_auth_context(token):
Alan Pevec 1a20c7b
-    creds = {}
Alan Pevec 1a20c7b
+    creds = {'is_delegated_auth': False}
Alan Pevec 1a20c7b
     token_data = token['token']
Alan Pevec 1a20c7b
     try:
Alan Pevec 1a20c7b
         creds['user_id'] = token_data['user']['id']
Alan Pevec 1a20c7b
@@ -87,11 +87,31 @@ def v3_token_to_auth_context(token):
Alan Pevec 1a20c7b
     creds['group_ids'] = [
Alan Pevec 1a20c7b
         g['id'] for g in token_data['user'].get(federation.FEDERATION, {}).get(
Alan Pevec 1a20c7b
             'groups', [])]
Alan Pevec 1a20c7b
+
Alan Pevec 1a20c7b
+    trust = token_data.get('OS-TRUST:trust')
Alan Pevec 1a20c7b
+    if trust is None:
Alan Pevec 1a20c7b
+        creds['trust_id'] = None
Alan Pevec 1a20c7b
+        creds['trustor_id'] = None
Alan Pevec 1a20c7b
+        creds['trustee_id'] = None
Alan Pevec 1a20c7b
+    else:
Alan Pevec 1a20c7b
+        creds['trust_id'] = trust['id']
Alan Pevec 1a20c7b
+        creds['trustor_id'] = trust['trustor_user']['id']
Alan Pevec 1a20c7b
+        creds['trustee_id'] = trust['trustee_user']['id']
Alan Pevec 1a20c7b
+        creds['is_delegated_auth'] = True
Alan Pevec 1a20c7b
+
Alan Pevec 1a20c7b
+    oauth1 = token_data.get('OS-OAUTH1')
Alan Pevec 1a20c7b
+    if oauth1 is None:
Alan Pevec 1a20c7b
+        creds['consumer_id'] = None
Alan Pevec 1a20c7b
+        creds['access_token_id'] = None
Alan Pevec 1a20c7b
+    else:
Alan Pevec 1a20c7b
+        creds['consumer_id'] = oauth1['consumer_id']
Alan Pevec 1a20c7b
+        creds['access_token_id'] = oauth1['access_token_id']
Alan Pevec 1a20c7b
+        creds['is_delegated_auth'] = True
Alan Pevec 1a20c7b
     return creds
Alan Pevec 1a20c7b
 
Alan Pevec 1a20c7b
 
Alan Pevec 1a20c7b
 def v2_token_to_auth_context(token):
Alan Pevec 1a20c7b
-    creds = {}
Alan Pevec 1a20c7b
+    creds = {'is_delegated_auth': False}
Alan Pevec 1a20c7b
     token_data = token['access']
Alan Pevec 1a20c7b
     try:
Alan Pevec 1a20c7b
         creds['user_id'] = token_data['user']['id']
Alan Pevec 1a20c7b
@@ -105,6 +125,18 @@ def v2_token_to_auth_context(token):
Alan Pevec 1a20c7b
     if 'roles' in token_data['user']:
Alan Pevec 1a20c7b
         creds['roles'] = [role['name'] for
Alan Pevec 1a20c7b
                           role in token_data['user']['roles']]
Alan Pevec 1a20c7b
+
Alan Pevec 1a20c7b
+    trust = token_data.get('trust')
Alan Pevec 1a20c7b
+    if trust is None:
Alan Pevec 1a20c7b
+        creds['trust_id'] = None
Alan Pevec 1a20c7b
+        creds['trustor_id'] = None
Alan Pevec 1a20c7b
+        creds['trustee_id'] = None
Alan Pevec 1a20c7b
+    else:
Alan Pevec 1a20c7b
+        creds['trust_id'] = trust.get('id')
Alan Pevec 1a20c7b
+        creds['trustor_id'] = trust.get('trustor_id')
Alan Pevec 1a20c7b
+        creds['trustee_id'] = trust.get('trustee_id')
Alan Pevec 1a20c7b
+        creds['is_delegated_auth'] = True
Alan Pevec 1a20c7b
+
Alan Pevec 1a20c7b
     return creds
Alan Pevec 1a20c7b
 
Alan Pevec 1a20c7b
 
Alan Pevec 1a20c7b
diff --git a/keystone/contrib/oauth1/controllers.py b/keystone/contrib/oauth1/controllers.py
Alan Pevec 1a20c7b
index 2c938ba..a185e4f 100644
Alan Pevec 1a20c7b
--- a/keystone/contrib/oauth1/controllers.py
Alan Pevec 1a20c7b
+++ b/keystone/contrib/oauth1/controllers.py
Alan Pevec 1a20c7b
@@ -95,6 +95,12 @@ class AccessTokenCrudV3(controller.V3Controller):
Alan Pevec 1a20c7b
 
Alan Pevec 1a20c7b
     @controller.protected()
Alan Pevec 1a20c7b
     def list_access_tokens(self, context, user_id):
Alan Pevec 1a20c7b
+        auth_context = context.get('environment',
Alan Pevec 1a20c7b
+                                   {}).get('KEYSTONE_AUTH_CONTEXT', {})
Alan Pevec 1a20c7b
+        if auth_context.get('is_delegated_auth'):
Alan Pevec 1a20c7b
+            raise exception.Forbidden(
Alan Pevec 1a20c7b
+                _('Cannot list request tokens'
Alan Pevec 1a20c7b
+                  ' with a token issued via delegation.'))
Alan Pevec 1a20c7b
         refs = self.oauth_api.list_access_tokens(user_id)
Alan Pevec 1a20c7b
         formatted_refs = ([self._format_token_entity(context, x)
Alan Pevec 1a20c7b
                            for x in refs])
Alan Pevec 1a20c7b
@@ -310,6 +316,12 @@ class OAuthControllerV3(controller.V3Controller):
Alan Pevec 1a20c7b
         there is not another easy way to make sure the user knows which roles
Alan Pevec 1a20c7b
         are being requested before authorizing.
Alan Pevec 1a20c7b
         """
Alan Pevec 1a20c7b
+        auth_context = context.get('environment',
Alan Pevec 1a20c7b
+                                   {}).get('KEYSTONE_AUTH_CONTEXT', {})
Alan Pevec 1a20c7b
+        if auth_context.get('is_delegated_auth'):
Alan Pevec 1a20c7b
+            raise exception.Forbidden(
Alan Pevec 1a20c7b
+                _('Cannot authorize a request token'
Alan Pevec 1a20c7b
+                  ' with a token issued via delegation.'))
Alan Pevec 1a20c7b
 
Alan Pevec 1a20c7b
         req_token = self.oauth_api.get_request_token(request_token_id)
Alan Pevec 1a20c7b
 
Alan Pevec 1a20c7b
diff --git a/keystone/tests/test_v3_auth.py b/keystone/tests/test_v3_auth.py
Alan Pevec 1a20c7b
index 5de7e02..8a27a38 100644
Alan Pevec 1a20c7b
--- a/keystone/tests/test_v3_auth.py
Alan Pevec 1a20c7b
+++ b/keystone/tests/test_v3_auth.py
Alan Pevec 1a20c7b
@@ -2777,6 +2777,42 @@ class TestTrustAuth(TestAuthInfo):
Alan Pevec 1a20c7b
         self.assertEqual(r.result['token']['project']['name'],
Alan Pevec 1a20c7b
                          self.project['name'])
Alan Pevec 1a20c7b
 
Alan Pevec 1a20c7b
+    def test_impersonation_token_cannot_create_new_trust(self):
Alan Pevec 1a20c7b
+        ref = self.new_trust_ref(
Alan Pevec 1a20c7b
+            trustor_user_id=self.user_id,
Alan Pevec 1a20c7b
+            trustee_user_id=self.trustee_user_id,
Alan Pevec 1a20c7b
+            project_id=self.project_id,
Alan Pevec 1a20c7b
+            impersonation=True,
Alan Pevec 1a20c7b
+            expires=dict(minutes=1),
Alan Pevec 1a20c7b
+            role_ids=[self.role_id])
Alan Pevec 1a20c7b
+        del ref['id']
Alan Pevec 1a20c7b
+
Alan Pevec 1a20c7b
+        r = self.post('/OS-TRUST/trusts', body={'trust': ref})
Alan Pevec 1a20c7b
+        trust = self.assertValidTrustResponse(r)
Alan Pevec 1a20c7b
+
Alan Pevec 1a20c7b
+        auth_data = self.build_authentication_request(
Alan Pevec 1a20c7b
+            user_id=self.trustee_user['id'],
Alan Pevec 1a20c7b
+            password=self.trustee_user['password'],
Alan Pevec 1a20c7b
+            trust_id=trust['id'])
Alan Pevec 1a20c7b
+        r = self.post('/auth/tokens', body=auth_data)
Alan Pevec 1a20c7b
+
Alan Pevec 1a20c7b
+        trust_token = r.headers['X-Subject-Token']
Alan Pevec 1a20c7b
+
Alan Pevec 1a20c7b
+        # Build second trust
Alan Pevec 1a20c7b
+        ref = self.new_trust_ref(
Alan Pevec 1a20c7b
+            trustor_user_id=self.user_id,
Alan Pevec 1a20c7b
+            trustee_user_id=self.trustee_user_id,
Alan Pevec 1a20c7b
+            project_id=self.project_id,
Alan Pevec 1a20c7b
+            impersonation=True,
Alan Pevec 1a20c7b
+            expires=dict(minutes=1),
Alan Pevec 1a20c7b
+            role_ids=[self.role_id])
Alan Pevec 1a20c7b
+        del ref['id']
Alan Pevec 1a20c7b
+
Alan Pevec 1a20c7b
+        self.post('/OS-TRUST/trusts',
Alan Pevec 1a20c7b
+                  body={'trust': ref},
Alan Pevec 1a20c7b
+                  token=trust_token,
Alan Pevec 1a20c7b
+                  expected_status=403)
Alan Pevec 1a20c7b
+
Alan Pevec 1a20c7b
     def assertTrustTokensRevoked(self, trust_id):
Alan Pevec 1a20c7b
         revocation_response = self.get('/OS-REVOKE/events',
Alan Pevec 1a20c7b
                                        expected_status=200)
Alan Pevec 1a20c7b
diff --git a/keystone/tests/test_v3_oauth1.py b/keystone/tests/test_v3_oauth1.py
Alan Pevec 1a20c7b
index b653855..d993889 100644
Alan Pevec 1a20c7b
--- a/keystone/tests/test_v3_oauth1.py
Alan Pevec 1a20c7b
+++ b/keystone/tests/test_v3_oauth1.py
Alan Pevec 1a20c7b
@@ -13,6 +13,8 @@
Alan Pevec 1a20c7b
 # under the License.
Alan Pevec 1a20c7b
 
Alan Pevec 1a20c7b
 import copy
Alan Pevec 1a20c7b
+import os
Alan Pevec 1a20c7b
+import tempfile
Alan Pevec 1a20c7b
 import uuid
Alan Pevec 1a20c7b
 
Alan Pevec 1a20c7b
 from six.moves import urllib
Alan Pevec 1a20c7b
@@ -26,6 +28,7 @@ from keystone.contrib.oauth1 import controllers
Alan Pevec 1a20c7b
 from keystone import exception
Alan Pevec 1a20c7b
 from keystone.openstack.common.db.sqlalchemy import migration
Alan Pevec 1a20c7b
 from keystone.openstack.common import importutils
Alan Pevec 1a20c7b
+from keystone.openstack.common import jsonutils
Alan Pevec 1a20c7b
 from keystone.tests import test_v3
Alan Pevec 1a20c7b
 
Alan Pevec 1a20c7b
 
Alan Pevec 1a20c7b
@@ -486,6 +489,100 @@ class AuthTokenTests(OAuthFlowTests):
Alan Pevec 1a20c7b
         self.assertRaises(exception.TokenNotFound, self.token_api.get_token,
Alan Pevec 1a20c7b
                           self.keystone_token_id)
Alan Pevec 1a20c7b
 
Alan Pevec 1a20c7b
+    def _create_trust_get_token(self):
Alan Pevec 1a20c7b
+        ref = self.new_trust_ref(
Alan Pevec 1a20c7b
+            trustor_user_id=self.user_id,
Alan Pevec 1a20c7b
+            trustee_user_id=self.user_id,
Alan Pevec 1a20c7b
+            project_id=self.project_id,
Alan Pevec 1a20c7b
+            impersonation=True,
Alan Pevec 1a20c7b
+            expires=dict(minutes=1),
Alan Pevec 1a20c7b
+            role_ids=[self.role_id])
Alan Pevec 1a20c7b
+        del ref['id']
Alan Pevec 1a20c7b
+
Alan Pevec 1a20c7b
+        r = self.post('/OS-TRUST/trusts', body={'trust': ref})
Alan Pevec 1a20c7b
+        trust = self.assertValidTrustResponse(r)
Alan Pevec 1a20c7b
+
Alan Pevec 1a20c7b
+        auth_data = self.build_authentication_request(
Alan Pevec 1a20c7b
+            user_id=self.user['id'],
Alan Pevec 1a20c7b
+            password=self.user['password'],
Alan Pevec 1a20c7b
+            trust_id=trust['id'])
Alan Pevec 1a20c7b
+        r = self.post('/auth/tokens', body=auth_data)
Alan Pevec 1a20c7b
+
Alan Pevec 1a20c7b
+        trust_token = r.headers['X-Subject-Token']
Alan Pevec 1a20c7b
+        return trust_token
Alan Pevec 1a20c7b
+
Alan Pevec 1a20c7b
+    def _approve_request_token_url(self):
Alan Pevec 1a20c7b
+        consumer = self._create_single_consumer()
Alan Pevec 1a20c7b
+        consumer_id = consumer['id']
Alan Pevec 1a20c7b
+        consumer_secret = consumer['secret']
Alan Pevec 1a20c7b
+        self.consumer = {'key': consumer_id, 'secret': consumer_secret}
Alan Pevec 1a20c7b
+        self.assertIsNotNone(self.consumer['secret'])
Alan Pevec 1a20c7b
+
Alan Pevec 1a20c7b
+        url, headers = self._create_request_token(self.consumer,
Alan Pevec 1a20c7b
+                                                  self.project_id)
Alan Pevec 1a20c7b
+        content = self.post(url, headers=headers)
Alan Pevec 1a20c7b
+        credentials = urllib.parse.parse_qs(content.result)
Alan Pevec 1a20c7b
+        request_key = credentials['oauth_token'][0]
Alan Pevec 1a20c7b
+        request_secret = credentials['oauth_token_secret'][0]
Alan Pevec 1a20c7b
+        self.request_token = oauth1.Token(request_key, request_secret)
Alan Pevec 1a20c7b
+        self.assertIsNotNone(self.request_token.key)
Alan Pevec 1a20c7b
+
Alan Pevec 1a20c7b
+        url = self._authorize_request_token(request_key)
Alan Pevec 1a20c7b
+
Alan Pevec 1a20c7b
+        return url
Alan Pevec 1a20c7b
+
Alan Pevec 1a20c7b
+    def test_oauth_token_cannot_create_new_trust(self):
Alan Pevec 1a20c7b
+        self.test_oauth_flow()
Alan Pevec 1a20c7b
+        ref = self.new_trust_ref(
Alan Pevec 1a20c7b
+            trustor_user_id=self.user_id,
Alan Pevec 1a20c7b
+            trustee_user_id=self.user_id,
Alan Pevec 1a20c7b
+            project_id=self.project_id,
Alan Pevec 1a20c7b
+            impersonation=True,
Alan Pevec 1a20c7b
+            expires=dict(minutes=1),
Alan Pevec 1a20c7b
+            role_ids=[self.role_id])
Alan Pevec 1a20c7b
+        del ref['id']
Alan Pevec 1a20c7b
+
Alan Pevec 1a20c7b
+        self.post('/OS-TRUST/trusts',
Alan Pevec 1a20c7b
+                  body={'trust': ref},
Alan Pevec 1a20c7b
+                  token=self.keystone_token_id,
Alan Pevec 1a20c7b
+                  expected_status=403)
Alan Pevec 1a20c7b
+
Alan Pevec 1a20c7b
+    def test_oauth_token_cannot_authorize_request_token(self):
Alan Pevec 1a20c7b
+        self.test_oauth_flow()
Alan Pevec 1a20c7b
+        url = self._approve_request_token_url()
Alan Pevec 1a20c7b
+        body = {'roles': [{'id': self.role_id}]}
Alan Pevec 1a20c7b
+        self.put(url, body=body, token=self.keystone_token_id,
Alan Pevec 1a20c7b
+                 expected_status=403)
Alan Pevec 1a20c7b
+
Alan Pevec 1a20c7b
+    def test_oauth_token_cannot_list_request_tokens(self):
Alan Pevec 1a20c7b
+        self._set_policy({"identity:list_access_tokens": [],
Alan Pevec 1a20c7b
+                          "identity:create_consumer": [],
Alan Pevec 1a20c7b
+                          "identity:authorize_request_token": []})
Alan Pevec 1a20c7b
+        self.test_oauth_flow()
Alan Pevec 1a20c7b
+        url = '/users/%s/OS-OAUTH1/access_tokens' % self.user_id
Alan Pevec 1a20c7b
+        self.get(url, token=self.keystone_token_id,
Alan Pevec 1a20c7b
+                 expected_status=403)
Alan Pevec 1a20c7b
+
Alan Pevec 1a20c7b
+    def _set_policy(self, new_policy):
Alan Pevec 1a20c7b
+        _unused, self.tmpfilename = tempfile.mkstemp()
Alan Pevec 1a20c7b
+        self.config_fixture.config(policy_file=self.tmpfilename)
Alan Pevec 1a20c7b
+        with open(self.tmpfilename, "w") as policyfile:
Alan Pevec 1a20c7b
+            policyfile.write(jsonutils.dumps(new_policy))
Alan Pevec 1a20c7b
+        self.addCleanup(os.remove, self.tmpfilename)
Alan Pevec 1a20c7b
+
Alan Pevec 1a20c7b
+    def test_trust_token_cannot_authorize_request_token(self):
Alan Pevec 1a20c7b
+        trust_token = self._create_trust_get_token()
Alan Pevec 1a20c7b
+        url = self._approve_request_token_url()
Alan Pevec 1a20c7b
+        body = {'roles': [{'id': self.role_id}]}
Alan Pevec 1a20c7b
+        self.put(url, body=body, token=trust_token, expected_status=403)
Alan Pevec 1a20c7b
+
Alan Pevec 1a20c7b
+    def test_trust_token_cannot_list_request_tokens(self):
Alan Pevec 1a20c7b
+        self._set_policy({"identity:list_access_tokens": [],
Alan Pevec 1a20c7b
+                          "identity:create_trust": []})
Alan Pevec 1a20c7b
+        trust_token = self._create_trust_get_token()
Alan Pevec 1a20c7b
+        url = '/users/%s/OS-OAUTH1/access_tokens' % self.user_id
Alan Pevec 1a20c7b
+        self.get(url, token=trust_token, expected_status=403)
Alan Pevec 1a20c7b
+
Alan Pevec 1a20c7b
 
Alan Pevec 1a20c7b
 class MaliciousOAuth1Tests(OAuth1Tests):
Alan Pevec 1a20c7b
 
Alan Pevec 1a20c7b
diff --git a/keystone/trust/controllers.py b/keystone/trust/controllers.py
Alan Pevec 1a20c7b
index cc3cc1f..552db44 100644
Alan Pevec 1a20c7b
--- a/keystone/trust/controllers.py
Alan Pevec 1a20c7b
+++ b/keystone/trust/controllers.py
Alan Pevec 1a20c7b
@@ -132,6 +132,15 @@ class TrustV3(controller.V3Controller):
Alan Pevec 1a20c7b
 
Alan Pevec 1a20c7b
         # TODO(ayoung): instead of raising ValidationError on the first
Alan Pevec 1a20c7b
         # problem, return a collection of all the problems.
Alan Pevec 1a20c7b
+
Alan Pevec 1a20c7b
+        # Explicitly prevent a trust token from creating a new trust.
Alan Pevec 1a20c7b
+        auth_context = context.get('environment',
Alan Pevec 1a20c7b
+                                   {}).get('KEYSTONE_AUTH_CONTEXT', {})
Alan Pevec 1a20c7b
+        if auth_context.get('is_delegated_auth'):
Alan Pevec 1a20c7b
+            raise exception.Forbidden(
Alan Pevec 1a20c7b
+                _('Cannot create a trust'
Alan Pevec 1a20c7b
+                  ' with a token issued via delegation.'))
Alan Pevec 1a20c7b
+
Alan Pevec 1a20c7b
         if not trust:
Alan Pevec 1a20c7b
             raise exception.ValidationError(attribute='trust',
Alan Pevec 1a20c7b
                                             target='request')