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