diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 6d9c3ab845fa..388e9c0d11c9 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -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: