Add exclusive option to authorized_keys

This option allows the module to ensure that ONLY the specified keys
exist in the authorized_keys file. All others will be removed. This is
quite useful when rotating keys and ensuring no other key will be
accepted.
This commit is contained in:
Jesse Keating 2015-02-09 14:03:20 -08:00
parent 095f8681db
commit e0c5b4340d

View file

@ -70,6 +70,15 @@ options:
required: false
default: null
version_added: "1.4"
exclusive:
description:
- Whether to remove all other non-specified keys from the
authorized_keys file. Multiple keys can be specified in a single
key= string value by separating them by newlines.
required: false
choices: [ yes", "no" ]
default: "no"
version_added: "1.9"
description:
- "Adds or removes authorized keys for particular user accounts"
author: Brad Olson
@ -101,6 +110,9 @@ EXAMPLES = '''
key="{{ lookup('file', '/home/charlie/.ssh/id_rsa.pub') }}"
key_options='no-port-forwarding,host="10.0.1.1"'
# Set up authorized_keys exclusively with one key
- authorized_keys: user=root key=public_keys/doe-jane state=present
exclusive=yes
'''
# Makes sure the public key line is present or absent in the user's .ssh/authorized_keys.
@ -336,6 +348,7 @@ def enforce_state(module, params):
manage_dir = params.get("manage_dir", True)
state = params.get("state", "present")
key_options = params.get("key_options", None)
exclusive = params.get("exclusive", False)
error_msg = "Error getting key from: %s"
# if the key is a url, request it and use it as key source
@ -357,6 +370,10 @@ def enforce_state(module, params):
params["keyfile"] = keyfile(module, user, do_write, path, manage_dir)
existing_keys = readkeys(module, params["keyfile"])
# Add a place holder for keys that should exist in the state=present and
# exclusive=true case
keys_to_exist = []
# Check our new keys, if any of them exist we'll continue.
for new_key in key:
parsed_new_key = parsekey(module, new_key)
@ -386,6 +403,7 @@ def enforce_state(module, params):
# handle idempotent state=present
if state=="present":
keys_to_exist.append(parsed_new_key[0])
if len(non_matching_keys) > 0:
for non_matching_key in non_matching_keys:
if non_matching_key[0] in existing_keys:
@ -402,6 +420,13 @@ def enforce_state(module, params):
del existing_keys[parsed_new_key[0]]
do_write = True
# remove all other keys to honor exclusive
if state == "present" and exclusive:
to_remove = frozenset(existing_keys).difference(keys_to_exist)
for key in to_remove:
del existing_keys[key]
do_write = True
if do_write:
if module.check_mode:
module.exit_json(changed=True)
@ -424,6 +449,7 @@ def main():
state = dict(default='present', choices=['absent','present']),
key_options = dict(required=False, type='str'),
unique = dict(default=False, type='bool'),
exclusive = dict(default=False, type='bool'),
),
supports_check_mode=True
)