~tribaal/txaws/xss-hardening

« back to all changes in this revision

Viewing changes to txaws/ec2/client.py

Merged 416109-arbitrary-endpoints [r=therve,jkakar] [f=416109].

The primary change of this branch is support of arbitrary endpoints (needed for
the support of Eucalyptus). In addition, the following was also performed:
 * Added a parse utility function from Twisted
 * Created a testing subpackage for use by txAWS unit tests
 * Created a service module for abstracting regions and associated
   serices/credentials

Show diffs side-by-side

added added

removed removed

Lines of Context:
3
3
 
4
4
"""EC2 client support."""
5
5
 
6
 
from base64 import b64encode
7
6
from urllib import quote
8
7
 
9
8
from twisted.web.client import getPage
10
9
 
11
 
from txaws import credentials
 
10
from txaws.credentials import AWSCredentials
 
11
from txaws.service import AWSServiceEndpoint
12
12
from txaws.util import iso8601time, XML
13
13
 
14
14
 
77
77
 
78
78
    name_space = '{http://ec2.amazonaws.com/doc/2008-12-01/}'
79
79
 
80
 
    def __init__(self, creds=None, query_factory=None):
 
80
    def __init__(self, creds=None, endpoint=None, query_factory=None):
81
81
        """Create an EC2Client.
82
82
 
83
 
        @param creds: Explicit credentials to use. If None, credentials are
84
 
            inferred as per txaws.credentials.AWSCredentials.
 
83
        @param creds: User authentication credentials to use.
 
84
        @param endpoint: The service endpoint URI.
 
85
        @param query_factory: The class or function that produces a query
 
86
            object for making requests to the EC2 service.
85
87
        """
86
 
        if creds is None:
87
 
            self.creds = credentials.AWSCredentials()
88
 
        else:
89
 
            self.creds = creds
 
88
        self.creds = creds or AWSCredentials()
 
89
        self.endpoint = endpoint or AWSServiceEndpoint()
90
90
        if query_factory is None:
91
91
            self.query_factory = Query
92
92
        else:
94
94
 
95
95
    def describe_instances(self):
96
96
        """Describe current instances."""
97
 
        q = self.query_factory('DescribeInstances', self.creds)
 
97
        q = self.query_factory('DescribeInstances', self.creds, self.endpoint)
98
98
        d = q.submit()
99
99
        return d.addCallback(self._parse_instances)
100
100
 
177
177
        instanceset = {}
178
178
        for pos, instance_id in enumerate(instance_ids):
179
179
            instanceset["InstanceId.%d" % (pos+1)] = instance_id
180
 
        q = self.query_factory('TerminateInstances', self.creds, instanceset)
 
180
        q = self.query_factory('TerminateInstances', self.creds, self.endpoint,
 
181
                               instanceset)
181
182
        d = q.submit()
182
183
        return d.addCallback(self._parse_terminate_instances)
183
184
 
200
201
class Query(object):
201
202
    """A query that may be submitted to EC2."""
202
203
 
203
 
    def __init__(self, action, creds, other_params=None, time_tuple=None):
 
204
    def __init__(self, action, creds, endpoint, other_params=None,
 
205
                 time_tuple=None):
204
206
        """Create a Query to submit to EC2."""
 
207
        self.creds = creds
 
208
        self.endpoint = endpoint
205
209
        # Require params (2008-12-01 API):
206
210
        # Version, SignatureVersion, SignatureMethod, Action, AWSAccessKeyId,
207
211
        # Timestamp || Expires, Signature, 
208
 
        self.params = {'Version': '2008-12-01',
 
212
        self.params = {
 
213
            'Version': '2008-12-01',
209
214
            'SignatureVersion': '2',
210
215
            'SignatureMethod': 'HmacSHA1',
211
216
            'Action': action,
212
 
            'AWSAccessKeyId': creds.access_key,
 
217
            'AWSAccessKeyId': self.creds.access_key,
213
218
            'Timestamp': iso8601time(time_tuple),
214
219
            }
215
220
        if other_params:
216
221
            self.params.update(other_params)
217
 
        self.method = 'GET'
218
 
        self.host = 'ec2.amazonaws.com'
219
 
        self.uri = '/'
220
 
        self.creds = creds
221
222
 
222
223
    def canonical_query_params(self):
223
224
        """Return the canonical query params (used in signing)."""
230
231
        """Encode a_string as per the canonicalisation encoding rules.
231
232
 
232
233
        See the AWS dev reference page 90 (2008-12-01 version).
233
 
        :return: a_string encoded.
 
234
        @return: a_string encoded.
234
235
        """
235
236
        return quote(a_string, safe='~')
236
237
 
237
238
    def signing_text(self):
238
239
        """Return the text to be signed when signing the query."""
239
 
        result = "%s\n%s\n%s\n%s" % (self.method, self.host, self.uri,
240
 
            self.canonical_query_params())
 
240
        result = "%s\n%s\n%s\n%s" % (self.endpoint.method, self.endpoint.host,
 
241
                                     self.endpoint.path,
 
242
                                     self.canonical_query_params())
241
243
        return result
242
244
 
243
245
    def sign(self):
244
 
        """Sign this query using its built in credentials.
 
246
        """Sign this query using its built in creds.
245
247
        
246
248
        This prepares it to be sent, and should be done as the last step before
247
249
        submitting the query. Signing is done automatically - this is a public
256
258
    def submit(self):
257
259
        """Submit this query.
258
260
 
259
 
        :return: A deferred from twisted.web.client.getPage
 
261
        @return: A deferred from twisted.web.client.getPage
260
262
        """
261
263
        self.sign()
262
 
        url = 'http://%s%s?%s' % (self.host, self.uri,
263
 
            self.canonical_query_params())
264
 
        return getPage(url, method=self.method)
 
264
        url = "%s?%s" % (self.endpoint.get_uri(), 
 
265
                         self.canonical_query_params())
 
266
        return getPage(url, method=self.endpoint.method)