from __future__ import absolute_import # Import python libs import logging import os try: import paramiko HAS_PARAMIKO = True except: HAS_PARAMIKO = False # Import Salt libs import salt.config import salt.wheel LOG = logging.getLogger(__name__) def __virtual__(): ''' Only load if paramiko library exist. ''' if not HAS_PARAMIKO: return ( False, 'Can not load module saltkey: paramiko library not found') return True def key_create(id_, host, force=False): ''' Generates minion keypair, accepts it on master and injects it to minion via SSH. :param id_: expected minion ID of target node :param host: IP address or resolvable hostname/FQDN of target node CLI Examples: .. code-block:: bash salt-call saltkey.key_create force=False ''' ret = { 'retcode': 0, 'msg': 'Salt Key for minion %s is already accepted' % id_, } opts = salt.config.master_config('/etc/salt/master') wheel = salt.wheel.WheelClient(opts) keys = wheel.cmd('key.gen_accept', arg=[id_], kwarg={'force': force}) pub_key = keys.get('pub', None) priv_key = keys.get('priv', None) if pub_key and priv_key: ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # Establish SSH connection to minion try: ssh.connect(host) except paramiko.ssh_exception.AuthenticationException: msg = ('Could not establish SSH connection to minion "%s" on address %s, please ensure ' 'that current user\'s SSH key is present in minions authorized_keys.') % (id_, host) LOG.error(msg) ret['retcode'] = 1 ret['msg'] = msg wheel.cmd_async({'fun': 'key.delete', 'match': id_}) return ret except Exception as e: msg = ('Unknown error occured while establishing SSH connection ' 'to minion "%s" on address %s: %s') % (id_, host, repr(e)) LOG.error(msg) ret['retcode'] = 1 ret['msg'] = msg wheel.cmd_async({'fun': 'key.delete', 'match': id_}) return ret # Setup the keys on minion side the ugly way, nice one didn't work key_path = '/etc/salt/pki/minion' command = ('echo "%(pub_key)s" > %(pub_path)s && chmod 644 %(pub_path)s && ' 'echo "%(priv_key)s" > %(priv_path)s && chmod 400 %(priv_path)s && ' 'salt-call --local service.restart salt-minion') % { 'pub_path': os.path.join(key_path, 'minion.pub'), 'pub_key': pub_key, 'priv_path': os.path.join(key_path, 'minion.pem'), 'priv_key': priv_key } ssh_chan = ssh.get_transport().open_session() ssh_chan.exec_command(command) # Wait for command return while True: if ssh_chan.exit_status_ready(): exit_status = ssh_chan.recv_exit_status() stderr = ssh_chan.recv_stderr(1000) stdout = ssh_chan.recv(1000) break ssh.close() # Evaluate SSH command exit status if exit_status != 0: msg = 'Keypair injection to Salt minion failed on target with following error: %s' % stderr LOG.error(msg) ret['retcode'] = exit_status ret['msg'] = msg return ret ret['msg'] = 'Salt Key successfully created' return ret