sailesh1993 / rpms / cloud-init

Forked from rpms/cloud-init a year ago
Clone
d9e4dd
From 857009723f14e9ad2f5f4c8614d72982b00ec27d Mon Sep 17 00:00:00 2001
d9e4dd
From: Emanuele Giuseppe Esposito <eesposit@redhat.com>
d9e4dd
Date: Mon, 12 Jul 2021 21:47:37 +0200
d9e4dd
Subject: [PATCH 2/2] ssh-util: allow cloudinit to merge all ssh keys into a
d9e4dd
 custom user file, defined in AuthorizedKeysFile (#937)
d9e4dd
d9e4dd
RH-Author: Emanuele Giuseppe Esposito <eesposit@redhat.com>
d9e4dd
RH-MergeRequest: 5: ssh-util: allow cloudinit to merge all ssh keys into a custom user file, defined in AuthorizedKeysFile (#937)
d9e4dd
RH-Commit: [1/1] 3ed352e47c34e2ed2a1f9f5d68bc8b8f9a1365a6 (eesposit/cloud-init-centos-)
d9e4dd
RH-Bugzilla: 1979099
d9e4dd
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
d9e4dd
RH-Acked-by: Mohamed Gamal Morsy <mmorsy@redhat.com>
d9e4dd
d9e4dd
Conflicts: upstream patch modifies tests/integration_tests/util.py, that is
d9e4dd
not present in RHEL.
d9e4dd
d9e4dd
commit 9b52405c6f0de5e00d5ee9c1d13540425d8f6bf5
d9e4dd
Author: Emanuele Giuseppe Esposito <eesposit@redhat.com>
d9e4dd
Date:   Mon Jul 12 20:21:02 2021 +0200
d9e4dd
d9e4dd
    ssh-util: allow cloudinit to merge all ssh keys into a custom user file, defined in AuthorizedKeysFile (#937)
d9e4dd
d9e4dd
    This patch aims to fix LP1911680, by analyzing the files provided
d9e4dd
    in sshd_config and merge all keys into an user-specific file. Also
d9e4dd
    introduces additional tests to cover this specific case.
d9e4dd
d9e4dd
    The file is picked by analyzing the path given in AuthorizedKeysFile.
d9e4dd
d9e4dd
    If it points inside the current user folder (path is /home/user/*), it
d9e4dd
    means it is an user-specific file, so we can copy all user-keys there.
d9e4dd
    If it contains a %u or %h, it means that there will be a specific
d9e4dd
    authorized_keys file for each user, so we can copy all user-keys there.
d9e4dd
    If no path points to an user-specific file, for example when only
d9e4dd
    /etc/ssh/authorized_keys is given, default to ~/.ssh/authorized_keys.
d9e4dd
    Note that if there are more than a single user-specific file, the last
d9e4dd
    one will be picked.
d9e4dd
d9e4dd
    Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
d9e4dd
    Co-authored-by: James Falcon <therealfalcon@gmail.com>
d9e4dd
d9e4dd
    LP: #1911680
d9e4dd
    RHBZ:1862967
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                         |  22 +-
d9e4dd
 .../assets/keys/id_rsa.test1                  |  38 +++
d9e4dd
 .../assets/keys/id_rsa.test1.pub              |   1 +
d9e4dd
 .../assets/keys/id_rsa.test2                  |  38 +++
d9e4dd
 .../assets/keys/id_rsa.test2.pub              |   1 +
d9e4dd
 .../assets/keys/id_rsa.test3                  |  38 +++
d9e4dd
 .../assets/keys/id_rsa.test3.pub              |   1 +
d9e4dd
 .../modules/test_ssh_keysfile.py              |  85 ++++++
d9e4dd
 tests/unittests/test_sshutil.py               | 246 +++++++++++++++++-
d9e4dd
 9 files changed, 456 insertions(+), 14 deletions(-)
d9e4dd
 create mode 100644 tests/integration_tests/assets/keys/id_rsa.test1
d9e4dd
 create mode 100644 tests/integration_tests/assets/keys/id_rsa.test1.pub
d9e4dd
 create mode 100644 tests/integration_tests/assets/keys/id_rsa.test2
d9e4dd
 create mode 100644 tests/integration_tests/assets/keys/id_rsa.test2.pub
d9e4dd
 create mode 100644 tests/integration_tests/assets/keys/id_rsa.test3
d9e4dd
 create mode 100644 tests/integration_tests/assets/keys/id_rsa.test3.pub
d9e4dd
 create mode 100644 tests/integration_tests/modules/test_ssh_keysfile.py
d9e4dd
d9e4dd
diff --git a/cloudinit/ssh_util.py b/cloudinit/ssh_util.py
d9e4dd
index c08042d6..89057262 100644
d9e4dd
--- a/cloudinit/ssh_util.py
d9e4dd
+++ b/cloudinit/ssh_util.py
d9e4dd
@@ -252,13 +252,15 @@ def render_authorizedkeysfile_paths(value, homedir, username):
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
+    user_authorizedkeys_file = default_authorizedkeys_file
d9e4dd
     auth_key_fns = []
d9e4dd
     with util.SeLinuxGuard(ssh_dir, recursive=True):
d9e4dd
         try:
d9e4dd
             ssh_cfg = parse_ssh_config_map(sshd_cfg_file)
d9e4dd
+            key_paths = ssh_cfg.get("authorizedkeysfile",
d9e4dd
+                                    "%h/.ssh/authorized_keys")
d9e4dd
             auth_key_fns = render_authorizedkeysfile_paths(
d9e4dd
-                ssh_cfg.get("authorizedkeysfile", "%h/.ssh/authorized_keys"),
d9e4dd
-                pw_ent.pw_dir, username)
d9e4dd
+                key_paths, pw_ent.pw_dir, username)
d9e4dd
 
d9e4dd
         except (IOError, OSError):
d9e4dd
             # Give up and use a default key filename
d9e4dd
@@ -267,8 +269,22 @@ 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
+    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
+
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 (default_authorizedkeys_file, parse_authorized_keys(auth_key_fns))
d9e4dd
+    return (user_authorizedkeys_file, parse_authorized_keys(auth_key_fns))
d9e4dd
 
d9e4dd
 
d9e4dd
 def setup_user_keys(keys, username, options=None):
d9e4dd
diff --git a/tests/integration_tests/assets/keys/id_rsa.test1 b/tests/integration_tests/assets/keys/id_rsa.test1
d9e4dd
new file mode 100644
d9e4dd
index 00000000..bd4c822e
d9e4dd
--- /dev/null
d9e4dd
+++ b/tests/integration_tests/assets/keys/id_rsa.test1
d9e4dd
@@ -0,0 +1,38 @@
d9e4dd
+-----BEGIN OPENSSH PRIVATE KEY-----
d9e4dd
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
d9e4dd
+NhAAAAAwEAAQAAAYEAtRlG96aJ23URvAgO/bBsuLl+lquc350aSwV98/i8vlvOn5GVcHye
d9e4dd
+t/rXQg4lZ4s0owG3kWyQFY8nvTk+G+UNU8fN0anAzBDi+4MzsejkF9scjTMFmXVrIpICqV
d9e4dd
+3bYQNjPv6r+ubQdkD01du3eB9t5/zl84gtshp0hBdofyz8u1/A25s7fVU67GyI7PdKvaS+
d9e4dd
+yvJSInZnb2e9VQzfJC+qAnN7gUZatBKjdgUtJeiUUeDaVnaS17b0aoT9iBO0sIcQtOTBlY
d9e4dd
+lCjFt1TAMLZ64Hj3SfGZB7Yj0Z+LzFB2IWX1zzsjI68YkYPKOSL/NYhQU9e55kJQ7WnngN
d9e4dd
+HY/2n/A7dNKSFDmgM5c9IWgeZ7fjpsfIYAoJ/CAxFIND+PEHd1gCS6xoEhaUVyh5WH/Xkw
d9e4dd
+Kv1nx4AiZ2BFCE+75kySRLZUJ+5y0r3DU5ktMXeURzVIP7pu0R8DCul+GU+M/+THyWtAEO
d9e4dd
+geaNJ6fYpo2ipDhbmTYt3kk2lMIapRxGBFs+37sdAAAFgGGJssNhibLDAAAAB3NzaC1yc2
d9e4dd
+EAAAGBALUZRvemidt1EbwIDv2wbLi5fparnN+dGksFffP4vL5bzp+RlXB8nrf610IOJWeL
d9e4dd
+NKMBt5FskBWPJ705PhvlDVPHzdGpwMwQ4vuDM7Ho5BfbHI0zBZl1ayKSAqld22EDYz7+q/
d9e4dd
+rm0HZA9NXbt3gfbef85fOILbIadIQXaH8s/LtfwNubO31VOuxsiOz3Sr2kvsryUiJ2Z29n
d9e4dd
+vVUM3yQvqgJze4FGWrQSo3YFLSXolFHg2lZ2kte29GqE/YgTtLCHELTkwZWJQoxbdUwDC2
d9e4dd
+euB490nxmQe2I9Gfi8xQdiFl9c87IyOvGJGDyjki/zWIUFPXueZCUO1p54DR2P9p/wO3TS
d9e4dd
+khQ5oDOXPSFoHme346bHyGAKCfwgMRSDQ/jxB3dYAkusaBIWlFcoeVh/15MCr9Z8eAImdg
d9e4dd
+RQhPu+ZMkkS2VCfuctK9w1OZLTF3lEc1SD+6btEfAwrpfhlPjP/kx8lrQBDoHmjSen2KaN
d9e4dd
+oqQ4W5k2Ld5JNpTCGqUcRgRbPt+7HQAAAAMBAAEAAAGBAJJCTOd70AC2ptEGbR0EHHqADT
d9e4dd
+Wgefy7A94tHFEqxTy0JscGq/uCGimaY7kMdbcPXT59B4VieWeAC2cuUPP0ZHQSfS5ke7oT
d9e4dd
+tU3N47U+0uBVbNS4rUAH7bOo2o9wptnOA5x/z+O+AARRZ6tEXQOd1oSy4gByLf2Wkh2QTi
d9e4dd
+vP6Hln1vlFgKEzcXg6G8fN3MYWxKRhWmZM3DLERMvorlqqSBLcs5VvfZfLKcsKWTExioAq
d9e4dd
+KgwEjYm8T9+rcpsw1xBus3j9k7wCI1Sus6PCDjq0pcYKLMYM7p8ygnU2tRYrOztdIxgWRA
d9e4dd
+w/1oenm1Mqq2tV5xJcBCwCLOGe6SFwkIRywOYc57j5McH98Xhhg9cViyyBdXy/baF0mro+
d9e4dd
+qPhOsWDxqwD4VKZ9UmQ6O8kPNKcc7QcIpFJhcO0g9zbp/MT0KueaWYrTKs8y4lUkTT7Xz6
d9e4dd
++MzlR122/JwlAbBo6Y2kWtB+y+XwBZ0BfyJsm2czDhKm7OI5KfuBNhq0tFfKwOlYBq4QAA
d9e4dd
+AMAyvUof1R8LLISkdO3EFTKn5RGNkPPoBJmGs6LwvU7NSjjLj/wPQe4jsIBc585tvbrddp
d9e4dd
+60h72HgkZ5tqOfdeBYOKqX0qQQBHUEvI6M+NeQTQRev8bCHMLXQ21vzpClnrwNzlja359E
d9e4dd
+uTRfiPRwIlyPLhOUiClBDSAnBI9h82Hkk3zzsQ/xGfsPB7iOjRbW69bMRSVCRpeweCVmWC
d9e4dd
+77DTsEOq69V2TdljhQNIXE5OcOWonIlfgPiI74cdd+dLhzc/AAAADBAO1/JXd2kYiRyNkZ
d9e4dd
+aXTLcwiSgBQIYbobqVP3OEtTclr0P1JAvby3Y4cCaEhkenx+fBqgXAku5lKM+U1Q9AEsMk
d9e4dd
+cjIhaDpb43rU7GPjMn4zHwgGsEKd5pC1yIQ2PlK+cHanAdsDjIg+6RR+fuvid/mBeBOYXb
d9e4dd
+Py0sa3HyekLJmCdx4UEyNASoiNaGFLQVAqo+RACsXy6VMxFH5dqDYlvwrfUQLwxJmse9Vb
d9e4dd
+GEuuPAsklNugZqssC2XOIujFVUpslduQAAAMEAwzVHQVtsc3icCSzEAARpDTUdTbI29OhB
d9e4dd
+/FMBnjzS9/3SWfLuBOSm9heNCHs2jdGNb8cPdKZuY7S9Fx6KuVUPyTbSSYkjj0F4fTeC9g
d9e4dd
+0ym4p4UWYdF67WSWwLORkaG8K0d+G/CXkz8hvKUg6gcZWKBHAE1ROrHu1nsc8v7mkiKq4I
d9e4dd
+bnTw5Q9TgjbWcQWtgPq0wXyyl/K8S1SFdkMCTOHDD0RQ+jTV2WNGVwFTodIRHenX+Rw2g4
d9e4dd
+CHbTWbsFrHR1qFAAAACmphbWVzQG5ld3Q=
d9e4dd
+-----END OPENSSH PRIVATE KEY-----
d9e4dd
diff --git a/tests/integration_tests/assets/keys/id_rsa.test1.pub b/tests/integration_tests/assets/keys/id_rsa.test1.pub
d9e4dd
new file mode 100644
d9e4dd
index 00000000..3d2e26e1
d9e4dd
--- /dev/null
d9e4dd
+++ b/tests/integration_tests/assets/keys/id_rsa.test1.pub
d9e4dd
@@ -0,0 +1 @@
d9e4dd
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC1GUb3ponbdRG8CA79sGy4uX6Wq5zfnRpLBX3z+Ly+W86fkZVwfJ63+tdCDiVnizSjAbeRbJAVjye9OT4b5Q1Tx83RqcDMEOL7gzOx6OQX2xyNMwWZdWsikgKpXdthA2M+/qv65tB2QPTV27d4H23n/OXziC2yGnSEF2h/LPy7X8Dbmzt9VTrsbIjs90q9pL7K8lIidmdvZ71VDN8kL6oCc3uBRlq0EqN2BS0l6JRR4NpWdpLXtvRqhP2IE7SwhxC05MGViUKMW3VMAwtnrgePdJ8ZkHtiPRn4vMUHYhZfXPOyMjrxiRg8o5Iv81iFBT17nmQlDtaeeA0dj/af8Dt00pIUOaAzlz0haB5nt+Omx8hgCgn8IDEUg0P48Qd3WAJLrGgSFpRXKHlYf9eTAq/WfHgCJnYEUIT7vmTJJEtlQn7nLSvcNTmS0xd5RHNUg/um7RHwMK6X4ZT4z/5MfJa0AQ6B5o0np9imjaKkOFuZNi3eSTaUwhqlHEYEWz7fux0= test1@host
d9e4dd
diff --git a/tests/integration_tests/assets/keys/id_rsa.test2 b/tests/integration_tests/assets/keys/id_rsa.test2
d9e4dd
new file mode 100644
d9e4dd
index 00000000..5854d901
d9e4dd
--- /dev/null
d9e4dd
+++ b/tests/integration_tests/assets/keys/id_rsa.test2
d9e4dd
@@ -0,0 +1,38 @@
d9e4dd
+-----BEGIN OPENSSH PRIVATE KEY-----
d9e4dd
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
d9e4dd
+NhAAAAAwEAAQAAAYEAvK50D2PWOc4ikyHVRJS6tDhqzjL5cKiivID4p1X8BYCVw83XAEGO
d9e4dd
+LnItUyVXHNADlh6fpVq1NY6A2JVtygoPF6ZFx8ph7IWMmnhDdnxLLyGsbhd1M1tiXJD/R+
d9e4dd
+3WnGHRJ4PKrQavMLgqHRrieV3QVVfjFSeo6jX/4TruP6ZmvITMZWJrXaGphxJ/pPykEdkO
d9e4dd
+i8AmKU9FNviojyPS2nNtj9B/635IdgWvrd7Vf5Ycsw9MR55LWSidwa856RH62Yl6LpEGTH
d9e4dd
+m1lJiMk1u88JPSqvohhaUkLKkFpcQwcB0m76W1KOyllJsmX8bNXrlZsI+WiiYI7Xl5vQm2
d9e4dd
+17DEuNeavtPAtDMxu8HmTg2UJ55Naxehbfe2lx2k5kYGGw3i1O1OVN2pZ2/OB71LucYd/5
d9e4dd
+qxPaz03wswcGOJYGPkNc40vdES/Scc7Yt8HsnZuzqkyOgzn0HiUCzoYUYLYTpLf+yGmwxS
d9e4dd
+yAEY056aOfkCsboKHOKiOmlJxNaZZFQkX1evep4DAAAFgC7HMbUuxzG1AAAAB3NzaC1yc2
d9e4dd
+EAAAGBALyudA9j1jnOIpMh1USUurQ4as4y+XCooryA+KdV/AWAlcPN1wBBji5yLVMlVxzQ
d9e4dd
+A5Yen6VatTWOgNiVbcoKDxemRcfKYeyFjJp4Q3Z8Sy8hrG4XdTNbYlyQ/0ft1pxh0SeDyq
d9e4dd
+0GrzC4Kh0a4nld0FVX4xUnqOo1/+E67j+mZryEzGVia12hqYcSf6T8pBHZDovAJilPRTb4
d9e4dd
+qI8j0tpzbY/Qf+t+SHYFr63e1X+WHLMPTEeeS1koncGvOekR+tmJei6RBkx5tZSYjJNbvP
d9e4dd
+CT0qr6IYWlJCypBaXEMHAdJu+ltSjspZSbJl/GzV65WbCPloomCO15eb0JttewxLjXmr7T
d9e4dd
+wLQzMbvB5k4NlCeeTWsXoW33tpcdpOZGBhsN4tTtTlTdqWdvzge9S7nGHf+asT2s9N8LMH
d9e4dd
+BjiWBj5DXONL3REv0nHO2LfB7J2bs6pMjoM59B4lAs6GFGC2E6S3/shpsMUsgBGNOemjn5
d9e4dd
+ArG6ChziojppScTWmWRUJF9Xr3qeAwAAAAMBAAEAAAGASj/kkEHbhbfmxzujL2/P4Sfqb+
d9e4dd
+aDXqAeGkwujbs6h/fH99vC5ejmSMTJrVSeaUo6fxLiBDIj6UWA0rpLEBzRP59BCpRL4MXV
d9e4dd
+RNxav/+9nniD4Hb+ug0WMhMlQmsH71ZW9lPYqCpfOq7ec8GmqdgPKeaCCEspH7HMVhfYtd
d9e4dd
+eHylwAC02lrpz1l5/h900sS5G9NaWR3uPA+xbzThDs4uZVkSidjlCNt1QZhDSSk7jA5n34
d9e4dd
+qJ5UTGu9WQDZqyxWKND+RIyQuFAPGQyoyCC1FayHO2sEhT5qHuumL14Mn81XpzoXFoKyql
d9e4dd
+rhBDe+pHhKArBYt92Evch0k1ABKblFxtxLXcvk4Fs7pHi+8k4+Cnazej2kcsu1kURlMZJB
d9e4dd
+w2QT/8BV4uImbH05LtyscQuwGzpIoxqrnHrvg5VbohStmhoOjYybzqqW3/M0qhkn5JgTiy
d9e4dd
+dJcHRJisRnAcmbmEchYtLDi6RW1e022H4I9AFXQqyr5HylBq6ugtWcFCsrcX8ibZ8xAAAA
d9e4dd
+wQCAOPgwae6yZLkrYzRfbxZtGKNmhpI0EtNSDCHYuQQapFZJe7EFENs/VAaIiiut0yajGj
d9e4dd
+c3aoKcwGIoT8TUM8E3GSNW6+WidUOC7H6W+/6N2OYZHRBACGz820xO+UBCl2oSk+dLBlfr
d9e4dd
+IQzBGUWn5uVYCs0/2nxfCdFyHtMK8dMF/ypbdG+o1rXz5y9b7PVG6Mn+o1Rjsdkq7VERmy
d9e4dd
+Pukd8hwATOIJqoKl3TuFyBeYFLqe+0e7uTeswQFw17PF31VjAAAADBAOpJRQb8c6qWqsvv
d9e4dd
+vkve0uMuL0DfWW0G6+SxjPLcV6aTWL5xu0Grd8uBxDkkHU/CDrAwpchXyuLsvbw21Eje/u
d9e4dd
+U5k9nLEscWZwcX7odxlK+EfAY2Bf5+Hd9bH5HMzTRJH8KkWK1EppOLPyiDxz4LZGzPLVyv
d9e4dd
+/1PgSuvXkSWk1KIE4SvSemyxGX2tPVI6uO+URqevfnPOS1tMB7BMQlgkR6eh4bugx9UYx9
d9e4dd
+mwlXonNa4dN0iQxZ7N4rKFBbT/uyB2bQAAAMEAzisnkD8k9Tn8uyhxpWLHwb03X4ZUUHDV
d9e4dd
+zu15e4a8dZ+mM8nHO986913Xz5JujlJKkGwFTvgWkIiR2zqTEauZHARH7gANpaweTm6lPd
d9e4dd
+E4p2S0M3ulY7xtp9lCFIrDhMPPkGq8SFZB6qhgucHcZSRLq6ZDou3S2IdNOzDTpBtkhRCS
d9e4dd
+0zFcdTLh3zZweoy8HGbW36bwB6s1CIL76Pd4F64i0Ms9CCCU6b+E5ArFhYQIsXiDbgHWbD
d9e4dd
+tZRSm2GEgnDGAvAAAACmphbWVzQG5ld3Q=
d9e4dd
+-----END OPENSSH PRIVATE KEY-----
d9e4dd
diff --git a/tests/integration_tests/assets/keys/id_rsa.test2.pub b/tests/integration_tests/assets/keys/id_rsa.test2.pub
d9e4dd
new file mode 100644
d9e4dd
index 00000000..f3831a57
d9e4dd
--- /dev/null
d9e4dd
+++ b/tests/integration_tests/assets/keys/id_rsa.test2.pub
d9e4dd
@@ -0,0 +1 @@
d9e4dd
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC8rnQPY9Y5ziKTIdVElLq0OGrOMvlwqKK8gPinVfwFgJXDzdcAQY4uci1TJVcc0AOWHp+lWrU1joDYlW3KCg8XpkXHymHshYyaeEN2fEsvIaxuF3UzW2JckP9H7dacYdEng8qtBq8wuCodGuJ5XdBVV+MVJ6jqNf/hOu4/pma8hMxlYmtdoamHEn+k/KQR2Q6LwCYpT0U2+KiPI9Lac22P0H/rfkh2Ba+t3tV/lhyzD0xHnktZKJ3BrznpEfrZiXoukQZMebWUmIyTW7zwk9Kq+iGFpSQsqQWlxDBwHSbvpbUo7KWUmyZfxs1euVmwj5aKJgjteXm9CbbXsMS415q+08C0MzG7weZODZQnnk1rF6Ft97aXHaTmRgYbDeLU7U5U3alnb84HvUu5xh3/mrE9rPTfCzBwY4lgY+Q1zjS90RL9Jxzti3weydm7OqTI6DOfQeJQLOhhRgthOkt/7IabDFLIARjTnpo5+QKxugoc4qI6aUnE1plkVCRfV696ngM= test2@host
d9e4dd
diff --git a/tests/integration_tests/assets/keys/id_rsa.test3 b/tests/integration_tests/assets/keys/id_rsa.test3
d9e4dd
new file mode 100644
d9e4dd
index 00000000..2596c762
d9e4dd
--- /dev/null
d9e4dd
+++ b/tests/integration_tests/assets/keys/id_rsa.test3
d9e4dd
@@ -0,0 +1,38 @@
d9e4dd
+-----BEGIN OPENSSH PRIVATE KEY-----
d9e4dd
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
d9e4dd
+NhAAAAAwEAAQAAAYEApPG4MdkYQKD57/qreFrh9GRC22y66qZOWZWRjC887rrbvBzO69hV
d9e4dd
+yJpTIXleJEvpWiHYcjMR5G6NNFsnNtZ4fxDqmSc4vcFj53JsE/XNqLKq6psXadCb5vkNpG
d9e4dd
+bxA+Z5bJlzJ969PgJIIEbgc86sei4kgR2MuPWqtZbY5GkpNCTqWuLYeFK+14oFruA2nyWH
d9e4dd
+9MOIRDHK/d597psHy+LTMtymO7ZPhO571abKw6jvvwiSeDxVE9kV7KAQIuM9/S3gftvgQQ
d9e4dd
+ron3GL34pgmIabdSGdbfHqGDooryJhlbquJZELBN236KgRNTCAjVvUzjjQr1eRP3xssGwV
d9e4dd
+O6ECBGCQLl/aYogAgtwnwj9iXqtfiLK3EwlgjquU4+JQ0CVtLhG3gIZB+qoMThco0pmHTr
d9e4dd
+jtfQCwrztsBBFunSa2/CstuV1mQ5O5ZrZ6ACo9yPRBNkns6+CiKdtMtCtzi3k2RDz9jpYm
d9e4dd
+Pcak03Lr7IkdC1Tp6+jA+//yPHSO1o4CqW89IQzNAAAFgEUd7lZFHe5WAAAAB3NzaC1yc2
d9e4dd
+EAAAGBAKTxuDHZGECg+e/6q3ha4fRkQttsuuqmTlmVkYwvPO6627wczuvYVciaUyF5XiRL
d9e4dd
+6Voh2HIzEeRujTRbJzbWeH8Q6pknOL3BY+dybBP1zaiyquqbF2nQm+b5DaRm8QPmeWyZcy
d9e4dd
+fevT4CSCBG4HPOrHouJIEdjLj1qrWW2ORpKTQk6lri2HhSvteKBa7gNp8lh/TDiEQxyv3e
d9e4dd
+fe6bB8vi0zLcpju2T4Tue9WmysOo778Ikng8VRPZFeygECLjPf0t4H7b4EEK6J9xi9+KYJ
d9e4dd
+iGm3UhnW3x6hg6KK8iYZW6riWRCwTdt+ioETUwgI1b1M440K9XkT98bLBsFTuhAgRgkC5f
d9e4dd
+2mKIAILcJ8I/Yl6rX4iytxMJYI6rlOPiUNAlbS4Rt4CGQfqqDE4XKNKZh0647X0AsK87bA
d9e4dd
+QRbp0mtvwrLbldZkOTuWa2egAqPcj0QTZJ7OvgoinbTLQrc4t5NkQ8/Y6WJj3GpNNy6+yJ
d9e4dd
+HQtU6evowPv/8jx0jtaOAqlvPSEMzQAAAAMBAAEAAAGAGaqbdPZJNdVWzyb8g6/wtSzc0n
d9e4dd
+Qq6dSTIJGLonq/So69HpqFAGIbhymsger24UMGvsXBfpO/1wH06w68HWZmPa+OMeLOi4iK
d9e4dd
+WTuO4dQ/+l5DBlq32/lgKSLcIpb6LhcxEdsW9j9Mx1dnjc45owun/yMq/wRwH1/q/nLIsV
d9e4dd
+JD3R9ZcGcYNDD8DWIm3D17gmw+qbG7hJES+0oh4n0xS2KyZpm7LFOEMDVEA8z+hE/HbryQ
d9e4dd
+vjD1NC91n+qQWD1wKfN3WZDRwip3z1I5VHMpvXrA/spHpa9gzHK5qXNmZSz3/dfA1zHjCR
d9e4dd
+2dHjJnrIUH8nyPfw8t+COC+sQBL3Nr0KUWEFPRM08cOcQm4ctzg17aDIZBONjlZGKlReR8
d9e4dd
+1zfAw84Q70q2spLWLBLXSFblHkaOfijEbejIbaz2UUEQT27WD7RHAORdQlkx7eitk66T9d
d9e4dd
+DzIq/cpYhm5Fs8KZsh3PLldp9nsHbD2Oa9J9LJyI4ryuIW0mVwRdvPSiiYi3K+mDCpAAAA
d9e4dd
+wBe+ugEEJ+V7orb1f4Zez0Bd4FNkEc52WZL4CWbaCtM+ZBg5KnQ6xW14JdC8IS9cNi/I5P
d9e4dd
+yLsBvG4bWPLGgQruuKY6oLueD6BFnKjqF6ACUCiSQldh4BAW1nYc2U48+FFvo3ZQyudFSy
d9e4dd
+QEFlhHmcaNMDo0AIJY5Xnq2BG3nEX7AqdtZ8hhenHwLCRQJatDwSYBHDpSDdh9vpTnGp/2
d9e4dd
+0jBz25Ko4UANzvSAc3sA4yN3jfpoM366TgdNf8x3g1v7yljQAAAMEA0HSQjzH5nhEwB58k
d9e4dd
+mYYxnBYp1wb86zIuVhAyjZaeinvBQSTmLow8sXIHcCVuD3CgBezlU2SX5d9YuvRU9rcthi
d9e4dd
+uzn4wWnbnzYy4SwzkMJXchUAkumFVD8Hq5TNPh2Z+033rLLE08EhYypSeVpuzdpFoStaS9
d9e4dd
+3DUZA2bR/zLZI9MOVZRUcYImNegqIjOYHY8Sbj3/0QPV6+WpUJFMPvvedWhfaOsRMTA6nr
d9e4dd
+VLG4pxkrieVl0UtuRGbzD/exXhXVi7AAAAwQDKkJj4ez/+KZFYlZQKiV0BrfUFcgS6ElFM
d9e4dd
+2CZIEagCtu8eedrwkNqx2FUX33uxdvUTr4c9I3NvWeEEGTB9pgD4lh1x/nxfuhyGXtimFM
d9e4dd
+GnznGV9oyz0DmKlKiKSEGwWf5G+/NiiCwwVJ7wsQQm7TqNtkQ9b8MhWWXC7xlXKUs7dmTa
d9e4dd
+e8AqAndCCMEnbS1UQFO/R5PNcZXkFWDggLQ/eWRYKlrXgdnUgH6h0saOcViKpNJBUXb3+x
d9e4dd
+eauhOY52PS/BcAAAAKamFtZXNAbmV3dAE=
d9e4dd
+-----END OPENSSH PRIVATE KEY-----
d9e4dd
diff --git a/tests/integration_tests/assets/keys/id_rsa.test3.pub b/tests/integration_tests/assets/keys/id_rsa.test3.pub
d9e4dd
new file mode 100644
d9e4dd
index 00000000..057db632
d9e4dd
--- /dev/null
d9e4dd
+++ b/tests/integration_tests/assets/keys/id_rsa.test3.pub
d9e4dd
@@ -0,0 +1 @@
d9e4dd
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCk8bgx2RhAoPnv+qt4WuH0ZELbbLrqpk5ZlZGMLzzuutu8HM7r2FXImlMheV4kS+laIdhyMxHkbo00Wyc21nh/EOqZJzi9wWPncmwT9c2osqrqmxdp0Jvm+Q2kZvED5nlsmXMn3r0+AkggRuBzzqx6LiSBHYy49aq1ltjkaSk0JOpa4th4Ur7XigWu4DafJYf0w4hEMcr93n3umwfL4tMy3KY7tk+E7nvVpsrDqO+/CJJ4PFUT2RXsoBAi4z39LeB+2+BBCuifcYvfimCYhpt1IZ1t8eoYOiivImGVuq4lkQsE3bfoqBE1MICNW9TOONCvV5E/fGywbBU7oQIEYJAuX9piiACC3CfCP2Jeq1+IsrcTCWCOq5Tj4lDQJW0uEbeAhkH6qgxOFyjSmYdOuO19ALCvO2wEEW6dJrb8Ky25XWZDk7lmtnoAKj3I9EE2Sezr4KIp20y0K3OLeTZEPP2OliY9xqTTcuvsiR0LVOnr6MD7//I8dI7WjgKpbz0hDM0= test3@host
d9e4dd
diff --git a/tests/integration_tests/modules/test_ssh_keysfile.py b/tests/integration_tests/modules/test_ssh_keysfile.py
d9e4dd
new file mode 100644
d9e4dd
index 00000000..f82d7649
d9e4dd
--- /dev/null
d9e4dd
+++ b/tests/integration_tests/modules/test_ssh_keysfile.py
d9e4dd
@@ -0,0 +1,85 @@
d9e4dd
+import paramiko
d9e4dd
+import pytest
d9e4dd
+from io import StringIO
d9e4dd
+from paramiko.ssh_exception import SSHException
d9e4dd
+
d9e4dd
+from tests.integration_tests.instances import IntegrationInstance
d9e4dd
+from tests.integration_tests.util import get_test_rsa_keypair
d9e4dd
+
d9e4dd
+TEST_USER1_KEYS = get_test_rsa_keypair('test1')
d9e4dd
+TEST_USER2_KEYS = get_test_rsa_keypair('test2')
d9e4dd
+TEST_DEFAULT_KEYS = get_test_rsa_keypair('test3')
d9e4dd
+
d9e4dd
+USERDATA = """\
d9e4dd
+#cloud-config
d9e4dd
+bootcmd:
d9e4dd
+ - sed -i 's;#AuthorizedKeysFile.*;AuthorizedKeysFile /etc/ssh/authorized_keys %h/.ssh/authorized_keys2;' /etc/ssh/sshd_config
d9e4dd
+ssh_authorized_keys:
d9e4dd
+ - {default}
d9e4dd
+users:
d9e4dd
+- default
d9e4dd
+- name: test_user1
d9e4dd
+  ssh_authorized_keys:
d9e4dd
+    - {user1}
d9e4dd
+- name: test_user2
d9e4dd
+  ssh_authorized_keys:
d9e4dd
+    - {user2}
d9e4dd
+""".format(  # noqa: E501
d9e4dd
+    default=TEST_DEFAULT_KEYS.public_key,
d9e4dd
+    user1=TEST_USER1_KEYS.public_key,
d9e4dd
+    user2=TEST_USER2_KEYS.public_key,
d9e4dd
+)
d9e4dd
+
d9e4dd
+
d9e4dd
+@pytest.mark.ubuntu
d9e4dd
+@pytest.mark.user_data(USERDATA)
d9e4dd
+def test_authorized_keys(client: IntegrationInstance):
d9e4dd
+    expected_keys = [
d9e4dd
+        ('test_user1', '/home/test_user1/.ssh/authorized_keys2',
d9e4dd
+         TEST_USER1_KEYS),
d9e4dd
+        ('test_user2', '/home/test_user2/.ssh/authorized_keys2',
d9e4dd
+         TEST_USER2_KEYS),
d9e4dd
+        ('ubuntu', '/home/ubuntu/.ssh/authorized_keys2',
d9e4dd
+         TEST_DEFAULT_KEYS),
d9e4dd
+        ('root', '/root/.ssh/authorized_keys2', TEST_DEFAULT_KEYS),
d9e4dd
+    ]
d9e4dd
+
d9e4dd
+    for user, filename, keys in expected_keys:
d9e4dd
+        contents = client.read_from_file(filename)
d9e4dd
+        if user in ['ubuntu', 'root']:
d9e4dd
+            # Our personal public key gets added by pycloudlib
d9e4dd
+            lines = contents.split('\n')
d9e4dd
+            assert len(lines) == 2
d9e4dd
+            assert keys.public_key.strip() in contents
d9e4dd
+        else:
d9e4dd
+            assert contents.strip() == keys.public_key.strip()
d9e4dd
+
d9e4dd
+        # Ensure we can actually connect
d9e4dd
+        ssh = paramiko.SSHClient()
d9e4dd
+        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
d9e4dd
+        paramiko_key = paramiko.RSAKey.from_private_key(StringIO(
d9e4dd
+            keys.private_key))
d9e4dd
+
d9e4dd
+        # Will fail with AuthenticationException if
d9e4dd
+        # we cannot connect
d9e4dd
+        ssh.connect(
d9e4dd
+            client.instance.ip,
d9e4dd
+            username=user,
d9e4dd
+            pkey=paramiko_key,
d9e4dd
+            look_for_keys=False,
d9e4dd
+            allow_agent=False,
d9e4dd
+        )
d9e4dd
+
d9e4dd
+        # Ensure other uses can't connect using our key
d9e4dd
+        other_users = [u[0] for u in expected_keys if u[2] != keys]
d9e4dd
+        for other_user in other_users:
d9e4dd
+            with pytest.raises(SSHException):
d9e4dd
+                print('trying to connect as {} with key from {}'.format(
d9e4dd
+                    other_user, user))
d9e4dd
+                ssh.connect(
d9e4dd
+                    client.instance.ip,
d9e4dd
+                    username=other_user,
d9e4dd
+                    pkey=paramiko_key,
d9e4dd
+                    look_for_keys=False,
d9e4dd
+                    allow_agent=False,
d9e4dd
+                )
d9e4dd
diff --git a/tests/unittests/test_sshutil.py b/tests/unittests/test_sshutil.py
d9e4dd
index fd1d1bac..bcb8044f 100644
d9e4dd
--- a/tests/unittests/test_sshutil.py
d9e4dd
+++ b/tests/unittests/test_sshutil.py
d9e4dd
@@ -570,20 +570,33 @@ class TestBasicAuthorizedKeyParse(test_helpers.CiTestCase):
d9e4dd
             ssh_util.render_authorizedkeysfile_paths(
d9e4dd
                 "%h/.keys", "/homedirs/bobby", "bobby"))
d9e4dd
 
d9e4dd
+    def test_all(self):
d9e4dd
+        self.assertEqual(
d9e4dd
+            ["/homedirs/bobby/.keys", "/homedirs/bobby/.secret/keys",
d9e4dd
+             "/keys/path1", "/opt/bobby/keys"],
d9e4dd
+            ssh_util.render_authorizedkeysfile_paths(
d9e4dd
+                "%h/.keys .secret/keys /keys/path1 /opt/%u/keys",
d9e4dd
+                "/homedirs/bobby", "bobby"))
d9e4dd
+
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='/home2/bobby')
d9e4dd
+        fpw = FakePwEnt(pw_name='bobby', pw_dir='/tmp/home2/bobby')
d9e4dd
         m_getpwnam.return_value = fpw
d9e4dd
-        authorized_keys = self.tmp_path('authorized_keys')
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
-        user_keys = self.tmp_path('user_keys')
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
-        sshd_config = self.tmp_path('sshd_config')
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" % (authorized_keys, user_keys)
d9e4dd
@@ -593,33 +606,244 @@ class TestMultipleSshAuthorizedKeysFile(test_helpers.CiTestCase):
d9e4dd
             fpw.pw_name, sshd_config)
d9e4dd
         content = ssh_util.update_authorized_keys(auth_key_entries, [])
d9e4dd
 
d9e4dd
-        self.assertEqual("%s/.ssh/authorized_keys" % fpw.pw_dir, auth_key_fn)
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
 
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='/home/suzie')
d9e4dd
+        fpw = FakePwEnt(pw_name='suzie', pw_dir='/tmp/home/suzie')
d9e4dd
         m_getpwnam.return_value = fpw
d9e4dd
-        authorized_keys = self.tmp_path('authorized_keys')
d9e4dd
+        user_ssh_folder = "%s/.ssh" % fpw.pw_dir
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
 
d9e4dd
-        user_keys = self.tmp_path('user_keys')
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
 
d9e4dd
-        sshd_config = self.tmp_path('sshd_config')
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" % (authorized_keys, user_keys)
d9e4dd
+            "AuthorizedKeysFile %s %s" % (user_keys, authorized_keys)
d9e4dd
         )
d9e4dd
 
d9e4dd
         (auth_key_fn, auth_key_entries) = ssh_util.extract_authorized_keys(
d9e4dd
-            fpw.pw_name, sshd_config
d9e4dd
+            fpw.pw_name, sshd_config)
d9e4dd
+        content = ssh_util.update_authorized_keys(auth_key_entries, [])
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
+
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
+
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/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
+
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
+        )
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
+
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
+
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
+
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
+
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
+        # /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
+
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
+        )
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
+
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
+
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
+
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
+
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
         )
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
 
d9e4dd
         self.assertEqual("%s/.ssh/authorized_keys" % fpw.pw_dir, auth_key_fn)
d9e4dd
         self.assertTrue(VALID_CONTENT['rsa'] in content)
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
+
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
+
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
+        )
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
+
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
+
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
+
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
+
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
+        # /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
+        # /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
+
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
+
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
+
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
+        )
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
+
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
+
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
+
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
 
d9e4dd
 # vi: ts=4 expandtab
d9e4dd
-- 
d9e4dd
2.27.0
d9e4dd