~tribaal/txaws/xss-hardening

« back to all changes in this revision

Viewing changes to txaws/ec2/client.py

  • Committer: Tristan Seligmann
  • Date: 2009-04-27 23:53:32 UTC
  • mfrom: (3.1.5 client)
  • Revision ID: mithrandi@mithrandi.net-20090427235332-l8azkl1kvn5pjtay
mergeĀ lp:~lifeless/txaws/client

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2009 Robert Collins <robertc@robertcollins.net>
 
2
# Licenced under the txaws licence available at /LICENSE in the txaws source.
 
3
 
 
4
"""EC2 client support."""
 
5
 
 
6
__all__ = ['EC2Client']
 
7
 
 
8
from base64 import b64encode
 
9
from urllib import quote
 
10
 
 
11
from twisted.web.client import getPage
 
12
 
 
13
from txaws import credentials
 
14
from txaws.util import iso8601time, XML
 
15
 
 
16
 
 
17
class Instance(object):
 
18
    """An Amazon EC2 Instance.
 
19
 
 
20
    :attrib instanceId: The instance ID of this instance.
 
21
    :attrib instanceState: The state of this instance.
 
22
    """
 
23
 
 
24
    def __init__(self, instanceId, instanceState):
 
25
        self.instanceId = instanceId
 
26
        self.instanceState = instanceState
 
27
 
 
28
 
 
29
class EC2Client(object):
 
30
    """A client for EC2."""
 
31
 
 
32
    NS = '{http://ec2.amazonaws.com/doc/2008-12-01/}'
 
33
 
 
34
    def __init__(self, creds=None, query_factory=None):
 
35
        """Create an EC2Client.
 
36
 
 
37
        :param creds: Explicit credentials to use. If None, credentials are
 
38
            inferred as per txaws.credentials.AWSCredentials.
 
39
        """
 
40
        if creds is None:
 
41
            self.creds = credentials.AWSCredentials()
 
42
        else:
 
43
            self.creds = creds
 
44
        if query_factory is None:
 
45
            self.query_factory = Query
 
46
        else:
 
47
            self.query_factory = query_factory
 
48
 
 
49
    def describe_instances(self):
 
50
        """Describe current instances."""
 
51
        q = self.query_factory('DescribeInstances', self.creds)
 
52
        d = q.submit()
 
53
        return d.addCallback(self._parse_Reservation)
 
54
 
 
55
    def _parse_Reservation(self, xml_bytes):
 
56
        root = XML(xml_bytes)
 
57
        result = []
 
58
        # May be a more elegant way to do this:
 
59
        for reservation in root.find(self.NS + 'reservationSet'):
 
60
            for instance in reservation.find(self.NS + 'instancesSet'):
 
61
                instanceId = instance.findtext(self.NS + 'instanceId')
 
62
                instanceState = instance.find(
 
63
                    self.NS + 'instanceState').findtext(self.NS + 'name')
 
64
                result.append(Instance(instanceId, instanceState))
 
65
        return result
 
66
 
 
67
 
 
68
class Query(object):
 
69
    """A query that may be submitted to EC2."""
 
70
 
 
71
    def __init__(self, action, creds, other_params=None, time_tuple=None):
 
72
        """Create a Query to submit to EC2."""
 
73
        # Require params (2008-12-01 API):
 
74
        # Version, SignatureVersion, SignatureMethod, Action, AWSAccessKeyId,
 
75
        # Timestamp || Expires, Signature, 
 
76
        self.params = {'Version': '2008-12-01',
 
77
            'SignatureVersion': '2',
 
78
            'SignatureMethod': 'HmacSHA1',
 
79
            'Action': action,
 
80
            'AWSAccessKeyId': creds.access_key,
 
81
            'Timestamp': iso8601time(time_tuple),
 
82
            }
 
83
        if other_params:
 
84
            self.params.update(other_params)
 
85
        self.method = 'GET'
 
86
        self.host = 'ec2.amazonaws.com'
 
87
        self.uri = '/'
 
88
        self.creds = creds
 
89
 
 
90
    def canonical_query_params(self):
 
91
        """Return the canonical query params (used in signing)."""
 
92
        result = []
 
93
        for key, value in self.sorted_params():
 
94
            result.append('%s=%s' % (self.encode(key), self.encode(value)))
 
95
        return '&'.join(result)
 
96
 
 
97
    def encode(self, a_string):
 
98
        """Encode a_string as per the canonicalisation encoding rules.
 
99
 
 
100
        See the AWS dev reference page 90 (2008-12-01 version).
 
101
        :return: a_string encoded.
 
102
        """
 
103
        return quote(a_string, safe='~')
 
104
 
 
105
    def signing_text(self):
 
106
        """Return the text to be signed when signing the query."""
 
107
        result = "%s\n%s\n%s\n%s" % (self.method, self.host, self.uri,
 
108
            self.canonical_query_params())
 
109
        return result
 
110
 
 
111
    def sign(self):
 
112
        """Sign this query using its built in credentials.
 
113
        
 
114
        This prepares it to be sent, and should be done as the last step before
 
115
        submitting the query. Signing is done automatically - this is a public
 
116
        method to facilitate testing.
 
117
        """
 
118
        self.params['Signature'] = self.creds.sign(self.signing_text())
 
119
 
 
120
    def sorted_params(self):
 
121
        """Return the query params sorted appropriately for signing."""
 
122
        return sorted(self.params.items())
 
123
 
 
124
    def submit(self):
 
125
        """Submit this query.
 
126
 
 
127
        :return: A deferred from twisted.web.client.getPage
 
128
        """
 
129
        self.sign()
 
130
        url = 'http://%s%s?%s' % (self.host, self.uri,
 
131
            self.canonical_query_params())
 
132
        return getPage(url, method=self.method)