Make ec2 auth support v4 signature format

Extract the signature and access key via whatever method is needed
for the version of the request (e.g headers for v4), and add the
headers and hashed body, which is required for keystone to calculate
the correct v4 signature when validating the request.

Conflicts:
        nova/api/ec2/__init__.py

Change-Id: I161eccc4ea48a21a80d689f6a328ca95cace2e6e
Closes-Bug: #1408987
(cherry picked from commit f7b1af9e13)
This commit is contained in:
Steven Hardy 2015-01-09 16:05:51 +00:00 committed by Matt Riedemann
parent d9bcfab1e9
commit 56e3dd3331
1 changed files with 54 additions and 6 deletions

View File

@ -18,6 +18,8 @@ Starting point for routing EC2 requests.
"""
import hashlib
from eventlet.green import httplib
from oslo.config import cfg
import six
@ -180,15 +182,51 @@ class Lockout(wsgi.Middleware):
class EC2KeystoneAuth(wsgi.Middleware):
"""Authenticate an EC2 request with keystone and convert to context."""
def _get_signature(self, req):
"""Extract the signature from the request.
This can be a get/post variable or for version 4 also in a header
called 'Authorization'.
- params['Signature'] == version 0,1,2,3
- params['X-Amz-Signature'] == version 4
- header 'Authorization' == version 4
"""
sig = req.params.get('Signature') or req.params.get('X-Amz-Signature')
if sig is None and 'Authorization' in req.headers:
auth_str = req.headers['Authorization']
sig = auth_str.partition("Signature=")[2].split(',')[0]
return sig
def _get_access(self, req):
"""Extract the access key identifier.
For version 0/1/2/3 this is passed as the AccessKeyId parameter, for
version 4 it is either an X-Amz-Credential parameter or a Credential=
field in the 'Authorization' header string.
"""
access = req.params.get('AWSAccessKeyId')
if access is None:
cred_param = req.params.get('X-Amz-Credential')
if cred_param:
access = cred_param.split("/")[0]
if access is None and 'Authorization' in req.headers:
auth_str = req.headers['Authorization']
cred_str = auth_str.partition("Credential=")[2].split(',')[0]
access = cred_str.split("/")[0]
return access
@webob.dec.wsgify(RequestClass=wsgi.Request)
def __call__(self, req):
request_id = context.generate_request_id()
signature = req.params.get('Signature')
signature = self._get_signature(req)
if not signature:
msg = _("Signature not provided")
return faults.ec2_error_response(request_id, "AuthFailure", msg,
status=400)
access = req.params.get('AWSAccessKeyId')
access = self._get_access(req)
if not access:
msg = _("Access key not provided")
return faults.ec2_error_response(request_id, "AuthFailure", msg,
@ -197,8 +235,9 @@ class EC2KeystoneAuth(wsgi.Middleware):
# Make a copy of args for authentication and signature verification.
auth_params = dict(req.params)
# Not part of authentication args
auth_params.pop('Signature')
auth_params.pop('Signature', None)
body_hash = hashlib.sha256(req.body).hexdigest()
cred_dict = {
'access': access,
'signature': signature,
@ -206,6 +245,8 @@ class EC2KeystoneAuth(wsgi.Middleware):
'verb': req.method,
'path': req.path,
'params': auth_params,
'headers': req.headers,
'body_hash': body_hash
}
if "ec2" in CONF.keystone_ec2_url:
creds = {'ec2Credentials': cred_dict}
@ -295,6 +336,9 @@ class Requestify(wsgi.Middleware):
@webob.dec.wsgify(RequestClass=wsgi.Request)
def __call__(self, req):
# Not all arguments are mandatory with v4 signatures, as some data is
# passed in the header, not query arguments.
required_args = ['Action', 'Version']
non_args = ['Action', 'Signature', 'AWSAccessKeyId', 'SignatureMethod',
'SignatureVersion', 'Version', 'Timestamp']
args = dict(req.params)
@ -309,14 +353,18 @@ class Requestify(wsgi.Middleware):
# Raise KeyError if omitted
action = req.params['Action']
# Fix bug lp:720157 for older (version 1) clients
version = req.params['SignatureVersion']
# If not present assume v4
version = req.params.get('SignatureVersion', 4)
if int(version) == 1:
non_args.remove('SignatureMethod')
if 'SignatureMethod' in args:
args.pop('SignatureMethod')
for non_arg in non_args:
# Remove, but raise KeyError if omitted
args.pop(non_arg)
if non_arg in required_args:
# Remove, but raise KeyError if omitted
args.pop(non_arg)
else:
args.pop(non_arg, None)
except KeyError:
raise webob.exc.HTTPBadRequest()
except exception.InvalidRequest as err: