Merge pull request #25420 from techhat/s3sig4

Move S3 to use AWS Signature Version 4
This commit is contained in:
Nicole Thomas 2015-07-14 16:03:09 -06:00
commit 9313804e27
2 changed files with 30 additions and 75 deletions

View file

@ -139,8 +139,9 @@ def sig2(method, endpoint, params, provider, aws_api_version):
return params_with_headers
def sig4(method, endpoint, params, prov_dict, aws_api_version, location,
product='ec2', uri='/', requesturl=None):
def sig4(method, endpoint, params, prov_dict,
aws_api_version=DEFAULT_AWS_API_VERSION, location=DEFAULT_LOCATION,
product='ec2', uri='/', requesturl=None, data=''):
'''
Sign a query against AWS services using Signature Version 4 Signing
Process. This is documented at:
@ -155,7 +156,8 @@ def sig4(method, endpoint, params, prov_dict, aws_api_version, location,
access_key_id, secret_access_key, token = creds(prov_dict)
params_with_headers = params.copy()
params_with_headers['Version'] = aws_api_version
if product != 's3':
params_with_headers['Version'] = aws_api_version
keys = sorted(params_with_headers.keys())
values = list(map(params_with_headers.get, keys))
querystring = urlencode(list(zip(keys, values))).replace('+', '%20')
@ -173,7 +175,7 @@ def sig4(method, endpoint, params, prov_dict, aws_api_version, location,
# Create payload hash (hash of the request body content). For GET
# requests, the payload is an empty string ('').
payload_hash = hashlib.sha256('').hexdigest()
payload_hash = hashlib.sha256(data).hexdigest()
# Combine elements to create create canonical request
canonical_request = '\n'.join((
@ -223,7 +225,8 @@ def sig4(method, endpoint, params, prov_dict, aws_api_version, location,
headers = {
'x-amz-date': amzdate,
'Authorization': authorization_header
'x-amz-content-sha256': payload_hash,
'Authorization': authorization_header,
}
# Add in security token if we have one

View file

@ -7,10 +7,6 @@ Connection library for Amazon S3
from __future__ import absolute_import
# Import Python libs
import binascii
import datetime
import hashlib
import hmac
import logging
# Import 3rd-party libs
@ -19,21 +15,22 @@ try:
HAS_REQUESTS = True # pylint: disable=W0612
except ImportError:
HAS_REQUESTS = False # pylint: disable=W0612
from salt.ext.six.moves.urllib.parse import urlencode # pylint: disable=no-name-in-module,import-error
# Import Salt libs
import salt.utils
import salt.utils.aws
import salt.utils.xmlutil as xml
import salt.utils.iam as iam
from salt._compat import ElementTree as ET
log = logging.getLogger(__name__)
DEFAULT_LOCATION = 'us-east-1'
def query(key, keyid, method='GET', params=None, headers=None,
requesturl=None, return_url=False, bucket=None, service_url=None,
path=None, return_bin=False, action=None, local_file=None,
verify_ssl=True):
path='', return_bin=False, action=None, local_file=None,
verify_ssl=True, location=DEFAULT_LOCATION):
'''
Perform a query against an S3-like API. This function requires that a
secret key and the id for that key are passed in. For instance:
@ -71,9 +68,6 @@ def query(key, keyid, method='GET', params=None, headers=None,
if not params:
params = {}
if path is None:
path = ''
if not service_url:
service_url = 's3.amazonaws.com'
@ -90,75 +84,33 @@ def query(key, keyid, method='GET', params=None, headers=None,
keyid = iam_creds['access_key']
token = iam_creds['security_token']
if not requesturl:
x_amz_date = datetime.datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT')
content_type = 'text/plain'
if method == 'GET':
if bucket:
can_resource = '/{0}/{1}'.format(bucket, path)
else:
can_resource = '/'
elif method == 'PUT' or method == 'HEAD' or method == 'DELETE':
if path:
can_resource = '/{0}/{1}'.format(bucket, path)
else:
can_resource = '/{0}/'.format(bucket)
if action:
can_resource += '?{0}'.format(action)
log.debug('CanonicalizedResource: {0}'.format(can_resource))
headers['Host'] = endpoint
headers['Content-type'] = content_type
headers['Date'] = x_amz_date
if token:
headers['x-amz-security-token'] = token
string_to_sign = '{0}\n'.format(method)
new_headers = []
for header in sorted(headers):
if header.lower().startswith('x-amz'):
log.debug(header.lower())
new_headers.append('{0}:{1}'.format(header.lower(),
headers[header]))
can_headers = '\n'.join(new_headers)
log.debug('CanonicalizedAmzHeaders: {0}'.format(can_headers))
string_to_sign += '\n{0}'.format(content_type)
string_to_sign += '\n{0}'.format(x_amz_date)
if can_headers:
string_to_sign += '\n{0}'.format(can_headers)
string_to_sign += '\n{0}'.format(can_resource)
log.debug('String To Sign:: \n{0}'.format(string_to_sign))
hashed = hmac.new(key, string_to_sign, hashlib.sha1)
sig = binascii.b2a_base64(hashed.digest())
headers['Authorization'] = 'AWS {0}:{1}'.format(keyid, sig.strip())
querystring = urlencode(params)
if action:
if querystring:
querystring = '{0}&{1}'.format(action, querystring)
else:
querystring = action
requesturl = 'https://{0}/'.format(endpoint)
if path:
requesturl += path
if querystring:
requesturl += '?{0}'.format(querystring)
data = None
data = ''
if method == 'PUT':
if local_file:
with salt.utils.fopen(local_file, 'r') as ifile:
data = ifile.read()
if not requesturl:
requesturl = 'https://{0}/{1}'.format(endpoint, path)
headers, requesturl = salt.utils.aws.sig4(
method,
endpoint,
params,
data=data,
uri='/{0}'.format(path),
prov_dict={'id': keyid, 'key': key},
location=location,
product='s3',
requesturl=requesturl,
)
log.debug('S3 Request: {0}'.format(requesturl))
log.debug('S3 Headers::')
log.debug(' Authorization: {0}'.format(headers['Authorization']))
if not data:
data = None
try:
result = requests.request(method, requesturl, headers=headers,
data=data,