From 63ba441163144120f5ad744943233e228902e584 Mon Sep 17 00:00:00 2001 From: Stephen Fromm Date: Fri, 19 Oct 2012 22:00:31 -0700 Subject: [PATCH 1/2] Add ability to create SSH key for user in user module This allows one to create a SSH key for user. You may define: ssh_key_type, ssh_key_bits, ssh_key_file, ssh_key_comment, and ssh_key_passphrase. If no passphrase is provided, the key will be passphrase-less. This will not overwrite an existing key. In the JSON returned, it will provide the ssh_fingerprint and ssh_key_file. --- library/user | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 137 insertions(+), 1 deletion(-) diff --git a/library/user b/library/user index ab642ac659..bce9614a85 100755 --- a/library/user +++ b/library/user @@ -102,11 +102,47 @@ options: description: - When used with I(state=absent), behavior is as with I(userdel --remove). + ssh_key: + required: false + choices: [ generate ] + description: + - Whether to generate a SSH key for the user in question. + This will B(not) overwrite an existing SSH key. + ssh_key_bits: + required: false + default: 2048 + description: + - Optionally specify number of bits in SSH key to create. + ssh_key_type: + required: false + default: rsa + description: + - Optionally specify the tyep of SSH key to generate. + Available SSH key types will depend on implementation + present on target host. + ssh_key_file: + required: false + default: $HOME/.ssh/id_rsa + description: + - Optionally specify the SSH key filename. + ssh_key_comment: + required: false + default: ansible-generated + description: + - Optionally define the comment for the SSH key. + ssh_key_passphrase: + required: false + description: + - Set a passphrase for the SSH key. If no + passphrase is provided, the SSH key will default to + having no passphrase. examples: - code: user name=johnd comment="John Doe" uid=1040 description: "Add the user 'johnd' with a specific uid and a primary group of 'admin'" - code: user name=johnd state=absent remove=yes description: "Remove the user 'johnd'" + - code: user name=jsmith ssh_key=generate ssh_key_bits=2048 + description: "Create a 2048-bit SSH key for user jsmith" ''' import os @@ -313,9 +349,78 @@ def user_password(user): passwd = line.split(':')[1] return passwd +def get_ssh_key_path(user, ssh_file): + info = user_info(user) + if os.path.isabs(ssh_file): + ssh_key_file = ssh_file + else: + ssh_key_file = "%s/%s" % (info[5], ssh_file) + return ssh_key_file + +def ssh_key_gen(module, user, ssh): + info = user_info(user) + if not os.path.exists(info[5]): + return (1, '', 'User %s home directory does not exist' % user) + ssh_key_file = get_ssh_key_path(user, ssh['file']) + ssh_dir = os.path.dirname(ssh_key_file) + if not os.path.exists(ssh_dir): + try: + os.mkdir(ssh_dir, 0700) + except OSError, e: + return (1, '', 'Failed to create %s: %s' % (ssh_dir, str(e))) + if os.path.exists(ssh_key_file): + return (None, 'Key already exists', '') + cmd = [module.get_bin_path('ssh-keygen', True)] + for key in ssh: + if key == 'type' and ssh[key] is not None: + cmd.append('-t') + cmd.append(ssh[key]) + elif key == 'bits' and ssh[key] is not None: + cmd.append('-b') + cmd.append(ssh[key]) + elif key == 'comment' and ssh[key] is not None: + cmd.append('-C') + cmd.append(ssh[key]) + elif key == 'file' and ssh[key] is not None: + cmd.append('-f') + cmd.append(ssh_key_file) + elif key == 'passphrase': + cmd.append('-N') + if ssh[key] is not None: + cmd.append(ssh['passphrase']) + else: + cmd.append('') + p = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (out, err) = p.communicate() + rc = p.returncode + return (rc, out, err) + +def ssh_key_fingerprint(module, user, ssh): + ssh_key_file = get_ssh_key_path(user, ssh['file']) + if not os.path.exists(ssh_key_file): + return (1, 'SSH Key file %s does not exist' % ssh_key_file, '') + cmd = [module.get_bin_path('ssh-keygen', True)] + cmd.append('-l') + cmd.append('-f') + cmd.append(ssh_key_file) + p = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (out, err) = p.communicate() + rc = p.returncode + return (rc, out, err) + + + # =========================================== def main(): + ssh_defaults = { + 'bits': '2048', + 'type': 'rsa', + 'passphrase': None, + 'comment': 'ansible-generated' + } + ssh_defaults['file'] = '.ssh/id_%s' % ssh_defaults['type'] + ssh = dict(ssh_defaults) module = AnsibleModule( argument_spec = dict( state=dict(default='present', choices=['present', 'absent']), @@ -334,7 +439,14 @@ def main(): createhome=dict(default='yes', choices=BOOLEANS), system=dict(default='no', choices=BOOLEANS), # following options are specific to usermod - append=dict(default='no', choices=BOOLEANS) + append=dict(default='no', choices=BOOLEANS), + # following are specific to ssh key generation + ssh_key=dict(choices=['generate']), + ssh_key_bits=dict(default=ssh_defaults['bits']), + ssh_key_type=dict(default=ssh_defaults['type']), + ssh_key_file=dict(default=ssh_defaults['file']), + ssh_key_comment=dict(default=ssh_defaults['comment']), + ssh_key_passphrase=dict(default=None) ) ) @@ -352,6 +464,17 @@ def main(): createhome = module.params['createhome'] system = module.params['system'] append = module.params['append'] + sshkeygen = module.params['ssh_key'] + + ssh['bits'] = module.params['ssh_key_bits'] + ssh['type'] = module.params['ssh_key_type'] + ssh['file'] = module.params['ssh_key_file'] + ssh['comment'] = module.params['ssh_key_comment'] + ssh['passphrase'] = module.params['ssh_key_passphrase'] + # If using default filename, make sure it is named appropriately + if ssh['file'] == ssh_defaults['file']: + ssh['file'] = '.ssh/id_%s' % ssh['type'] + rc = None out = '' @@ -408,6 +531,19 @@ def main(): result['uid'] = info[2] if len(groups) > 0: result['groups'] = groups + if sshkeygen: + (rc, out, err) = ssh_key_gen(module, name, ssh) + if rc is not None and rc != 0: + module.fail_json(name=name, msg=err, rc=rc) + if rc == 0: + result['changed'] = True + (rc, out, err) = ssh_key_fingerprint(module, name, ssh) + if rc == 0: + result['ssh_fingerprint'] = out.strip() + else: + result['ssh_fingerprint'] = err.strip() + result['ssh_key_file'] = get_ssh_key_path(name, ssh['file']) + module.exit_json(**result) From 87954f21108b4f11e669871b0cf320201bb5959a Mon Sep 17 00:00:00 2001 From: Stephen Fromm Date: Fri, 19 Oct 2012 22:22:27 -0700 Subject: [PATCH 2/2] Use os.path.join where appropriate --- library/user | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/user b/library/user index bce9614a85..b041648896 100755 --- a/library/user +++ b/library/user @@ -354,7 +354,7 @@ def get_ssh_key_path(user, ssh_file): if os.path.isabs(ssh_file): ssh_key_file = ssh_file else: - ssh_key_file = "%s/%s" % (info[5], ssh_file) + ssh_key_file = os.path.join(info[5], ssh_file) return ssh_key_file def ssh_key_gen(module, user, ssh): @@ -419,7 +419,7 @@ def main(): 'passphrase': None, 'comment': 'ansible-generated' } - ssh_defaults['file'] = '.ssh/id_%s' % ssh_defaults['type'] + ssh_defaults['file'] = os.path.join('.ssh', 'id_%s' % ssh_defaults['type']) ssh = dict(ssh_defaults) module = AnsibleModule( argument_spec = dict( @@ -473,7 +473,7 @@ def main(): ssh['passphrase'] = module.params['ssh_key_passphrase'] # If using default filename, make sure it is named appropriately if ssh['file'] == ssh_defaults['file']: - ssh['file'] = '.ssh/id_%s' % ssh['type'] + ssh['file'] = os.path.join('.ssh', 'id_%s' % ssh_defaults['type']) rc = None