Skip to content

Instantly share code, notes, and snippets.

@gregswift
Last active December 14, 2015 15:59
Show Gist options
  • Save gregswift/5112230 to your computer and use it in GitHub Desktop.
Save gregswift/5112230 to your computer and use it in GitHub Desktop.
Implemented the option of providing an alternate location for a users authorized_key file. This can be useful if AuthorizedKeyFiles is changed in /etc/ssh/sshd_config
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Ansible module to add authorized_keys for ssh logins.
(c) 2012, Brad Olson <brado@movedbylight.com>
This file is part of Ansible
Ansible is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Ansible is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Ansible. If not, see <http://www.gnu.org/licenses/>.
"""
DOCUMENTATION = '''
---
module: authorized_key
short_description: Adds or removes an SSH authorized key
description:
- Adds or removes an SSH authorized key for a user from a remote host.
version_added: "0.5"
options:
user:
description:
- Name of the user who should have access to the remote host
required: true
default: null
aliases: []
key:
description:
- the SSH public key, as a string
required: true
default: null
sshdir:
description:
- Path to directory where authorized_key file is stored, as a string
required: false
default: null
keysfile:
description:
- Alternate name of authorized_key file, as a string
required: false
default: null
state:
description:
- whether the given key should or should not be in the file
required: false
choices: [ "present", "absent" ]
default: "present"
examples:
- code: 'authorized_key: user=charlie key="ssh-dss ASDF1234L+8BTwaRYr/rycsBF1D8e5pTxEsXHQs4iq+mZdyWqlW++L6pMiam1A8yweP+rKtgjK2httVS6GigVsuWWfOd7/sdWippefq74nppVUELHPKkaIOjJNN1zUHFoL/YMwAAAEBALnAsQN10TNGsRDe5arBsW8cTOjqLyYBcIqgPYTZW8zENErFxt7ij3fW3Jh/sCpnmy8rkS7FyK8ULX0PEy/2yDx8/5rXgMIICbRH/XaBy9Ud5bRBFVkEDu/r+rXP33wFPHjWjwvHAtfci1NRBAudQI/98DbcGQw5HmE89CjgZRo5ktkC5yu/8agEPocVjdHyZr7PaHfxZGUDGKtGRL2QzRYukCmWo1cZbMBHcI5FzImvTHS9/8B3SATjXMPgbfBuEeBwuBK5EjL+CtHY5bWs9kmYjmeo0KfUMH8hY4MAXDoKhQ7DhBPIrcjS5jPtoGxIREZjba67r6/P2XKXaCZH6Fc= charlie@example.org 2011-01-17"'
description: "Example from Ansible Playbooks"
- code: "authorized_key: user=charlie key='$FILE(/home/charlie/.ssh/id_rsa.pub)'"
description: "Shorthand available in Ansible 0.8 and later"
- code: "authorized_key: user=charlie key='$FILE(/home/charlie/.ssh/id_rsa.pub)' sshdir='/etc/ssh/authorized_keys' keysfile='charlie'"
description: "Advanced usage with an alternate AuthorizedKeysFile configuration"
author: Brad Olson
'''
# Makes sure the public key line is present or absent in the user's .ssh/authorized_keys.
#
# Arguments
# =========
# user = username
# key = line to add to authorized_keys for user
# sshdir = path to directory where key file exists (default: ~/.ssh)
# keysfile = name of authorized key file (default: authorized_keys)
# state = absent|present (default: present)
#
# see example in examples/playbooks
import sys
import os
import pwd
import os.path
import tempfile
import shutil
def keyfile(module, user, write=False, sshdir=None, keysfile=None):
"""
Calculate name of authorized keys file, optionally creating the
directories and file, properly setting permissions.
:param str user: name of user in passwd file
:param bool write: if True, write changes to authorized_keys file (creating directories if needed)
:param str sshdir: if not None, use provided path rather than default of user homedir
:param str keysfile: if not None, use provided path rather than default of 'authorized_keys'
:return: full path string to authorized_keys for user
"""
try:
user_entry = pwd.getpwnam(user)
except KeyError, e:
module.fail_json(msg="Failed to lookup user %s: %s" % (user, str(e)))
homedir = user_entry.pw_dir
if sshdir is None:
sshdir = os.path.join(homedir, ".ssh")
if keysfile is None:
keysfile = "authorized_keys"
keysfile = os.path.join(sshdir, keysfile)
if not write:
return keysfile
uid = user_entry.pw_uid
gid = user_entry.pw_gid
if not os.path.exists(sshdir):
os.mkdir(sshdir, 0700)
if module.selinux_enabled():
module.set_default_selinux_context(sshdir, False)
os.chown(sshdir, uid, gid)
os.chmod(sshdir, 0700)
if not os.path.exists(keysfile):
try:
f = open(keysfile, "w") #touches file so we can set ownership and perms
finally:
f.close()
if module.selinux_enabled():
module.set_default_selinux_context(keysfile, False)
os.chown(keysfile, uid, gid)
os.chmod(keysfile, 0600)
return keysfile
def readkeys(filename):
if not os.path.isfile(filename):
return []
f = open(filename)
keys = [line.rstrip() for line in f.readlines()]
f.close()
return keys
def writekeys(module, filename, keys):
fd, tmp_path = tempfile.mkstemp('', 'tmp', os.path.dirname(filename))
f = open(tmp_path,"w")
try:
f.writelines( (key + "\n" for key in keys) )
except IOError, e:
module.fail_json(msg="Failed to write to file %s: %s" % (tmp_path, str(e)))
f.close()
module.atomic_replace(tmp_path, filename)
def enforce_state(module, params):
"""
Add or remove key.
"""
user = params["user"]
key = params["key"]
sshdir = params["sshdir"]
keysfile = params["keysfile"]
state = params.get("state", "present")
if '\n' in key:
module.fail_json(msg="key= can only contain a single key")
# check current state -- just get the filename, don't create file
write = False
params["keyfile"] = keyfile(module, user, write, sshdir, keysfile)
keys = readkeys(params["keyfile"])
present = key in keys
# handle idempotent state=present
if state=="present":
if present:
module.exit_json(changed=False)
keys.append(key)
write = True
writekeys(module, keyfile(module, user, write, sshdir, keysfile), keys)
elif state=="absent":
if not present:
module.exit_json(changed=False)
keys.remove(key)
write = True
writekeys(module, keyfile(module, user, write, sshdir, keysfile), keys)
params['changed'] = True
return params
def main():
module = AnsibleModule(
argument_spec = dict(
user = dict(required=True),
key = dict(required=True),
sshdir = dict(required=True),
keysfile = dict(required=False),
state = dict(default='present', choices=['absent','present'])
)
)
results = enforce_state(module, module.params)
module.exit_json(**results)
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment