authorized_key 6.24 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
#!/usr/bin/env python
"""Ansible module to add authorized_keys for ssh logins.

(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

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/>.
"""

try:
    import json
except ImportError:
    import simplejson as json

import sys, os, shlex, pwd, syslog
from os.path import expanduser, exists, isfile, join

params = {}
msg=""

def exit_json(rc=0, **kwargs):
    if 'name' in kwargs:
        add_user_info(kwargs)
    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"
    with file(sys.argv[1]) as f:            #read the args file
        args = f.read()

    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, create=False):
    """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 create: make directories and authorized key file if True
    :return: full path string to authorized_keys for user
    """

    global msg
    msg = "Reading system user entry."
    user_entry = pwd.getpwnam(user)
    homedir = user_entry.pw_dir
    sshdir = join(homedir, ".ssh")
    keysfile = join(sshdir, "authorized_keys")
    if not create: return keysfile

    #create directories and files for authorized keys
    msg = "Reading user and group info."
    uid = user_entry.pw_uid
    gid = user_entry.pw_gid
    msg = "Making ~/.ssh."
    if not exists(sshdir): os.mkdir(sshdir, 0700)
    os.chown(sshdir, uid, gid)
    os.chmod(sshdir, 0700)
    msg = "Touching authorized keys file."
    if not exists( keysfile):
        with file(keysfile, "w") as f: 
            f.write("#Authorized Keys File created by Ansible.")
    os.chown(keysfile, uid, gid)
    os.chmod(keysfile, 0600)
    return keysfile

def readkeys( filename):
    global msg
    msg = "Reading authorized_keys."
    if not isfile(filename): return []
    with file(filename) as f:
        keys = [line.rstrip() for line in f.readlines()]
    return keys

def writekeys( filename, keys):
    global msg
    msg = "Writing authorized_keys."
    with file(filename,"w") as f:
        f.writelines( (key + "\n" for key in keys) )

def enforce_state( params):
    """Add or remove key.

    :return: True=changed, False=unchanged
    """
    global msg

    #== scrub params
    msg = "Invalid or missing param: user."
    user = params["user"]
    msg = "Invalid or missing param: key."
    key = params["key"]
    state = params.get("state", "present")

    #== check current state
    params["keyfile"] = keyfile(user)
    keys = readkeys( params["keyfile"])
    present = key in keys

    #== handle idempotent state=present
    if state=="present":
        if present: return False #nothing to do
        keys.append(key)
        writekeys(keyfile(user,create=True), keys)
    elif state=="absent":
        if not present: return False #nothing to do
        keys.remove(key)
        writekeys(keyfile(user,create=True), keys)
    else:
        msg = "Invalid param: state."
        raise StandardError(msg)
    return True

#===== MAIN SCRIPT ===================================================

try:
    params = get_params()
    changed = enforce_state( params)
    msg = ""
except:
    msg = "Error %s" % msg

# Don't do sys.exit() within try...except 
if msg:
    fail_json(msg=msg)
else:
    exit_json( user=params["user"], changed=changed)