Ejemplo n.º 1
0
 def _connect_services(self):
     if self.aws_secret_key and self.aws_access_key and self._clc_ip:
         self._serviceconnection = ServiceConnection(hostname=self._clc_ip,
                                                     aws_access_key=self.aws_access_key,
                                                     aws_secret_key=self.aws_secret_key)
     return self._serviceconnection
Ejemplo n.º 2
0
class AutoCreds(Eucarc):
    def __init__(self,
                 auto_create=True,
                 aws_access_key=None,
                 aws_secret_key=None,
                 aws_account_name=None,
                 region_domain=None,
                 service_port=8773,
                 https=False,
                 aws_user_name=None,
                 machine=None,
                 hostname=None,
                 username='******',
                 password=None,
                 keypath=None,
                 credpath=None,
                 proxy_hostname=None,
                 proxy_username='******',
                 proxy_password=None,
                 proxy_keypath=None,
                 logger=None,
                 log_level='INFO',
                 eucarc_obj=None,
                 existing_certs=True,
                 service_connection=None,
                 keysdir=None,
                 string=None,
                 clc_connect_kwargs=None):
        super(AutoCreds, self).__init__(logger=logger, loglevel=log_level)
        self._local_files = None
        self._serviceconnection = service_connection
        self._clc_ip = hostname
        # Todo implement building creds from a passed string buffer
        self._string = string
        self._keysdir = keysdir
        self._machine = machine
        self._credpath = credpath
        self._account_name = aws_account_name
        self._user_name = aws_user_name
        self._has_existing_cert = existing_certs
        self.aws_secret_key = aws_secret_key
        self.aws_access_key = aws_access_key
        self._region_domain = region_domain
        self._service_port = service_port
        self._https = https
        self._has_updated_connect_args = False  # used to speed up auto find credentials
        if (username != 'root' or proxy_username != 'root' or password or keypath or
                proxy_hostname or proxy_keypath or proxy_password):
            self._has_updated_connect_args = True
        clc_connect_kwargs = clc_connect_kwargs or {}
        self._clc_connect_kwargs = {
            'hostname': hostname,
            'username': username,
            'password': password,
            'keypath': keypath,
            'proxy_hostname': proxy_hostname,
            'proxy_username': proxy_username,
            'proxy_password': proxy_password,
            'proxy_keypath': proxy_keypath
        }
        self._clc_connect_kwargs.update(clc_connect_kwargs)
        if eucarc_obj:
            self.__dict__.update(eucarc_obj.__dict__)
        if not (self.aws_access_key and self.aws_secret_key) and auto_create:
            self.auto_find_credentials()

    @property
    def is_https(self):
        return self._https

    @is_https.setter
    def is_https(self, value):
        assert isinstance(value, bool), 'is_https must be of type bool, got:{0}/{1}'\
            .format(value, type(value))
        if value != self.is_https:
            self._https = value
            self.update_attrs_from_cloud_services()


    @property
    def region_domain(self):
        if not self._region_domain and self.serviceconnection:
            domain_prop = self.serviceconnection.get_property('system.dns.dnsdomain')
            domain = domain_prop.value
            if domain:
                self._region_domain = domain
        return self._region_domain

    @region_domain.setter
    def region_domain(self, domain):
        if domain != self.region_domain:
            self._region_domain = domain
            self.update_attrs_from_cloud_services()

    @property
    def service_port(self):
        if not self._service_port and self.serviceconnection:
            port_prop = self.serviceconnection.get_property('bootstrap.webservices.port')
            port = port_prop.value
            if port:
                self._service_port = port
        return self._service_port

    @service_port.setter
    def service_port(self, port):
        if port != self.service_port:
            self._service_port = port
            self.update_attrs_from_cloud_services()

    @property
    def creds_machine(self):
        if not self._machine:
            self._machine = self.connect_to_creds_machine()
        return self._machine

    @creds_machine.setter
    def creds_machine(self, machine):
        self._machine = machine

    @property
    def serviceconnection(self):
        if not self._serviceconnection:
            self._serviceconnection = self._connect_services()
        return self._serviceconnection

    def _connect_services(self):
        if self.aws_secret_key and self.aws_access_key and self._clc_ip:
            self._serviceconnection = ServiceConnection(hostname=self._clc_ip,
                                                        aws_access_key=self.aws_access_key,
                                                        aws_secret_key=self.aws_secret_key)
        return self._serviceconnection

    def _close_adminpi(self):
        """
        If open, Attempts to close/cleanup the AutoCred's serviceconnection obj
        """
        if self._serviceconnection:
            try:
                self._serviceconnection.close()
                self._serviceconnection = None
            except:
                pass

    def update_attrs_from_cloud_services(self):
        """
        Attempts to update the current eucarc artifacts (service paths) from services
        gathered via the Eucalyptus admin api interface
        :returns dict mapping eucarc common key-values to the discovered service URIs.
        """
        if not self.serviceconnection and not self.region_domain:
            raise RuntimeError('Can not fetch service paths from cloud without a "region_domain"'
                               'and/or a ServiceConnection\n A ServiceConnection requires: '
                               'clc_ip, or aws_access_key + aws_secret_key and bootstrap endpoint')
        if self.region_domain:
            path_dict = self._get_service_paths_from_domain(domain=self.region_domain,
                                                            port=self.service_port,
                                                            secure=self.is_https)
        else:
            path_dict = self._get_service_paths_from_serviceconnection(self.serviceconnection)
        if not path_dict.get('ec2_access_key'):
            path_dict['ec2_access_key'] = self.aws_access_key
        if not path_dict.get('ec2_secret_key'):
            path_dict['ec2_secret_key'] = self.aws_secret_key
        for key, value in path_dict.iteritems():
            setattr(self, key, value)
        # self.__dict__.update(path_dict)
        #self._close_adminpi()
        return path_dict

    @classmethod
    def _get_service_paths_from_domain(cls, domain, port=8773, secure=False):
        ret_dict = {}
        for service_prefix, mapping in aws_to_euca_service_map.iteritems():
            # http://monitoring.h-12.autoqa.qa1.eucalyptus-systems.com:8773/
            if secure:
                url = "http://"
            else:
                url = "https://"
            if port:
                str_port = ":" + str(port)
            else:
                str_port = ""
            url = "{0}{1}.{2}{3}/".format(url, service_prefix, domain, str_port)
            ret_dict[mapping.eucarc] = url
        return ret_dict

    @classmethod
    def _get_service_paths_from_serviceconnection(cls, serviceconnection, secure=False):
        """
        Reads the Eucalyptus services, maps them to common eucarc key values, and returns
        the dict of the mapping.
        :params serviceconnection: an ServiceConnection obj w/ active connection.
        :returns dict mapping eucarc common key-values to the discovered service URIs.
        """
        assert isinstance(serviceconnection, ServiceConnection)
        services = serviceconnection.get_services()
        ret_dict = {}
        try:
            domain_prop = serviceconnection.get_property('system.dns.dnsdomain')
            domain = domain_prop.value
            port_prop = serviceconnection.get_property('bootstrap.webservices.port')
            port = port_prop.value
            return cls._get_service_paths_from_domain(domain=domain, port=port, secure=secure)
        except Exception as DE:
            print
            for service in services:
                for service_name, mapping in aws_to_euca_service_map.iteritems():
                    if service.type == mapping.euca_service:
                        ret_dict[mapping.eucarc] = str(service.uri)
        return ret_dict

    def get_local_eucarc(self, credpath):
        """
        Reads in eucarc contents from a local file path. Checks to make sure the credpath
        given is an existing file, if a dir was provide it will check for a file name 'eucarc'
        in that dir.
        :params credpath: string representing the path to the eucarc file
        :return dict of eucarc read in
        """
        if not str(credpath).endswith('eucarc') and os.path.isdir(credpath):
            credpath = os.path.join(credpath, 'eucarc')
        if os.path.isfile(credpath):
            return self._from_filepath(credpath)
        return None

    def get_remote_eucarc(self, credpath, machine=None):
        """
        Reads in eucarc contents from a remote file path on the provided Machine().
        Checks to make sure the credpath given is an existing file, if a dir was provide it will
        check for a file name 'eucarc' in that dir.
        :params credpath: string representing the path to the eucarc file
        :params machine: Machine() obj
        :returns dict of eucarc read in
        """
        machine = machine or self.creds_machine
        if not str(credpath).endswith('eucarc') and machine.is_dir(credpath):
            credpath = os.path.join(credpath, 'eucarc')
        if machine.is_file(credpath):
            return self._from_filepath(filepath=credpath, sshconnection=machine._ssh)
        return None

    def connect_to_creds_machine(self, connect_kwargs=None):
        """
        Attempts to create a Machine by connecting to the remote host, usually the CLC.
        :params connect_kwargs: Dictionary set of arguments to be provided to Machine().__init__()
        returns machine obj upon success
        """
        connect_kwargs = connect_kwargs or self._clc_connect_kwargs
        machine = Machine(**connect_kwargs)
        self.creds_machine = machine
        return machine

    def assume_role_on_remote_clc(self, machine=None):
        machine = machine or self.creds_machine
        cred_string = []
        out = machine.sys('clcadmin-assume-system-credentials', code=0)
        for line in out:
            if line:
                line = line.strip(';')
            line = str(line).replace('127.0.0.1', machine.hostname)
            cred_string.append(line)
        return self._from_string(string=cred_string)


    def auto_find_credentials(self, from_file=True, assume_admin=True, service_connection=True,
                              from_remote=True, from_db=True):
        """
        Convenience method which attempts to automatically produce credentials based upon the
        information provided to this AutoCreds obj.
        - If any ssh connect arguments (outside of hostname) are provided then only the remote
        machine tried for existing creds at 'self._credpath'.
        - If credpath was provided the local file system will first be tried for existing
        credentials
        - If aws access and secret keys were provided allong with hostname, will attempt to
        derivce service credpaths from the Eucalyptus Admin api.
        -Finally if a hostname was provided an ssh attempt will be made (with any other
         connection kwargs provided)to fetch from the remote system as well.
         If password or keypath was not provided, this assumes keys have been sync'd between the
         localhost and the remote machine.
        Upon the first successful discovery of credentials, the local obj is populated with
        eucarc attributes and returns.
        """

        def try_from_file(self):
            self.log.debug('Trying creds from local file...')
            if self._credpath:
                try:
                    res = self.get_local_eucarc(credpath=self._credpath)
                    if res:
                        self.log.debug('Found local creds at: "{0}"'.format(self._credpath))
                        return res
                except IOError:
                    pass

        def try_serviceconnection(self):
            self.log.debug('Trying creds from service connection...')
            if self.aws_secret_key and self.aws_access_key and self._clc_ip:
                self._connect_services()
                try:
                    res = self.update_attrs_from_cloud_services()
                    if res:
                        self.log.debug('Derived creds from serviceconnection')
                        return res
                except RuntimeError as RE:
                    self.log.debug('{0}\nFailed to update creds using serviceconnection, err:"{1}"'
                               .format(get_traceback(), str(RE)))
                    self._close_adminpi()

        def try_assume_admin_on_clc(self):
            self.log.debug('Trying creds from assume admin role on clc...')
            if not self.aws_secret_key and not self.aws_access_key:
                try:
                    self.assume_role_on_remote_clc()
                    res = try_serviceconnection(self)
                    try:
                        # With keys, try filling in remainder with service urls/attributes
                        # using the admin api interface...
                        res = self.update_attrs_from_cloud_services()
                    except:
                        pass
                    return res
                except Exception as AE:
                    self.log.debug('{0}\nFailed to update creds using '
                               '"clcadmin-assume-system-credentials", err:"{1}"'
                               .format(get_traceback(), str(AE)))

        def try_remote(self):
            self.log.debug('Trying creds from remote machine at credpath...')
            if self.creds_machine and self._credpath:
                try:
                    machine = self.creds_machine or self.connect_to_creds_machine()
                    if machine:
                        try:
                            if not self._keysdir:
                                self._keysdir = machine.get_abs_path(self._credpath)
                        except:
                            pass
                        res = self.get_remote_eucarc(credpath=self._credpath, machine=machine)
                        if res:
                            self.log.debug('Found remote creds on:"{0}", at path:"{1}"'
                                       .format(self.creds_machine.ssh.host, self._credpath))
                            return res
                except Exception as e:
                    self.log.debug("{0}\nFailed to fetch creds remotely, err:'{1}'"
                               .format(get_traceback(), str(e)))

        def try_clc_db(self):
            self.log.debug('Trying creds from CLC DB...')
            if self.creds_machine and self.aws_account_name and self.aws_user_name:
                machine = self.creds_machine or self.connect_to_creds_machine()
                if machine:
                    try:
                        res = self.get_existing_keys_from_clc(account=self.aws_account_name,
                                                              user=self.aws_user_name,
                                                              machine=machine)
                        try:
                            # With keys, try filling in remainder with service urls/attributes
                            # using the admin api interface...
                            res = self.update_attrs_from_cloud_services()
                        except:
                            pass
                        return res
                    except RuntimeError as RE:
                        self.log.debug('{0}\nFailed to fetch creds from clc db, err:{1}'
                                   .format(get_traceback(), str(RE)))

        default_order = []
        if from_file:
            default_order.append(try_from_file)
        if service_connection:
            default_order.append(try_serviceconnection)
        if assume_admin:
            default_order.append(try_assume_admin_on_clc)
        if from_remote:
            default_order.append(try_remote)
        if from_db:
            default_order.append(try_clc_db)
        if self._clc_ip and self._credpath and \
                (self._has_updated_connect_args or self._sshconnection):
            # if any ssh related arguments were provided, assume the user would like
            # to try remote first
            if try_remote(self):
                return
            default_order.remove(try_remote)
            raise ValueError('Could not find "remote" creds with provided information.')
        else:
            for meth in default_order:
                if meth(self):
                    return
            raise ValueError("Could not find path with provided information.")


    def get_existing_keys_from_clc(self, account, user, machine=None, eucahome=None, port=8777,
                                   dbuser='******', p12file=None, pkfile=None,
                                   passphrase=None, db=None, pargs=None, do_cert=None,
                                   verbose=False):
        ret = {}
        db = db or 'eucalyptus_shared'
        pargs = pargs or ""
        machine = machine or self.creds_machine
        passphrase = None or 'eucalyptus'
        if do_cert is None:
            do_cert = self._has_existing_cert
        if hasattr(machine, 'eucalyptus_home'):
            eucahome = machine.eucalyptus_home
        else:
            eucahome = eucahome or EucaHost._get_eucalyptus_home(machine) or '/'
        EucaP12File = p12file or os.path.join(eucahome, '/var/lib/eucalyptus/keys/euca.p12')
        CloudPKFile = pkfile or os.path.join(eucahome, '/var/lib/eucalyptus/keys/cloud-pk.pem')
        cmd = ("echo -n '{0}' | openssl SHA256  -sign {1} | sha256sum"
               .format(passphrase, CloudPKFile))
        out = machine.sys(cmd, code=0, verbose=verbose)
        if out:
            dbpass = str(out[0]).split()[0]

        if do_cert:
            dbsel = ("\"select k.auth_access_key_query_id, k.auth_access_key_key, "
                     "a.auth_account_number, a.auth_account_name, c.auth_certificate_pem "
                     "from eucalyptus_auth.auth_access_key k "
                     "join eucalyptus_auth.auth_user u on k.auth_access_key_owning_user=u.id "
                     "join eucalyptus_auth.auth_cert c on c.auth_certificate_owning_user=u.id "
                     "join eucalyptus_auth.auth_group_has_users gu on gu.auth_user_id = u.id "
                     "join eucalyptus_auth.auth_group g on gu.auth_group_id=g.id "
                     "join eucalyptus_auth.auth_account a on g.auth_group_owning_account=a.id "
                     "where a.auth_account_name = '{0}' and g.auth_group_name = '{1}'\";"
                     .format(account, "_" + user))
        else:
            dbsel = ("\"select k.auth_access_key_query_id, k.auth_access_key_key, "
                     "a.auth_account_number, a.auth_account_name "
                     "from eucalyptus_auth.auth_access_key k "
                     "join eucalyptus_auth.auth_user u on k.auth_access_key_owning_user=u.id "
                     "join eucalyptus_auth.auth_group_has_users gu on gu.auth_user_id = u.id "
                     "join eucalyptus_auth.auth_group g on gu.auth_group_id=g.id "
                     "join eucalyptus_auth.auth_account a on g.auth_group_owning_account=a.id "
                     "where a.auth_account_name = '{0}' and g.auth_group_name = '{1}'\";"
                     .format(account, "_" + user))
        dbcmd = ('export PGPASSWORD={0}; psql {1} -A -F "," -h 127.0.0.1 -p {2} -U {3} -d {4} '
                 '-c {5}'.format(dbpass, pargs, port, dbuser, db, dbsel))
        qout = machine.sys(dbcmd, code=0, verbose=verbose)
        if verbose:
                self.log.info('Result of cmd:"{0}"\n"{1}"'.format(dbcmd, qout))
        if qout:
            try:
                names = qout[0].split(',')
                values = qout[1].split(',')
                ret['AWS_ACCESS_KEY'] = values[names.index('auth_access_key_query_id')]
                ret['AWS_SECRET_KEY'] = values[names.index('auth_access_key_key')]
                ret['EC2_ACCOUNT_NUMBER'] = values[names.index('auth_account_number')]
                ret['EC2_ACCOUNT_NAME'] = values[names.index('auth_account_name')]
                if do_cert:
                    ret['CERT'] = values[names.index('auth_certificate_pem')]
                self.aws_access_key = ret['AWS_ACCESS_KEY']
                self.aws_secret_key = ret['AWS_SECRET_KEY']
                self.ec2_user_id = ret['EC2_ACCOUNT_NUMBER']
                self.ec2_account_number = ret['EC2_ACCOUNT_NUMBER']
                self.ec2_account_name = ret['EC2_ACCOUNT_NAME']
            except Exception as PE:
                self.log.error('{0}\nOutput:\nFailed fetching existing creds for account:"{1}", '
                               'user:"******".\nRemote Command Output:"{3}"'
                               .format(get_traceback(), account, user, "\n".join(qout), str(PE)))
                raise PE
        return ret

    def create_local_creds(self, local_destdir, machine=None, keydir=None, overwrite=False,
                           zipfilename=None, ziponly=False):
        """
        Attempts to create a local set of files containing the current credential artifacts
        in this AutoCreds obj. The following files will be written to the provided
        'local_destdir' directory:
        - A eucarc file containing the "export key=value" syntax to resolve service urls
          and the location of any credentials related files.
        - Any current attributes with an sftp:// uri will be downloaded to local_destdir. At this
        time the AutoCred eucarc attributes will be updated to represent their now local filepath,
        an the local eucarc written will also reflect the new location.

        :param local_destdir: local directory to write cred files to.
                               Will create if does not exist.
        :param machine: The Machine() obj to download any sftp:// files from
        :param keydir: optional String representing path to key dir, otherwise auto-populated
        :param overwrite: bool, if True will overwrite any existing items at 'local_destdir'
        :param zipfilename: string representing the zip archive filename to be creat in the
                            'local_destdir' directory. If "None" a zip archive will not be created.
        :param ziponly: boolean, if true only a zip archive will be created
        :return: list of filepaths
        """
        def make_local_dir(dirpath):
            if dirpath:
                try:
                    os.makedirs(dirpath)
                except OSError as exc:
                    if exc.errno == errno.EEXIST and os.path.isdir(dirpath):
                        pass
                    else:
                        raise


        filepaths = []
        local_destdir = os.path.abspath(local_destdir or "")
        if zipfilename:
            make_local_dir(local_destdir)
            zip_path = os.path.join(local_destdir, zipfilename)
            if os.path.exists(zip_path):
                raise ValueError('Will not overwrite existing credentials archive at:"{0}"'
                                 .format(zip_path))
            zip_file = zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED, False)
        else:
            zip_file = None
        machine = machine or self.creds_machine
        if keydir is None:
            keydir = "EUCA_KEY_DIR=$(cd $(dirname ${BASH_SOURCE:-$0}); pwd -P)"
        has_sftp_items = False

        for key, value in self.get_eucarc_attrs().iteritems():
            if isinstance(value, basestring) and re.search('^sftp://', value):
                has_sftp_items = True
        if has_sftp_items:
            if not machine:
                if not self._has_updated_connect_args:
                    self.log.info('Remote machine info has not been provided, '
                                  'skipping remote creds download')
                else:
                    machine = self.connect_to_creds_machine()
            if zip_file and ziponly:
                filepaths = self._download_remote_artifacts(local_destdir=None,
                                                            machine=machine,
                                                            overwrite=overwrite,
                                                            zip_file=zip_file)
            else:
                filepaths = self._download_remote_artifacts(local_destdir=local_destdir,
                                                            machine=machine,
                                                            overwrite=overwrite)
        if zip_file and ziponly:
            if self.serviceconnection is not None:
                certs = self.serviceconnection.get_service_certs()
                if not certs:
                    raise ValueError('No service certs found in DescribeServiceCerts response')
                cert = certs[0]
                certbody = cert.certificate
                if not certbody:
                    raise ValueError('Certbody not found in retrieved cert')
                zip_file.writestr('cloud-cert.pem', certbody)
            eucarc = ""

            eucarc += keydir + "\n"
            eucarc += 'export EUCALYPTUS_CERT="${EUCA_KEY_DIR}/cloud-cert.pem"\n'
            for key, value in self.get_eucarc_attrs().iteritems():
                if isinstance(value, basestring) and not re.search('^sftp://', value):
                    eucarc += 'export {0}="{1}"\n'.format(str(key).upper(), value)
            zip_file.writestr('eucarc', eucarc)
            zip_file.close()
            filepaths.append(zipfilename)
        else:
            # Now write the eucarc file. Any downloaded files should have updated the
            # local euarc attrs replacing the sftp uri with a local file path
            make_local_dir(local_destdir)
            cloud_cert_path = os.path.join(local_destdir, 'cloud-cert.pem')
            if os.path.exists(cloud_cert_path):
                filepaths.append(cloud_cert_path)
            elif self.serviceconnection is not None:
                self.serviceconnection.write_service_cert_to_file(cloud_cert_path)
                filepaths.append(cloud_cert_path)
            eucarc_path = os.path.join(local_destdir, 'eucarc')
            filepaths.append(eucarc_path)
            with open(eucarc_path, 'w') as eucarc:
                eucarc.seek(0)
                eucarc.write(keydir + "\n")
                eucarc.write('export EUCALYPTUS_CERT="${EUCA_KEY_DIR}/cloud-cert.pem"\n')
                for key, value in self.get_eucarc_attrs().iteritems():
                    if isinstance(value, basestring) and not re.search('^sftp://', value):
                        eucarc.write('export {0}="{1}"\n'.format(str(key).upper(), value))
                eucarc.flush()
            self.log.debug('Finished creating new local creds at: {0}'.format(local_destdir))
            self._local_files = filepaths
            if zip_file:
                for path in filepaths:
                    fname = os.path.basename(path)
                    zip_file.write(path, fname)
                zip_file.close()
                filepaths.append(zipfilename)
        return filepaths

    def _download_remote_artifacts(self, local_destdir, machine, zip_file=None,
                                   sftp_prefix='^sftp://', overwrite=False, maxlen=5000000):
        """
        Attempts to download any eucarc artifacts which current has an sftp:// url.
        To see these values use self.show() or self.get_eucarc_attrs() dict.
        :params local_destdir: Local directory to download the remote files to
        :params machine: remote machine object to download the files from
        :params sftp_prefeix: The search criteria for determining which eucarc artifacts
                              should be downloaded.
        :params overwrite: bool, if True will overwrite any existing items at 'local_destdir'
        returns list. The local paths (strings) items were downloaded to upon success
        """
        filepaths = []
        if not isinstance(machine, Machine):
            raise ValueError('_download_remote_artifacts requires Machine() type. Got:"{0}/{1}"'
                             .format(machine, type(machine)))
        if zip_file is not None:
            if not isinstance(zip_file, zipfile.ZipFile):
                raise TypeError('zip_file must be of type ZipFile, got:"{0}/{1}"'
                                .format(zipfile, type(zipfile)))
            if local_destdir is not None:
                raise ValueError('local_destdir must be None if providing a zipfile')
        elif not isinstance(local_destdir, basestring):
            raise ValueError('_download_remote_artifacts requires string for local_destdir(). '
                             'Got:"{0}/{1}"'.format(local_destdir, type(local_destdir)))
        if not os.path.exists(local_destdir):
            os.makedirs(local_destdir)
        else:
            if not os.path.isdir(local_destdir):
                raise ValueError('Provided local_destdir exists and is not a directory:"{0}"'
                                 .format(local_destdir))
            if not overwrite:
                raise ValueError('local_destdir exists. set "overwrite=True" to write over '
                                 'existing contents: {0}'.format(local_destdir))
        if local_destdir is not None:
            local_destdir = os.path.abspath(local_destdir)
        for key, path in self.get_eucarc_attrs().iteritems():
            if not key.startswith('_') and re.search(sftp_prefix, str(path)):
                urlp = urlparse(path)
                if not self.creds_machine.hostname == urlp.hostname:
                    raise ValueError('sftp uri hostname:{0} does not match current Machines:{1}'
                                     .format(urlp.hostname, machine.hostname))
                artifact_name = os.path.basename(urlp.path)
                if local_destdir is not None:
                    localpath = os.path.join(local_destdir, artifact_name)
                    machine.sftp.get(remotepath=urlp.path, localpath=localpath)
                    self.log.debug('Wrote: {0} to local:{1}'.format(key, localpath))
                elif zip_file:
                    with machine.sftp.open(urlp.path) as remote_file:
                        data = remote_file.read(maxlen)
                    zip_file.writestr(artifact_name, data)
                    self.log.debug('Wrote: {0} to zipfile object'.format(key))
                filepaths.append(localpath)
                setattr(self, key, localpath)
        return filepaths
Ejemplo n.º 3
0
class AutoCreds(Eucarc):
    def __init__(self,
                 auto_create=True,
                 aws_access_key=None,
                 aws_secret_key=None,
                 aws_account_name=None,
                 region_domain=None,
                 service_port=8773,
                 https=False,
                 aws_user_name=None,
                 machine=None,
                 hostname=None,
                 username='******',
                 password=None,
                 keypath=None,
                 credpath=None,
                 proxy_hostname=None,
                 proxy_username='******',
                 proxy_password=None,
                 proxy_keypath=None,
                 logger=None,
                 log_level='INFO',
                 eucarc_obj=None,
                 existing_certs=None,
                 service_connection=None,
                 keysdir=None,
                 string=None,
                 clc_connect_kwargs=None):
        super(AutoCreds, self).__init__(logger=logger, loglevel=log_level)
        self._local_files = None
        self._serviceconnection = service_connection
        self._clc_ip = hostname
        # Todo implement building creds from a passed string buffer
        self._string = string
        self._keysdir = keysdir
        self._machine = machine
        self._credpath = credpath
        self._account_name = aws_account_name
        self._user_name = aws_user_name
        self._has_existing_cert = existing_certs
        self.aws_secret_key = aws_secret_key
        self.aws_access_key = aws_access_key
        if region_domain is not None:
            if not re.search("\w", region_domain):
                region_domain = ""
        self._region_domain = region_domain
        self._service_port = service_port
        self._https = https
        self._has_updated_connect_args = False  # used to speed up auto find credentials
        if (username != 'root' or proxy_username != 'root' or password or keypath or
                proxy_hostname or proxy_keypath or proxy_password):
            self._has_updated_connect_args = True
        clc_connect_kwargs = clc_connect_kwargs or {}
        self._clc_connect_kwargs = {
            'hostname': hostname,
            'username': username,
            'password': password,
            'keypath': keypath,
            'proxy_hostname': proxy_hostname,
            'proxy_username': proxy_username,
            'proxy_password': proxy_password,
            'proxy_keypath': proxy_keypath
        }
        self._clc_connect_kwargs.update(clc_connect_kwargs)
        if eucarc_obj:
            self.__dict__.update(eucarc_obj.__dict__)
        if not (self.aws_access_key and self.aws_secret_key) and auto_create:
            self.auto_find_credentials()

    @property
    def is_https(self):
        return self._https

    @is_https.setter
    def is_https(self, value):
        assert isinstance(value, bool), 'is_https must be of type bool, got:{0}/{1}'\
            .format(value, type(value))
        if value != self.is_https:
            self._https = value
            self.update_attrs_from_cloud_services()


    @property
    def region_domain(self):
        if self._region_domain is None and self.serviceconnection:
            self.log.debug('Attempting to fetch service domain from property')
            domain_prop = self.serviceconnection.get_property('system.dns.dnsdomain')
            self.log.debug('Got service domain: {0}'.format(domain_prop.value))
            domain = domain_prop.value
            if domain:
                self._region_domain = domain
        return self._region_domain

    @region_domain.setter
    def region_domain(self, domain):
        if domain != self.region_domain:
            self._region_domain = domain
            self.update_attrs_from_cloud_services()

    @property
    def service_port(self):
        if not self._service_port and self.serviceconnection:
            port_prop = self.serviceconnection.get_property('bootstrap.webservices.port')
            port = port_prop.value
            if port:
                self._service_port = port
        return self._service_port

    @service_port.setter
    def service_port(self, port):
        if port != self.service_port:
            self._service_port = port
            self.update_attrs_from_cloud_services()

    @property
    def creds_machine(self):
        if not self._machine:
            self._machine = self.connect_to_creds_machine()
        return self._machine

    @creds_machine.setter
    def creds_machine(self, machine):
        self._machine = machine

    @property
    def serviceconnection(self):
        if not self._serviceconnection:
            self._serviceconnection = self._connect_services()
        return self._serviceconnection

    def _connect_services(self):
        if self.aws_secret_key and self.aws_access_key and self._clc_ip:
            self._serviceconnection = ServiceConnection(hostname=self._clc_ip,
                                                        aws_access_key=self.aws_access_key,
                                                        aws_secret_key=self.aws_secret_key)
        return self._serviceconnection

    def _close_adminpi(self):
        """
        If open, Attempts to close/cleanup the AutoCred's serviceconnection obj
        """
        if self._serviceconnection:
            try:
                self._serviceconnection.close()
                self._serviceconnection = None
            except:
                pass

    def update_attrs_from_cloud_services(self):
        """
        Attempts to update the current eucarc artifacts (service paths) from services
        gathered via the Eucalyptus admin api interface
        :returns dict mapping eucarc common key-values to the discovered service URIs.
        """
        if not self.serviceconnection and self.region_domain:
            raise RuntimeError('Can not fetch service paths from cloud without a "region_domain"'
                               'and/or a ServiceConnection\n A ServiceConnection requires: '
                               'clc_ip, or aws_access_key + aws_secret_key and bootstrap endpoint')
        if self.region_domain:
            path_dict = self._get_service_paths_from_domain(domain=self.region_domain,
                                                            port=self.service_port,
                                                            secure=self.is_https)
        elif self.region_domain is not None:
            path_dict = self._get_service_paths_from_service_host_urls(self.serviceconnection)
        else:
            path_dict = self._get_service_paths_from_serviceconnection(self.serviceconnection,
                                                                       logger=self.log)
        if not path_dict.get('ec2_access_key'):
            path_dict['ec2_access_key'] = self.aws_access_key
        if not path_dict.get('ec2_secret_key'):
            path_dict['ec2_secret_key'] = self.aws_secret_key
        for key, value in path_dict.iteritems():
            setattr(self, key, value)
        # self.__dict__.update(path_dict)
        #self._close_adminpi()
        return path_dict

    @classmethod
    def _get_service_paths_from_domain(cls, domain, port=8773, secure=False):
        ret_dict = {}
        for service_prefix, mapping in aws_to_euca_service_map.iteritems():
            # http://monitoring.h-12.autoqa.qa1.eucalyptus-systems.com:8773/
            if secure:
                url = "https://"
            else:
                url = "http://"
            if port:
                str_port = ":" + str(port)
            else:
                str_port = ""
            url = "{0}{1}.{2}{3}/".format(url, service_prefix, domain, str_port)
            ret_dict[mapping.eucarc] = url
        return ret_dict

    @classmethod
    def _get_service_paths_from_serviceconnection(cls, serviceconnection, try_domain=True,
                                                  secure=False, logger=None):
        """
        Reads the Eucalyptus services, maps them to common eucarc key values, and returns
        the dict of the mapping.
        :params serviceconnection: an ServiceConnection obj w/ active connection.
        :returns dict mapping eucarc common key-values to the discovered service URIs.
        """
        if not isinstance(serviceconnection, ServiceConnection):
            raise ValueError('Unknown type for service connection, got: "{0}/{1}"'
                             .format(serviceconnection, type(serviceconnection)))
        if try_domain:
            try:
                domain_prop = serviceconnection.get_property('system.dns.dnsdomain')
                domain = domain_prop.value
                port_prop = serviceconnection.get_property('bootstrap.webservices.port')
                port = port_prop.value
                return cls._get_service_paths_from_domain(domain=domain, port=port, secure=secure)
            except Exception as DE:
                if logger:
                    logger.warn('Could not fetch domain and port info from service '
                                'connection, err: "{0}"'.format(DE))
        return cls._get_service_paths_from_service_host_urls(serviceconnection)

    @classmethod
    def _get_service_paths_from_service_host_urls(cls, serviceconnection):
        if not isinstance(serviceconnection, ServiceConnection):
            raise ValueError('Unknown type for service connection, got: "{0}/{1}"'
                             .format(serviceconnection, type(serviceconnection)))
        services = serviceconnection.get_services()
        ret_dict = {}
        for service in services:
            for service_name, mapping in aws_to_euca_service_map.iteritems():
                if service.type == mapping.euca_service:
                    ret_dict[mapping.eucarc] = str(service.uri)
        return ret_dict




    def get_local_eucarc(self, credpath):
        """
        Reads in eucarc contents from a local file path. Checks to make sure the credpath
        given is an existing file, if a dir was provide it will check for a file name 'eucarc'
        in that dir.
        :params credpath: string representing the path to the eucarc file
        :return dict of eucarc read in
        """
        if not str(credpath).endswith('eucarc') and os.path.isdir(credpath):
            credpath = os.path.join(credpath, 'eucarc')
        if os.path.isfile(credpath):
            return self._from_filepath(credpath)
        return None

    def get_remote_eucarc(self, credpath, machine=None):
        """
        Reads in eucarc contents from a remote file path on the provided Machine().
        Checks to make sure the credpath given is an existing file, if a dir was provide it will
        check for a file name 'eucarc' in that dir.
        :params credpath: string representing the path to the eucarc file
        :params machine: Machine() obj
        :returns dict of eucarc read in
        """
        machine = machine or self.creds_machine
        if not str(credpath).endswith('eucarc') and machine.is_dir(credpath):
            credpath = os.path.join(credpath, 'eucarc')
        if machine.is_file(credpath):
            return self._from_filepath(filepath=credpath, sshconnection=machine._ssh)
        return None

    def connect_to_creds_machine(self, connect_kwargs=None):
        """
        Attempts to create a Machine by connecting to the remote host, usually the CLC.
        :params connect_kwargs: Dictionary set of arguments to be provided to Machine().__init__()
        returns machine obj upon success
        """
        connect_kwargs = connect_kwargs or self._clc_connect_kwargs
        machine = Machine(**connect_kwargs)
        self.creds_machine = machine
        return machine

    def assume_role_on_remote_clc(self, machine=None):
        machine = machine or self.creds_machine
        cred_string = []
        out = machine.sys('clcadmin-assume-system-credentials', code=0)
        for line in out:
            if line:
                line = line.strip(';')
            line = str(line).replace('127.0.0.1', machine.hostname)
            cred_string.append(line)
        return self._from_string(string=cred_string)


    def auto_find_credentials(self, from_file=True, assume_admin=True, service_connection=True,
                              from_remote=True, from_db=True):
        """
        Convenience method which attempts to automatically produce credentials based upon the
        information provided to this AutoCreds obj.
        - If any ssh connect arguments (outside of hostname) are provided then only the remote
        machine tried for existing creds at 'self._credpath'.
        - If credpath was provided the local file system will first be tried for existing
        credentials
        - If aws access and secret keys were provided allong with hostname, will attempt to
        derivce service credpaths from the Eucalyptus Admin api.
        -Finally if a hostname was provided an ssh attempt will be made (with any other
         connection kwargs provided)to fetch from the remote system as well.
         If password or keypath was not provided, this assumes keys have been sync'd between the
         localhost and the remote machine.
        Upon the first successful discovery of credentials, the local obj is populated with
        eucarc attributes and returns.
        """

        def try_from_file(self):
            self.log.debug('Trying creds from local file...')
            if self._credpath:
                try:
                    res = self.get_local_eucarc(credpath=self._credpath)
                    if res:
                        self.log.debug('Found local creds at: "{0}"'.format(self._credpath))
                        return res
                except IOError:
                    pass

        def try_serviceconnection(self):
            self.log.debug('Trying creds from service connection...')
            if self.aws_secret_key and self.aws_access_key and self._clc_ip:
                self._connect_services()
                try:
                    res = self.update_attrs_from_cloud_services()
                    if res:
                        self.log.debug('Derived creds from serviceconnection')
                        return res
                except RuntimeError as RE:
                    self.log.debug('{0}\nFailed to update creds using serviceconnection, err:"{1}"'
                               .format(get_traceback(), str(RE)))
                    self._close_adminpi()

        def try_assume_admin_on_clc(self):
            self.log.debug('Trying creds from assume admin role on clc...')
            if not self.aws_secret_key and not self.aws_access_key:
                try:
                    self.assume_role_on_remote_clc()
                    res = try_serviceconnection(self)
                    try:
                        # With keys, try filling in remainder with service urls/attributes
                        # using the admin api interface...
                        res = self.update_attrs_from_cloud_services()
                    except:
                        pass
                    return res
                except Exception as AE:
                    self.log.debug('{0}\nFailed to update creds using '
                               '"clcadmin-assume-system-credentials", err:"{1}"'
                               .format(get_traceback(), str(AE)))

        def try_remote(self):
            self.log.debug('Trying creds from remote machine at credpath...')
            if self.creds_machine and self._credpath:
                try:
                    machine = self.creds_machine or self.connect_to_creds_machine()
                    if machine:
                        try:
                            if not self._keysdir:
                                self._keysdir = machine.get_abs_path(self._credpath)
                        except:
                            pass
                        res = self.get_remote_eucarc(credpath=self._credpath, machine=machine)
                        if res:
                            self.log.debug('Found remote creds on:"{0}", at path:"{1}"'
                                       .format(self.creds_machine.ssh.host, self._credpath))
                            return res
                except Exception as e:
                    self.log.debug("{0}\nFailed to fetch creds remotely, err:'{1}'"
                               .format(get_traceback(), str(e)))

        def try_clc_db(self):
            self.log.debug('Trying creds from CLC DB...')
            if self.creds_machine and self.aws_account_name and self.aws_user_name:
                machine = self.creds_machine or self.connect_to_creds_machine()
                if machine:
                    try:
                        res = self.get_existing_keys_from_clc(account=self.aws_account_name,
                                                              user=self.aws_user_name,
                                                              machine=machine)
                        try:
                            # With keys, try filling in remainder with service urls/attributes
                            # using the admin api interface...
                            res = self.update_attrs_from_cloud_services()
                        except:
                            pass
                        return res
                    except RuntimeError as RE:
                        self.log.debug('{0}\nFailed to fetch creds from clc db, err:{1}'
                                   .format(get_traceback(), str(RE)))

        default_order = []
        if from_file:
            default_order.append(try_from_file)
        if service_connection:
            default_order.append(try_serviceconnection)
        if assume_admin:
            default_order.append(try_assume_admin_on_clc)
        if from_remote:
            default_order.append(try_remote)
        if from_db:
            default_order.append(try_clc_db)
        if self._clc_ip and self._credpath and \
                (self._has_updated_connect_args or self._sshconnection):
            # if any ssh related arguments were provided, assume the user would like
            # to try remote first
            if try_remote(self):
                return
            default_order.remove(try_remote)
            raise ValueError('Could not find "remote" creds with provided information.')
        else:
            for meth in default_order:
                if meth(self):
                    return
            raise ValueError("Could not find path with provided information.")


    def get_existing_keys_from_clc(self, account, user, machine=None, eucahome=None, port=8777,
                                   dbuser='******', p12file=None, pkfile=None,
                                   passphrase=None, db=None, pargs=None, do_cert=None,
                                   verbose=False):
        ret = {}
        db = db or 'eucalyptus_shared'
        pargs = pargs or ""
        machine = machine or self.creds_machine
        passphrase = None or 'eucalyptus'
        if do_cert is None:
            do_cert = self._has_existing_cert
        if hasattr(machine, 'eucalyptus_home'):
            eucahome = machine.eucalyptus_home
        else:
            eucahome = eucahome or EucaHost._get_eucalyptus_home(machine) or '/'
        EucaP12File = p12file or os.path.join(eucahome, '/var/lib/eucalyptus/keys/euca.p12')
        CloudPKFile = pkfile or os.path.join(eucahome, '/var/lib/eucalyptus/keys/cloud-pk.pem')
        cmd = ("echo -n '{0}' | openssl SHA256  -sign {1} | sha256sum"
               .format(passphrase, CloudPKFile))
        out = machine.sys(cmd, code=0, verbose=verbose)
        if out:
            dbpass = str(out[0]).split()[0]

        if do_cert:
            dbsel = ("\"select k.auth_access_key_query_id, k.auth_access_key_key, "
                     "a.auth_account_number, a.auth_account_name, c.auth_certificate_pem "
                     "from eucalyptus_auth.auth_access_key k "
                     "join eucalyptus_auth.auth_user u on k.auth_access_key_owning_user=u.id "
                     "join eucalyptus_auth.auth_cert c on c.auth_certificate_owning_user=u.id "
                     "join eucalyptus_auth.auth_group_has_users gu on gu.auth_user_id = u.id "
                     "join eucalyptus_auth.auth_group g on gu.auth_group_id=g.id "
                     "join eucalyptus_auth.auth_account a on g.auth_group_owning_account=a.id "
                     "where a.auth_account_name = '{0}' and g.auth_group_name = '{1}'\";"
                     .format(account, "_" + user))
        else:
            dbsel = ("\"select k.auth_access_key_query_id, k.auth_access_key_key, "
                     "a.auth_account_number, a.auth_account_name "
                     "from eucalyptus_auth.auth_access_key k "
                     "join eucalyptus_auth.auth_user u on k.auth_access_key_owning_user=u.id "
                     "join eucalyptus_auth.auth_group_has_users gu on gu.auth_user_id = u.id "
                     "join eucalyptus_auth.auth_group g on gu.auth_group_id=g.id "
                     "join eucalyptus_auth.auth_account a on g.auth_group_owning_account=a.id "
                     "where a.auth_account_name = '{0}' and g.auth_group_name = '{1}'\";"
                     .format(account, "_" + user))
        dbcmd = ('export PGPASSWORD={0}; psql {1} -A -F "," -h 127.0.0.1 -p {2} -U {3} -d {4} '
                 '-c {5}'.format(dbpass, pargs, port, dbuser, db, dbsel))
        qout = machine.sys(dbcmd, code=0, verbose=verbose)
        if verbose:
                self.log.info('Result of cmd:"{0}"\n"{1}"'.format(dbcmd, qout))
        if qout:
            try:
                names = qout[0].split(',')
                values = qout[1].split(',')
                ret['AWS_ACCESS_KEY'] = values[names.index('auth_access_key_query_id')]
                ret['AWS_SECRET_KEY'] = values[names.index('auth_access_key_key')]
                ret['EC2_ACCOUNT_NUMBER'] = values[names.index('auth_account_number')]
                ret['EC2_ACCOUNT_NAME'] = values[names.index('auth_account_name')]
                if do_cert:
                    ret['CERT'] = values[names.index('auth_certificate_pem')]
                self.aws_access_key = ret['AWS_ACCESS_KEY']
                self.aws_secret_key = ret['AWS_SECRET_KEY']
                self.ec2_user_id = ret['EC2_ACCOUNT_NUMBER']
                self.ec2_account_number = ret['EC2_ACCOUNT_NUMBER']
                self.ec2_account_name = ret['EC2_ACCOUNT_NAME']
            except Exception as PE:
                self.log.error('{0}\nOutput:\nFailed fetching existing creds for account:"{1}", '
                               'user:"******".\nRemote Command Output:"{3}"'
                               .format(get_traceback(), account, user, "\n".join(qout), str(PE)))
                raise PE
        return ret

    def create_local_creds(self, local_destdir, machine=None, keydir=None, overwrite=False,
                           zipfilename=None, ziponly=False):
        """
        Attempts to create a local set of files containing the current credential artifacts
        in this AutoCreds obj. The following files will be written to the provided
        'local_destdir' directory:
        - A eucarc file containing the "export key=value" syntax to resolve service urls
          and the location of any credentials related files.
        - Any current attributes with an sftp:// uri will be downloaded to local_destdir. At this
        time the AutoCred eucarc attributes will be updated to represent their now local filepath,
        an the local eucarc written will also reflect the new location.

        :param local_destdir: local directory to write cred files to.
                               Will create if does not exist.
        :param machine: The Machine() obj to download any sftp:// files from
        :param keydir: optional String representing path to key dir, otherwise auto-populated
        :param overwrite: bool, if True will overwrite any existing items at 'local_destdir'
        :param zipfilename: string representing the zip archive filename to be creat in the
                            'local_destdir' directory. If "None" a zip archive will not be created.
        :param ziponly: boolean, if true only a zip archive will be created
        :return: list of filepaths
        """
        def make_local_dir(dirpath):
            if dirpath:
                try:
                    os.makedirs(dirpath)
                except OSError as exc:
                    if exc.errno == errno.EEXIST and os.path.isdir(dirpath):
                        pass
                    else:
                        raise


        filepaths = []
        local_destdir = os.path.abspath(local_destdir or "")
        if zipfilename:
            make_local_dir(local_destdir)
            zip_path = os.path.join(local_destdir, zipfilename)
            if os.path.exists(zip_path):
                raise ValueError('Will not overwrite existing credentials archive at:"{0}"'
                                 .format(zip_path))
            zip_file = zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED, False)
        else:
            zip_file = None
        machine = machine or self.creds_machine
        if keydir is None:
            keydir = "EUCA_KEY_DIR=$(cd $(dirname ${BASH_SOURCE:-$0}); pwd -P)"
        has_sftp_items = False

        for key, value in self.get_eucarc_attrs().iteritems():
            if isinstance(value, basestring) and re.search('^sftp://', value):
                has_sftp_items = True
        if has_sftp_items:
            if not machine:
                if not self._has_updated_connect_args:
                    self.log.info('Remote machine info has not been provided, '
                                  'skipping remote creds download')
                else:
                    machine = self.connect_to_creds_machine()
            if zip_file and ziponly:
                filepaths = self._download_remote_artifacts(local_destdir=None,
                                                            machine=machine,
                                                            overwrite=overwrite,
                                                            zip_file=zip_file)
            else:
                filepaths = self._download_remote_artifacts(local_destdir=local_destdir,
                                                            machine=machine,
                                                            overwrite=overwrite)
        if zip_file and ziponly:
            if self.serviceconnection is not None:
                certs = self.serviceconnection.get_service_certs()
                if not certs:
                    raise ValueError('No service certs found in DescribeServiceCerts response')
                cert = certs[0]
                certbody = cert.certificate
                if not certbody:
                    raise ValueError('Certbody not found in retrieved cert')
                zip_file.writestr('cloud-cert.pem', certbody)
            eucarc = ""

            eucarc += keydir + "\n"
            eucarc += 'export EUCALYPTUS_CERT="${EUCA_KEY_DIR}/cloud-cert.pem"\n'
            for key, value in self.get_eucarc_attrs().iteritems():
                if isinstance(value, basestring) and not re.search('^sftp://', value):
                    eucarc += 'export {0}="{1}"\n'.format(str(key).upper(), value)
            zip_file.writestr('eucarc', eucarc)
            zip_file.close()
            filepaths.append(zipfilename)
        else:
            # Now write the eucarc file. Any downloaded files should have updated the
            # local euarc attrs replacing the sftp uri with a local file path
            make_local_dir(local_destdir)
            cloud_cert_path = os.path.join(local_destdir, 'cloud-cert.pem')
            if os.path.exists(cloud_cert_path):
                filepaths.append(cloud_cert_path)
            elif self.serviceconnection is not None:
                self.serviceconnection.write_service_cert_to_file(cloud_cert_path)
                filepaths.append(cloud_cert_path)
            eucarc_path = os.path.join(local_destdir, 'eucarc')
            filepaths.append(eucarc_path)
            with open(eucarc_path, 'w') as eucarc:
                eucarc.seek(0)
                eucarc.write(keydir + "\n")
                eucarc.write('export EUCALYPTUS_CERT="${EUCA_KEY_DIR}/cloud-cert.pem"\n')
                for key, value in self.get_eucarc_attrs().iteritems():
                    if isinstance(value, basestring) and not re.search('^sftp://', value):
                        eucarc.write('export {0}="{1}"\n'.format(str(key).upper(), value))
                eucarc.flush()
            self.log.debug('Finished creating new local creds at: {0}'.format(local_destdir))
            self._local_files = filepaths
            if zip_file:
                for path in filepaths:
                    fname = os.path.basename(path)
                    zip_file.write(path, fname)
                zip_file.close()
                filepaths.append(zipfilename)
        return filepaths

    def _download_remote_artifacts(self, local_destdir, machine, zip_file=None,
                                   sftp_prefix='^sftp://', overwrite=False, maxlen=5000000):
        """
        Attempts to download any eucarc artifacts which current has an sftp:// url.
        To see these values use self.show() or self.get_eucarc_attrs() dict.
        :params local_destdir: Local directory to download the remote files to
        :params machine: remote machine object to download the files from
        :params sftp_prefeix: The search criteria for determining which eucarc artifacts
                              should be downloaded.
        :params overwrite: bool, if True will overwrite any existing items at 'local_destdir'
        returns list. The local paths (strings) items were downloaded to upon success
        """
        filepaths = []
        if not isinstance(machine, Machine):
            raise ValueError('_download_remote_artifacts requires Machine() type. Got:"{0}/{1}"'
                             .format(machine, type(machine)))
        if zip_file is not None:
            if not isinstance(zip_file, zipfile.ZipFile):
                raise TypeError('zip_file must be of type ZipFile, got:"{0}/{1}"'
                                .format(zipfile, type(zipfile)))
            if local_destdir is not None:
                raise ValueError('local_destdir must be None if providing a zipfile')
        elif not isinstance(local_destdir, basestring):
            raise ValueError('_download_remote_artifacts requires string for local_destdir(). '
                             'Got:"{0}/{1}"'.format(local_destdir, type(local_destdir)))
        if not os.path.exists(local_destdir):
            os.makedirs(local_destdir)
        else:
            if not os.path.isdir(local_destdir):
                raise ValueError('Provided local_destdir exists and is not a directory:"{0}"'
                                 .format(local_destdir))
            if not overwrite:
                raise ValueError('local_destdir exists. set "overwrite=True" to write over '
                                 'existing contents: {0}'.format(local_destdir))
        if local_destdir is not None:
            local_destdir = os.path.abspath(local_destdir)
        for key, path in self.get_eucarc_attrs().iteritems():
            if not key.startswith('_') and re.search(sftp_prefix, str(path)):
                urlp = urlparse(path)
                if not self.creds_machine.hostname == urlp.hostname:
                    raise ValueError('sftp uri hostname:{0} does not match current Machines:{1}'
                                     .format(urlp.hostname, machine.hostname))
                artifact_name = os.path.basename(urlp.path)
                if local_destdir is not None:
                    localpath = os.path.join(local_destdir, artifact_name)
                    machine.sftp.get(remotepath=urlp.path, localpath=localpath)
                    self.log.debug('Wrote: {0} to local:{1}'.format(key, localpath))
                elif zip_file:
                    with machine.sftp.open(urlp.path) as remote_file:
                        data = remote_file.read(maxlen)
                    zip_file.writestr(artifact_name, data)
                    self.log.debug('Wrote: {0} to zipfile object'.format(key))
                filepaths.append(localpath)
                setattr(self, key, localpath)
        return filepaths
Ejemplo n.º 4
0
 def _connect_services(self):
     if self.aws_secret_key and self.aws_access_key and self._clc_ip:
         self._serviceconnection = ServiceConnection(hostname=self._clc_ip,
                                                     aws_access_key=self.aws_access_key,
                                                     aws_secret_key=self.aws_secret_key)
     return self._serviceconnection
Ejemplo n.º 5
0
class AutoCreds(Eucarc):
    def __init__(self,
                 auto_create=True,
                 aws_access_key=None,
                 aws_secret_key=None,
                 aws_account_name=None,
                 aws_user_name=None,
                 hostname=None,
                 username='******',
                 password=None,
                 keypath=None,
                 credpath=None,
                 proxy_hostname=None,
                 proxy_username='******',
                 proxy_password=None,
                 proxy_keypath=None,
                 logger=None,
                 eucarc_obj=None):
        super(AutoCreds, self).__init__(logger=logger)
        self._serviceconnection = None
        self._clc_ip = hostname
        self._clc_machine = None
        self._credpath = credpath
        self._account_name = aws_account_name
        self._user_name = aws_user_name
        self.aws_secret_key = aws_secret_key
        self.aws_access_key = aws_access_key
        self.debug = self.log.debug
        self._has_updated_connect_args = False  # used to speed up auto find credentials
        if (username != 'root' or proxy_username != 'root' or password or keypath or
                proxy_hostname or proxy_keypath or proxy_password):
            self._has_updated_connect_args = True
        self._clc_connect_kwargs = {
            'hostname': hostname,
            'username': username,
            'password': password,
            'keypath': keypath,
            'proxy_hostname': proxy_hostname,
            'proxy_username': proxy_username,
            'proxy_password': proxy_password,
            'proxy_keypath': proxy_keypath
        }
        if eucarc_obj:
            self.__dict__.update(eucarc_obj.__dict__)
        if not eucarc_obj and auto_create:
            self.auto_find_credentials()

    @property
    def clc_machine(self):
        if not self._clc_machine:
            self._clc_machine = self.connect_to_clc()
        return self._clc_machine

    @clc_machine.setter
    def clc_machine(self, newclc):
        self._clc_machine = newclc

    @property
    def serviceconnection(self):
        if not self._serviceconnection:
            self._serviceconnection = self._connect_services()
        return self._serviceconnection

    def _connect_services(self):
        if self.aws_secret_key and self.aws_access_key and self._clc_ip:
            self._serviceconnection = ServiceConnection(hostname=self._clc_ip,
                                                        aws_access_key=self.aws_access_key,
                                                        aws_secret_key=self.aws_secret_key)
        return self._serviceconnection

    def _close_adminpi(self):
        """
        If open, Attempts to close/cleanup the AutoCred's serviceconnection obj
        """
        if self._serviceconnection:
            try:
                self._serviceconnection.close()
                self._serviceconnection = None
            except:
                pass

    def update_attrs_from_cloud_services(self):
        """
        Attempts to update the current eucarc artifacts (service paths) from services
        gathered via the Eucalyptus admin api interface
        :returns dict mapping eucarc common key-values to the discovered service URIs.
        """
        if not self.serviceconnection:
            raise RuntimeError('Can not fetch service paths from cloud without an '
                               'ServiceConnection\n This requires: clc_ip, aws_access_key, '
                               'aws_secret_key')
        path_dict = self._get_service_paths_from_serviceconnection(self.serviceconnection)
        if not path_dict.get('ec2_access_key'):
            path_dict['ec2_access_key'] = self.aws_access_key
        if not path_dict.get('ec2_secret_key'):
            path_dict['ec2_secret_key'] = self.aws_secret_key
        self.__dict__.update(path_dict)
        self._close_adminpi()
        return path_dict

    @classmethod
    def _get_service_paths_from_serviceconnection(cls, serviceconnection):
        """
        Reads the Eucalyptus services, maps them to common eucarc key values, and returns
        the dict of the mapping.
        :params serviceconnection: an ServiceConnection obj w/ active connection.
        :returns dict mapping eucarc common key-values to the discovered service URIs.
        """
        assert isinstance(serviceconnection, ServiceConnection)
        services = serviceconnection.get_services()
        ret_dict = {}
        for service in services:
            for key, serv_value in eucarc_to_service_map.iteritems():
                if service.type == serv_value:
                    ret_dict[key] = str(service.uri)
        return ret_dict

    def get_local_eucarc(self, credpath):
        """
        Reads in eucarc contents from a local file path. Checks to make sure the credpath
        given is an existing file, if a dir was provide it will check for a file name 'eucarc'
        in that dir.
        :params credpath: string representing the path to the eucarc file
        :return dict of eucarc read in
        """
        if not str(credpath).endswith('eucarc') and os.path.isdir(credpath):
            credpath = os.path.join(credpath, 'eucarc')
        if os.path.isfile(credpath):
            return self._from_filepath(credpath)
        return None

    def get_remote_eucarc(self, credpath, machine=None):
        """
        Reads in eucarc contents from a remote file path on the provided Machine().
        Checks to make sure the credpath given is an existing file, if a dir was provide it will
        check for a file name 'eucarc' in that dir.
        :params credpath: string representing the path to the eucarc file
        :params machine: Machine() obj
        :returns dict of eucarc read in
        """
        machine = machine or self.clc_machine
        if not str(credpath).endswith('eucarc') and machine.is_dir(credpath):
            credpath = os.path.join(credpath, 'eucarc')
        if machine.is_file(credpath):
            return self._from_filepath(filepath=credpath, sshconnection=machine._ssh)
        return None

    def connect_to_clc(self, connect_kwargs=None):
        """
        Attempts to create a Machine by connecting to the remote host, usually the CLC.
        :params connect_kwargs: Dictionary set of arguments to be provided to Machine().__init__()
        returns machine obj upon success
        """
        connect_kwargs = connect_kwargs or self._clc_connect_kwargs
        machine = Machine(**connect_kwargs)
        self.clc_machine = machine
        return machine

    def assume_role_on_remote_clc(self, machine=None):
        machine = machine or self.clc_machine
        cred_string = []
        out = machine.sys('clcadmin-assume-system-credentials', code=0)
        for line in out:
            if line:
                line = line.strip(';')
            line = str(line).replace('127.0.0.1', machine.hostname)
            cred_string.append(line)
        return self._from_string(string=cred_string)


    def auto_find_credentials(self):
        """
        Convenience method which attempts to automatically produce credentials based upon the
        information provided to this AutoCreds obj.
        - If any ssh connect arguments (outside of hostname) are provided then only the remote
        machine tried for existing creds at 'self._credpath'.
        - If credpath was provided the local file system will first be tried for existing
        credentials
        - If aws access and secret keys were provided allong with hostname, will attempt to
        derivce service credpaths from the Eucalyptus Admin api.
        -Finally if a hostname was provided an ssh attempt will be made (with any other
         connection kwargs provided)to fetch from the remote system as well.
         If password or keypath was not provided, this assumes keys have been sync'd between the
         localhost and the remote machine.
        Upon the first successful discovery of credentials, the local obj is populated with
        eucarc attributes and returns.
        """

        def try_local(self):
            if self._credpath:
                try:
                    res = self.get_local_eucarc(credpath=self._credpath)
                    if res:
                        self.debug('Found local creds at: "{0}"'.format(self._credpath))
                        return res
                except IOError:
                    pass

        def try_serviceconnection(self):
            if self.aws_secret_key and self.aws_access_key and self._clc_ip:
                self._connect_services()
                try:
                    res = self.update_attrs_from_cloud_services()
                    if res:
                        self.debug('Derived creds from serviceconnection')
                        return res
                except RuntimeError as RE:
                    self.debug('{0}\nFailed to update creds using serviceconnection, err:"{1}"'
                               .format(get_traceback(), str(RE)))
                    self._close_adminpi()

        def try_assume_admin_on_clc(self):
            if not self.aws_secret_key and not self.aws_access_key:
                try:
                    self.assume_role_on_remote_clc()
                    res = try_serviceconnection(self)
                    return res
                except Exception as AE:
                    self.debug('{0}\nFailed to update creds using '
                               '"clcadmin-assume-system-credentials", err:"{1}"'
                               .format(get_traceback(), str(AE)))

        def try_remote(self):
            if self._clc_ip and self._credpath:
                try:
                    machine = self.clc_machine or self.connect_to_clc()
                    if machine:
                        try:
                            if not self._keysdir:
                                self._keysdir = machine.get_abs_path(self._credpath)
                        except:
                            pass
                        res = self.get_remote_eucarc(credpath=self._credpath, machine=machine)
                        if res:
                            self.debug('Found remote creds on:"{0}", at path:"{1}"'
                                       .format(self.clc_machine.ssh.host, self._credpath))
                            return res
                except Exception as e:
                    self.debug("{0}\nFailed to fetch creds remotely, err:'{1}'"
                               .format(get_traceback(), str(e)))

        def try_clc_db(self):
            self.debug('trying clc db...')
            if self._clc_ip and self.aws_account_name and self.aws_user_name:
                machine = self.clc_machine or self.connect_to_clc()
                if machine:
                    try:
                        res = self.get_existing_keys_from_clc(account=self.aws_account_name,
                                                              user=self.aws_user_name,
                                                              machine=machine)
                        try:
                            # With keys, try filling in remainder with service urls/attributes
                            # using the admin api interface...
                            res = self.update_attrs_from_cloud_services()
                        except:
                            pass
                        return res
                    except RuntimeError as RE:
                        self.debug('{0}\nFailed to fetch creds from clc db, err:{1}'
                                   .format(get_traceback(), str(RE)))

        default_order = [try_local, try_serviceconnection, try_assume_admin_on_clc,
                         try_remote, try_clc_db]
        if self._clc_ip and self._credpath and self._has_updated_connect_args:
            # if any ssh related arguements were provided, assume the user would like
            # to try remote first
            if try_remote(self):
                return
            default_order.remove(try_remote)
            raise ValueError('Could not find "remote" creds with provided information.')
        else:
            for meth in default_order:
                if meth(self):
                    return
            raise ValueError("Could not find path with provided information.")

    def get_existing_keys_from_clc(self, account, user, machine=None, eucahome=None, port=8777,
                                   dbuser='******', p12file=None, pkfile=None,
                                   passphrase=None, db=None, pargs=None, verbose=False):
        ret = {}
        db = db or 'eucalyptus_shared'
        pargs = pargs or ""
        machine = machine or self.clc_machine
        passphrase = None or 'eucalyptus'
        eucahome = eucahome or EucaHost._get_eucalyptus_home(machine) or '/'
        EucaP12File = p12file or os.path.join(eucahome, '/var/lib/eucalyptus/keys/euca.p12')
        CloudPKFile = pkfile or os.path.join(eucahome, '/var/lib/eucalyptus/keys/cloud-pk.pem')
        cmd = ("echo -n '{0}' | openssl SHA256  -sign {1} | sha256sum"
               .format(passphrase, CloudPKFile))
        out = machine.sys(cmd, code=0, verbose=verbose)
        if out:
            dbpass = str(out[0]).split()[0]

        dbsel = ("\"select k.auth_access_key_query_id, k.auth_access_key_key, "
                 "a.auth_account_number, a.auth_account_name, c.auth_certificate_pem "
                 "from eucalyptus_auth.auth_access_key k "
                 "join eucalyptus_auth.auth_user u on k.auth_access_key_owning_user=u.id "
                 "join eucalyptus_auth.auth_cert c on c.auth_certificate_owning_user=u.id "
                 "join eucalyptus_auth.auth_group_has_users gu on gu.auth_user_id = u.id "
                 "join eucalyptus_auth.auth_group g on gu.auth_group_id=g.id "
                 "join eucalyptus_auth.auth_account a on g.auth_group_owning_account=a.id "
                 "where a.auth_account_name = '{0}' and g.auth_group_name = '{1}'\";"
                 .format(account, "_" + user))
        dbcmd = ('export PGPASSWORD={0}; psql {1} -A -F "," -h 127.0.0.1 -p {2} -U {3} -d {4} '
                 '-c {5}'.format(dbpass, pargs, port, dbuser, db, dbsel))
        qout = machine.sys(dbcmd, code=0, verbose=verbose)
        if qout:
            try:
                names = qout[0].split(',')
                values = qout[1].split(',')
                ret['AWS_ACCESS_KEY'] = values[names.index('auth_access_key_query_id')]
                ret['AWS_SECRET_KEY'] = values[names.index('auth_access_key_key')]
                ret['EC2_ACCOUNT_NUMBER'] = values[names.index('auth_account_number')]
                ret['EC2_ACCOUNT_NAME'] = values[names.index('auth_account_name')]
                ret['CERT'] = values[names.index('auth_certificate_pem')]
                self.aws_access_key = ret['AWS_ACCESS_KEY']
                self.aws_secret_key = ret['AWS_SECRET_KEY']
                self.ec2_user_id = ret['EC2_ACCOUNT_NUMBER']
                self.ec2_account_number = ret['EC2_ACCOUNT_NUMBER']
                self.ec2_account_name = ret['EC2_ACCOUNT_NAME']
            except Exception as PE:
                self.log.error('Output:\n{0}\nFailed parsing creds for account:"{0}", '
                               'user:"******".\nLookup commands output:{2}'
                               .format(account, user, "\n".join(qout), str(PE)))
                raise PE
        return ret

    def create_local_creds(self, local_destdir, machine=None, overwrite=False):
        """
        Attempts to create a local set of files containing the current credential artifacts
        in this AutoCreds obj. The following files will be written to the provided
        'local_destdir' directory:
        - A eucarc file containing the "export key=value" syntax to resolve service urls
          and the location of any credentials related files.
        - Any current attributes with an sftp:// uri will be downloaded to local_destdir. At this
        time the AutoCred eucarc attributes will be updated to represent their now local filepath,
        an the local eucarc written will also reflect the new location.
        :params local_destdir: local directory to write cred files to.
                               Will create if does not exist.
        :params machine: The Machine() obj to download any sftp:// files from
        :params overwrite: bool, if True will overwrite any existing items at 'local_destdir'
        """
        machine = machine or self.clc_machine
        has_sftp_items = False
        local_destdir = os.path.abspath(local_destdir)
        for key, value in self.get_eucarc_attrs().iteritems():
            if re.search('^sftp://', value):
                has_sftp_items = True
        if has_sftp_items:
            if not machine:
                if not self._has_updated_connect_args:
                    self.log.info('Remote machine info has not been provided, '
                                  'skipping remote creds download')
                else:
                    machine = self.connect_to_clc()
            self._download_remote_artifacts(local_destdir=local_destdir, machine=machine,
                                            overwrite=overwrite)
        self.debug('Finished creating new local creds at: {0}'.format(local_destdir))
        # Now write the eucarc file. Any downloaded files should have updated the
        # local euarc attrs replacing the sftp uri with a local file path
        eucarc_path = os.path.join(local_destdir, 'eucarc')
        with open(eucarc_path, 'w') as eucarc:
            eucarc.seek(0)
            for key, value in self.get_eucarc_attrs().iteritems():
                if not re.search('^sftp://', value):
                    eucarc.write("export {0}={1}\n".format(str(key).upper(), value))
            eucarc.flush()
        self.debug('Finished creating new local creds at: {0}'.format(local_destdir))

    def _download_remote_artifacts(self, local_destdir, machine, sftp_prefix='^sftp://',
                                   overwrite=False):
        """
        Attempts to download any eucarc artifacts which current has an sftp:// url.
        To see these values use self.show() or self.get_eucarc_attrs() dict.
        :params local_destdir: Local directory to download the remote files to
        :params machine: remote machine object to download the files from
        :params sftp_prefeix: The search criteria for determining which eucarc artifacts
                              should be downloaded.
        :params overwrite: bool, if True will overwrite any existing items at 'local_destdir'
        returns the local path (string) items were downloaded to upon success
        """
        if not isinstance(machine, Machine):
            raise ValueError('_download_remote_artifacts requires Machine() type. Got:"{0}/{1}"'
                             .format(machine, type(machine)))
        if not isinstance(local_destdir, basestring):
            raise ValueError('_download_remote_artifacts requires string for local_destdir(). '
                             'Got:"{0}/{1}"'.format(local_destdir, type(local_destdir)))
        if not os.path.exists(local_destdir):
            os.makedirs(local_destdir)
        else:
            if not os.path.isdir(local_destdir):
                raise ValueError('Provided local_destdir exists and is not a directory:"{0}"'
                                 .format(local_destdir))
            if not overwrite:
                raise ValueError('local_destdir exists. set "overwrite=True" to write over '
                                 'existing contents: {0}'.format(local_destdir))
        local_destdir = os.path.abspath(local_destdir)
        for key, path in self.get_eucarc_attrs().iteritems():
            if not key.startswith('_') and re.search(sftp_prefix, str(path)):
                urlp = urlparse(path)
                if not self.clc_machine.hostname == urlp.hostname:
                    raise ValueError('sftp uri hostname:{0} does not match current Machines:{1}'
                                     .format(urlp.hostname, machine.hostname))
                artifact_name = os.path.basename(urlp.path)
                localpath = os.path.join(local_destdir, artifact_name)
                machine.sftp.get(remotepath=urlp.path, localpath=localpath)
                setattr(self, key, localpath)
                self.debug('Wrote: {0} to local:{1}'.format(key, localpath))
        return local_destdir

    # Todo Clean up the legacy methods below...

    def _legacy_create_credentials(self, clc, admin_cred_dir, account, user, zipfile='creds.zip'):
        zipfilepath = os.path.join(admin_cred_dir, zipfile)
        output = self.credential_exist_on_remote_machine(zipfilepath)
        if output['status'] == 0:
            self.debug("Found creds file, skipping euca_conf --get-credentials.")
        else:
            cmd_download_creds = str("{0}/usr/sbin/euca_conf --get-credentials {1}/creds.zip "
                                     "--cred-user {2} --cred-account {3}"
                                     .format(self.eucapath, admin_cred_dir, user, account))
            if self.clc.found(cmd_download_creds, "The MySQL server is not responding"):
                raise IOError("Error downloading credentials, looks like CLC was not running")
            if self.clc.found("unzip -o {0}/creds.zip -d {1}"
                              .format(admin_cred_dir, admin_cred_dir),
                              "cannot find zipfile directory"):
                raise IOError("Empty ZIP file returned by CLC")
        return zipfilepath

    def get_active_cert_for_creds(self, credzippath=None, account=None, user=None, update=True,
                                  machine=None):
        if credzippath is None:
            if hasattr(self, 'cred_zipfile') and self.cred_zipfile:
                credzippath = self.cred_zipfile
            elif self.credpath:
                credzippath = self.credpath
            else:
                raise ValueError('cred zip file not provided or set for AutoCred obj')
        machine = machine or self.clc_machine
        account = account or self.account_name
        user = user or self.aws_username
        admin_cred_dir = os.path.dirname(credzippath)
        clc_eucarc = os.path.join(admin_cred_dir, 'eucarc')
        # backward compatibility
        certpath_in_eucarc = machine.sys(". {0} &>/dev/null && "
                                         "echo $EC2_CERT".format(clc_eucarc))
        if certpath_in_eucarc:
            certpath_in_eucarc = certpath_in_eucarc[0]
        self.debug('Current EC2_CERT path for {0}: {1}'.format(clc_eucarc, certpath_in_eucarc))
        if certpath_in_eucarc and self.get_active_id_for_cert(certpath_in_eucarc):
            self.debug("Cert/pk already exist and is active in '" +
                       admin_cred_dir + "/eucarc' file.")
        else:
            # Try to find existing active cert/key on clc first. Check admin_cred_dir then
            # do a recursive search from ssh user's home dir (likely root/)
            self.debug('Attempting to find an active cert for this account on the CLC...')
            certpaths = (self.find_active_cert_and_key_in_dir(dir=admin_cred_dir) or
                         self.find_active_cert_and_key_in_dir())
            self.debug('Found Active cert and key paths')
            if not certpaths:
                # No existing and active certs found, create new ones...
                self.debug('Could not find any existing active certs on clc, '
                           'trying to create new ones...')
                certpaths = self.create_new_user_certs(admin_cred_dir, account, user)
            # Copy cert and key into admin_cred_dir
            certpath = certpaths.get('certpath')
            keypath = certpaths.get('keypath')
            newcertpath = os.path.join(admin_cred_dir, os.path.basename(certpath))
            newkeypath = os.path.join(admin_cred_dir, os.path.basename(keypath))
            self.debug('Using certpath:{0} and keypath:{1} on clc'
                       .format(newcertpath, newkeypath))
            machine.sys('cp {0} {1}'.format(certpath, newcertpath))
            machine.sys('cp {0} {1}'.format(keypath, newkeypath))
            # Update the existing eucarc with new cert and key path info...
            self.debug("Setting cert/pk in '" + admin_cred_dir + "/eucarc'")
            machine.sys("echo 'export EC2_CERT=${EUCA_KEY_DIR}/" + "{0}' >> {1}"
                        .format(os.path.basename(newcertpath), clc_eucarc))
            machine.sys("echo 'export EC2_PRIVATE_KEY=${EUCA_KEY_DIR}/" + "{0}' >> {1}"
                        .format(os.path.basename(newkeypath), clc_eucarc))
            self.debug('updating zip file with new cert, key and eucarc: {0}'
                       .format(credzippath))
            for updatefile in [os.path.basename(newcertpath), os.path.basename(newkeypath),
                               os.path.basename(clc_eucarc)]:
                machine.sys('cd {0} && zip -g {1} {2}'
                            .format(os.path.dirname(credzippath),
                                    os.path.basename(credzippath),
                                    updatefile), code=0)
            return credzippath

    def create_new_user_certs(self, admin_cred_dir, account, user, force_cert_create=False,
                              newcertpath=None, newkeypath=None, machine=None):
        machine = machine or self.clc_machine
        eucarcpath = os.path.join(admin_cred_dir, 'eucarc')
        newcertpath = newcertpath or os.path.join(admin_cred_dir, "euca2-cert.pem")
        newkeypath = newkeypath or os.path.join(admin_cred_dir, "/euca2-pk.pem")
        # admin_certs = machine.sys("source {0} && /usr/bin/euare-userlistcerts | grep -v Active"
        #                           .format(eucarcpath))
        admin_certs = []
        for cert in self.get_active_certs():
            admin_certs.append(cert.get('certificate_id'))
        if len(admin_certs) > 1:
            if force_cert_create:
                self.debug("Found more than one certs, deleting last cert")
                machine.sys(". {0} &>/dev/null && "
                            "/usr/bin/euare-userdelcert -c {1} --user-name {2}"
                            .format(eucarcpath,
                                    admin_certs[admin_certs.pop()],
                                    user),
                            code=0)
            else:
                raise RuntimeWarning('No active certs were found on the clc, and there are 2'
                                     'certs outstanding. Either delete an existing '
                                     'cert or move and active cert into clc root dir.'
                                     'The option "force_cert_create" will "delete" an existing'
                                     'cert automatically and replace it.'
                                     'Warning: deleting existing certs may leave signed'
                                     'objects in cloud unrecoverable.')
        self.debug("Creating a new signing certificate for user '{0}' in account '{1}'."
                   .format(user, account))
        self.debug('New cert name:{0}, keyname:{1}'.format(os.path.basename(newcertpath),
                                                           os.path.basename(newkeypath)))

        machine.sys(". {0} &>/dev/null && "
                    "/usr/bin/euare-usercreatecert --user-name {1} --out {2} --keyout {3}"
                    .format(eucarcpath,
                            user,
                            newcertpath,
                            newkeypath),
                    code=0)
        return {"certpath": newcertpath, "keypath": newkeypath}

    def get_active_certs(self):
        """
        Query system for active certs list
        :returns :list of active cert dicts
        """
        if not hasattr(self, 'euare') or not self.euare:
            self.critical(self.markup('Cant update certs until euare interface '
                                      'is initialized', 91))
            return []
        certs = []
        resp = self.euare.get_all_signing_certs()
        if resp:
            cresp = resp.get('list_signing_certificates_response')
            if cresp:
                lscr = cresp.get('list_signing_certificates_result')
                if lscr:
                    certs = lscr.get('certificates', [])
        return certs

    def get_active_id_for_cert(self, certpath, machine=None):
        """
        Attempt to get the cloud's active id for a certificate at 'certpath' on
        the 'machine' filesystem. Also see is_ec2_cert_active() for validating the current
        cert in use or the body (string buffer) of a cert.
        :param certpath: string representing the certificate path on the machines filesystem
        :param machine: Machine obj which certpath exists on
        :returns :str() certificate id (if cert is found to be active) else None
        """
        if not certpath:
            raise ValueError('No ec2 certpath provided or set for eutester obj')
        machine = machine or self.clc
        self.debug('Verifying cert: "{0}"...'.format(certpath))
        body = str("\n".join(machine.sys('cat {0}'.format(certpath), verbose=False))).strip()
        certs = []
        if body:
            certs = self.get_active_certs()
        for cert in certs:
            if str(cert.get('certificate_body')).strip() == body:
                self.debug('verified certificate with id "{0}" is still valid'
                           .format(cert.get('certificate_id')))
                return cert.get('certificate_id')
        self.debug('Cert: "{0}" is NOT active'.format(certpath or body))
        return None

    def find_active_cert_and_key_in_dir(self, dir="", machine=None, recursive=True):
        """
        Attempts to find an "active" cert and the matching key files in the provided
        directory 'dir' on the provided 'machine' via ssh.
        If recursive is enabled, will attempt a recursive search from the provided directory.
        :param dir: the base dir to search in on the machine provided
        :param machine: a Machine() obj used for ssh search commands
        :param recursive: boolean, if set will attempt to search recursively from the dir provided
        :returns dict w/ values 'certpath' and 'keypath' or {} if not found.
        """
        machine = machine or self.clc_machine
        ret_dict = {}
        if dir and not dir.endswith("/"):
            dir += "/"
        if recursive:
            rec = "r"
        else:
            rec = ""
        certfiles = machine.sys(
            'grep "{0}" -l{1} {2}*.pem'.format('^-*BEGIN CERTIFICATE', rec, dir))
        for f in certfiles:
            if self.get_active_id_for_cert(f, machine=machine):
                dir = os.path.dirname(f)
                keypath = self.get_key_for_cert(certpath=f, keydir=dir, machine=machine)
                if keypath:
                    self.debug('Found existing active cert and key on clc: {0}, {1}'
                               .format(f, keypath))
                    return {'certpath': f, 'keypath': keypath}
        return ret_dict

    def get_key_for_cert(self, certpath, keydir, machine=None, recursive=True):
        """
        Attempts to find a matching key for cert at 'certpath' in the provided directory 'dir'
        on the provided 'machine'.
        If recursive is enabled, will attempt a recursive search from the provided directory.
        :param dir: the base dir to search in on the machine provided
        :param machine: a Machine() obj used for ssh search commands
        :param recursive: boolean, if set will attempt to search recursively from the dir provided
        :returns string representing the path to the key found or None if not found.
        """
        machine = machine or self.clc_machine
        self.debug('Looking for key to go with cert...')
        if keydir and not keydir.endswith("/"):
            keydir += "/"
        if recursive:
            rec = "r"
        else:
            rec = ""
        certmodmd5 = machine.sys('openssl x509 -noout -modulus -in {0}  | md5sum'
                                 .format(certpath))
        if certmodmd5:
            certmodmd5 = str(certmodmd5[0]).strip()
        else:
            return None
        keyfiles = machine.sys('grep "{0}" -lz{1} {2}*.pem'
                               .format("^\-*BEGIN RSA PRIVATE KEY.*\n.*END RSA PRIVATE KEY\-*",
                                       rec, keydir))
        for kf in keyfiles:
            keymodmd5 = machine.sys('openssl rsa -noout -modulus -in {0} | md5sum'.format(kf))
            if keymodmd5:
                keymodmd5 = str(keymodmd5[0]).strip()
            if keymodmd5 == certmodmd5:
                self.debug('Found key {0} for cert {1}'.format(kf, certpath))
                return kf
        return None

    def is_ec2_cert_active(self, certbody=None):
        """
        Attempts to verify if the current self.ec2_cert @ self.ec2_certpath is still active.
        :param certbody
        :returns the cert id if found active, otherwise returns None
        """
        certbody = certbody or self.ec2_cert
        if not certbody:
            raise ValueError('No ec2 cert body provided or set for eutester to check for active')
        if isinstance(certbody, dict):
            checkbody = certbody.get('certificate_body')
            if not checkbody:
                raise ValueError('Invalid certbody provided, did not have "certificate body" attr')
        for cert in self.get_active_certs():
            body = str(cert.get('certificate_body')).strip()
            if body and body == str(certbody).strip():
                return cert.get('certificate_id')
        return None

    def credential_exist_on_remote_machine(self, cred_path, machine=None):
        machine = machine or self.clc_machine
        return machine.ssh.cmd("test -e " + cred_path)

    def download_creds_from_clc(self, admin_cred_dir, zipfile="creds.zip"):

        zipfilepath = os.path.join(admin_cred_dir, zipfile)
        self.debug("Downloading credentials from " + self.clc.hostname + ", path:" + zipfilepath +
                   " to local file: " + str(zipfile))
        self.sftp.get(zipfilepath, zipfilepath)
        unzip_cmd = "unzip -o {0} -d {1}".format(zipfilepath, admin_cred_dir)
        self.debug('Trying unzip cmd: ' + str(unzip_cmd))
        self.local(unzip_cmd)
        # backward compatibility
        cert_exists_in_eucarc = self.found("cat " + admin_cred_dir + "/eucarc", "export EC2_CERT")
        if cert_exists_in_eucarc:
            self.debug("Cert/pk already exist in '" + admin_cred_dir + "/eucarc' file.")
        else:
            self.download_certs_from_clc(admin_cred_dir=admin_cred_dir, update_eucarc=True)

    def download_certs_from_clc(self, admin_cred_dir=None, update_eucarc=True, machine=None):
        machine = machine or self.clc_machine
        admin_cred_dir = admin_cred_dir or self.credpath
        self.debug("Downloading certs from " + self.clc.hostname + ", path:" +
                   admin_cred_dir + "/")
        clc_eucarc = os.path.join(admin_cred_dir, 'eucarc')
        local_eucarc = os.path.join(admin_cred_dir, 'eucarc')
        remotecertpath = machine.sys(". {0} &>/dev/null && "
                                     "echo $EC2_CERT".format(clc_eucarc))
        if remotecertpath:
            remotecertpath = remotecertpath[0]
        remotekeypath = machine.sys(". {0} &>/dev/null && "
                                    "echo $EC2_PRIVATE_KEY".format(clc_eucarc))
        if remotekeypath:
            remotekeypath = remotekeypath[0]
        if not remotecertpath or not remotekeypath:
            self.critical('CERT and KEY paths not provided in {0}'.format(clc_eucarc))
            return {}
        localcertpath = os.path.join(admin_cred_dir, os.path.basename(remotecertpath))
        localkeypath = os.path.join(admin_cred_dir, os.path.basename(remotekeypath))
        self.sftp.get(remotecertpath, localcertpath)
        self.sftp.get(remotekeypath, localkeypath)
        if update_eucarc:
            self.debug("Setting cert/pk in '{0}".format(local_eucarc))
            self.local("echo 'export EC2_CERT=${EUCA_KEY_DIR}/" +
                       str(os.path.basename(localcertpath)) + "' >> " + local_eucarc)
            self.local("echo 'export EC2_PRIVATE_KEY=${EUCA_KEY_DIR}/" +
                       str(os.path.basename(localkeypath)) + "' >> " + local_eucarc)
        return {'certpath': localcertpath, 'keypath': localkeypath}

    def send_creds_to_machine(self, admin_cred_dir, machine, filename='creds.zip'):
        filepath = os.path.join(admin_cred_dir, filename)
        self.debug("Sending credentials to " + machine.hostname)
        localmd5 = None
        remotemd5 = None
        try:
            machine.sys('ls ' + filepath, code=0)
            remotemd5 = self.get_md5_for_file(filepath, machine=machine)
            localmd5 = self.get_md5_for_file(filepath)
        except CommandExitCodeException:
            pass
        if not remotemd5 or (remotemd5 != localmd5):
            machine.sys("mkdir " + admin_cred_dir)
            machine.sftp.put(admin_cred_dir + "/creds.zip", admin_cred_dir + "/creds.zip")
            machine.sys("unzip -o " + admin_cred_dir + "/creds.zip -d " + admin_cred_dir)
        else:
            self.debug("Machine " + machine.hostname + " already has credentials in place not "
                                                       " sending")

    def setup_local_creds_dir(self, admin_cred_dir):
        if not os.path.exists(admin_cred_dir):
            os.mkdir(admin_cred_dir)

    def setup_remote_creds_dir(self, admin_cred_dir):
        self.sys("mkdir " + admin_cred_dir)