authorized_key 5.44 KB
Newer Older
1
#!/usr/bin/env python
2 3
# -*- coding: utf-8 -*-

4 5
"""
Ansible module to add authorized_keys for ssh logins.
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
(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/>.
"""

24 25 26
DOCUMENTATION = '''
---
module: authorized_key
27
short_description: Adds or removes an SSH authorized key
28
description:
29
     - Adds or removes an SSH authorized key for a user from a remote host.
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
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
  state:
    description:
      - whether the given key should or should not be in the file
    required: false
    choices: [ "present", "absent" ]
    default: "present"
examples:
50
   - 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"'
51
     description: "Example from Ansible Playbooks"
52
   - code: "authorized_key: user=charlie key='$FILE(/home/charlie/.ssh/id_rsa.pub)'"
53
     description: "Shorthand available in Ansible 0.8 and later"
54 55 56
author: Brad Olson
'''

57 58 59 60 61 62 63 64 65 66 67 68 69 70
# 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)
#
# see example in examples/playbooks

import sys
import os
import pwd
import os.path
71 72
import tempfile
import shutil
73

74
def keyfile(module, user, write=False):
75
    """
76
    Calculate name of authorized keys file, optionally creating the
77 78 79
    directories and file, properly setting permissions.

    :param str user: name of user in passwd file
80
    :param bool write: if True, write changes to authorized_keys file (creating directories if needed)
81 82 83
    :return: full path string to authorized_keys for user
    """

84 85 86 87
    try:
        user_entry = pwd.getpwnam(user)
    except KeyError, e:
        module.fail_json(msg="Failed to lookup user %s: %s" % (user, str(e)))
88 89 90 91
    homedir    = user_entry.pw_dir
    sshdir     = os.path.join(homedir, ".ssh")
    keysfile   = os.path.join(sshdir, "authorized_keys")

92
    if not write:
93 94
        return keysfile

95 96
    uid = user_entry.pw_uid
    gid = user_entry.pw_gid
97

98
    if not os.path.exists(sshdir):
99
        os.mkdir(sshdir, 0700)
100 101
    os.chown(sshdir, uid, gid)
    os.chmod(sshdir, 0700)
102 103

    if not os.path.exists( keysfile):
104
        try:
105
            f = open(keysfile, "w") #touches file so we can set ownership and perms
106 107
        finally:
            f.close()
108

109 110 111 112
    os.chown(keysfile, uid, gid)
    os.chmod(keysfile, 0600)
    return keysfile

113 114
def readkeys(filename):

115
    if not os.path.isfile(filename):
116 117 118 119
        return []
    f = open(filename)
    keys = [line.rstrip() for line in f.readlines()]
    f.close()
120 121
    return keys

122
def writekeys(module, filename, keys):
123

124
    fd, tmp_path = tempfile.mkstemp('', 'tmp', os.path.dirname(filename))
125 126 127 128 129
    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)))
130
    f.close()
131
    module.atomic_replace(tmp_path, filename)
132 133 134 135

def enforce_state(module, params):
    """
    Add or remove key.
136 137
    """

138 139
    user  = params["user"]
    key   = params["key"]
140 141
    state = params.get("state", "present")

142
    # check current state -- just get the filename, don't create file
143
    params["keyfile"] = keyfile(module, user, write=False)
144
    keys = readkeys(params["keyfile"])
145 146
    present = key in keys

147
    # handle idempotent state=present
148
    if state=="present":
149
        if present:
150
            module.exit_json(changed=False)
151
        keys.append(key)
152
        writekeys(module, keyfile(module, user,write=True), keys)
153

154
    elif state=="absent":
155
        if not present:
156
            module.exit_json(changed=False)
157
        keys.remove(key)
158
        writekeys(module, keyfile(module, user,write=True), keys)
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178

    params['changed'] = True
    return params

def main():

    module = AnsibleModule(
        argument_spec = dict(
           user  = dict(required=True),
           key   = dict(required=True),
           state = dict(default='present', choices=['absent','present'])
        )
    )

    params = module.params
    results = enforce_state(module, module.params)
    module.exit_json(**results)

# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
179
main()