← Back to team overview

cloud-init-dev team mailing list archive

[Merge] ~yeazelm/cloud-init:ssh_authorizedkeys into cloud-init:master

 

Matthew Yeazel has proposed merging ~yeazelm/cloud-init:ssh_authorizedkeys into cloud-init:master.

Requested reviews:
  cloud-init commiters (cloud-init-dev)

For more details, see:
https://code.launchpad.net/~yeazelm/cloud-init/+git/cloud-init/+merge/331897

Fix issue with multiple AuthorizedKeysFile entries

If a user puts two entries in the AuthorizedKeysFile, cloud-init
will manage the keys in the first file.

https://bugs.launchpad.net/cloud-init/+bug/1404060
-- 
Your team cloud-init commiters is requested to review the proposed merge of ~yeazelm/cloud-init:ssh_authorizedkeys into cloud-init:master.
diff --git a/cloudinit/ssh_util.py b/cloudinit/ssh_util.py
index b95b956..1f8feae 100644
--- a/cloudinit/ssh_util.py
+++ b/cloudinit/ssh_util.py
@@ -9,6 +9,8 @@
 import os
 import pwd
 
+from shlex import split as shell_split
+
 from cloudinit import log as logging
 from cloudinit import util
 
@@ -220,6 +222,11 @@ def extract_authorized_keys(username):
             # authenticated and %u is replaced by the username of that user.
             ssh_cfg = parse_ssh_config_map(DEF_SSHD_CFG)
             auth_key_fn = ssh_cfg.get("authorizedkeysfile", '').strip()
+            # AuthorizedKeysFile can have multiple entries separated with
+            # whitespace, this will set auth_key_fn to the first entry, we
+            # will manage keys in the first file listed, but not break
+            # configuration for more than one file.
+            auth_key_fn = shell_split(auth_key_fn)[0]
             if not auth_key_fn:
                 auth_key_fn = "%h/.ssh/authorized_keys"
             auth_key_fn = auth_key_fn.replace("%h", pw_ent.pw_dir)
diff --git a/tests/unittests/test_sshutil.py b/tests/unittests/test_sshutil.py
index 2a8e6ab..32d7fc9 100644
--- a/tests/unittests/test_sshutil.py
+++ b/tests/unittests/test_sshutil.py
@@ -1,6 +1,9 @@
 # This file is part of cloud-init. See LICENSE file for license information.
 
 from mock import patch
+from .helpers import mock
+
+from collections import namedtuple
 
 from cloudinit import ssh_util
 from cloudinit.tests import helpers as test_helpers
@@ -193,4 +196,24 @@ class TestParseSSHConfig(test_helpers.TestCase):
         self.assertEqual('foo', ret[0].key)
         self.assertEqual('bar', ret[0].value)
 
+
+def dummy_users_ssh_info(username):
+    # Faking a passwd entry for mocking
+    Passwd = namedtuple("Passwd", ['pw_dir', 'pw_gecos', 'pw_gid', 'pw_name', 'pw_passwd', 'pw_shell', 'pw_uid'])
+    mock_user = Passwd(pw_name=username, pw_passwd="***", pw_uid="101", pw_gid="101", pw_gecos=username,
+                       pw_dir = "/home/{}".format(username), pw_shell = "/bin/bash")
+    return '/home/{}/.ssh'.format(username), mock_user
+
+
+class TestExtractAuthorizedKeys(test_helpers.TestCase):
+
+    @mock.patch("cloudinit.ssh_util.parse_ssh_config_map")
+    def test_two_entries(self, mock_parse_ssh_config_map):
+        both_entries = '%h/.ssh/authorized_keys /etc/ssh/authorized_keys/%u'
+        ssh_util.users_ssh_info = dummy_users_ssh_info
+        mock_parse_ssh_config_map.return_value = {"authorizedkeysfile": both_entries}
+
+        ret = ssh_util.extract_authorized_keys('testuser')
+        self.assertEqual('/home/testuser/.ssh/authorized_keys', ret[0])
+
 # vi: ts=4 expandtab

Follow ups