1# Copyright (c) 2015 Amazon.com, Inc. or its affiliates.  All Rights Reserved
2#
3# Permission is hereby granted, free of charge, to any person obtaining a
4# copy of this software and associated documentation files (the
5# "Software"), to deal in the Software without restriction, including
6# without limitation the rights to use, copy, modify, merge, publish, dis-
7# tribute, sublicense, and/or sell copies of the Software, and to permit
8# persons to whom the Software is furnished to do so, subject to the fol-
9# lowing conditions:
10#
11# The above copyright notice and this permission notice shall be included
12# in all copies or substantial portions of the Software.
13#
14# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
16# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
17# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
18# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20# IN THE SOFTWARE.
21#
22
23import boto
24from boto.compat import json
25from boto.connection import AWSQueryConnection
26from boto.regioninfo import RegionInfo
27from boto.exception import JSONResponseError
28from boto.cloudhsm import exceptions
29
30
31class CloudHSMConnection(AWSQueryConnection):
32    """
33    AWS CloudHSM Service
34    """
35    APIVersion = "2014-05-30"
36    DefaultRegionName = "us-east-1"
37    DefaultRegionEndpoint = "cloudhsm.us-east-1.amazonaws.com"
38    ServiceName = "CloudHSM"
39    TargetPrefix = "CloudHsmFrontendService"
40    ResponseError = JSONResponseError
41
42    _faults = {
43        "InvalidRequestException": exceptions.InvalidRequestException,
44        "CloudHsmServiceException": exceptions.CloudHsmServiceException,
45        "CloudHsmInternalException": exceptions.CloudHsmInternalException,
46    }
47
48
49    def __init__(self, **kwargs):
50        region = kwargs.pop('region', None)
51        if not region:
52            region = RegionInfo(self, self.DefaultRegionName,
53                                self.DefaultRegionEndpoint)
54
55        if 'host' not in kwargs or kwargs['host'] is None:
56            kwargs['host'] = region.endpoint
57
58        super(CloudHSMConnection, self).__init__(**kwargs)
59        self.region = region
60
61    def _required_auth_capability(self):
62        return ['hmac-v4']
63
64    def create_hapg(self, label):
65        """
66        Creates a high-availability partition group. A high-
67        availability partition group is a group of partitions that
68        spans multiple physical HSMs.
69
70        :type label: string
71        :param label: The label of the new high-availability partition group.
72
73        """
74        params = {'Label': label, }
75        return self.make_request(action='CreateHapg',
76                                 body=json.dumps(params))
77
78    def create_hsm(self, subnet_id, ssh_key, iam_role_arn, subscription_type,
79                   eni_ip=None, external_id=None, client_token=None,
80                   syslog_ip=None):
81        """
82        Creates an uninitialized HSM instance. Running this command
83        provisions an HSM appliance and will result in charges to your
84        AWS account for the HSM.
85
86        :type subnet_id: string
87        :param subnet_id: The identifier of the subnet in your VPC in which to
88            place the HSM.
89
90        :type ssh_key: string
91        :param ssh_key: The SSH public key to install on the HSM.
92
93        :type eni_ip: string
94        :param eni_ip: The IP address to assign to the HSM's ENI.
95
96        :type iam_role_arn: string
97        :param iam_role_arn: The ARN of an IAM role to enable the AWS CloudHSM
98            service to allocate an ENI on your behalf.
99
100        :type external_id: string
101        :param external_id: The external ID from **IamRoleArn**, if present.
102
103        :type subscription_type: string
104        :param subscription_type: The subscription type.
105
106        :type client_token: string
107        :param client_token: A user-defined token to ensure idempotence.
108            Subsequent calls to this action with the same token will be
109            ignored.
110
111        :type syslog_ip: string
112        :param syslog_ip: The IP address for the syslog monitoring server.
113
114        """
115        params = {
116            'SubnetId': subnet_id,
117            'SshKey': ssh_key,
118            'IamRoleArn': iam_role_arn,
119            'SubscriptionType': subscription_type,
120        }
121        if eni_ip is not None:
122            params['EniIp'] = eni_ip
123        if external_id is not None:
124            params['ExternalId'] = external_id
125        if client_token is not None:
126            params['ClientToken'] = client_token
127        if syslog_ip is not None:
128            params['SyslogIp'] = syslog_ip
129        return self.make_request(action='CreateHsm',
130                                 body=json.dumps(params))
131
132    def create_luna_client(self, certificate, label=None):
133        """
134        Creates an HSM client.
135
136        :type label: string
137        :param label: The label for the client.
138
139        :type certificate: string
140        :param certificate: The contents of a Base64-Encoded X.509 v3
141            certificate to be installed on the HSMs used by this client.
142
143        """
144        params = {'Certificate': certificate, }
145        if label is not None:
146            params['Label'] = label
147        return self.make_request(action='CreateLunaClient',
148                                 body=json.dumps(params))
149
150    def delete_hapg(self, hapg_arn):
151        """
152        Deletes a high-availability partition group.
153
154        :type hapg_arn: string
155        :param hapg_arn: The ARN of the high-availability partition group to
156            delete.
157
158        """
159        params = {'HapgArn': hapg_arn, }
160        return self.make_request(action='DeleteHapg',
161                                 body=json.dumps(params))
162
163    def delete_hsm(self, hsm_arn):
164        """
165        Deletes an HSM. Once complete, this operation cannot be undone
166        and your key material cannot be recovered.
167
168        :type hsm_arn: string
169        :param hsm_arn: The ARN of the HSM to delete.
170
171        """
172        params = {'HsmArn': hsm_arn, }
173        return self.make_request(action='DeleteHsm',
174                                 body=json.dumps(params))
175
176    def delete_luna_client(self, client_arn):
177        """
178        Deletes a client.
179
180        :type client_arn: string
181        :param client_arn: The ARN of the client to delete.
182
183        """
184        params = {'ClientArn': client_arn, }
185        return self.make_request(action='DeleteLunaClient',
186                                 body=json.dumps(params))
187
188    def describe_hapg(self, hapg_arn):
189        """
190        Retrieves information about a high-availability partition
191        group.
192
193        :type hapg_arn: string
194        :param hapg_arn: The ARN of the high-availability partition group to
195            describe.
196
197        """
198        params = {'HapgArn': hapg_arn, }
199        return self.make_request(action='DescribeHapg',
200                                 body=json.dumps(params))
201
202    def describe_hsm(self, hsm_arn=None, hsm_serial_number=None):
203        """
204        Retrieves information about an HSM. You can identify the HSM
205        by its ARN or its serial number.
206
207        :type hsm_arn: string
208        :param hsm_arn: The ARN of the HSM. Either the HsmArn or the
209            SerialNumber parameter must be specified.
210
211        :type hsm_serial_number: string
212        :param hsm_serial_number: The serial number of the HSM. Either the
213            HsmArn or the HsmSerialNumber parameter must be specified.
214
215        """
216        params = {}
217        if hsm_arn is not None:
218            params['HsmArn'] = hsm_arn
219        if hsm_serial_number is not None:
220            params['HsmSerialNumber'] = hsm_serial_number
221        return self.make_request(action='DescribeHsm',
222                                 body=json.dumps(params))
223
224    def describe_luna_client(self, client_arn=None,
225                             certificate_fingerprint=None):
226        """
227        Retrieves information about an HSM client.
228
229        :type client_arn: string
230        :param client_arn: The ARN of the client.
231
232        :type certificate_fingerprint: string
233        :param certificate_fingerprint: The certificate fingerprint.
234
235        """
236        params = {}
237        if client_arn is not None:
238            params['ClientArn'] = client_arn
239        if certificate_fingerprint is not None:
240            params['CertificateFingerprint'] = certificate_fingerprint
241        return self.make_request(action='DescribeLunaClient',
242                                 body=json.dumps(params))
243
244    def get_config(self, client_arn, client_version, hapg_list):
245        """
246        Gets the configuration files necessary to connect to all high
247        availability partition groups the client is associated with.
248
249        :type client_arn: string
250        :param client_arn: The ARN of the client.
251
252        :type client_version: string
253        :param client_version: The client version.
254
255        :type hapg_list: list
256        :param hapg_list: A list of ARNs that identify the high-availability
257            partition groups that are associated with the client.
258
259        """
260        params = {
261            'ClientArn': client_arn,
262            'ClientVersion': client_version,
263            'HapgList': hapg_list,
264        }
265        return self.make_request(action='GetConfig',
266                                 body=json.dumps(params))
267
268    def list_available_zones(self):
269        """
270        Lists the Availability Zones that have available AWS CloudHSM
271        capacity.
272
273
274        """
275        params = {}
276        return self.make_request(action='ListAvailableZones',
277                                 body=json.dumps(params))
278
279    def list_hapgs(self, next_token=None):
280        """
281        Lists the high-availability partition groups for the account.
282
283        This operation supports pagination with the use of the
284        NextToken member. If more results are available, the NextToken
285        member of the response contains a token that you pass in the
286        next call to ListHapgs to retrieve the next set of items.
287
288        :type next_token: string
289        :param next_token: The NextToken value from a previous call to
290            ListHapgs. Pass null if this is the first call.
291
292        """
293        params = {}
294        if next_token is not None:
295            params['NextToken'] = next_token
296        return self.make_request(action='ListHapgs',
297                                 body=json.dumps(params))
298
299    def list_hsms(self, next_token=None):
300        """
301        Retrieves the identifiers of all of the HSMs provisioned for
302        the current customer.
303
304        This operation supports pagination with the use of the
305        NextToken member. If more results are available, the NextToken
306        member of the response contains a token that you pass in the
307        next call to ListHsms to retrieve the next set of items.
308
309        :type next_token: string
310        :param next_token: The NextToken value from a previous call to
311            ListHsms. Pass null if this is the first call.
312
313        """
314        params = {}
315        if next_token is not None:
316            params['NextToken'] = next_token
317        return self.make_request(action='ListHsms',
318                                 body=json.dumps(params))
319
320    def list_luna_clients(self, next_token=None):
321        """
322        Lists all of the clients.
323
324        This operation supports pagination with the use of the
325        NextToken member. If more results are available, the NextToken
326        member of the response contains a token that you pass in the
327        next call to ListLunaClients to retrieve the next set of
328        items.
329
330        :type next_token: string
331        :param next_token: The NextToken value from a previous call to
332            ListLunaClients. Pass null if this is the first call.
333
334        """
335        params = {}
336        if next_token is not None:
337            params['NextToken'] = next_token
338        return self.make_request(action='ListLunaClients',
339                                 body=json.dumps(params))
340
341    def modify_hapg(self, hapg_arn, label=None, partition_serial_list=None):
342        """
343        Modifies an existing high-availability partition group.
344
345        :type hapg_arn: string
346        :param hapg_arn: The ARN of the high-availability partition group to
347            modify.
348
349        :type label: string
350        :param label: The new label for the high-availability partition group.
351
352        :type partition_serial_list: list
353        :param partition_serial_list: The list of partition serial numbers to
354            make members of the high-availability partition group.
355
356        """
357        params = {'HapgArn': hapg_arn, }
358        if label is not None:
359            params['Label'] = label
360        if partition_serial_list is not None:
361            params['PartitionSerialList'] = partition_serial_list
362        return self.make_request(action='ModifyHapg',
363                                 body=json.dumps(params))
364
365    def modify_hsm(self, hsm_arn, subnet_id=None, eni_ip=None,
366                   iam_role_arn=None, external_id=None, syslog_ip=None):
367        """
368        Modifies an HSM.
369
370        :type hsm_arn: string
371        :param hsm_arn: The ARN of the HSM to modify.
372
373        :type subnet_id: string
374        :param subnet_id: The new identifier of the subnet that the HSM is in.
375
376        :type eni_ip: string
377        :param eni_ip: The new IP address for the elastic network interface
378            attached to the HSM.
379
380        :type iam_role_arn: string
381        :param iam_role_arn: The new IAM role ARN.
382
383        :type external_id: string
384        :param external_id: The new external ID.
385
386        :type syslog_ip: string
387        :param syslog_ip: The new IP address for the syslog monitoring server.
388
389        """
390        params = {'HsmArn': hsm_arn, }
391        if subnet_id is not None:
392            params['SubnetId'] = subnet_id
393        if eni_ip is not None:
394            params['EniIp'] = eni_ip
395        if iam_role_arn is not None:
396            params['IamRoleArn'] = iam_role_arn
397        if external_id is not None:
398            params['ExternalId'] = external_id
399        if syslog_ip is not None:
400            params['SyslogIp'] = syslog_ip
401        return self.make_request(action='ModifyHsm',
402                                 body=json.dumps(params))
403
404    def modify_luna_client(self, client_arn, certificate):
405        """
406        Modifies the certificate used by the client.
407
408        This action can potentially start a workflow to install the
409        new certificate on the client's HSMs.
410
411        :type client_arn: string
412        :param client_arn: The ARN of the client.
413
414        :type certificate: string
415        :param certificate: The new certificate for the client.
416
417        """
418        params = {
419            'ClientArn': client_arn,
420            'Certificate': certificate,
421        }
422        return self.make_request(action='ModifyLunaClient',
423                                 body=json.dumps(params))
424
425    def make_request(self, action, body):
426        headers = {
427            'X-Amz-Target': '%s.%s' % (self.TargetPrefix, action),
428            'Host': self.region.endpoint,
429            'Content-Type': 'application/x-amz-json-1.1',
430            'Content-Length': str(len(body)),
431        }
432        http_request = self.build_base_http_request(
433            method='POST', path='/', auth_path='/', params={},
434            headers=headers, data=body)
435        response = self._mexe(http_request, sender=None,
436                              override_num_retries=10)
437        response_body = response.read().decode('utf-8')
438        boto.log.debug(response_body)
439        if response.status == 200:
440            if response_body:
441                return json.loads(response_body)
442        else:
443            json_body = json.loads(response_body)
444            fault_name = json_body.get('__type', None)
445            exception_class = self._faults.get(fault_name, self.ResponseError)
446            raise exception_class(response.status, response.reason,
447                                  body=json_body)
448
449