saltkey custom module to handle Salt minion key generation and injection to target node

Change-Id: Ic40eeb417175dff9f22fb4c34509b4c495cb5c36
diff --git a/_modules/saltkey.py b/_modules/saltkey.py
new file mode 100644
index 0000000..97dc5c9
--- /dev/null
+++ b/_modules/saltkey.py
@@ -0,0 +1,106 @@
+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.
+
+    CLI Examples:
+
+    .. code-block:: bash
+
+        salt-call saltkey.key_create <MINION_ID> <MINION_IP_ADDRESS> 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
+