From e0c5b4340ddd9698c4321dca3edaf1a55bf3e3f2 Mon Sep 17 00:00:00 2001 From: Jesse Keating Date: Mon, 9 Feb 2015 14:03:20 -0800 Subject: [PATCH] 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. --- system/authorized_key.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/system/authorized_key.py b/system/authorized_key.py index e1ac18a701d..06d16da5ee7 100644 --- a/system/authorized_key.py +++ b/system/authorized_key.py @@ -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 )