Instantly share code, notes, and snippets.
Last active
December 14, 2015 15:59
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
# -*- coding: utf-8 -*- | |
""" | |
Ansible module to add authorized_keys for ssh logins. | |
(c) 2012, Brad Olson <[email protected]> | |
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= [email protected] 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