|
| 1 | +# AWS Version 4 signing example |
| 2 | + |
| 3 | +# Based on example from the source: http://docs.aws.amazon.com/general/latest/gr/sigv4-signed-request-examples.html |
| 4 | + |
| 5 | +# Amazon Lex API request |
| 6 | + |
| 7 | +# See: http://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html |
| 8 | +# This version makes a POST request and passes request parameters |
| 9 | +# in the body (payload) of the request. Auth information is passed in |
| 10 | +# an Authorization header. |
| 11 | +import datetime |
| 12 | +import hashlib |
| 13 | +import hmac |
| 14 | +import os |
| 15 | +import sys |
| 16 | + |
| 17 | +import requests |
| 18 | + |
| 19 | +# easy_install pip |
| 20 | +# pip install requests |
| 21 | + |
| 22 | +# ************* REQUEST VALUES ************* |
| 23 | +method = 'POST' |
| 24 | +service = 'lex' |
| 25 | +host = 'runtime.lex.us-east-1.amazonaws.com' |
| 26 | +region = 'us-east-1' |
| 27 | +endpoint = 'https://runtime.lex.us-east-1.amazonaws.com' |
| 28 | + |
| 29 | +request_text = 'this is the request text' # request text |
| 30 | + |
| 31 | +request_parameters = '{"inputText": "' + request_text + '", "sessionAttributes": {"attr_name" : "value"}}' #request text (and optional session attributes) to the Lex bot |
| 32 | +# for post_action 'content' - sessionAttributes are passed as the x-amz-lex-session-attributes HTTP header. |
| 33 | + |
| 34 | +bot_name = 'TestBotForRequest' # bot name |
| 35 | +bot_alias = 'testbotforrequest' # bot's alias |
| 36 | +user_id = 'myUserId' # some user-id - not used for authentication |
| 37 | +# for text request (json); {"inputText": "string", "sessionAttributes": {"string" : "string" }}) |
| 38 | +post_action = 'text' |
| 39 | +# for stream request (text or audio stream) |
| 40 | +#post_action = 'content' |
| 41 | + |
| 42 | +# POST requests use a content type header. For Lex, |
| 43 | +# the content is JSON or stream. |
| 44 | +# for post_action = 'text': |
| 45 | +content_type = 'application/json' |
| 46 | +# for post_action = 'content', audio stream in PCM format: |
| 47 | +#content_type = 'audio/l16;rate=16000;channels=1' |
| 48 | +#content_type = 'audio/x-l16;sample-rate=16000;channel-count=1' |
| 49 | +# for post_action = 'content', audio stream in Opus format: |
| 50 | +#content_type = 'audio/x-cbr-opus-with-preamble;preamble-size=0;bit-rate=256000;frame-size-milliseconds=4' |
| 51 | +# for post_action = 'content', text: |
| 52 | +#content_type = 'text/plain;charset=utf-8' |
| 53 | + |
| 54 | +# Key derivation functions. See: |
| 55 | +# http://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html#signature-v4-examples-python |
| 56 | +def sign(key, msg): |
| 57 | + return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest() |
| 58 | + |
| 59 | +def getSignatureKey(key, dateStamp, regionName, serviceName): |
| 60 | + kDate = sign(('AWS4' + key).encode('utf-8'), dateStamp) |
| 61 | + kRegion = sign(kDate, regionName) |
| 62 | + kService = sign(kRegion, serviceName) |
| 63 | + kSigning = sign(kService, 'aws4_request') |
| 64 | + return kSigning |
| 65 | + |
| 66 | +# Read AWS access key from env. variables or configuration file. Best practice is NOT |
| 67 | +# to embed credentials in code. |
| 68 | +access_key = os.environ.get('AWS_ACCESS_KEY_ID') |
| 69 | +secret_key = os.environ.get('AWS_SECRET_ACCESS_KEY') |
| 70 | +if access_key is None or secret_key is None: |
| 71 | + print 'No access key is available.' |
| 72 | + sys.exit() |
| 73 | + |
| 74 | +# Create a date for headers and the credential string |
| 75 | +t = datetime.datetime.utcnow() |
| 76 | +amz_date = t.strftime('%Y%m%dT%H%M%SZ')#'20170714T010101Z' |
| 77 | +date_stamp = t.strftime('%Y%m%d') # Date w/o time, used in credential scope '20170714' |
| 78 | + |
| 79 | + |
| 80 | +# ************* TASK 1: CREATE A CANONICAL REQUEST ************* |
| 81 | +# http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html |
| 82 | + |
| 83 | +# Step 1 is to define the verb (GET, POST, etc.)--already done. |
| 84 | + |
| 85 | +# Step 2: Create canonical URI--the part of the URI from domain to query |
| 86 | +# string (use '/' if no path) |
| 87 | +canonical_uri = '/bot/' + bot_name + '/alias/' + bot_alias + '/user/' + user_id + '/' + post_action |
| 88 | + |
| 89 | +## Step 3: Create the canonical query string. In this example, request |
| 90 | +# parameters are passed in the body of the request and the query string |
| 91 | +# is blank. |
| 92 | +canonical_querystring = '' |
| 93 | + |
| 94 | +# Step 4: Create the canonical headers. Header names must be trimmed |
| 95 | +# and lowercase, and sorted in code point order from low to high. |
| 96 | +# Note that there is a trailing \n. |
| 97 | +canonical_headers = 'content-type:' + content_type + '\n' + 'host:' + host + '\n' + 'x-amz-date:' + amz_date + '\n' |
| 98 | + |
| 99 | +# Step 5: Create the list of signed headers. This lists the headers |
| 100 | +# in the canonical_headers list, delimited with ";" and in alpha order. |
| 101 | +# Note: The request can include any headers; canonical_headers and |
| 102 | +# signed_headers include those that you want to be included in the |
| 103 | +# hash of the request. "Host" and "x-amz-date" are always required. |
| 104 | +# For Lex, content-type and x-amz-target are also required. |
| 105 | +signed_headers = 'content-type;host;x-amz-date' |
| 106 | + |
| 107 | +# Step 6: Create payload hash. In this example, the payload (body of |
| 108 | +# the request) contains the request parameters. |
| 109 | +payload_hash = hashlib.sha256(request_parameters).hexdigest() |
| 110 | + |
| 111 | +# Step 7: Combine elements to create create canonical request |
| 112 | +canonical_request = method + '\n' + canonical_uri + '\n' + canonical_querystring + '\n' + canonical_headers + '\n' + signed_headers + '\n' + payload_hash |
| 113 | + |
| 114 | + |
| 115 | +# ************* TASK 2: CREATE THE STRING TO SIGN************* |
| 116 | +# Match the algorithm to the hashing algorithm you use, either SHA-1 or |
| 117 | +# SHA-256 (recommended) |
| 118 | +algorithm = 'AWS4-HMAC-SHA256' |
| 119 | +credential_scope = date_stamp + '/' + region + '/' + service + '/' + 'aws4_request' |
| 120 | +string_to_sign = algorithm + '\n' + amz_date + '\n' + credential_scope + '\n' + hashlib.sha256(canonical_request).hexdigest() |
| 121 | + |
| 122 | + |
| 123 | +# ************* TASK 3: CALCULATE THE SIGNATURE ************* |
| 124 | +# Create the signing key using the function defined above. |
| 125 | +signing_key = getSignatureKey(secret_key, date_stamp, region, service) |
| 126 | + |
| 127 | +# Sign the string_to_sign using the signing_key |
| 128 | +signature = hmac.new(signing_key, string_to_sign.encode('utf-8'), hashlib.sha256).hexdigest() |
| 129 | + |
| 130 | + |
| 131 | +# ************* TASK 4: ADD SIGNING INFORMATION TO THE REQUEST ************* |
| 132 | +# Put the signature information in a header named Authorization. |
| 133 | +authorization_header = algorithm + ' ' + 'Credential=' + access_key + '/' + credential_scope + ', ' + 'SignedHeaders=' + signed_headers + ', ' + 'Signature=' + signature |
| 134 | + |
| 135 | +# For Lex, the request can include any headers, but MUST include "host", "x-amz-date", |
| 136 | +# "x-amz-target", "content-type", and "Authorization". Except for the authorization |
| 137 | +# header, the headers must be included in the canonical_headers and signed_headers values, as |
| 138 | +# noted earlier. Order here is not significant. |
| 139 | +# # Python note: The 'host' header is added automatically by the Python 'requests' library. |
| 140 | +headers = {'Content-Type':content_type, |
| 141 | + 'X-Amz-Date':amz_date, |
| 142 | + 'Authorization':authorization_header} |
| 143 | + |
| 144 | + |
| 145 | +# ************* SEND THE REQUEST ************* |
| 146 | +print '\nBEGIN REQUEST++++++++++++++++++++++++++++++++++++' |
| 147 | +print 'Request URL = ' + endpoint |
| 148 | + |
| 149 | +r = requests.post(endpoint + canonical_uri, data=request_parameters, headers=headers) |
| 150 | + |
| 151 | +print '\nRESPONSE++++++++++++++++++++++++++++++++++++' |
| 152 | +print 'Response code: %d\n' % r.status_code |
| 153 | +print r.text |
0 commit comments