Source code for kingpin.actors.aws.iam

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Copyright 2014 Nextdoor.com, Inc

"""
:mod:`kingpin.actors.aws.iam`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
"""


import logging

from boto.exception import BotoServerError
from tornado import concurrent
from tornado import gen

from kingpin.actors.aws import base
from kingpin.actors import exceptions
from kingpin.constants import REQUIRED

log = logging.getLogger(__name__)

__author__ = 'Mikhail Simin <mikhail@nextdoor.com>'


# This executor is used by the tornado.concurrent.run_on_executor()
# decorator. We would like this to be a class variable so its shared
# across RightScale objects, but we see testing IO errors when we
# do this.
EXECUTOR = concurrent.futures.ThreadPoolExecutor(10)


[docs]class IAMBaseActor(base.AWSBaseActor): """Base class for IAM actors."""
[docs]class UploadCert(IAMBaseActor): """Uploads a new SSL Cert to AWS IAM. **Options** :private_key_path: (str) Path to the private key. :path: (str) The AWS "path" for the server certificate. Default: "/" :public_key_path: (str) Path to the public key certificate. :name: (str) The name for the server certificate. :cert_chain_path: (str) Path to the certificate chain. Optional. **Example** .. code-block:: json { "actor": "aws.iam.UploadCert", "desc": "Upload a new cert", "options": { "name": "new-cert", "private_key_path": "/cert.key", "public_key_path": "/cert.pem", "cert_chain_path": "/cert-chain.pem" } } **Dry run** Checks that the passed file paths are valid. In the future will also validate that the files are of correct format and content. """ all_options = { 'name': (str, REQUIRED, 'The name for the server certificate.'), 'public_key_path': (str, REQUIRED, 'Path to the public key certificate.'), 'private_key_path': (str, REQUIRED, 'Path to the private key.'), 'cert_chain_path': (str, None, 'Path to the certificate chain.'), 'path': (str, None, 'The path for the server certificate.') } @gen.coroutine def _upload(self, cert_name, cert_body, private_key, cert_chain, path): """Create a new server certificate in AWS IAM.""" yield self.thread( self.iam_conn.upload_server_cert, cert_name=cert_name, cert_body=cert_body, private_key=private_key, cert_chain=cert_chain, path=path) @gen.coroutine def _execute(self): """Gather all the cert data and upload it. The `boto` library requires actual cert contents, but this actor expects paths to files. """ # Gather needed cert data cert_chain_body = None if self.option('cert_chain_path'): cert_chain_body = self.readfile(self.option('cert_chain_path')) cert_body = self.readfile(self.option('public_key_path')) private_key = self.readfile(self.option('private_key_path')) # Upload it if self._dry: self.log.info('Would upload cert "%s"' % self.option('name')) raise gen.Return() self.log.info('Uploading cert "%s"' % self.option('name')) yield self._upload( cert_name=self.option('name'), cert_body=cert_body, private_key=private_key, cert_chain=cert_chain_body, path=self.option('path'))
[docs]class DeleteCert(IAMBaseActor): """Delete an existing SSL Cert in AWS IAM. **Options** :name: (str) The name for the server certificate. **Example** .. code-block:: json { "actor": "aws.iam.DeleteCert", "desc": "Run DeleteCert", "options": { "name": "fill-in" } } **Dry run** Will find the cert by name or raise an exception if it's not found. """ all_options = { 'name': (str, REQUIRED, 'The name for the server certificate.') } @gen.coroutine def _find_cert(self, name): """Find a cert by name.""" self.log.debug('Searching for cert "%s"...' % name) try: yield self.thread(self.iam_conn.get_server_certificate, name) except BotoServerError as e: raise exceptions.UnrecoverableActorFailure( 'Could not find cert %s. Reason: %s' % (name, e)) @gen.coroutine def _delete(self, cert_name): """Delete a server certificate in AWS IAM.""" yield self.thread(self.iam_conn.delete_server_cert, cert_name) @gen.coroutine def _execute(self): if self._dry: self.log.info('Checking that the cert exists...') yield self._find_cert(self.option('name')) self.log.info('Would delete cert "%s"' % self.option('name')) raise gen.Return() self.log.info('Deleting cert "%s"' % self.option('name')) yield self._delete(cert_name=self.option('name'))