fc6e82
From 3b68aff3b7b1dc567ef6721a269c2d4e054b729f Mon Sep 17 00:00:00 2001
fc6e82
From: Emanuele Giuseppe Esposito <eesposit@redhat.com>
fc6e82
Date: Mon, 9 Aug 2021 23:41:44 +0200
fc6e82
Subject: [PATCH] Stop copying ssh system keys and check folder permissions
fc6e82
 (#956)
fc6e82
fc6e82
RH-Author: Emanuele Giuseppe Esposito <eesposit@redhat.com>
fc6e82
RH-MergeRequest: 28: Stop copying ssh system keys and check folder permissions (#956)
fc6e82
RH-Commit: [1/1] 7cada613be82f2f525ee56b86ef9f71edf40d2ef (eesposit/cloud-init)
fc6e82
RH-Bugzilla: 1862967
fc6e82
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
fc6e82
RH-Acked-by: Eduardo Otubo <otubo@redhat.com>
fc6e82
fc6e82
TESTED: By me and QA
fc6e82
BREW: 38818284
fc6e82
fc6e82
This is a continuation of previous MR 25 and upstream PR #937.
fc6e82
There were still issues when using non-standard file paths like
fc6e82
/etc/ssh/userkeys/%u or /etc/ssh/authorized_keys, and the choice
fc6e82
of storing the keys of all authorized_keys files into a single
fc6e82
one was not ideal. This fix modifies cloudinit to support
fc6e82
all different cases of authorized_keys file locations, and
fc6e82
picks a user-specific file where to copy the new keys that
fc6e82
complies with ssh permissions.
fc6e82
fc6e82
commit 00dbaf1e9ab0e59d81662f0f3561897bef499a3f
fc6e82
Author: Emanuele Giuseppe Esposito <eesposit@redhat.com>
fc6e82
Date:   Mon Aug 9 16:49:56 2021 +0200
fc6e82
fc6e82
    Stop copying ssh system keys and check folder permissions (#956)
fc6e82
fc6e82
    In /etc/ssh/sshd_config, it is possible to define a custom
fc6e82
    authorized_keys file that will contain the keys allowed to access the
fc6e82
    machine via the AuthorizedKeysFile option. Cloudinit is able to add
fc6e82
    user-specific keys to the existing ones, but we need to be careful on
fc6e82
    which of the authorized_keys files listed to pick.
fc6e82
    Chosing a file that is shared by all user will cause security
fc6e82
    issues, because the owner of that key can then access also other users.
fc6e82
fc6e82
    We therefore pick an authorized_keys file only if it satisfies the
fc6e82
    following conditions:
fc6e82
    1. it is not a "global" file, ie it must be defined in
fc6e82
       AuthorizedKeysFile with %u, %h or be in  /home/<user>. This avoids
fc6e82
       security issues.
fc6e82
    2. it must comply with ssh permission requirements, otherwise the ssh
fc6e82
       agent won't use that file.
fc6e82
fc6e82
    If it doesn't meet either of those conditions, write to
fc6e82
    ~/.ssh/authorized_keys
fc6e82
fc6e82
    We also need to consider the case when the chosen authorized_keys file
fc6e82
    does not exist. In this case, the existing behavior of cloud-init is
fc6e82
    to create the new file. We therefore need to be sure that the file
fc6e82
    complies with ssh permissions too, by setting:
fc6e82
    - the actual file to permission 600, and owned by the user
fc6e82
    - the directories in the path that do not exist must be root owned and
fc6e82
      with permission 755.
fc6e82
fc6e82
Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
fc6e82
---
fc6e82
 cloudinit/ssh_util.py           | 133 ++++-
fc6e82
 cloudinit/util.py               |  51 +-
fc6e82
 tests/unittests/test_sshutil.py | 952 +++++++++++++++++++++++++-------
fc6e82
 3 files changed, 920 insertions(+), 216 deletions(-)
fc6e82
fc6e82
diff --git a/cloudinit/ssh_util.py b/cloudinit/ssh_util.py
fc6e82
index 89057262..b8a3c8f7 100644
fc6e82
--- a/cloudinit/ssh_util.py
fc6e82
+++ b/cloudinit/ssh_util.py
fc6e82
@@ -249,6 +249,113 @@ def render_authorizedkeysfile_paths(value, homedir, username):
fc6e82
     return rendered
fc6e82
 
fc6e82
 
fc6e82
+# Inspired from safe_path() in openssh source code (misc.c).
fc6e82
+def check_permissions(username, current_path, full_path, is_file, strictmodes):
fc6e82
+    """Check if the file/folder in @current_path has the right permissions.
fc6e82
+
fc6e82
+    We need to check that:
fc6e82
+    1. If StrictMode is enabled, the owner is either root or the user
fc6e82
+    2. the user can access the file/folder, otherwise ssh won't use it
fc6e82
+    3. If StrictMode is enabled, no write permission is given to group
fc6e82
+       and world users (022)
fc6e82
+    """
fc6e82
+
fc6e82
+    # group/world can only execute the folder (access)
fc6e82
+    minimal_permissions = 0o711
fc6e82
+    if is_file:
fc6e82
+        # group/world can only read the file
fc6e82
+        minimal_permissions = 0o644
fc6e82
+
fc6e82
+    # 1. owner must be either root or the user itself
fc6e82
+    owner = util.get_owner(current_path)
fc6e82
+    if strictmodes and owner != username and owner != "root":
fc6e82
+        LOG.debug("Path %s in %s must be own by user %s or"
fc6e82
+                  " by root, but instead is own by %s. Ignoring key.",
fc6e82
+                  current_path, full_path, username, owner)
fc6e82
+        return False
fc6e82
+
fc6e82
+    parent_permission = util.get_permissions(current_path)
fc6e82
+    # 2. the user can access the file/folder, otherwise ssh won't use it
fc6e82
+    if owner == username:
fc6e82
+        # need only the owner permissions
fc6e82
+        minimal_permissions &= 0o700
fc6e82
+    else:
fc6e82
+        group_owner = util.get_group(current_path)
fc6e82
+        user_groups = util.get_user_groups(username)
fc6e82
+
fc6e82
+        if group_owner in user_groups:
fc6e82
+            # need only the group permissions
fc6e82
+            minimal_permissions &= 0o070
fc6e82
+        else:
fc6e82
+            # need only the world permissions
fc6e82
+            minimal_permissions &= 0o007
fc6e82
+
fc6e82
+    if parent_permission & minimal_permissions == 0:
fc6e82
+        LOG.debug("Path %s in %s must be accessible by user %s,"
fc6e82
+                  " check its permissions",
fc6e82
+                  current_path, full_path, username)
fc6e82
+        return False
fc6e82
+
fc6e82
+    # 3. no write permission (w) is given to group and world users (022)
fc6e82
+    # Group and world user can still have +rx.
fc6e82
+    if strictmodes and parent_permission & 0o022 != 0:
fc6e82
+        LOG.debug("Path %s in %s must not give write"
fc6e82
+                  "permission to group or world users. Ignoring key.",
fc6e82
+                  current_path, full_path)
fc6e82
+        return False
fc6e82
+
fc6e82
+    return True
fc6e82
+
fc6e82
+
fc6e82
+def check_create_path(username, filename, strictmodes):
fc6e82
+    user_pwent = users_ssh_info(username)[1]
fc6e82
+    root_pwent = users_ssh_info("root")[1]
fc6e82
+    try:
fc6e82
+        # check the directories first
fc6e82
+        directories = filename.split("/")[1:-1]
fc6e82
+
fc6e82
+        # scan in order, from root to file name
fc6e82
+        parent_folder = ""
fc6e82
+        # this is to comply also with unit tests, and
fc6e82
+        # strange home directories
fc6e82
+        home_folder = os.path.dirname(user_pwent.pw_dir)
fc6e82
+        for directory in directories:
fc6e82
+            parent_folder += "/" + directory
fc6e82
+            if home_folder.startswith(parent_folder):
fc6e82
+                continue
fc6e82
+
fc6e82
+            if not os.path.isdir(parent_folder):
fc6e82
+                # directory does not exist, and permission so far are good:
fc6e82
+                # create the directory, and make it accessible by everyone
fc6e82
+                # but owned by root, as it might be used by many users.
fc6e82
+                with util.SeLinuxGuard(parent_folder):
fc6e82
+                    os.makedirs(parent_folder, mode=0o755, exist_ok=True)
fc6e82
+                    util.chownbyid(parent_folder, root_pwent.pw_uid,
fc6e82
+                                   root_pwent.pw_gid)
fc6e82
+
fc6e82
+            permissions = check_permissions(username, parent_folder,
fc6e82
+                                            filename, False, strictmodes)
fc6e82
+            if not permissions:
fc6e82
+                return False
fc6e82
+
fc6e82
+        # check the file
fc6e82
+        if not os.path.exists(filename):
fc6e82
+            # if file does not exist: we need to create it, since the
fc6e82
+            # folders at this point exist and have right permissions
fc6e82
+            util.write_file(filename, '', mode=0o600, ensure_dir_exists=True)
fc6e82
+            util.chownbyid(filename, user_pwent.pw_uid, user_pwent.pw_gid)
fc6e82
+
fc6e82
+        permissions = check_permissions(username, filename,
fc6e82
+                                        filename, True, strictmodes)
fc6e82
+        if not permissions:
fc6e82
+            return False
fc6e82
+    except (IOError, OSError) as e:
fc6e82
+        util.logexc(LOG, str(e))
fc6e82
+        return False
fc6e82
+
fc6e82
+    return True
fc6e82
+
fc6e82
+
fc6e82
 def extract_authorized_keys(username, sshd_cfg_file=DEF_SSHD_CFG):
fc6e82
     (ssh_dir, pw_ent) = users_ssh_info(username)
fc6e82
     default_authorizedkeys_file = os.path.join(ssh_dir, 'authorized_keys')
fc6e82
@@ -259,6 +366,7 @@ def extract_authorized_keys(username, sshd_cfg_file=DEF_SSHD_CFG):
fc6e82
             ssh_cfg = parse_ssh_config_map(sshd_cfg_file)
fc6e82
             key_paths = ssh_cfg.get("authorizedkeysfile",
fc6e82
                                     "%h/.ssh/authorized_keys")
fc6e82
+            strictmodes = ssh_cfg.get("strictmodes", "yes")
fc6e82
             auth_key_fns = render_authorizedkeysfile_paths(
fc6e82
                 key_paths, pw_ent.pw_dir, username)
fc6e82
 
fc6e82
@@ -269,31 +377,31 @@ def extract_authorized_keys(username, sshd_cfg_file=DEF_SSHD_CFG):
fc6e82
                         "config from %r, using 'AuthorizedKeysFile' file "
fc6e82
                         "%r instead", DEF_SSHD_CFG, auth_key_fns[0])
fc6e82
 
fc6e82
-    # check if one of the keys is the user's one
fc6e82
+    # check if one of the keys is the user's one and has the right permissions
fc6e82
     for key_path, auth_key_fn in zip(key_paths.split(), auth_key_fns):
fc6e82
         if any([
fc6e82
             '%u' in key_path,
fc6e82
             '%h' in key_path,
fc6e82
             auth_key_fn.startswith('{}/'.format(pw_ent.pw_dir))
fc6e82
         ]):
fc6e82
-            user_authorizedkeys_file = auth_key_fn
fc6e82
+            permissions_ok = check_create_path(username, auth_key_fn,
fc6e82
+                                               strictmodes == "yes")
fc6e82
+            if permissions_ok:
fc6e82
+                user_authorizedkeys_file = auth_key_fn
fc6e82
+                break
fc6e82
 
fc6e82
     if user_authorizedkeys_file != default_authorizedkeys_file:
fc6e82
         LOG.debug(
fc6e82
             "AuthorizedKeysFile has an user-specific authorized_keys, "
fc6e82
             "using %s", user_authorizedkeys_file)
fc6e82
 
fc6e82
-    # always store all the keys in the user's private file
fc6e82
-    return (user_authorizedkeys_file, parse_authorized_keys(auth_key_fns))
fc6e82
+    return (
fc6e82
+        user_authorizedkeys_file,
fc6e82
+        parse_authorized_keys([user_authorizedkeys_file])
fc6e82
+    )
fc6e82
 
fc6e82
 
fc6e82
 def setup_user_keys(keys, username, options=None):
fc6e82
-    # Make sure the users .ssh dir is setup accordingly
fc6e82
-    (ssh_dir, pwent) = users_ssh_info(username)
fc6e82
-    if not os.path.isdir(ssh_dir):
fc6e82
-        util.ensure_dir(ssh_dir, mode=0o700)
fc6e82
-        util.chownbyid(ssh_dir, pwent.pw_uid, pwent.pw_gid)
fc6e82
-
fc6e82
     # Turn the 'update' keys given into actual entries
fc6e82
     parser = AuthKeyLineParser()
fc6e82
     key_entries = []
fc6e82
@@ -302,11 +410,10 @@ def setup_user_keys(keys, username, options=None):
fc6e82
 
fc6e82
     # Extract the old and make the new
fc6e82
     (auth_key_fn, auth_key_entries) = extract_authorized_keys(username)
fc6e82
+    ssh_dir = os.path.dirname(auth_key_fn)
fc6e82
     with util.SeLinuxGuard(ssh_dir, recursive=True):
fc6e82
         content = update_authorized_keys(auth_key_entries, key_entries)
fc6e82
-        util.ensure_dir(os.path.dirname(auth_key_fn), mode=0o700)
fc6e82
-        util.write_file(auth_key_fn, content, mode=0o600)
fc6e82
-        util.chownbyid(auth_key_fn, pwent.pw_uid, pwent.pw_gid)
fc6e82
+        util.write_file(auth_key_fn, content, preserve_mode=True)
fc6e82
 
fc6e82
 
fc6e82
 class SshdConfigLine(object):
fc6e82
diff --git a/cloudinit/util.py b/cloudinit/util.py
fc6e82
index 4e0a72db..343976ad 100644
fc6e82
--- a/cloudinit/util.py
fc6e82
+++ b/cloudinit/util.py
fc6e82
@@ -35,6 +35,7 @@ from base64 import b64decode, b64encode
fc6e82
 from errno import ENOENT
fc6e82
 from functools import lru_cache
fc6e82
 from urllib import parse
fc6e82
+from typing import List
fc6e82
 
fc6e82
 from cloudinit import importer
fc6e82
 from cloudinit import log as logging
fc6e82
@@ -1830,6 +1831,53 @@ def chmod(path, mode):
fc6e82
             os.chmod(path, real_mode)
fc6e82
 
fc6e82
 
fc6e82
+def get_permissions(path: str) -> int:
fc6e82
+    """
fc6e82
+    Returns the octal permissions of the file/folder pointed by the path,
fc6e82
+    encoded as an int.
fc6e82
+
fc6e82
+    @param path: The full path of the file/folder.
fc6e82
+    """
fc6e82
+
fc6e82
+    return stat.S_IMODE(os.stat(path).st_mode)
fc6e82
+
fc6e82
+
fc6e82
+def get_owner(path: str) -> str:
fc6e82
+    """
fc6e82
+    Returns the owner of the file/folder pointed by the path.
fc6e82
+
fc6e82
+    @param path: The full path of the file/folder.
fc6e82
+    """
fc6e82
+    st = os.stat(path)
fc6e82
+    return pwd.getpwuid(st.st_uid).pw_name
fc6e82
+
fc6e82
+
fc6e82
+def get_group(path: str) -> str:
fc6e82
+    """
fc6e82
+    Returns the group of the file/folder pointed by the path.
fc6e82
+
fc6e82
+    @param path: The full path of the file/folder.
fc6e82
+    """
fc6e82
+    st = os.stat(path)
fc6e82
+    return grp.getgrgid(st.st_gid).gr_name
fc6e82
+
fc6e82
+
fc6e82
+def get_user_groups(username: str) -> List[str]:
fc6e82
+    """
fc6e82
+    Returns a list of all groups to which the user belongs
fc6e82
+
fc6e82
+    @param username: the user we want to check
fc6e82
+    """
fc6e82
+    groups = []
fc6e82
+    for group in grp.getgrall():
fc6e82
+        if username in group.gr_mem:
fc6e82
+            groups.append(group.gr_name)
fc6e82
+
fc6e82
+    gid = pwd.getpwnam(username).pw_gid
fc6e82
+    groups.append(grp.getgrgid(gid).gr_name)
fc6e82
+    return groups
fc6e82
+
fc6e82
+
fc6e82
 def write_file(
fc6e82
     filename,
fc6e82
     content,
fc6e82
@@ -1856,8 +1904,7 @@ def write_file(
fc6e82
 
fc6e82
     if preserve_mode:
fc6e82
         try:
fc6e82
-            file_stat = os.stat(filename)
fc6e82
-            mode = stat.S_IMODE(file_stat.st_mode)
fc6e82
+            mode = get_permissions(filename)
fc6e82
         except OSError:
fc6e82
             pass
fc6e82
 
fc6e82
diff --git a/tests/unittests/test_sshutil.py b/tests/unittests/test_sshutil.py
fc6e82
index bcb8044f..a66788bf 100644
fc6e82
--- a/tests/unittests/test_sshutil.py
fc6e82
+++ b/tests/unittests/test_sshutil.py
fc6e82
@@ -1,6 +1,9 @@
fc6e82
 # This file is part of cloud-init. See LICENSE file for license information.
fc6e82
 
fc6e82
+import os
fc6e82
+
fc6e82
 from collections import namedtuple
fc6e82
+from functools import partial
fc6e82
 from unittest.mock import patch
fc6e82
 
fc6e82
 from cloudinit import ssh_util
fc6e82
@@ -8,13 +11,48 @@ from cloudinit.tests import helpers as test_helpers
fc6e82
 from cloudinit import util
fc6e82
 
fc6e82
 # https://stackoverflow.com/questions/11351032/
fc6e82
-FakePwEnt = namedtuple(
fc6e82
-    'FakePwEnt',
fc6e82
-    ['pw_dir', 'pw_gecos', 'pw_name', 'pw_passwd', 'pw_shell', 'pwd_uid'])
fc6e82
+FakePwEnt = namedtuple('FakePwEnt', [
fc6e82
+    'pw_name',
fc6e82
+    'pw_passwd',
fc6e82
+    'pw_uid',
fc6e82
+    'pw_gid',
fc6e82
+    'pw_gecos',
fc6e82
+    'pw_dir',
fc6e82
+    'pw_shell',
fc6e82
+])
fc6e82
 FakePwEnt.__new__.__defaults__ = tuple(
fc6e82
     "UNSET_%s" % n for n in FakePwEnt._fields)
fc6e82
 
fc6e82
 
fc6e82
+def mock_get_owner(updated_permissions, value):
fc6e82
+    try:
fc6e82
+        return updated_permissions[value][0]
fc6e82
+    except ValueError:
fc6e82
+        return util.get_owner(value)
fc6e82
+
fc6e82
+
fc6e82
+def mock_get_group(updated_permissions, value):
fc6e82
+    try:
fc6e82
+        return updated_permissions[value][1]
fc6e82
+    except ValueError:
fc6e82
+        return util.get_group(value)
fc6e82
+
fc6e82
+
fc6e82
+def mock_get_user_groups(username):
fc6e82
+    return username
fc6e82
+
fc6e82
+
fc6e82
+def mock_get_permissions(updated_permissions, value):
fc6e82
+    try:
fc6e82
+        return updated_permissions[value][2]
fc6e82
+    except ValueError:
fc6e82
+        return util.get_permissions(value)
fc6e82
+
fc6e82
+
fc6e82
+def mock_getpwnam(users, username):
fc6e82
+    return users[username]
fc6e82
+
fc6e82
+
fc6e82
 # Do not use these public keys, most of them are fetched from
fc6e82
 # the testdata for OpenSSH, and their private keys are available
fc6e82
 # https://github.com/openssh/openssh-portable/tree/master/regress/unittests/sshkey/testdata
fc6e82
@@ -552,12 +590,30 @@ class TestBasicAuthorizedKeyParse(test_helpers.CiTestCase):
fc6e82
             ssh_util.render_authorizedkeysfile_paths(
fc6e82
                 "/opt/%u/keys", "/home/bobby", "bobby"))
fc6e82
 
fc6e82
+    def test_user_file(self):
fc6e82
+        self.assertEqual(
fc6e82
+            ["/opt/bobby"],
fc6e82
+            ssh_util.render_authorizedkeysfile_paths(
fc6e82
+                "/opt/%u", "/home/bobby", "bobby"))
fc6e82
+
fc6e82
+    def test_user_file2(self):
fc6e82
+        self.assertEqual(
fc6e82
+            ["/opt/bobby/bobby"],
fc6e82
+            ssh_util.render_authorizedkeysfile_paths(
fc6e82
+                "/opt/%u/%u", "/home/bobby", "bobby"))
fc6e82
+
fc6e82
     def test_multiple(self):
fc6e82
         self.assertEqual(
fc6e82
             ["/keys/path1", "/keys/path2"],
fc6e82
             ssh_util.render_authorizedkeysfile_paths(
fc6e82
                 "/keys/path1 /keys/path2", "/home/bobby", "bobby"))
fc6e82
 
fc6e82
+    def test_multiple2(self):
fc6e82
+        self.assertEqual(
fc6e82
+            ["/keys/path1", "/keys/bobby"],
fc6e82
+            ssh_util.render_authorizedkeysfile_paths(
fc6e82
+                "/keys/path1 /keys/%u", "/home/bobby", "bobby"))
fc6e82
+
fc6e82
     def test_relative(self):
fc6e82
         self.assertEqual(
fc6e82
             ["/home/bobby/.secret/keys"],
fc6e82
@@ -581,269 +637,763 @@ class TestBasicAuthorizedKeyParse(test_helpers.CiTestCase):
fc6e82
 
fc6e82
 class TestMultipleSshAuthorizedKeysFile(test_helpers.CiTestCase):
fc6e82
 
fc6e82
-    @patch("cloudinit.ssh_util.pwd.getpwnam")
fc6e82
-    def test_multiple_authorizedkeys_file_order1(self, m_getpwnam):
fc6e82
-        fpw = FakePwEnt(pw_name='bobby', pw_dir='/tmp/home2/bobby')
fc6e82
-        m_getpwnam.return_value = fpw
fc6e82
-        user_ssh_folder = "%s/.ssh" % fpw.pw_dir
fc6e82
-
fc6e82
-        # /tmp/home2/bobby/.ssh/authorized_keys = rsa
fc6e82
-        authorized_keys = self.tmp_path('authorized_keys', dir=user_ssh_folder)
fc6e82
-        util.write_file(authorized_keys, VALID_CONTENT['rsa'])
fc6e82
-
fc6e82
-        # /tmp/home2/bobby/.ssh/user_keys = dsa
fc6e82
-        user_keys = self.tmp_path('user_keys', dir=user_ssh_folder)
fc6e82
-        util.write_file(user_keys, VALID_CONTENT['dsa'])
fc6e82
-
fc6e82
-        # /tmp/sshd_config
fc6e82
+    def create_fake_users(self, names, mock_permissions,
fc6e82
+                          m_get_group, m_get_owner, m_get_permissions,
fc6e82
+                          m_getpwnam, users):
fc6e82
+        homes = []
fc6e82
+
fc6e82
+        root = '/tmp/root'
fc6e82
+        fpw = FakePwEnt(pw_name="root", pw_dir=root)
fc6e82
+        users["root"] = fpw
fc6e82
+
fc6e82
+        for name in names:
fc6e82
+            home = '/tmp/home/' + name
fc6e82
+            fpw = FakePwEnt(pw_name=name, pw_dir=home)
fc6e82
+            users[name] = fpw
fc6e82
+            homes.append(home)
fc6e82
+
fc6e82
+        m_get_permissions.side_effect = partial(
fc6e82
+            mock_get_permissions, mock_permissions)
fc6e82
+        m_get_owner.side_effect = partial(mock_get_owner, mock_permissions)
fc6e82
+        m_get_group.side_effect = partial(mock_get_group, mock_permissions)
fc6e82
+        m_getpwnam.side_effect = partial(mock_getpwnam, users)
fc6e82
+        return homes
fc6e82
+
fc6e82
+    def create_user_authorized_file(self, home, filename, content_key, keys):
fc6e82
+        user_ssh_folder = "%s/.ssh" % home
fc6e82
+        # /tmp/home/<user>/.ssh/authorized_keys = content_key
fc6e82
+        authorized_keys = self.tmp_path(filename, dir=user_ssh_folder)
fc6e82
+        util.write_file(authorized_keys, VALID_CONTENT[content_key])
fc6e82
+        keys[authorized_keys] = content_key
fc6e82
+        return authorized_keys
fc6e82
+
fc6e82
+    def create_global_authorized_file(self, filename, content_key, keys):
fc6e82
+        authorized_keys = self.tmp_path(filename, dir='/tmp')
fc6e82
+        util.write_file(authorized_keys, VALID_CONTENT[content_key])
fc6e82
+        keys[authorized_keys] = content_key
fc6e82
+        return authorized_keys
fc6e82
+
fc6e82
+    def create_sshd_config(self, authorized_keys_files):
fc6e82
         sshd_config = self.tmp_path('sshd_config', dir="/tmp")
fc6e82
         util.write_file(
fc6e82
             sshd_config,
fc6e82
-            "AuthorizedKeysFile %s %s" % (authorized_keys, user_keys)
fc6e82
+            "AuthorizedKeysFile " + authorized_keys_files
fc6e82
         )
fc6e82
+        return sshd_config
fc6e82
 
fc6e82
+    def execute_and_check(self, user, sshd_config, solution, keys,
fc6e82
+                          delete_keys=True):
fc6e82
         (auth_key_fn, auth_key_entries) = ssh_util.extract_authorized_keys(
fc6e82
-            fpw.pw_name, sshd_config)
fc6e82
+            user, sshd_config)
fc6e82
         content = ssh_util.update_authorized_keys(auth_key_entries, [])
fc6e82
 
fc6e82
-        self.assertEqual(user_keys, auth_key_fn)
fc6e82
-        self.assertTrue(VALID_CONTENT['rsa'] in content)
fc6e82
-        self.assertTrue(VALID_CONTENT['dsa'] in content)
fc6e82
+        self.assertEqual(auth_key_fn, solution)
fc6e82
+        for path, key in keys.items():
fc6e82
+            if path == solution:
fc6e82
+                self.assertTrue(VALID_CONTENT[key] in content)
fc6e82
+            else:
fc6e82
+                self.assertFalse(VALID_CONTENT[key] in content)
fc6e82
+
fc6e82
+        if delete_keys and os.path.isdir("/tmp/home/"):
fc6e82
+            util.delete_dir_contents("/tmp/home/")
fc6e82
 
fc6e82
     @patch("cloudinit.ssh_util.pwd.getpwnam")
fc6e82
-    def test_multiple_authorizedkeys_file_order2(self, m_getpwnam):
fc6e82
-        fpw = FakePwEnt(pw_name='suzie', pw_dir='/tmp/home/suzie')
fc6e82
-        m_getpwnam.return_value = fpw
fc6e82
-        user_ssh_folder = "%s/.ssh" % fpw.pw_dir
fc6e82
+    @patch("cloudinit.util.get_permissions")
fc6e82
+    @patch("cloudinit.util.get_owner")
fc6e82
+    @patch("cloudinit.util.get_group")
fc6e82
+    def test_single_user_two_local_files(
fc6e82
+        self, m_get_group, m_get_owner, m_get_permissions, m_getpwnam
fc6e82
+    ):
fc6e82
+        user_bobby = 'bobby'
fc6e82
+        keys = {}
fc6e82
+        users = {}
fc6e82
+        mock_permissions = {
fc6e82
+            '/tmp/home/bobby': ('bobby', 'bobby', 0o700),
fc6e82
+            '/tmp/home/bobby/.ssh': ('bobby', 'bobby', 0o700),
fc6e82
+            '/tmp/home/bobby/.ssh/user_keys': ('bobby', 'bobby', 0o600),
fc6e82
+            '/tmp/home/bobby/.ssh/authorized_keys': ('bobby', 'bobby', 0o600),
fc6e82
+        }
fc6e82
+
fc6e82
+        homes = self.create_fake_users(
fc6e82
+            [user_bobby], mock_permissions, m_get_group, m_get_owner,
fc6e82
+            m_get_permissions, m_getpwnam, users
fc6e82
+        )
fc6e82
+        home = homes[0]
fc6e82
 
fc6e82
-        # /tmp/home/suzie/.ssh/authorized_keys = rsa
fc6e82
-        authorized_keys = self.tmp_path('authorized_keys', dir=user_ssh_folder)
fc6e82
-        util.write_file(authorized_keys, VALID_CONTENT['rsa'])
fc6e82
+        # /tmp/home/bobby/.ssh/authorized_keys = rsa
fc6e82
+        authorized_keys = self.create_user_authorized_file(
fc6e82
+            home, 'authorized_keys', 'rsa', keys
fc6e82
+        )
fc6e82
 
fc6e82
-        # /tmp/home/suzie/.ssh/user_keys = dsa
fc6e82
-        user_keys = self.tmp_path('user_keys', dir=user_ssh_folder)
fc6e82
-        util.write_file(user_keys, VALID_CONTENT['dsa'])
fc6e82
+        # /tmp/home/bobby/.ssh/user_keys = dsa
fc6e82
+        user_keys = self.create_user_authorized_file(
fc6e82
+            home, 'user_keys', 'dsa', keys
fc6e82
+        )
fc6e82
 
fc6e82
         # /tmp/sshd_config
fc6e82
-        sshd_config = self.tmp_path('sshd_config', dir="/tmp")
fc6e82
-        util.write_file(
fc6e82
-            sshd_config,
fc6e82
-            "AuthorizedKeysFile %s %s" % (user_keys, authorized_keys)
fc6e82
+        options = "%s %s" % (authorized_keys, user_keys)
fc6e82
+        sshd_config = self.create_sshd_config(options)
fc6e82
+
fc6e82
+        self.execute_and_check(user_bobby, sshd_config, authorized_keys, keys)
fc6e82
+
fc6e82
+    @patch("cloudinit.ssh_util.pwd.getpwnam")
fc6e82
+    @patch("cloudinit.util.get_permissions")
fc6e82
+    @patch("cloudinit.util.get_owner")
fc6e82
+    @patch("cloudinit.util.get_group")
fc6e82
+    def test_single_user_two_local_files_inverted(
fc6e82
+        self, m_get_group, m_get_owner, m_get_permissions, m_getpwnam
fc6e82
+    ):
fc6e82
+        user_bobby = 'bobby'
fc6e82
+        keys = {}
fc6e82
+        users = {}
fc6e82
+        mock_permissions = {
fc6e82
+            '/tmp/home/bobby': ('bobby', 'bobby', 0o700),
fc6e82
+            '/tmp/home/bobby/.ssh': ('bobby', 'bobby', 0o700),
fc6e82
+            '/tmp/home/bobby/.ssh/user_keys': ('bobby', 'bobby', 0o600),
fc6e82
+            '/tmp/home/bobby/.ssh/authorized_keys': ('bobby', 'bobby', 0o600),
fc6e82
+        }
fc6e82
+
fc6e82
+        homes = self.create_fake_users(
fc6e82
+            [user_bobby], mock_permissions, m_get_group, m_get_owner,
fc6e82
+            m_get_permissions, m_getpwnam, users
fc6e82
         )
fc6e82
+        home = homes[0]
fc6e82
 
fc6e82
-        (auth_key_fn, auth_key_entries) = ssh_util.extract_authorized_keys(
fc6e82
-            fpw.pw_name, sshd_config)
fc6e82
-        content = ssh_util.update_authorized_keys(auth_key_entries, [])
fc6e82
+        # /tmp/home/bobby/.ssh/authorized_keys = rsa
fc6e82
+        authorized_keys = self.create_user_authorized_file(
fc6e82
+            home, 'authorized_keys', 'rsa', keys
fc6e82
+        )
fc6e82
 
fc6e82
-        self.assertEqual(authorized_keys, auth_key_fn)
fc6e82
-        self.assertTrue(VALID_CONTENT['rsa'] in content)
fc6e82
-        self.assertTrue(VALID_CONTENT['dsa'] in content)
fc6e82
+        # /tmp/home/bobby/.ssh/user_keys = dsa
fc6e82
+        user_keys = self.create_user_authorized_file(
fc6e82
+            home, 'user_keys', 'dsa', keys
fc6e82
+        )
fc6e82
 
fc6e82
-    @patch("cloudinit.ssh_util.pwd.getpwnam")
fc6e82
-    def test_multiple_authorizedkeys_file_local_global(self, m_getpwnam):
fc6e82
-        fpw = FakePwEnt(pw_name='bobby', pw_dir='/tmp/home2/bobby')
fc6e82
-        m_getpwnam.return_value = fpw
fc6e82
-        user_ssh_folder = "%s/.ssh" % fpw.pw_dir
fc6e82
+        # /tmp/sshd_config
fc6e82
+        options = "%s %s" % (user_keys, authorized_keys)
fc6e82
+        sshd_config = self.create_sshd_config(options)
fc6e82
 
fc6e82
-        # /tmp/home2/bobby/.ssh/authorized_keys = rsa
fc6e82
-        authorized_keys = self.tmp_path('authorized_keys', dir=user_ssh_folder)
fc6e82
-        util.write_file(authorized_keys, VALID_CONTENT['rsa'])
fc6e82
+        self.execute_and_check(user_bobby, sshd_config, user_keys, keys)
fc6e82
 
fc6e82
-        # /tmp/home2/bobby/.ssh/user_keys = dsa
fc6e82
-        user_keys = self.tmp_path('user_keys', dir=user_ssh_folder)
fc6e82
-        util.write_file(user_keys, VALID_CONTENT['dsa'])
fc6e82
+    @patch("cloudinit.ssh_util.pwd.getpwnam")
fc6e82
+    @patch("cloudinit.util.get_permissions")
fc6e82
+    @patch("cloudinit.util.get_owner")
fc6e82
+    @patch("cloudinit.util.get_group")
fc6e82
+    def test_single_user_local_global_files(
fc6e82
+        self, m_get_group, m_get_owner, m_get_permissions, m_getpwnam
fc6e82
+    ):
fc6e82
+        user_bobby = 'bobby'
fc6e82
+        keys = {}
fc6e82
+        users = {}
fc6e82
+        mock_permissions = {
fc6e82
+            '/tmp/home/bobby': ('bobby', 'bobby', 0o700),
fc6e82
+            '/tmp/home/bobby/.ssh': ('bobby', 'bobby', 0o700),
fc6e82
+            '/tmp/home/bobby/.ssh/user_keys': ('bobby', 'bobby', 0o600),
fc6e82
+            '/tmp/home/bobby/.ssh/authorized_keys': ('bobby', 'bobby', 0o600),
fc6e82
+        }
fc6e82
+
fc6e82
+        homes = self.create_fake_users(
fc6e82
+            [user_bobby], mock_permissions, m_get_group, m_get_owner,
fc6e82
+            m_get_permissions, m_getpwnam, users
fc6e82
+        )
fc6e82
+        home = homes[0]
fc6e82
 
fc6e82
-        # /tmp/etc/ssh/authorized_keys = ecdsa
fc6e82
-        authorized_keys_global = self.tmp_path('etc/ssh/authorized_keys',
fc6e82
-                                               dir="/tmp")
fc6e82
-        util.write_file(authorized_keys_global, VALID_CONTENT['ecdsa'])
fc6e82
+        # /tmp/home/bobby/.ssh/authorized_keys = rsa
fc6e82
+        authorized_keys = self.create_user_authorized_file(
fc6e82
+            home, 'authorized_keys', 'rsa', keys
fc6e82
+        )
fc6e82
 
fc6e82
-        # /tmp/sshd_config
fc6e82
-        sshd_config = self.tmp_path('sshd_config', dir="/tmp")
fc6e82
-        util.write_file(
fc6e82
-            sshd_config,
fc6e82
-            "AuthorizedKeysFile %s %s %s" % (authorized_keys_global,
fc6e82
-                                             user_keys, authorized_keys)
fc6e82
+        # /tmp/home/bobby/.ssh/user_keys = dsa
fc6e82
+        user_keys = self.create_user_authorized_file(
fc6e82
+            home, 'user_keys', 'dsa', keys
fc6e82
         )
fc6e82
 
fc6e82
-        (auth_key_fn, auth_key_entries) = ssh_util.extract_authorized_keys(
fc6e82
-            fpw.pw_name, sshd_config)
fc6e82
-        content = ssh_util.update_authorized_keys(auth_key_entries, [])
fc6e82
+        authorized_keys_global = self.create_global_authorized_file(
fc6e82
+            'etc/ssh/authorized_keys', 'ecdsa', keys
fc6e82
+        )
fc6e82
 
fc6e82
-        self.assertEqual(authorized_keys, auth_key_fn)
fc6e82
-        self.assertTrue(VALID_CONTENT['rsa'] in content)
fc6e82
-        self.assertTrue(VALID_CONTENT['ecdsa'] in content)
fc6e82
-        self.assertTrue(VALID_CONTENT['dsa'] in content)
fc6e82
+        options = "%s %s %s" % (authorized_keys_global, user_keys,
fc6e82
+                                authorized_keys)
fc6e82
+        sshd_config = self.create_sshd_config(options)
fc6e82
 
fc6e82
-    @patch("cloudinit.ssh_util.pwd.getpwnam")
fc6e82
-    def test_multiple_authorizedkeys_file_local_global2(self, m_getpwnam):
fc6e82
-        fpw = FakePwEnt(pw_name='bobby', pw_dir='/tmp/home2/bobby')
fc6e82
-        m_getpwnam.return_value = fpw
fc6e82
-        user_ssh_folder = "%s/.ssh" % fpw.pw_dir
fc6e82
+        self.execute_and_check(user_bobby, sshd_config, user_keys, keys)
fc6e82
 
fc6e82
-        # /tmp/home2/bobby/.ssh/authorized_keys2 = rsa
fc6e82
-        authorized_keys = self.tmp_path('authorized_keys2',
fc6e82
-                                        dir=user_ssh_folder)
fc6e82
-        util.write_file(authorized_keys, VALID_CONTENT['rsa'])
fc6e82
+    @patch("cloudinit.ssh_util.pwd.getpwnam")
fc6e82
+    @patch("cloudinit.util.get_permissions")
fc6e82
+    @patch("cloudinit.util.get_owner")
fc6e82
+    @patch("cloudinit.util.get_group")
fc6e82
+    def test_single_user_local_global_files_inverted(
fc6e82
+        self, m_get_group, m_get_owner, m_get_permissions, m_getpwnam
fc6e82
+    ):
fc6e82
+        user_bobby = 'bobby'
fc6e82
+        keys = {}
fc6e82
+        users = {}
fc6e82
+        mock_permissions = {
fc6e82
+            '/tmp/home/bobby': ('bobby', 'bobby', 0o700),
fc6e82
+            '/tmp/home/bobby/.ssh': ('bobby', 'bobby', 0o700),
fc6e82
+            '/tmp/home/bobby/.ssh/user_keys3': ('bobby', 'bobby', 0o600),
fc6e82
+            '/tmp/home/bobby/.ssh/authorized_keys2': ('bobby', 'bobby', 0o600),
fc6e82
+        }
fc6e82
+
fc6e82
+        homes = self.create_fake_users(
fc6e82
+            [user_bobby], mock_permissions, m_get_group, m_get_owner,
fc6e82
+            m_get_permissions, m_getpwnam, users
fc6e82
+        )
fc6e82
+        home = homes[0]
fc6e82
 
fc6e82
-        # /tmp/home2/bobby/.ssh/user_keys3 = dsa
fc6e82
-        user_keys = self.tmp_path('user_keys3', dir=user_ssh_folder)
fc6e82
-        util.write_file(user_keys, VALID_CONTENT['dsa'])
fc6e82
+        # /tmp/home/bobby/.ssh/authorized_keys = rsa
fc6e82
+        authorized_keys = self.create_user_authorized_file(
fc6e82
+            home, 'authorized_keys2', 'rsa', keys
fc6e82
+        )
fc6e82
 
fc6e82
-        # /tmp/etc/ssh/authorized_keys = ecdsa
fc6e82
-        authorized_keys_global = self.tmp_path('etc/ssh/authorized_keys',
fc6e82
-                                               dir="/tmp")
fc6e82
-        util.write_file(authorized_keys_global, VALID_CONTENT['ecdsa'])
fc6e82
+        # /tmp/home/bobby/.ssh/user_keys = dsa
fc6e82
+        user_keys = self.create_user_authorized_file(
fc6e82
+            home, 'user_keys3', 'dsa', keys
fc6e82
+        )
fc6e82
 
fc6e82
-        # /tmp/sshd_config
fc6e82
-        sshd_config = self.tmp_path('sshd_config', dir="/tmp")
fc6e82
-        util.write_file(
fc6e82
-            sshd_config,
fc6e82
-            "AuthorizedKeysFile %s %s %s" % (authorized_keys_global,
fc6e82
-                                             authorized_keys, user_keys)
fc6e82
+        authorized_keys_global = self.create_global_authorized_file(
fc6e82
+            'etc/ssh/authorized_keys', 'ecdsa', keys
fc6e82
         )
fc6e82
 
fc6e82
-        (auth_key_fn, auth_key_entries) = ssh_util.extract_authorized_keys(
fc6e82
-            fpw.pw_name, sshd_config)
fc6e82
-        content = ssh_util.update_authorized_keys(auth_key_entries, [])
fc6e82
+        options = "%s %s %s" % (authorized_keys_global, authorized_keys,
fc6e82
+                                user_keys)
fc6e82
+        sshd_config = self.create_sshd_config(options)
fc6e82
 
fc6e82
-        self.assertEqual(user_keys, auth_key_fn)
fc6e82
-        self.assertTrue(VALID_CONTENT['rsa'] in content)
fc6e82
-        self.assertTrue(VALID_CONTENT['ecdsa'] in content)
fc6e82
-        self.assertTrue(VALID_CONTENT['dsa'] in content)
fc6e82
+        self.execute_and_check(user_bobby, sshd_config, authorized_keys, keys)
fc6e82
 
fc6e82
     @patch("cloudinit.ssh_util.pwd.getpwnam")
fc6e82
-    def test_multiple_authorizedkeys_file_global(self, m_getpwnam):
fc6e82
-        fpw = FakePwEnt(pw_name='bobby', pw_dir='/tmp/home2/bobby')
fc6e82
-        m_getpwnam.return_value = fpw
fc6e82
+    @patch("cloudinit.util.get_permissions")
fc6e82
+    @patch("cloudinit.util.get_owner")
fc6e82
+    @patch("cloudinit.util.get_group")
fc6e82
+    def test_single_user_global_file(
fc6e82
+        self, m_get_group, m_get_owner, m_get_permissions, m_getpwnam
fc6e82
+    ):
fc6e82
+        user_bobby = 'bobby'
fc6e82
+        keys = {}
fc6e82
+        users = {}
fc6e82
+        mock_permissions = {
fc6e82
+            '/tmp/home/bobby': ('bobby', 'bobby', 0o700),
fc6e82
+            '/tmp/home/bobby/.ssh': ('bobby', 'bobby', 0o700),
fc6e82
+            '/tmp/home/bobby/.ssh/authorized_keys': ('bobby', 'bobby', 0o600),
fc6e82
+        }
fc6e82
+
fc6e82
+        homes = self.create_fake_users(
fc6e82
+            [user_bobby], mock_permissions, m_get_group, m_get_owner,
fc6e82
+            m_get_permissions, m_getpwnam, users
fc6e82
+        )
fc6e82
+        home = homes[0]
fc6e82
 
fc6e82
         # /tmp/etc/ssh/authorized_keys = rsa
fc6e82
-        authorized_keys_global = self.tmp_path('etc/ssh/authorized_keys',
fc6e82
-                                               dir="/tmp")
fc6e82
-        util.write_file(authorized_keys_global, VALID_CONTENT['rsa'])
fc6e82
+        authorized_keys_global = self.create_global_authorized_file(
fc6e82
+            'etc/ssh/authorized_keys', 'rsa', keys
fc6e82
+        )
fc6e82
 
fc6e82
-        # /tmp/sshd_config
fc6e82
-        sshd_config = self.tmp_path('sshd_config')
fc6e82
-        util.write_file(
fc6e82
-            sshd_config,
fc6e82
-            "AuthorizedKeysFile %s" % (authorized_keys_global)
fc6e82
+        options = "%s" % authorized_keys_global
fc6e82
+        sshd_config = self.create_sshd_config(options)
fc6e82
+
fc6e82
+        default = "%s/.ssh/authorized_keys" % home
fc6e82
+        self.execute_and_check(user_bobby, sshd_config, default, keys)
fc6e82
+
fc6e82
+    @patch("cloudinit.ssh_util.pwd.getpwnam")
fc6e82
+    @patch("cloudinit.util.get_permissions")
fc6e82
+    @patch("cloudinit.util.get_owner")
fc6e82
+    @patch("cloudinit.util.get_group")
fc6e82
+    def test_two_users_local_file_standard(
fc6e82
+        self, m_get_group, m_get_owner, m_get_permissions, m_getpwnam
fc6e82
+    ):
fc6e82
+        keys = {}
fc6e82
+        users = {}
fc6e82
+        mock_permissions = {
fc6e82
+            '/tmp/home/bobby': ('bobby', 'bobby', 0o700),
fc6e82
+            '/tmp/home/bobby/.ssh': ('bobby', 'bobby', 0o700),
fc6e82
+            '/tmp/home/bobby/.ssh/authorized_keys': ('bobby', 'bobby', 0o600),
fc6e82
+            '/tmp/home/suzie': ('suzie', 'suzie', 0o700),
fc6e82
+            '/tmp/home/suzie/.ssh': ('suzie', 'suzie', 0o700),
fc6e82
+            '/tmp/home/suzie/.ssh/authorized_keys': ('suzie', 'suzie', 0o600),
fc6e82
+        }
fc6e82
+
fc6e82
+        user_bobby = 'bobby'
fc6e82
+        user_suzie = 'suzie'
fc6e82
+        homes = self.create_fake_users(
fc6e82
+            [user_bobby, user_suzie], mock_permissions, m_get_group,
fc6e82
+            m_get_owner, m_get_permissions, m_getpwnam, users
fc6e82
         )
fc6e82
+        home_bobby = homes[0]
fc6e82
+        home_suzie = homes[1]
fc6e82
 
fc6e82
-        (auth_key_fn, auth_key_entries) = ssh_util.extract_authorized_keys(
fc6e82
-            fpw.pw_name, sshd_config)
fc6e82
-        content = ssh_util.update_authorized_keys(auth_key_entries, [])
fc6e82
+        # /tmp/home/bobby/.ssh/authorized_keys = rsa
fc6e82
+        authorized_keys = self.create_user_authorized_file(
fc6e82
+            home_bobby, 'authorized_keys', 'rsa', keys
fc6e82
+        )
fc6e82
 
fc6e82
-        self.assertEqual("%s/.ssh/authorized_keys" % fpw.pw_dir, auth_key_fn)
fc6e82
-        self.assertTrue(VALID_CONTENT['rsa'] in content)
fc6e82
+        # /tmp/home/suzie/.ssh/authorized_keys = rsa
fc6e82
+        authorized_keys2 = self.create_user_authorized_file(
fc6e82
+            home_suzie, 'authorized_keys', 'ssh-xmss@openssh.com', keys
fc6e82
+        )
fc6e82
+
fc6e82
+        options = ".ssh/authorized_keys"
fc6e82
+        sshd_config = self.create_sshd_config(options)
fc6e82
+
fc6e82
+        self.execute_and_check(
fc6e82
+            user_bobby, sshd_config, authorized_keys, keys, delete_keys=False
fc6e82
+        )
fc6e82
+        self.execute_and_check(user_suzie, sshd_config, authorized_keys2, keys)
fc6e82
 
fc6e82
     @patch("cloudinit.ssh_util.pwd.getpwnam")
fc6e82
-    def test_multiple_authorizedkeys_file_multiuser(self, m_getpwnam):
fc6e82
-        fpw = FakePwEnt(pw_name='bobby', pw_dir='/tmp/home2/bobby')
fc6e82
-        m_getpwnam.return_value = fpw
fc6e82
-        user_ssh_folder = "%s/.ssh" % fpw.pw_dir
fc6e82
-        # /tmp/home2/bobby/.ssh/authorized_keys2 = rsa
fc6e82
-        authorized_keys = self.tmp_path('authorized_keys2',
fc6e82
-                                        dir=user_ssh_folder)
fc6e82
-        util.write_file(authorized_keys, VALID_CONTENT['rsa'])
fc6e82
-        # /tmp/home2/bobby/.ssh/user_keys3 = dsa
fc6e82
-        user_keys = self.tmp_path('user_keys3', dir=user_ssh_folder)
fc6e82
-        util.write_file(user_keys, VALID_CONTENT['dsa'])
fc6e82
-
fc6e82
-        fpw2 = FakePwEnt(pw_name='suzie', pw_dir='/tmp/home/suzie')
fc6e82
-        user_ssh_folder = "%s/.ssh" % fpw2.pw_dir
fc6e82
-        # /tmp/home/suzie/.ssh/authorized_keys2 = ssh-xmss@openssh.com
fc6e82
-        authorized_keys2 = self.tmp_path('authorized_keys2',
fc6e82
-                                         dir=user_ssh_folder)
fc6e82
-        util.write_file(authorized_keys2,
fc6e82
-                        VALID_CONTENT['ssh-xmss@openssh.com'])
fc6e82
+    @patch("cloudinit.util.get_permissions")
fc6e82
+    @patch("cloudinit.util.get_owner")
fc6e82
+    @patch("cloudinit.util.get_group")
fc6e82
+    def test_two_users_local_file_custom(
fc6e82
+        self, m_get_group, m_get_owner, m_get_permissions, m_getpwnam
fc6e82
+    ):
fc6e82
+        keys = {}
fc6e82
+        users = {}
fc6e82
+        mock_permissions = {
fc6e82
+            '/tmp/home/bobby': ('bobby', 'bobby', 0o700),
fc6e82
+            '/tmp/home/bobby/.ssh': ('bobby', 'bobby', 0o700),
fc6e82
+            '/tmp/home/bobby/.ssh/authorized_keys2': ('bobby', 'bobby', 0o600),
fc6e82
+            '/tmp/home/suzie': ('suzie', 'suzie', 0o700),
fc6e82
+            '/tmp/home/suzie/.ssh': ('suzie', 'suzie', 0o700),
fc6e82
+            '/tmp/home/suzie/.ssh/authorized_keys2': ('suzie', 'suzie', 0o600),
fc6e82
+        }
fc6e82
+
fc6e82
+        user_bobby = 'bobby'
fc6e82
+        user_suzie = 'suzie'
fc6e82
+        homes = self.create_fake_users(
fc6e82
+            [user_bobby, user_suzie], mock_permissions, m_get_group,
fc6e82
+            m_get_owner, m_get_permissions, m_getpwnam, users
fc6e82
+        )
fc6e82
+        home_bobby = homes[0]
fc6e82
+        home_suzie = homes[1]
fc6e82
 
fc6e82
-        # /tmp/etc/ssh/authorized_keys = ecdsa
fc6e82
-        authorized_keys_global = self.tmp_path('etc/ssh/authorized_keys2',
fc6e82
-                                               dir="/tmp")
fc6e82
-        util.write_file(authorized_keys_global, VALID_CONTENT['ecdsa'])
fc6e82
+        # /tmp/home/bobby/.ssh/authorized_keys2 = rsa
fc6e82
+        authorized_keys = self.create_user_authorized_file(
fc6e82
+            home_bobby, 'authorized_keys2', 'rsa', keys
fc6e82
+        )
fc6e82
 
fc6e82
-        # /tmp/sshd_config
fc6e82
-        sshd_config = self.tmp_path('sshd_config', dir="/tmp")
fc6e82
-        util.write_file(
fc6e82
-            sshd_config,
fc6e82
-            "AuthorizedKeysFile %s %%h/.ssh/authorized_keys2 %s" %
fc6e82
-            (authorized_keys_global, user_keys)
fc6e82
+        # /tmp/home/suzie/.ssh/authorized_keys2 = rsa
fc6e82
+        authorized_keys2 = self.create_user_authorized_file(
fc6e82
+            home_suzie, 'authorized_keys2', 'ssh-xmss@openssh.com', keys
fc6e82
         )
fc6e82
 
fc6e82
-        # process first user
fc6e82
-        (auth_key_fn, auth_key_entries) = ssh_util.extract_authorized_keys(
fc6e82
-            fpw.pw_name, sshd_config)
fc6e82
-        content = ssh_util.update_authorized_keys(auth_key_entries, [])
fc6e82
+        options = ".ssh/authorized_keys2"
fc6e82
+        sshd_config = self.create_sshd_config(options)
fc6e82
+
fc6e82
+        self.execute_and_check(
fc6e82
+            user_bobby, sshd_config, authorized_keys, keys, delete_keys=False
fc6e82
+        )
fc6e82
+        self.execute_and_check(user_suzie, sshd_config, authorized_keys2, keys)
fc6e82
 
fc6e82
-        self.assertEqual(user_keys, auth_key_fn)
fc6e82
-        self.assertTrue(VALID_CONTENT['rsa'] in content)
fc6e82
-        self.assertTrue(VALID_CONTENT['ecdsa'] in content)
fc6e82
-        self.assertTrue(VALID_CONTENT['dsa'] in content)
fc6e82
-        self.assertFalse(VALID_CONTENT['ssh-xmss@openssh.com'] in content)
fc6e82
+    @patch("cloudinit.ssh_util.pwd.getpwnam")
fc6e82
+    @patch("cloudinit.util.get_permissions")
fc6e82
+    @patch("cloudinit.util.get_owner")
fc6e82
+    @patch("cloudinit.util.get_group")
fc6e82
+    def test_two_users_local_global_files(
fc6e82
+        self, m_get_group, m_get_owner, m_get_permissions, m_getpwnam
fc6e82
+    ):
fc6e82
+        keys = {}
fc6e82
+        users = {}
fc6e82
+        mock_permissions = {
fc6e82
+            '/tmp/home/bobby': ('bobby', 'bobby', 0o700),
fc6e82
+            '/tmp/home/bobby/.ssh': ('bobby', 'bobby', 0o700),
fc6e82
+            '/tmp/home/bobby/.ssh/authorized_keys2': ('bobby', 'bobby', 0o600),
fc6e82
+            '/tmp/home/bobby/.ssh/user_keys3': ('bobby', 'bobby', 0o600),
fc6e82
+            '/tmp/home/suzie': ('suzie', 'suzie', 0o700),
fc6e82
+            '/tmp/home/suzie/.ssh': ('suzie', 'suzie', 0o700),
fc6e82
+            '/tmp/home/suzie/.ssh/authorized_keys2': ('suzie', 'suzie', 0o600),
fc6e82
+            '/tmp/home/suzie/.ssh/user_keys3': ('suzie', 'suzie', 0o600),
fc6e82
+        }
fc6e82
+
fc6e82
+        user_bobby = 'bobby'
fc6e82
+        user_suzie = 'suzie'
fc6e82
+        homes = self.create_fake_users(
fc6e82
+            [user_bobby, user_suzie], mock_permissions, m_get_group,
fc6e82
+            m_get_owner, m_get_permissions, m_getpwnam, users
fc6e82
+        )
fc6e82
+        home_bobby = homes[0]
fc6e82
+        home_suzie = homes[1]
fc6e82
 
fc6e82
-        m_getpwnam.return_value = fpw2
fc6e82
-        # process second user
fc6e82
-        (auth_key_fn, auth_key_entries) = ssh_util.extract_authorized_keys(
fc6e82
-            fpw2.pw_name, sshd_config)
fc6e82
-        content = ssh_util.update_authorized_keys(auth_key_entries, [])
fc6e82
+        # /tmp/home/bobby/.ssh/authorized_keys2 = rsa
fc6e82
+        self.create_user_authorized_file(
fc6e82
+            home_bobby, 'authorized_keys2', 'rsa', keys
fc6e82
+        )
fc6e82
+        # /tmp/home/bobby/.ssh/user_keys3 = dsa
fc6e82
+        user_keys = self.create_user_authorized_file(
fc6e82
+            home_bobby, 'user_keys3', 'dsa', keys
fc6e82
+        )
fc6e82
+
fc6e82
+        # /tmp/home/suzie/.ssh/authorized_keys2 = rsa
fc6e82
+        authorized_keys2 = self.create_user_authorized_file(
fc6e82
+            home_suzie, 'authorized_keys2', 'ssh-xmss@openssh.com', keys
fc6e82
+        )
fc6e82
+
fc6e82
+        # /tmp/etc/ssh/authorized_keys = ecdsa
fc6e82
+        authorized_keys_global = self.create_global_authorized_file(
fc6e82
+            'etc/ssh/authorized_keys2', 'ecdsa', keys
fc6e82
+        )
fc6e82
+
fc6e82
+        options = "%s %s %%h/.ssh/authorized_keys2" % \
fc6e82
+            (authorized_keys_global, user_keys)
fc6e82
+        sshd_config = self.create_sshd_config(options)
fc6e82
 
fc6e82
-        self.assertEqual(authorized_keys2, auth_key_fn)
fc6e82
-        self.assertTrue(VALID_CONTENT['ssh-xmss@openssh.com'] in content)
fc6e82
-        self.assertTrue(VALID_CONTENT['ecdsa'] in content)
fc6e82
-        self.assertTrue(VALID_CONTENT['dsa'] in content)
fc6e82
-        self.assertFalse(VALID_CONTENT['rsa'] in content)
fc6e82
+        self.execute_and_check(
fc6e82
+            user_bobby, sshd_config, user_keys, keys, delete_keys=False
fc6e82
+        )
fc6e82
+        self.execute_and_check(user_suzie, sshd_config, authorized_keys2, keys)
fc6e82
 
fc6e82
+    @patch("cloudinit.util.get_user_groups")
fc6e82
     @patch("cloudinit.ssh_util.pwd.getpwnam")
fc6e82
-    def test_multiple_authorizedkeys_file_multiuser2(self, m_getpwnam):
fc6e82
-        fpw = FakePwEnt(pw_name='bobby', pw_dir='/tmp/home/bobby')
fc6e82
-        m_getpwnam.return_value = fpw
fc6e82
-        user_ssh_folder = "%s/.ssh" % fpw.pw_dir
fc6e82
+    @patch("cloudinit.util.get_permissions")
fc6e82
+    @patch("cloudinit.util.get_owner")
fc6e82
+    @patch("cloudinit.util.get_group")
fc6e82
+    def test_two_users_local_global_files_badguy(
fc6e82
+        self, m_get_group, m_get_owner, m_get_permissions, m_getpwnam,
fc6e82
+        m_get_user_groups
fc6e82
+    ):
fc6e82
+        keys = {}
fc6e82
+        users = {}
fc6e82
+        mock_permissions = {
fc6e82
+            '/tmp/home/bobby': ('bobby', 'bobby', 0o700),
fc6e82
+            '/tmp/home/bobby/.ssh': ('bobby', 'bobby', 0o700),
fc6e82
+            '/tmp/home/bobby/.ssh/authorized_keys2': ('bobby', 'bobby', 0o600),
fc6e82
+            '/tmp/home/bobby/.ssh/user_keys3': ('bobby', 'bobby', 0o600),
fc6e82
+            '/tmp/home/badguy': ('root', 'root', 0o755),
fc6e82
+            '/tmp/home/badguy/home': ('root', 'root', 0o755),
fc6e82
+            '/tmp/home/badguy/home/bobby': ('root', 'root', 0o655),
fc6e82
+        }
fc6e82
+
fc6e82
+        user_bobby = 'bobby'
fc6e82
+        user_badguy = 'badguy'
fc6e82
+        home_bobby, *_ = self.create_fake_users(
fc6e82
+            [user_bobby, user_badguy], mock_permissions, m_get_group,
fc6e82
+            m_get_owner, m_get_permissions, m_getpwnam, users
fc6e82
+        )
fc6e82
+        m_get_user_groups.side_effect = mock_get_user_groups
fc6e82
+
fc6e82
         # /tmp/home/bobby/.ssh/authorized_keys2 = rsa
fc6e82
-        authorized_keys = self.tmp_path('authorized_keys2',
fc6e82
-                                        dir=user_ssh_folder)
fc6e82
-        util.write_file(authorized_keys, VALID_CONTENT['rsa'])
fc6e82
+        authorized_keys = self.create_user_authorized_file(
fc6e82
+            home_bobby, 'authorized_keys2', 'rsa', keys
fc6e82
+        )
fc6e82
         # /tmp/home/bobby/.ssh/user_keys3 = dsa
fc6e82
-        user_keys = self.tmp_path('user_keys3', dir=user_ssh_folder)
fc6e82
-        util.write_file(user_keys, VALID_CONTENT['dsa'])
fc6e82
+        user_keys = self.create_user_authorized_file(
fc6e82
+            home_bobby, 'user_keys3', 'dsa', keys
fc6e82
+        )
fc6e82
 
fc6e82
-        fpw2 = FakePwEnt(pw_name='badguy', pw_dir='/tmp/home/badguy')
fc6e82
-        user_ssh_folder = "%s/.ssh" % fpw2.pw_dir
fc6e82
         # /tmp/home/badguy/home/bobby = ""
fc6e82
         authorized_keys2 = self.tmp_path('home/bobby', dir="/tmp/home/badguy")
fc6e82
+        util.write_file(authorized_keys2, '')
fc6e82
 
fc6e82
         # /tmp/etc/ssh/authorized_keys = ecdsa
fc6e82
-        authorized_keys_global = self.tmp_path('etc/ssh/authorized_keys2',
fc6e82
-                                               dir="/tmp")
fc6e82
-        util.write_file(authorized_keys_global, VALID_CONTENT['ecdsa'])
fc6e82
+        authorized_keys_global = self.create_global_authorized_file(
fc6e82
+            'etc/ssh/authorized_keys2', 'ecdsa', keys
fc6e82
+        )
fc6e82
 
fc6e82
         # /tmp/sshd_config
fc6e82
-        sshd_config = self.tmp_path('sshd_config', dir="/tmp")
fc6e82
-        util.write_file(
fc6e82
-            sshd_config,
fc6e82
-            "AuthorizedKeysFile %s %%h/.ssh/authorized_keys2 %s %s" %
fc6e82
-            (authorized_keys_global, user_keys, authorized_keys2)
fc6e82
+        options = "%s %%h/.ssh/authorized_keys2 %s %s" % \
fc6e82
+            (authorized_keys2, authorized_keys_global, user_keys)
fc6e82
+        sshd_config = self.create_sshd_config(options)
fc6e82
+
fc6e82
+        self.execute_and_check(
fc6e82
+            user_bobby, sshd_config, authorized_keys, keys, delete_keys=False
fc6e82
+        )
fc6e82
+        self.execute_and_check(
fc6e82
+            user_badguy, sshd_config, authorized_keys2, keys
fc6e82
         )
fc6e82
 
fc6e82
-        # process first user
fc6e82
-        (auth_key_fn, auth_key_entries) = ssh_util.extract_authorized_keys(
fc6e82
-            fpw.pw_name, sshd_config)
fc6e82
-        content = ssh_util.update_authorized_keys(auth_key_entries, [])
fc6e82
+    @patch("cloudinit.util.get_user_groups")
fc6e82
+    @patch("cloudinit.ssh_util.pwd.getpwnam")
fc6e82
+    @patch("cloudinit.util.get_permissions")
fc6e82
+    @patch("cloudinit.util.get_owner")
fc6e82
+    @patch("cloudinit.util.get_group")
fc6e82
+    def test_two_users_unaccessible_file(
fc6e82
+        self, m_get_group, m_get_owner, m_get_permissions, m_getpwnam,
fc6e82
+        m_get_user_groups
fc6e82
+    ):
fc6e82
+        keys = {}
fc6e82
+        users = {}
fc6e82
+        mock_permissions = {
fc6e82
+            '/tmp/home/bobby': ('bobby', 'bobby', 0o700),
fc6e82
+            '/tmp/home/bobby/.ssh': ('bobby', 'bobby', 0o700),
fc6e82
+            '/tmp/home/bobby/.ssh/authorized_keys': ('bobby', 'bobby', 0o600),
fc6e82
+
fc6e82
+            '/tmp/etc': ('root', 'root', 0o755),
fc6e82
+            '/tmp/etc/ssh': ('root', 'root', 0o755),
fc6e82
+            '/tmp/etc/ssh/userkeys': ('root', 'root', 0o700),
fc6e82
+            '/tmp/etc/ssh/userkeys/bobby': ('bobby', 'bobby', 0o600),
fc6e82
+            '/tmp/etc/ssh/userkeys/badguy': ('badguy', 'badguy', 0o600),
fc6e82
+
fc6e82
+            '/tmp/home/badguy': ('badguy', 'badguy', 0o700),
fc6e82
+            '/tmp/home/badguy/.ssh': ('badguy', 'badguy', 0o700),
fc6e82
+            '/tmp/home/badguy/.ssh/authorized_keys':
fc6e82
+                ('badguy', 'badguy', 0o600),
fc6e82
+        }
fc6e82
+
fc6e82
+        user_bobby = 'bobby'
fc6e82
+        user_badguy = 'badguy'
fc6e82
+        homes = self.create_fake_users(
fc6e82
+            [user_bobby, user_badguy], mock_permissions, m_get_group,
fc6e82
+            m_get_owner, m_get_permissions, m_getpwnam, users
fc6e82
+        )
fc6e82
+        m_get_user_groups.side_effect = mock_get_user_groups
fc6e82
+        home_bobby = homes[0]
fc6e82
+        home_badguy = homes[1]
fc6e82
 
fc6e82
-        self.assertEqual(user_keys, auth_key_fn)
fc6e82
-        self.assertTrue(VALID_CONTENT['rsa'] in content)
fc6e82
-        self.assertTrue(VALID_CONTENT['ecdsa'] in content)
fc6e82
-        self.assertTrue(VALID_CONTENT['dsa'] in content)
fc6e82
+        # /tmp/home/bobby/.ssh/authorized_keys = rsa
fc6e82
+        authorized_keys = self.create_user_authorized_file(
fc6e82
+            home_bobby, 'authorized_keys', 'rsa', keys
fc6e82
+        )
fc6e82
+        # /tmp/etc/ssh/userkeys/bobby = dsa
fc6e82
+        # assume here that we can bypass userkeys, despite permissions
fc6e82
+        self.create_global_authorized_file(
fc6e82
+            'etc/ssh/userkeys/bobby', 'dsa', keys
fc6e82
+        )
fc6e82
 
fc6e82
-        m_getpwnam.return_value = fpw2
fc6e82
-        # process second user
fc6e82
-        (auth_key_fn, auth_key_entries) = ssh_util.extract_authorized_keys(
fc6e82
-            fpw2.pw_name, sshd_config)
fc6e82
-        content = ssh_util.update_authorized_keys(auth_key_entries, [])
fc6e82
+        # /tmp/home/badguy/.ssh/authorized_keys = ssh-xmss@openssh.com
fc6e82
+        authorized_keys2 = self.create_user_authorized_file(
fc6e82
+            home_badguy, 'authorized_keys', 'ssh-xmss@openssh.com', keys
fc6e82
+        )
fc6e82
 
fc6e82
-        # badguy should not take the key from the other user!
fc6e82
-        self.assertEqual(authorized_keys2, auth_key_fn)
fc6e82
-        self.assertTrue(VALID_CONTENT['ecdsa'] in content)
fc6e82
-        self.assertTrue(VALID_CONTENT['dsa'] in content)
fc6e82
-        self.assertFalse(VALID_CONTENT['rsa'] in content)
fc6e82
+        # /tmp/etc/ssh/userkeys/badguy = ecdsa
fc6e82
+        self.create_global_authorized_file(
fc6e82
+            'etc/ssh/userkeys/badguy', 'ecdsa', keys
fc6e82
+        )
fc6e82
+
fc6e82
+        # /tmp/sshd_config
fc6e82
+        options = "/tmp/etc/ssh/userkeys/%u .ssh/authorized_keys"
fc6e82
+        sshd_config = self.create_sshd_config(options)
fc6e82
+
fc6e82
+        self.execute_and_check(
fc6e82
+            user_bobby, sshd_config, authorized_keys, keys, delete_keys=False
fc6e82
+        )
fc6e82
+        self.execute_and_check(
fc6e82
+            user_badguy, sshd_config, authorized_keys2, keys
fc6e82
+        )
fc6e82
+
fc6e82
+    @patch("cloudinit.util.get_user_groups")
fc6e82
+    @patch("cloudinit.ssh_util.pwd.getpwnam")
fc6e82
+    @patch("cloudinit.util.get_permissions")
fc6e82
+    @patch("cloudinit.util.get_owner")
fc6e82
+    @patch("cloudinit.util.get_group")
fc6e82
+    def test_two_users_accessible_file(
fc6e82
+        self, m_get_group, m_get_owner, m_get_permissions, m_getpwnam,
fc6e82
+        m_get_user_groups
fc6e82
+    ):
fc6e82
+        keys = {}
fc6e82
+        users = {}
fc6e82
+        mock_permissions = {
fc6e82
+            '/tmp/home/bobby': ('bobby', 'bobby', 0o700),
fc6e82
+            '/tmp/home/bobby/.ssh': ('bobby', 'bobby', 0o700),
fc6e82
+            '/tmp/home/bobby/.ssh/authorized_keys': ('bobby', 'bobby', 0o600),
fc6e82
+
fc6e82
+            '/tmp/etc': ('root', 'root', 0o755),
fc6e82
+            '/tmp/etc/ssh': ('root', 'root', 0o755),
fc6e82
+            '/tmp/etc/ssh/userkeys': ('root', 'root', 0o755),
fc6e82
+            '/tmp/etc/ssh/userkeys/bobby': ('bobby', 'bobby', 0o600),
fc6e82
+            '/tmp/etc/ssh/userkeys/badguy': ('badguy', 'badguy', 0o600),
fc6e82
+
fc6e82
+            '/tmp/home/badguy': ('badguy', 'badguy', 0o700),
fc6e82
+            '/tmp/home/badguy/.ssh': ('badguy', 'badguy', 0o700),
fc6e82
+            '/tmp/home/badguy/.ssh/authorized_keys':
fc6e82
+                ('badguy', 'badguy', 0o600),
fc6e82
+        }
fc6e82
+
fc6e82
+        user_bobby = 'bobby'
fc6e82
+        user_badguy = 'badguy'
fc6e82
+        homes = self.create_fake_users(
fc6e82
+            [user_bobby, user_badguy], mock_permissions, m_get_group,
fc6e82
+            m_get_owner, m_get_permissions, m_getpwnam, users
fc6e82
+        )
fc6e82
+        m_get_user_groups.side_effect = mock_get_user_groups
fc6e82
+        home_bobby = homes[0]
fc6e82
+        home_badguy = homes[1]
fc6e82
+
fc6e82
+        # /tmp/home/bobby/.ssh/authorized_keys = rsa
fc6e82
+        self.create_user_authorized_file(
fc6e82
+            home_bobby, 'authorized_keys', 'rsa', keys
fc6e82
+        )
fc6e82
+        # /tmp/etc/ssh/userkeys/bobby = dsa
fc6e82
+        # assume here that we can bypass userkeys, despite permissions
fc6e82
+        authorized_keys = self.create_global_authorized_file(
fc6e82
+            'etc/ssh/userkeys/bobby', 'dsa', keys
fc6e82
+        )
fc6e82
+
fc6e82
+        # /tmp/home/badguy/.ssh/authorized_keys = ssh-xmss@openssh.com
fc6e82
+        self.create_user_authorized_file(
fc6e82
+            home_badguy, 'authorized_keys', 'ssh-xmss@openssh.com', keys
fc6e82
+        )
fc6e82
+
fc6e82
+        # /tmp/etc/ssh/userkeys/badguy = ecdsa
fc6e82
+        authorized_keys2 = self.create_global_authorized_file(
fc6e82
+            'etc/ssh/userkeys/badguy', 'ecdsa', keys
fc6e82
+        )
fc6e82
+
fc6e82
+        # /tmp/sshd_config
fc6e82
+        options = "/tmp/etc/ssh/userkeys/%u .ssh/authorized_keys"
fc6e82
+        sshd_config = self.create_sshd_config(options)
fc6e82
+
fc6e82
+        self.execute_and_check(
fc6e82
+            user_bobby, sshd_config, authorized_keys, keys, delete_keys=False
fc6e82
+        )
fc6e82
+        self.execute_and_check(
fc6e82
+            user_badguy, sshd_config, authorized_keys2, keys
fc6e82
+        )
fc6e82
+
fc6e82
+    @patch("cloudinit.util.get_user_groups")
fc6e82
+    @patch("cloudinit.ssh_util.pwd.getpwnam")
fc6e82
+    @patch("cloudinit.util.get_permissions")
fc6e82
+    @patch("cloudinit.util.get_owner")
fc6e82
+    @patch("cloudinit.util.get_group")
fc6e82
+    def test_two_users_hardcoded_single_user_file(
fc6e82
+        self, m_get_group, m_get_owner, m_get_permissions, m_getpwnam,
fc6e82
+        m_get_user_groups
fc6e82
+    ):
fc6e82
+        keys = {}
fc6e82
+        users = {}
fc6e82
+        mock_permissions = {
fc6e82
+            '/tmp/home/bobby': ('bobby', 'bobby', 0o700),
fc6e82
+            '/tmp/home/bobby/.ssh': ('bobby', 'bobby', 0o700),
fc6e82
+            '/tmp/home/bobby/.ssh/authorized_keys': ('bobby', 'bobby', 0o600),
fc6e82
+
fc6e82
+            '/tmp/home/suzie': ('suzie', 'suzie', 0o700),
fc6e82
+            '/tmp/home/suzie/.ssh': ('suzie', 'suzie', 0o700),
fc6e82
+            '/tmp/home/suzie/.ssh/authorized_keys': ('suzie', 'suzie', 0o600),
fc6e82
+        }
fc6e82
+
fc6e82
+        user_bobby = 'bobby'
fc6e82
+        user_suzie = 'suzie'
fc6e82
+        homes = self.create_fake_users(
fc6e82
+            [user_bobby, user_suzie], mock_permissions, m_get_group,
fc6e82
+            m_get_owner, m_get_permissions, m_getpwnam, users
fc6e82
+        )
fc6e82
+        home_bobby = homes[0]
fc6e82
+        home_suzie = homes[1]
fc6e82
+        m_get_user_groups.side_effect = mock_get_user_groups
fc6e82
+
fc6e82
+        # /tmp/home/bobby/.ssh/authorized_keys = rsa
fc6e82
+        authorized_keys = self.create_user_authorized_file(
fc6e82
+            home_bobby, 'authorized_keys', 'rsa', keys
fc6e82
+        )
fc6e82
+
fc6e82
+        # /tmp/home/suzie/.ssh/authorized_keys = ssh-xmss@openssh.com
fc6e82
+        self.create_user_authorized_file(
fc6e82
+            home_suzie, 'authorized_keys', 'ssh-xmss@openssh.com', keys
fc6e82
+        )
fc6e82
+
fc6e82
+        # /tmp/sshd_config
fc6e82
+        options = "%s" % (authorized_keys)
fc6e82
+        sshd_config = self.create_sshd_config(options)
fc6e82
+
fc6e82
+        self.execute_and_check(
fc6e82
+            user_bobby, sshd_config, authorized_keys, keys, delete_keys=False
fc6e82
+        )
fc6e82
+        default = "%s/.ssh/authorized_keys" % home_suzie
fc6e82
+        self.execute_and_check(user_suzie, sshd_config, default, keys)
fc6e82
+
fc6e82
+    @patch("cloudinit.util.get_user_groups")
fc6e82
+    @patch("cloudinit.ssh_util.pwd.getpwnam")
fc6e82
+    @patch("cloudinit.util.get_permissions")
fc6e82
+    @patch("cloudinit.util.get_owner")
fc6e82
+    @patch("cloudinit.util.get_group")
fc6e82
+    def test_two_users_hardcoded_single_user_file_inverted(
fc6e82
+        self, m_get_group, m_get_owner, m_get_permissions, m_getpwnam,
fc6e82
+        m_get_user_groups
fc6e82
+    ):
fc6e82
+        keys = {}
fc6e82
+        users = {}
fc6e82
+        mock_permissions = {
fc6e82
+            '/tmp/home/bobby': ('bobby', 'bobby', 0o700),
fc6e82
+            '/tmp/home/bobby/.ssh': ('bobby', 'bobby', 0o700),
fc6e82
+            '/tmp/home/bobby/.ssh/authorized_keys': ('bobby', 'bobby', 0o600),
fc6e82
+
fc6e82
+            '/tmp/home/suzie': ('suzie', 'suzie', 0o700),
fc6e82
+            '/tmp/home/suzie/.ssh': ('suzie', 'suzie', 0o700),
fc6e82
+            '/tmp/home/suzie/.ssh/authorized_keys': ('suzie', 'suzie', 0o600),
fc6e82
+        }
fc6e82
+
fc6e82
+        user_bobby = 'bobby'
fc6e82
+        user_suzie = 'suzie'
fc6e82
+        homes = self.create_fake_users(
fc6e82
+            [user_bobby, user_suzie], mock_permissions, m_get_group,
fc6e82
+            m_get_owner, m_get_permissions, m_getpwnam, users
fc6e82
+        )
fc6e82
+        home_bobby = homes[0]
fc6e82
+        home_suzie = homes[1]
fc6e82
+        m_get_user_groups.side_effect = mock_get_user_groups
fc6e82
+
fc6e82
+        # /tmp/home/bobby/.ssh/authorized_keys = rsa
fc6e82
+        self.create_user_authorized_file(
fc6e82
+            home_bobby, 'authorized_keys', 'rsa', keys
fc6e82
+        )
fc6e82
+
fc6e82
+        # /tmp/home/suzie/.ssh/authorized_keys = ssh-xmss@openssh.com
fc6e82
+        authorized_keys2 = self.create_user_authorized_file(
fc6e82
+            home_suzie, 'authorized_keys', 'ssh-xmss@openssh.com', keys
fc6e82
+        )
fc6e82
+
fc6e82
+        # /tmp/sshd_config
fc6e82
+        options = "%s" % (authorized_keys2)
fc6e82
+        sshd_config = self.create_sshd_config(options)
fc6e82
+
fc6e82
+        default = "%s/.ssh/authorized_keys" % home_bobby
fc6e82
+        self.execute_and_check(
fc6e82
+            user_bobby, sshd_config, default, keys, delete_keys=False
fc6e82
+        )
fc6e82
+        self.execute_and_check(user_suzie, sshd_config, authorized_keys2, keys)
fc6e82
+
fc6e82
+    @patch("cloudinit.util.get_user_groups")
fc6e82
+    @patch("cloudinit.ssh_util.pwd.getpwnam")
fc6e82
+    @patch("cloudinit.util.get_permissions")
fc6e82
+    @patch("cloudinit.util.get_owner")
fc6e82
+    @patch("cloudinit.util.get_group")
fc6e82
+    def test_two_users_hardcoded_user_files(
fc6e82
+        self, m_get_group, m_get_owner, m_get_permissions, m_getpwnam,
fc6e82
+        m_get_user_groups
fc6e82
+    ):
fc6e82
+        keys = {}
fc6e82
+        users = {}
fc6e82
+        mock_permissions = {
fc6e82
+            '/tmp/home/bobby': ('bobby', 'bobby', 0o700),
fc6e82
+            '/tmp/home/bobby/.ssh': ('bobby', 'bobby', 0o700),
fc6e82
+            '/tmp/home/bobby/.ssh/authorized_keys': ('bobby', 'bobby', 0o600),
fc6e82
+
fc6e82
+            '/tmp/home/suzie': ('suzie', 'suzie', 0o700),
fc6e82
+            '/tmp/home/suzie/.ssh': ('suzie', 'suzie', 0o700),
fc6e82
+            '/tmp/home/suzie/.ssh/authorized_keys': ('suzie', 'suzie', 0o600),
fc6e82
+        }
fc6e82
+
fc6e82
+        user_bobby = 'bobby'
fc6e82
+        user_suzie = 'suzie'
fc6e82
+        homes = self.create_fake_users(
fc6e82
+            [user_bobby, user_suzie], mock_permissions, m_get_group,
fc6e82
+            m_get_owner, m_get_permissions, m_getpwnam, users
fc6e82
+        )
fc6e82
+        home_bobby = homes[0]
fc6e82
+        home_suzie = homes[1]
fc6e82
+        m_get_user_groups.side_effect = mock_get_user_groups
fc6e82
+
fc6e82
+        # /tmp/home/bobby/.ssh/authorized_keys = rsa
fc6e82
+        authorized_keys = self.create_user_authorized_file(
fc6e82
+            home_bobby, 'authorized_keys', 'rsa', keys
fc6e82
+        )
fc6e82
+
fc6e82
+        # /tmp/home/suzie/.ssh/authorized_keys = ssh-xmss@openssh.com
fc6e82
+        authorized_keys2 = self.create_user_authorized_file(
fc6e82
+            home_suzie, 'authorized_keys', 'ssh-xmss@openssh.com', keys
fc6e82
+        )
fc6e82
+
fc6e82
+        # /tmp/etc/ssh/authorized_keys = ecdsa
fc6e82
+        authorized_keys_global = self.create_global_authorized_file(
fc6e82
+            'etc/ssh/authorized_keys', 'ecdsa', keys
fc6e82
+        )
fc6e82
+
fc6e82
+        # /tmp/sshd_config
fc6e82
+        options = "%s %s %s" % \
fc6e82
+            (authorized_keys_global, authorized_keys, authorized_keys2)
fc6e82
+        sshd_config = self.create_sshd_config(options)
fc6e82
+
fc6e82
+        self.execute_and_check(
fc6e82
+            user_bobby, sshd_config, authorized_keys, keys, delete_keys=False
fc6e82
+        )
fc6e82
+        self.execute_and_check(user_suzie, sshd_config, authorized_keys2, keys)
fc6e82
 
fc6e82
 # vi: ts=4 expandtab
fc6e82
-- 
fc6e82
2.27.0
fc6e82