|
|
@ -1,35 +1,8 @@
|
|
|
|
#!/usr/bin/env python
|
|
|
|
#!/usr/bin/env python
|
|
|
|
"""Ansible module to add authorized_keys for ssh logins.
|
|
|
|
"""
|
|
|
|
|
|
|
|
Ansible module to add authorized_keys for ssh logins.
|
|
|
|
(c) 2012, Brad Olson <brado@movedbylight.com>
|
|
|
|
(c) 2012, Brad Olson <brado@movedbylight.com>
|
|
|
|
|
|
|
|
|
|
|
|
Results: 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
|
|
|
|
|
|
|
|
state = absent|present (default: present)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Command Line Example
|
|
|
|
|
|
|
|
====================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ansible somehost -m authorized_key -a user=charlie key="ssh-dss AAAABUfOL+8BTwaRYr/rycsBF1D8e5pTxEsXHQs4iq+mZdyWqlW++L6pMiam1A8yweP+rKtgjK2httVS6GigVsuWWfOd7/sdWippefq74nppVUELHPKkaIOjJNN1zUHFoL/YMwAAAEBALnAsQN10TNGsRDe5arBsW8cTOjqLyYBcIqgPYTZW8zENErFxt7ij3fW3Jh/sCpnmy8rkS7FyK8ULX0PEy/2yDx8/5rXgMIICbRH/XaBy9Ud5bRBFVkEDu/r+rXP33wFPHjWjwvHAtfci1NRBAudQI/98DbcGQw5HmE89CjgZRo5ktkC5yu/8agEPocVjdHyZr7PaHfxZGUDGKtGRL2QzRYukCmWo1cZbMBHcI5FzImvTHS9/8B3SATjXMPgbfBuEeBwuBK5EjL+CtHY5bWs9kmYjmeo0KfUMH8hY4MAXDoKhQ7DhBPIrcjS5jPtoGxIREZjba67r6/P2XKXaCZH6Fc= charlie@somemail.org 2011-01-17"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Playbook Example
|
|
|
|
|
|
|
|
================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
# include like this:
|
|
|
|
|
|
|
|
# - include: tasks/logins.yaml users=charlie,sue
|
|
|
|
|
|
|
|
- name: create user charlie
|
|
|
|
|
|
|
|
action: user name=charlie shell=/bin/bash createhome=yes groups=www-data
|
|
|
|
|
|
|
|
only_if: "'charlie' in '$users'"
|
|
|
|
|
|
|
|
- name: add public key for charlie
|
|
|
|
|
|
|
|
action: authorized_key user=charlie key="ssh-dss AAAABUfOL+8BTwaRYr/rycsBF1D8e5pTxEsXHQs4iq+mZdyWqlW++L6pMiam1A8yweP+rKtgjK2httVS6GigVsuWWfOd7/sdWippefq74nppVUELHPKkaIOjJNN1zUHFoL/YMwAAAEBALnAsQN10TNGsRDe5arBsW8cTOjqLyYBcIqgPYTZW8zENErFxt7ij3fW3Jh/sCpnmy8rkS7FyK8ULX0PEy/2yDx8/5rXgMIICbRH/XaBy9Ud5bRBFVkEDu/r+rXP33wFPHjWjwvHAtfci1NRBAudQI/98DbcGQw5HmE89CjgZRo5ktkC5yu/8agEPocVjdHyZr7PaHfxZGUDGKtGRL2QzRYukCmWo1cZbMBHcI5FzImvTHS9/8B3SATjXMPgbfBuEeBwuBK5EjL+CtHY5bWs9kmYjmeo0KfUMH8hY4MAXDoKhQ7DhBPIrcjS5jPtoGxIREZjba67r6/P2XKXaCZH6Fc= charlie@somemail.org 2011-01-17"
|
|
|
|
|
|
|
|
only_if: "'charlie' in '$users'"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
This file is part of Ansible
|
|
|
|
This file is part of Ansible
|
|
|
|
|
|
|
|
|
|
|
|
Ansible is free software: you can redistribute it and/or modify
|
|
|
|
Ansible is free software: you can redistribute it and/or modify
|
|
|
@ -46,57 +19,24 @@ You should have received a copy of the GNU General Public License
|
|
|
|
along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
# Makes sure the public key line is present or absent in the user's .ssh/authorized_keys.
|
|
|
|
import json
|
|
|
|
#
|
|
|
|
except ImportError:
|
|
|
|
# Arguments
|
|
|
|
import simplejson as json
|
|
|
|
# =========
|
|
|
|
|
|
|
|
# user = username
|
|
|
|
import sys, os, shlex, pwd, syslog
|
|
|
|
# key = line to add to authorized_keys for user
|
|
|
|
from os.path import expanduser, exists, isfile, join
|
|
|
|
# state = absent|present (default: present)
|
|
|
|
|
|
|
|
#
|
|
|
|
params = {}
|
|
|
|
# see example in examples/playbooks
|
|
|
|
msg=""
|
|
|
|
|
|
|
|
|
|
|
|
import sys
|
|
|
|
def exit_json(rc=0, **kwargs):
|
|
|
|
import os
|
|
|
|
if 'name' in kwargs:
|
|
|
|
import pwd
|
|
|
|
add_user_info(kwargs)
|
|
|
|
import os.path
|
|
|
|
print json.dumps(kwargs)
|
|
|
|
|
|
|
|
sys.exit(rc)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def fail_json(**kwargs):
|
|
|
|
|
|
|
|
kwargs['failed'] = True
|
|
|
|
|
|
|
|
exit_json(rc=1, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_params():
|
|
|
|
|
|
|
|
"""Startup tasks and read params.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:return: parameters as dictionary.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
global msg
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
msg = "reading params"
|
|
|
|
|
|
|
|
argfile = sys.argv[1]
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
f = open(argfile,"r")
|
|
|
|
|
|
|
|
args = f.read()
|
|
|
|
|
|
|
|
finally:
|
|
|
|
|
|
|
|
f.close()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
msg = "writing syslog."
|
|
|
|
|
|
|
|
syslog.openlog('ansible-%s' % os.path.basename(__file__))
|
|
|
|
|
|
|
|
syslog.syslog(syslog.LOG_NOTICE, 'Invoked with %s' % args)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
msg = "parsing params"
|
|
|
|
|
|
|
|
params = dict( # make a dictionary of...
|
|
|
|
|
|
|
|
[ arg.split("=", 1) # assignment pairs
|
|
|
|
|
|
|
|
for arg in shlex.split(args) # using shell lexing
|
|
|
|
|
|
|
|
if "=" in arg # ignoring tokens without assignment
|
|
|
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return params
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def keyfile(user, write=False):
|
|
|
|
def keyfile(user, write=False):
|
|
|
|
"""Calculate name of authorized keys file, optionally creating the
|
|
|
|
"""
|
|
|
|
|
|
|
|
Calculate name of authorized keys file, optionally creating the
|
|
|
|
directories and file, properly setting permissions.
|
|
|
|
directories and file, properly setting permissions.
|
|
|
|
|
|
|
|
|
|
|
|
:param str user: name of user in passwd file
|
|
|
|
:param str user: name of user in passwd file
|
|
|
@ -104,98 +44,91 @@ def keyfile(user, write=False):
|
|
|
|
:return: full path string to authorized_keys for user
|
|
|
|
:return: full path string to authorized_keys for user
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
global msg
|
|
|
|
|
|
|
|
msg = "Reading system user entry."
|
|
|
|
|
|
|
|
user_entry = pwd.getpwnam(user)
|
|
|
|
user_entry = pwd.getpwnam(user)
|
|
|
|
msg = "Calculating special directories"
|
|
|
|
homedir = user_entry.pw_dir
|
|
|
|
homedir = user_entry.pw_dir
|
|
|
|
sshdir = os.path.join(homedir, ".ssh")
|
|
|
|
sshdir = join(homedir, ".ssh")
|
|
|
|
keysfile = os.path.join(sshdir, "authorized_keys")
|
|
|
|
keysfile = join(sshdir, "authorized_keys")
|
|
|
|
|
|
|
|
if not write: return keysfile
|
|
|
|
if not write:
|
|
|
|
|
|
|
|
return keysfile
|
|
|
|
#create directories and files for authorized keys
|
|
|
|
|
|
|
|
msg = "Reading user and group info."
|
|
|
|
|
|
|
|
uid = user_entry.pw_uid
|
|
|
|
uid = user_entry.pw_uid
|
|
|
|
gid = user_entry.pw_gid
|
|
|
|
gid = user_entry.pw_gid
|
|
|
|
msg = "Making ~/.ssh."
|
|
|
|
|
|
|
|
if not exists(sshdir): os.mkdir(sshdir, 0700)
|
|
|
|
if not os.path.exists(sshdir):
|
|
|
|
|
|
|
|
os.mkdir(sshdir, 0700)
|
|
|
|
os.chown(sshdir, uid, gid)
|
|
|
|
os.chown(sshdir, uid, gid)
|
|
|
|
os.chmod(sshdir, 0700)
|
|
|
|
os.chmod(sshdir, 0700)
|
|
|
|
msg = "Touching authorized keys file."
|
|
|
|
|
|
|
|
if not exists( keysfile):
|
|
|
|
if not os.path.exists( keysfile):
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
f = open(keysfile, "w") #touches file so we can set ownership and perms
|
|
|
|
f = open(keysfile, "w") #touches file so we can set ownership and perms
|
|
|
|
finally:
|
|
|
|
finally:
|
|
|
|
f.close()
|
|
|
|
f.close()
|
|
|
|
|
|
|
|
|
|
|
|
os.chown(keysfile, uid, gid)
|
|
|
|
os.chown(keysfile, uid, gid)
|
|
|
|
os.chmod(keysfile, 0600)
|
|
|
|
os.chmod(keysfile, 0600)
|
|
|
|
return keysfile
|
|
|
|
return keysfile
|
|
|
|
|
|
|
|
|
|
|
|
def readkeys( filename):
|
|
|
|
def readkeys(filename):
|
|
|
|
global msg
|
|
|
|
|
|
|
|
msg = "Reading authorized_keys."
|
|
|
|
if not os.path.isfile(filename):
|
|
|
|
if not isfile(filename): return []
|
|
|
|
return []
|
|
|
|
try:
|
|
|
|
f = open(filename)
|
|
|
|
f = open(filename)
|
|
|
|
keys = [line.rstrip() for line in f.readlines()]
|
|
|
|
keys = [line.rstrip() for line in f.readlines()]
|
|
|
|
f.close()
|
|
|
|
finally:
|
|
|
|
|
|
|
|
f.close()
|
|
|
|
|
|
|
|
return keys
|
|
|
|
return keys
|
|
|
|
|
|
|
|
|
|
|
|
def writekeys( filename, keys):
|
|
|
|
def writekeys( filename, keys):
|
|
|
|
global msg
|
|
|
|
|
|
|
|
msg = "Writing authorized_keys."
|
|
|
|
f = open(filename,"w")
|
|
|
|
try:
|
|
|
|
f.writelines( (key + "\n" for key in keys) )
|
|
|
|
f = open(filename,"w")
|
|
|
|
f.close()
|
|
|
|
f.writelines( (key + "\n" for key in keys) )
|
|
|
|
|
|
|
|
finally:
|
|
|
|
def enforce_state(module, params):
|
|
|
|
f.close()
|
|
|
|
"""
|
|
|
|
|
|
|
|
Add or remove key.
|
|
|
|
def enforce_state( params):
|
|
|
|
|
|
|
|
"""Add or remove key.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:return: True=changed, False=unchanged
|
|
|
|
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
global msg
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#== scrub params
|
|
|
|
user = params["user"]
|
|
|
|
msg = "Invalid or missing param: user."
|
|
|
|
key = params["key"]
|
|
|
|
user = params["user"]
|
|
|
|
|
|
|
|
msg = "Invalid or missing param: key."
|
|
|
|
|
|
|
|
key = params["key"]
|
|
|
|
|
|
|
|
state = params.get("state", "present")
|
|
|
|
state = params.get("state", "present")
|
|
|
|
|
|
|
|
|
|
|
|
#== check current state
|
|
|
|
# check current state -- just get the filename, don't create file
|
|
|
|
params["keyfile"] = keyfile(user, write=False) #just get the filename, don't create file
|
|
|
|
params["keyfile"] = keyfile(user, write=False)
|
|
|
|
keys = readkeys( params["keyfile"])
|
|
|
|
keys = readkeys(params["keyfile"])
|
|
|
|
present = key in keys
|
|
|
|
present = key in keys
|
|
|
|
|
|
|
|
|
|
|
|
#== handle idempotent state=present
|
|
|
|
# handle idempotent state=present
|
|
|
|
if state=="present":
|
|
|
|
if state=="present":
|
|
|
|
if present: return False #nothing to do
|
|
|
|
if present:
|
|
|
|
|
|
|
|
module.exit_json(changed=False)
|
|
|
|
keys.append(key)
|
|
|
|
keys.append(key)
|
|
|
|
writekeys(keyfile(user,write=True), keys)
|
|
|
|
writekeys(keyfile(user,write=True), keys)
|
|
|
|
|
|
|
|
|
|
|
|
elif state=="absent":
|
|
|
|
elif state=="absent":
|
|
|
|
if not present: return False #nothing to do
|
|
|
|
if not present:
|
|
|
|
|
|
|
|
module.exit_json(changed=False)
|
|
|
|
keys.remove(key)
|
|
|
|
keys.remove(key)
|
|
|
|
writekeys(keyfile(user,write=True), keys)
|
|
|
|
writekeys(keyfile(user,write=True), keys)
|
|
|
|
else:
|
|
|
|
|
|
|
|
msg = "Invalid param: state."
|
|
|
|
params['changed'] = True
|
|
|
|
raise StandardError(msg)
|
|
|
|
return params
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
#===== MAIN SCRIPT ===================================================
|
|
|
|
|
|
|
|
|
|
|
|
module = AnsibleModule(
|
|
|
|
try:
|
|
|
|
argument_spec = dict(
|
|
|
|
params = get_params()
|
|
|
|
user = dict(required=True),
|
|
|
|
changed = enforce_state( params)
|
|
|
|
key = dict(required=True),
|
|
|
|
msg = ""
|
|
|
|
state = dict(default='present', choices=['absent','present'])
|
|
|
|
except:
|
|
|
|
)
|
|
|
|
msg = "Error %s" % msg
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# Don't do sys.exit() within try...except
|
|
|
|
params = module.params
|
|
|
|
if msg:
|
|
|
|
results = enforce_state(module, module.params)
|
|
|
|
fail_json(msg=msg)
|
|
|
|
module.exit_json(**results)
|
|
|
|
else:
|
|
|
|
|
|
|
|
exit_json( user=params["user"], changed=changed)
|
|
|
|
# this is magic, see lib/ansible/module_common.py
|
|
|
|
|
|
|
|
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
|
|
|
|
|
|
|
main()
|
|
|
|