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