aws-requests-auth-0.4.1/0000755000076600000240000000000013163236430016553 5ustar davidmullerstaff00000000000000aws-requests-auth-0.4.1/aws_requests_auth/0000755000076600000240000000000013163236430022321 5ustar davidmullerstaff00000000000000aws-requests-auth-0.4.1/aws_requests_auth/__init__.py0000644000076600000240000000000012612574012024417 0ustar davidmullerstaff00000000000000aws-requests-auth-0.4.1/aws_requests_auth/aws_auth.py0000644000076600000240000002311313163236213024505 0ustar davidmullerstaff00000000000000import hmac import hashlib import datetime try: # python 2 from urllib import quote from urlparse import urlparse except ImportError: # python 3 from urllib.parse import quote, urlparse import requests def sign(key, msg): """ Copied from https://docs.aws.amazon.com/general/latest/gr/sigv4-signed-request-examples.html """ return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest() def getSignatureKey(key, dateStamp, regionName, serviceName): """ Copied from https://docs.aws.amazon.com/general/latest/gr/sigv4-signed-request-examples.html """ kDate = sign(('AWS4' + key).encode('utf-8'), dateStamp) kRegion = sign(kDate, regionName) kService = sign(kRegion, serviceName) kSigning = sign(kService, 'aws4_request') return kSigning class AWSRequestsAuth(requests.auth.AuthBase): """ Auth class that allows us to connect to AWS services via Amazon's signature version 4 signing process Adapted from https://docs.aws.amazon.com/general/latest/gr/sigv4-signed-request-examples.html """ def __init__(self, aws_access_key, aws_secret_access_key, aws_host, aws_region, aws_service, aws_token=None): """ Example usage for talking to an AWS Elasticsearch Service: AWSRequestsAuth(aws_access_key='YOURKEY', aws_secret_access_key='YOURSECRET', aws_host='search-service-foobar.us-east-1.es.amazonaws.com', aws_region='us-east-1', aws_service='es', aws_token='...') The aws_token is optional and is used only if you are using STS temporary credentials. """ self.aws_access_key = aws_access_key self.aws_secret_access_key = aws_secret_access_key self.aws_host = aws_host self.aws_region = aws_region self.service = aws_service self.aws_token = aws_token def __call__(self, r): """ Adds the authorization headers required by Amazon's signature version 4 signing process to the request. Adapted from https://docs.aws.amazon.com/general/latest/gr/sigv4-signed-request-examples.html """ aws_headers = self.get_aws_request_headers_handler(r) r.headers.update(aws_headers) return r def get_aws_request_headers_handler(self, r): """ Override get_aws_request_headers_handler() if you have a subclass that needs to call get_aws_request_headers() with an arbitrary set of AWS credentials. The default implementation calls get_aws_request_headers() with self.aws_access_key, self.aws_secret_access_key, and self.aws_token """ return self.get_aws_request_headers(r=r, aws_access_key=self.aws_access_key, aws_secret_access_key=self.aws_secret_access_key, aws_token=self.aws_token) def get_aws_request_headers(self, r, aws_access_key, aws_secret_access_key, aws_token): """ Returns a dictionary containing the necessary headers for Amazon's signature version 4 signing process. An example return value might look like { 'Authorization': 'AWS4-HMAC-SHA256 Credential=YOURKEY/20160618/us-east-1/es/aws4_request, ' 'SignedHeaders=host;x-amz-date, ' 'Signature=ca0a856286efce2a4bd96a978ca6c8966057e53184776c0685169d08abd74739', 'x-amz-date': '20160618T220405Z', } """ # Create a date for headers and the credential string t = datetime.datetime.utcnow() amzdate = t.strftime('%Y%m%dT%H%M%SZ') datestamp = t.strftime('%Y%m%d') # Date w/o time for credential_scope canonical_uri = AWSRequestsAuth.get_canonical_path(r) canonical_querystring = AWSRequestsAuth.get_canonical_querystring(r) # Create the canonical headers and signed headers. Header names # and value must be trimmed and lowercase, and sorted in ASCII order. # Note that there is a trailing \n. canonical_headers = ('host:' + self.aws_host + '\n' + 'x-amz-date:' + amzdate + '\n') if aws_token: canonical_headers += 'x-amz-security-token:' + aws_token + '\n' # Create the list of signed headers. This lists the headers # in the canonical_headers list, delimited with ";" and in alpha order. # Note: The request can include any headers; canonical_headers and # signed_headers lists those that you want to be included in the # hash of the request. "Host" and "x-amz-date" are always required. signed_headers = 'host;x-amz-date' if aws_token: signed_headers += ';x-amz-security-token' # Create payload hash (hash of the request body content). For GET # requests, the payload is an empty string (''). body = r.body if r.body else bytes() try: body = body.encode('utf-8') except (AttributeError, UnicodeDecodeError): # On py2, if unicode characters in present in `body`, # encode() throws UnicodeDecodeError, but we can safely # pass unencoded `body` to execute hexdigest(). # # For py3, encode() will execute successfully regardless # of the presence of unicode data body = body payload_hash = hashlib.sha256(body).hexdigest() # Combine elements to create create canonical request canonical_request = (r.method + '\n' + canonical_uri + '\n' + canonical_querystring + '\n' + canonical_headers + '\n' + signed_headers + '\n' + payload_hash) # Match the algorithm to the hashing algorithm you use, either SHA-1 or # SHA-256 (recommended) algorithm = 'AWS4-HMAC-SHA256' credential_scope = (datestamp + '/' + self.aws_region + '/' + self.service + '/' + 'aws4_request') string_to_sign = (algorithm + '\n' + amzdate + '\n' + credential_scope + '\n' + hashlib.sha256(canonical_request.encode('utf-8')).hexdigest()) # Create the signing key using the function defined above. signing_key = getSignatureKey(aws_secret_access_key, datestamp, self.aws_region, self.service) # Sign the string_to_sign using the signing_key string_to_sign_utf8 = string_to_sign.encode('utf-8') signature = hmac.new(signing_key, string_to_sign_utf8, hashlib.sha256).hexdigest() # The signing information can be either in a query string value or in # a header named Authorization. This code shows how to use a header. # Create authorization header and add to request headers authorization_header = (algorithm + ' ' + 'Credential=' + aws_access_key + '/' + credential_scope + ', ' + 'SignedHeaders=' + signed_headers + ', ' + 'Signature=' + signature) headers = { 'Authorization': authorization_header, 'x-amz-date': amzdate, } if aws_token: headers['X-Amz-Security-Token'] = aws_token return headers @classmethod def get_canonical_path(cls, r): """ Create canonical URI--the part of the URI from domain to query string (use '/' if no path) """ parsedurl = urlparse(r.url) # safe chars adapted from boto's use of urllib.parse.quote # https://github.com/boto/boto/blob/d9e5cfe900e1a58717e393c76a6e3580305f217a/boto/auth.py#L393 return quote(parsedurl.path if parsedurl.path else '/', safe='/-_.~') @classmethod def get_canonical_querystring(cls, r): """ Create the canonical query string. According to AWS, by the end of this function our query string values must be URL-encoded (space=%20) and the parameters must be sorted by name. This method assumes that the query params in `r` are *already* url encoded. If they are not url encoded by the time they make it to this function, AWS may complain that the signature for your request is incorrect. It appears elasticsearc-py url encodes query paramaters on its own: https://github.com/elastic/elasticsearch-py/blob/5dfd6985e5d32ea353d2b37d01c2521b2089ac2b/elasticsearch/connection/http_requests.py#L64 If you are using a different client than elasticsearch-py, it will be your responsibility to urleconde your query params before this method is called. """ canonical_querystring = '' parsedurl = urlparse(r.url) querystring_sorted = '&'.join(sorted(parsedurl.query.split('&'))) for query_param in querystring_sorted.split('&'): key_val_split = query_param.split('=', 1) key = key_val_split[0] if len(key_val_split) > 1: val = key_val_split[1] else: val = '' if key: if canonical_querystring: canonical_querystring += "&" canonical_querystring += u'='.join([key, val]) return canonical_querystring aws-requests-auth-0.4.1/aws_requests_auth/boto_utils.py0000644000076600000240000000450313156522240025057 0ustar davidmullerstaff00000000000000""" Functions in this file are included as a convenience for working with AWSRequestsAuth. External libraries, like boto, that this file imports are not a strict requirement for the aws-requests-auth package. """ from botocore.session import Session from .aws_auth import AWSRequestsAuth def get_credentials(credentials_obj=None): """ Interacts with boto to retrieve AWS credentials, and returns a dictionary of kwargs to be used in AWSRequestsAuth. boto automatically pulls AWS credentials from a variety of sources including but not limited to credentials files and IAM role. AWS credentials are pulled in the order listed here: http://boto3.readthedocs.io/en/latest/guide/configuration.html#configuring-credentials """ if credentials_obj is None: credentials_obj = Session().get_credentials() # use get_frozen_credentials to avoid the race condition where one or more # properties may be refreshed and the other(s) not refreshed frozen_credentials = credentials_obj.get_frozen_credentials() return { 'aws_access_key': frozen_credentials.access_key, 'aws_secret_access_key': frozen_credentials.secret_key, 'aws_token': frozen_credentials.token, } class BotoAWSRequestsAuth(AWSRequestsAuth): def __init__(self, aws_host, aws_region, aws_service): """ Example usage for talking to an AWS Elasticsearch Service: BotoAWSRequestsAuth(aws_host='search-service-foobar.us-east-1.es.amazonaws.com', aws_region='us-east-1', aws_service='es') The aws_access_key, aws_secret_access_key, and aws_token are discovered automatically from the environment, in the order described here: http://boto3.readthedocs.io/en/latest/guide/configuration.html#configuring-credentials """ super(BotoAWSRequestsAuth, self).__init__(None, None, aws_host, aws_region, aws_service) self._refreshable_credentials = Session().get_credentials() def get_aws_request_headers_handler(self, r): # provide credentials explicitly during each __call__, to take advantage # of botocore's underlying logic to refresh expired credentials credentials = get_credentials(self._refreshable_credentials) return self.get_aws_request_headers(r, **credentials) aws-requests-auth-0.4.1/aws_requests_auth/tests/0000755000076600000240000000000013163236430023463 5ustar davidmullerstaff00000000000000aws-requests-auth-0.4.1/aws_requests_auth/tests/__init__.py0000644000076600000240000000000012614212267025564 0ustar davidmullerstaff00000000000000aws-requests-auth-0.4.1/aws_requests_auth/tests/test_aws_auth.py0000644000076600000240000002260013162563535026717 0ustar davidmullerstaff00000000000000import datetime import mock import sys import unittest from aws_requests_auth.aws_auth import AWSRequestsAuth class TestAWSRequestsAuth(unittest.TestCase): """ Tests for AWSRequestsAuth """ def test_no_query_params(self): """ Assert we generate the 'correct' cannonical query string and canonical path for a request with no query params Correct is relative here b/c 'correct' simply means what the AWS Elasticsearch service expects """ url = 'http://search-foo.us-east-1.es.amazonaws.com:80/' mock_request = mock.Mock() mock_request.url = url self.assertEqual('/', AWSRequestsAuth.get_canonical_path(mock_request)) self.assertEqual('', AWSRequestsAuth.get_canonical_querystring(mock_request)) def test_characters_escaped_in_path(self): """ Assert we generate the 'correct' cannonical query string and path a request with characters that need to be escaped """ url = 'http://search-foo.us-east-1.es.amazonaws.com:80/+foo.*/_stats' mock_request = mock.Mock() mock_request.url = url self.assertEqual('/%2Bfoo.%2A/_stats', AWSRequestsAuth.get_canonical_path(mock_request)) self.assertEqual('', AWSRequestsAuth.get_canonical_querystring(mock_request)) def test_path_with_querystring(self): """ Assert we generate the 'correct' cannonical query string and path for request that includes a query stirng """ url = 'http://search-foo.us-east-1.es.amazonaws.com:80/my_index/?pretty=True' mock_request = mock.Mock() mock_request.url = url self.assertEqual('/my_index/', AWSRequestsAuth.get_canonical_path(mock_request)) self.assertEqual('pretty=True', AWSRequestsAuth.get_canonical_querystring(mock_request)) def test_multiple_get_params(self): """ Assert we generate the 'correct' cannonical query string for request that includes more than one query parameter """ url = 'http://search-foo.us-east-1.es.amazonaws.com:80/index/type/_search?scroll=5m&search_type=scan' mock_request = mock.Mock() mock_request.url = url self.assertEqual('scroll=5m&search_type=scan', AWSRequestsAuth.get_canonical_querystring(mock_request)) def test_post_request_with_get_param(self): """ Assert we generate the 'correct' cannonical query string for a post request that includes GET-parameters """ url = 'http://search-foo.us-east-1.es.amazonaws.com:80/index/type/1/_update?version=1' mock_request = mock.Mock() mock_request.url = url mock_request.method = "POST" self.assertEqual('version=1', AWSRequestsAuth.get_canonical_querystring(mock_request)) def test_auth_for_get(self): auth = AWSRequestsAuth(aws_access_key='YOURKEY', aws_secret_access_key='YOURSECRET', aws_host='search-foo.us-east-1.es.amazonaws.com', aws_region='us-east-1', aws_service='es') url = 'http://search-foo.us-east-1.es.amazonaws.com:80/' mock_request = mock.Mock() mock_request.url = url mock_request.method = "GET" mock_request.body = None mock_request.headers = {} frozen_datetime = datetime.datetime(2016, 6, 18, 22, 4, 5) with mock.patch('datetime.datetime') as mock_datetime: mock_datetime.utcnow.return_value = frozen_datetime auth(mock_request) self.assertEqual({ 'Authorization': 'AWS4-HMAC-SHA256 Credential=YOURKEY/20160618/us-east-1/es/aws4_request, ' 'SignedHeaders=host;x-amz-date, ' 'Signature=ca0a856286efce2a4bd96a978ca6c8966057e53184776c0685169d08abd74739', 'x-amz-date': '20160618T220405Z', }, mock_request.headers) def test_auth_for_post(self): auth = AWSRequestsAuth(aws_access_key='YOURKEY', aws_secret_access_key='YOURSECRET', aws_host='search-foo.us-east-1.es.amazonaws.com', aws_region='us-east-1', aws_service='es') url = 'http://search-foo.us-east-1.es.amazonaws.com:80/' mock_request = mock.Mock() mock_request.url = url mock_request.method = "POST" mock_request.body = b'foo=bar' mock_request.headers = { 'Content-Type': 'application/x-www-form-urlencoded', } frozen_datetime = datetime.datetime(2016, 6, 18, 22, 4, 5) with mock.patch('datetime.datetime') as mock_datetime: mock_datetime.utcnow.return_value = frozen_datetime auth(mock_request) self.assertEqual({ 'Authorization': 'AWS4-HMAC-SHA256 Credential=YOURKEY/20160618/us-east-1/es/aws4_request, ' 'SignedHeaders=host;x-amz-date, ' 'Signature=a6fd88e5f5c43e005482894001d9b05b43f6710e96be6098bcfcfccdeb8ed812', 'Content-Type': 'application/x-www-form-urlencoded', 'x-amz-date': '20160618T220405Z', }, mock_request.headers) def test_auth_for_post_with_str_body(self): auth = AWSRequestsAuth(aws_access_key='YOURKEY', aws_secret_access_key='YOURSECRET', aws_host='search-foo.us-east-1.es.amazonaws.com', aws_region='us-east-1', aws_service='es') url = 'http://search-foo.us-east-1.es.amazonaws.com:80/' mock_request = mock.Mock() mock_request.url = url mock_request.method = "POST" mock_request.body = 'foo=bar' mock_request.headers = { 'Content-Type': 'application/x-www-form-urlencoded', } frozen_datetime = datetime.datetime(2016, 6, 18, 22, 4, 5) with mock.patch('datetime.datetime') as mock_datetime: mock_datetime.utcnow.return_value = frozen_datetime auth(mock_request) self.assertEqual({ 'Authorization': 'AWS4-HMAC-SHA256 Credential=YOURKEY/20160618/us-east-1/es/aws4_request, ' 'SignedHeaders=host;x-amz-date, ' 'Signature=a6fd88e5f5c43e005482894001d9b05b43f6710e96be6098bcfcfccdeb8ed812', 'Content-Type': 'application/x-www-form-urlencoded', 'x-amz-date': '20160618T220405Z', }, mock_request.headers) @unittest.skipIf( int(sys.version[0]) > 2, 'python3 produces a different hash that we\'re comparing.', ) def test_auth_for_post_with_unicode_body_python2(self): auth = AWSRequestsAuth(aws_access_key='YOURKEY', aws_secret_access_key='YOURSECRET', aws_host='search-foo.us-east-1.es.amazonaws.com', aws_region='us-east-1', aws_service='es') url = 'http://search-foo.us-east-1.es.amazonaws.com:80/' mock_request = mock.Mock() mock_request.url = url mock_request.method = "POST" mock_request.body = 'foo=bar\xc3' mock_request.headers = { 'Content-Type': 'application/x-www-form-urlencoded', } frozen_datetime = datetime.datetime(2016, 6, 18, 22, 4, 5) with mock.patch('datetime.datetime') as mock_datetime: mock_datetime.utcnow.return_value = frozen_datetime auth(mock_request) self.assertEqual({ 'Authorization': 'AWS4-HMAC-SHA256 Credential=YOURKEY/20160618/us-east-1/es/aws4_request, ' 'SignedHeaders=host;x-amz-date, ' 'Signature=88046be72423b267de5e7e604aaffb2c5668c3fd9022ef4aac8287b82ab71124', 'Content-Type': 'application/x-www-form-urlencoded', 'x-amz-date': '20160618T220405Z', }, mock_request.headers) @unittest.skipIf( int(sys.version[0]) < 3, 'python3 produces a different hash that we\'re comparing.' ) def test_auth_for_post_with_unicode_body_python3(self): auth = AWSRequestsAuth(aws_access_key='YOURKEY', aws_secret_access_key='YOURSECRET', aws_host='search-foo.us-east-1.es.amazonaws.com', aws_region='us-east-1', aws_service='es') url = 'http://search-foo.us-east-1.es.amazonaws.com:80/' mock_request = mock.Mock() mock_request.url = url mock_request.method = "POST" mock_request.body = 'foo=bar\xc3' mock_request.headers = { 'Content-Type': 'application/x-www-form-urlencoded', } frozen_datetime = datetime.datetime(2016, 6, 18, 22, 4, 5) with mock.patch('datetime.datetime') as mock_datetime: mock_datetime.utcnow.return_value = frozen_datetime auth(mock_request) self.assertEqual({ 'Authorization': 'AWS4-HMAC-SHA256 Credential=YOURKEY/20160618/us-east-1/es/aws4_request, ' 'SignedHeaders=host;x-amz-date, ' 'Signature=0836dae4bce95c1bcdbd3751c84c0c7e589ba7c81331bab92d0e1acb94adcdd9', 'Content-Type': 'application/x-www-form-urlencoded', 'x-amz-date': '20160618T220405Z', }, mock_request.headers) aws-requests-auth-0.4.1/aws_requests_auth/tests/test_boto_utils.py0000644000076600000240000000425213156524241027264 0ustar davidmullerstaff00000000000000import datetime import os import unittest import mock from aws_requests_auth.aws_auth import AWSRequestsAuth from aws_requests_auth.boto_utils import BotoAWSRequestsAuth, get_credentials class TestBotoUtils(unittest.TestCase): """ Tests for boto_utils module. """ def setUp(self): self.saved_env = {} for var in ['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_SESSION_TOKEN']: self.saved_env[var] = os.environ.get(var) os.environ[var] = 'test-%s' % var def tearDown(self): for k, v in self.saved_env.items(): if v is None: os.environ.pop(k) else: os.environ[k] = v def test_get_credentials(self): creds = get_credentials() # botocore should discover these from os.environ self.assertEqual(creds['aws_access_key'], 'test-AWS_ACCESS_KEY_ID') self.assertEqual(creds['aws_secret_access_key'], 'test-AWS_SECRET_ACCESS_KEY') self.assertEqual(creds['aws_token'], 'test-AWS_SESSION_TOKEN') def test_boto_class(self): boto_auth_inst = BotoAWSRequestsAuth( aws_host='search-foo.us-east-1.es.amazonaws.com', aws_region='us-east-1', aws_service='es', ) url = 'http://search-foo.us-east-1.es.amazonaws.com:80/' mock_request = mock.Mock() mock_request.url = url mock_request.method = "GET" mock_request.body = None mock_request.headers = {} frozen_datetime = datetime.datetime(2016, 6, 18, 22, 4, 5) with mock.patch('datetime.datetime') as mock_datetime: mock_datetime.utcnow.return_value = frozen_datetime boto_auth_inst(mock_request) self.assertEqual({ 'Authorization': 'AWS4-HMAC-SHA256 Credential=test-AWS_ACCESS_KEY_ID/20160618/us-east-1/es/aws4_request, ' 'SignedHeaders=host;x-amz-date;x-amz-security-token, ' 'Signature=9d35f096395c7aa5061e69aca897417dd41bb8fb01a465bb78343624f8f123bf', 'x-amz-date': '20160618T220405Z', 'X-Amz-Security-Token': 'test-AWS_SESSION_TOKEN' }, mock_request.headers) aws-requests-auth-0.4.1/CHANGELOG.md0000644000076600000240000000536213163236323020373 0ustar davidmullerstaff00000000000000Changelog (aws-requests-auth) ================== 0.4.1 ------------------ - Allow utf-8 encoding failures for python2 on the request body for hashing - Contributed by @bigjust: https://github.com/DavidMuller/aws-requests-auth/pull/30 0.4.0 ------------------ - Add `BotoAWSRequestsAuth` convenience class which automatically gathers (and refreshes) AWS credentials using botocore - Contributed by @tobiasmcnulty: https://github.com/DavidMuller/aws-requests-auth/pull/29 0.3.3 ------------------ - Add classifiers to the pypi distribution 0.3.2 ------------------ - Add convenience methods for dynamically pulling AWS credentials via boto3 - Thanks to @schobster: https://github.com/DavidMuller/aws-requests-auth/pull/22 0.3.1 ------------------ - Patch encoding error on python 3.6 - See https://github.com/DavidMuller/aws-requests-auth/pull/21 0.3.0 ------------------ - Add python3 support -- thanks to @jlaine, and @anantasty - See https://github.com/DavidMuller/aws-requests-auth/pull/16 0.2.5 ------------------ - Stop urlencoding query params in get_canonical_querystring(). The urlencoding in get_canonical_querystring() was causing "double encoding issues" because elasticsearch-py already apperas to urlencode query params - If you are using a client other than elasticsearch-py, you will need to be sure that your client urlecondes your query params before they are passed to the `AWSRequests` auth class - See https://github.com/DavidMuller/aws-requests-auth/pull/13 for more details 0.2.4 ------------------ - Add support for [AWS STS](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp.html) using the `aws_token` keyword argument to `AWSRequestsAuth` - See [issue #9](https://github.com/DavidMuller/aws-requests-auth/issues/9) and [PR #11](https://github.com/DavidMuller/aws-requests-auth/pull/11 for) for additional details 0.2.3 ------------------ - Fix handling of multiple query parameters - For example, the two `pretty=True` query paramaters in the following url `http://search-service-foobar.us-east-1.es.amazonaws.com?pretty=True&pretty=True` are now handled properly - see https://github.com/DavidMuller/aws-requests-auth/pull/7 0.2.2 ------------------ - Update url quoting for canonical uri and canonical query string 0.2.1 ------------------ - Fix bug where cannonical uri and query string was not url encoded appropriately for the signing process 0.2.0 ------------------ - Fix typos of `aws_secret_access_key` : https://github.com/DavidMuller/aws-requests-auth/pull/1 - This is a breaking change. The `AWSRequestsAuth` constructor now expects the kwarg `aws_secret_access_key` (instead of the incorrectly spelled `aws_secret_acces_key`). 0.1.0 ------------------ Initial release aws-requests-auth-0.4.1/LICENSE0000644000076600000240000000271212665431316017570 0ustar davidmullerstaff00000000000000Copyright (c) David Muller. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The names of its contributors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.aws-requests-auth-0.4.1/MANIFEST.in0000644000076600000240000000025512613500006020303 0ustar davidmullerstaff00000000000000include README.md include LICENSE include CHANGELOG.md include MANIFEST.in recursive-include aws_requests_auth * recursive-exclude * __pycache__ recursive-exclude * *.py[co]aws-requests-auth-0.4.1/PKG-INFO0000644000076600000240000000103613163236430017650 0ustar davidmullerstaff00000000000000Metadata-Version: 1.1 Name: aws-requests-auth Version: 0.4.1 Summary: AWS signature version 4 signing process for the python requests module Home-page: https://github.com/davidmuller/aws-requests-auth Author: David Muller Author-email: davehmuller@gmail.com License: UNKNOWN Description: See https://github.com/davidmuller/aws-requests-auth for installation and usage instructions. Platform: UNKNOWN Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 aws-requests-auth-0.4.1/README.md0000644000076600000240000001365213156526261020047 0ustar davidmullerstaff00000000000000[![Build Status](https://travis-ci.org/DavidMuller/aws-requests-auth.svg?branch=master)](https://travis-ci.org/DavidMuller/aws-requests-auth) # AWS Signature Version 4 Signing Process with python requests This package allows you to authenticate to AWS with Amazon's [signature version 4 signing process](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html) with the python [requests](http://docs.python-requests.org/en/latest/) library. Tested with both python `2.7` and `3.4`. # Installation ``` pip install aws-requests-auth ``` # Motivation This code came about because Amazon's Elasticsearch Service [does not currently support VPC](https://forums.aws.amazon.com/thread.jspa?threadID=217059&tstart=0). This authentication class allows us to talk to our Elasticsearch cluster via [IAM](https://aws.amazon.com/iam/). Conceivably, the authentication class is flexible enough to be used with any AWS service that supports the signature version 4 signing process. However, I've only tested it with the Elasticsearch service. # Usage ```python import requests from aws_requests_auth.aws_auth import AWSRequestsAuth # let's talk to our AWS Elasticsearch cluster auth = AWSRequestsAuth(aws_access_key='YOURKEY', aws_secret_access_key='YOURSECRET', aws_host='search-service-foobar.us-east-1.es.amazonaws.com', aws_region='us-east-1', aws_service='es') response = requests.get('http://search-service-foobar.us-east-1.es.amazonaws.com', auth=auth) print response.content { "status" : 200, "name" : "Stevie Hunter", "cluster_name" : "elasticsearch", "version" : { "number" : "1.5.2", etc.... }, "tagline" : "You Know, for Search" } ``` ## elasticsearch-py Client Usage Example It's possible to inject the `AWSRequestsAuth` class directly into the [elasticsearch-py](https://elasticsearch-py.readthedocs.org/en/master/) library so you can talk to your Amazon AWS cluster directly through the elasticsearch-py client. ```python from aws_requests_auth.aws_auth import AWSRequestsAuth from elasticsearch import Elasticsearch, RequestsHttpConnection es_host = 'search-service-foobar.us-east-1.es.amazonaws.com' auth = AWSRequestsAuth(aws_access_key='YOURKEY', aws_secret_access_key='YOURSECRET', aws_host=es_host, aws_region='us-east-1', aws_service='es') # use the requests connection_class and pass in our custom auth class es_client = Elasticsearch(host=es_host, port=80, connection_class=RequestsHttpConnection, http_auth=auth) print es_client.info() ``` ## Temporary Security Credentials If you are using [AWS STS](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp.html) to grant temporary access to your Elasticsearch resource, you can use the `aws_token` keyword argument to include your credentials in `AWSRequestsAuth`. See [issue #9](https://github.com/DavidMuller/aws-requests-auth/issues/9) and [PR #11](https://github.com/DavidMuller/aws-requests-auth/pull/11) for additional details. ## AWS Lambda Quickstart Example If you are using an AWS lamba to talk to your Elasticsearch cluster and you've assigned an IAM role to your lambda function that allows the lambda to communicate with your Elasticserach cluster, you can instantiate an instance of AWSRequestsAuth by reading your credentials from environment variables: ```python import os from aws_requests_auth.aws_auth import AWSRequestsAuth def lambda_handler(event, context): auth = AWSRequestsAuth(aws_access_key=os.environ['AWS_ACCESS_KEY_ID'], aws_secret_access_key=os.environ['AWS_SECRET_ACCESS_KEY'], aws_token=os.environ['AWS_SESSION_TOKEN'], aws_host='search-service-foobar.us-east-1.es.amazonaws.com', aws_region='us-east-1', aws_service='es') print 'My lambda finished executing' ``` `'AWS_ACCESS_KEY_ID'`, `'AWS_SECRET_ACCESS_KEY'`, `'AWS_SESSION_TOKEN'` are [reserved environment variables in AWS lambdas](https://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html#lambda-environment-variables). ## Using Boto To Automatically Gather AWS Credentials `botocore` (the core functionality of `boto3`) is not a strict requirement of `aws-requests-auth`, but we do provide some convenience methods if you'd like to use `botocore` to automatically retrieve your AWS credentials for you. `botocore` [can dynamically pull AWS credentials from environment variables, AWS config files, IAM Role, and other locations](http://boto3.readthedocs.io/en/latest/guide/configuration.html#configuring-credentials). Dynamic credential fetching can come in handy if you need to run a program leveraging `aws-requests-auth` in several places where you may authenticate in different manners. For example, you may rely on a `.aws/credentials` file when running on your local machine, but use an IAM role when running your program in a docker container in the cloud. To take advantage of these conveniences, and help you authenticate wherever `botocore` finds AWS credentials, you can import the `boto_utils` file and initialize `BotoAWSRequestsAuth` as follows: ```python # note that this line will fail if you do not have botocore installed # botocore installation instructions available here: # https://boto3.readthedocs.io/en/latest/guide/quickstart.html#installation from aws_requests_auth.boto_utils import BotoAWSRequestsAuth auth = BotoAWSRequestsAuth(aws_host='search-service-foobar.us-east-1.es.amazonaws.com', aws_region='us-east-1', aws_service='es') ``` Credentials are only accessed when needed at runtime, and they will be refreshed using the underlying methods in `botocore` if needed. aws-requests-auth-0.4.1/setup.py0000644000076600000240000000124213163236332020265 0ustar davidmullerstaff00000000000000from distutils.core import setup setup( name='aws-requests-auth', version='0.4.1', author='David Muller', author_email='davehmuller@gmail.com', packages=['aws_requests_auth'], url='https://github.com/davidmuller/aws-requests-auth', description='AWS signature version 4 signing process for the python requests module', long_description='See https://github.com/davidmuller/aws-requests-auth for installation and usage instructions.', install_requires=['requests>=0.14.0'], classifiers=[ 'License :: OSI Approved :: BSD License', 'Programming Language :: Python', 'Programming Language :: Python :: 3', ] )