Saturday, March 26, 2011

Signing an EC2 API request in Python

My last post was about a simple script I wrote to start up an EC2 instance and verify the SSH fingerprint. It did the trick but left me itching for something that was a little easier to extend and generalize.

I've also been itching to learn some Python, so for my next experiment I wrote a Python script (my first) that generates the security signature for an EC2 API request. Google pointed me to several solutions, but they were all either outdated (version 1 of the signature specification), incomplete, inaccurate or all of the above.

The following script works for me. It is still hard coded to perform a single type of simple request, but it should be a useful example for anybody that just wants to sign an EC2 request in Python.

The boto project also provides what looks like a fairly complete Python interface to most of the Amazon web services.

#!/usr/bin/python3

import base64,hashlib,hmac,datetime,urllib.parse,urllib.request

endpoint = 'ec2.us-west-1.amazonaws.com'
aws_secret_access_key = b'YOUR_KEY_SECRET'

params = {}
params['Version'] = '2011-01-01'
params['AWSAccessKeyId'] = 'YOUR_ACCESS_KEY'

now = datetime.datetime.utcnow()
params['Timestamp'] = now.strftime("%Y-%m-%dT%H:%M:%S.000Z")

# this can be omitted
expires = now + datetime.timedelta(minutes=10)
params['Expires'] = expires.strftime("%Y-%m-%dT%H:%M:%SZ")

params['SignatureMethod'] = 'HmacSHA256'
params['SignatureVersion'] = '2'

params['Action'] = 'DescribeInstances'

keys = sorted(params.keys())
values = map(params.get, keys)

query_string = urllib.parse.urlencode( list(zip(keys,values)) )

# construct the string to sign
string_to_sign = '\n'.join(['GET', endpoint, '/', query_string])

# sign the request
signature = hmac.new(
    key=aws_secret_access_key,
    msg=bytes(string_to_sign, 'UTF-8'),
    digestmod=hashlib.sha256).digest()
 
# base64 encode the signature.  URL safe base64 encoding does NOT work 
signature = base64.b64encode(signature)

signature = urllib.parse.quote(signature) 

url = 'https://' + endpoint + '/?' + query_string + '&Signature=' + signature

response = urllib.request.urlopen(url)
xml = response.read()

# learning a little about xml parsing in Python is my next project
print(xml)

No comments: