Change url scheme passed to oauth signature verifier

Change 461736 modifies the url passed to oauth signature verifier to
request url. But in some deployments, https endpoints are terminated
at haproxy and http request is sent to keystone. So request url will
have http as url scheme whereas the endpoint is registered with https
and signature at client is done with https url. This results in OAUTH
signature validation failure.

Update URL sent for OAUTH signature verification with the scheme of
the base url.

Change-Id: Iaba285985b616a35e3dfe33cdd45667174e7c69d
Partial-Bug: #1687593
This commit is contained in:
Hemanth Nakkina 2017-05-15 14:30:24 +05:30
parent b7bd6e3019
commit b7aece57d2
2 changed files with 48 additions and 6 deletions

View File

@ -18,6 +18,7 @@ from oslo_log import log
from oslo_serialization import jsonutils
from oslo_utils import timeutils
from six.moves import http_client
from six.moves.urllib import parse as urlparse
from keystone.common import authorization
from keystone.common import controller
@ -217,6 +218,15 @@ class OAuthControllerV3(controller.V3Controller):
collection_name = 'not_used'
member_name = 'not_used'
def _update_url_scheme(self, request):
"""Update request url scheme with base url scheme."""
url = self.base_url(request.context_dict, request.context_dict['path'])
url_scheme = list(urlparse.urlparse(url))[0]
req_url_list = list(urlparse.urlparse(request.url))
req_url_list[0] = url_scheme
req_url = urlparse.urlunparse(req_url_list)
return req_url
def create_request_token(self, request):
oauth_headers = oauth1.get_oauth_headers(request.headers)
consumer_id = oauth_headers.get('oauth_consumer_key')
@ -233,13 +243,14 @@ class OAuthControllerV3(controller.V3Controller):
self.resource_api.get_project(requested_project_id)
self.oauth_api.get_consumer(consumer_id)
url = self._update_url_scheme(request)
req_headers = {'Requested-Project-Id': requested_project_id}
req_headers.update(request.headers)
request_verifier = oauth1.RequestTokenEndpoint(
request_validator=validator.OAuthValidator(),
token_generator=oauth1.token_generator)
h, b, s = request_verifier.create_request_token_response(
request.url,
url,
http_method='POST',
body=request.params,
headers=req_headers)
@ -299,12 +310,13 @@ class OAuthControllerV3(controller.V3Controller):
if now > expires:
raise exception.Unauthorized(_('Request token is expired'))
url = self._update_url_scheme(request)
access_verifier = oauth1.AccessTokenEndpoint(
request_validator=validator.OAuthValidator(),
token_generator=oauth1.token_generator)
try:
h, b, s = access_verifier.create_access_token_response(
request.url,
url,
http_method='POST',
body=request.params,
headers=request.headers)

View File

@ -23,6 +23,7 @@ from oslo_serialization import jsonutils
from pycadf import cadftaxonomy
from six.moves import http_client
from six.moves import urllib
from six.moves.urllib import parse as urlparse
import keystone.conf
from keystone import exception
@ -636,6 +637,13 @@ class UUIDAuthTokenTests(AuthTokenTests, OAuthFlowTests):
class MaliciousOAuth1Tests(OAuth1Tests):
def _switch_baseurl_scheme(self):
"""Switch the base url scheme."""
base_url_list = list(urlparse.urlparse(self.base_url))
base_url_list[0] = 'https' if base_url_list[0] == 'http' else 'http'
bad_url = urlparse.urlunparse(base_url_list)
return bad_url
def test_bad_consumer_secret(self):
consumer = self._create_single_consumer()
consumer_id = consumer['id']
@ -655,6 +663,17 @@ class MaliciousOAuth1Tests(OAuth1Tests):
self.post(url, headers=headers,
expected_status=http_client.UNAUTHORIZED)
def test_bad_request_url_scheme(self):
consumer = self._create_single_consumer()
consumer_id = consumer['id']
consumer_secret = consumer['secret']
consumer = {'key': consumer_id, 'secret': consumer_secret}
bad_url_scheme = self._switch_baseurl_scheme()
url, headers = self._create_request_token(consumer, self.project_id,
base_url=bad_url_scheme)
self.post(url, headers=headers,
expected_status=http_client.UNAUTHORIZED)
def test_bad_request_token_key(self):
consumer = self._create_single_consumer()
consumer_id = consumer['id']
@ -748,7 +767,18 @@ class MaliciousOAuth1Tests(OAuth1Tests):
self.assertIn('Invalid signature',
resp_data.get('error', {}).get('message'))
# 2. Invalid signature.
# 2. Invalid base url scheme.
# Update the base url scheme, so it will fail to validate signature.
bad_url_scheme = self._switch_baseurl_scheme()
url, headers = self._create_access_token(consumer, request_token,
base_url=bad_url_scheme)
resp = self.post(url, headers=headers,
expected_status=http_client.UNAUTHORIZED)
resp_data = jsonutils.loads(resp.body)
self.assertIn('Invalid signature',
resp_data.get('error', {}).get('message'))
# 3. Invalid signature.
# Update the secret, so it will fail to validate the signature.
consumer.update({'secret': uuid.uuid4().hex})
url, headers = self._create_access_token(consumer, request_token)
@ -758,7 +788,7 @@ class MaliciousOAuth1Tests(OAuth1Tests):
self.assertIn('Invalid signature',
resp_data.get('error', {}).get('message'))
# 3. Invalid verifier.
# 4. Invalid verifier.
# Even though the verifier is well formatted, it is not verifier
# that is stored in the backend, this is different with the testcase
# above `test_bad_verifier` where it test that `verifier` is not
@ -773,7 +803,7 @@ class MaliciousOAuth1Tests(OAuth1Tests):
self.assertIn('Provided verifier',
resp_data.get('error', {}).get('message'))
# 4. The provided consumer does not exist.
# 5. The provided consumer does not exist.
consumer.update({'key': uuid.uuid4().hex})
url, headers = self._create_access_token(consumer, request_token)
resp = self.post(url, headers=headers,
@ -782,7 +812,7 @@ class MaliciousOAuth1Tests(OAuth1Tests):
self.assertIn('Provided consumer does not exist',
resp_data.get('error', {}).get('message'))
# 5. The consumer key provided does not match stored consumer key.
# 6. The consumer key provided does not match stored consumer key.
consumer2 = self._create_single_consumer()
consumer.update({'key': consumer2['id']})
url, headers = self._create_access_token(consumer, request_token)