08c715
From 71989367e7a634fdd2af8ef58473975e0ef60464 Mon Sep 17 00:00:00 2001
08c715
From: Emanuele Giuseppe Esposito <eesposit@redhat.com>
08c715
Date: Sat, 21 Aug 2021 13:53:27 +0200
08c715
Subject: [PATCH] Fix home permissions modified by ssh module (SC-338) (#984)
08c715
08c715
RH-Author: Emanuele Giuseppe Esposito <eesposit@redhat.com>
08c715
RH-MergeRequest: 29: Fix home permissions modified by ssh module (SC-338) (#984)
08c715
RH-Commit: [1/1] c409f2609b1d7e024eba77b55a196a4cafadd1d7 (eesposit/cloud-init)
08c715
RH-Bugzilla: 1995840
08c715
RH-Acked-by: Mohamed Gamal Morsy <mmorsy@redhat.com>
08c715
RH-Acked-by: Eduardo Otubo <otubo@redhat.com>
08c715
08c715
TESTED: By me and QA
08c715
BREW: 39178090
08c715
08c715
Fix home permissions modified by ssh module (SC-338) (#984)
08c715
08c715
commit 7d3f5d750f6111c2716143364ea33486df67c927
08c715
Author: James Falcon <therealfalcon@gmail.com>
08c715
Date:   Fri Aug 20 17:09:49 2021 -0500
08c715
08c715
    Fix home permissions modified by ssh module (SC-338) (#984)
08c715
08c715
    Fix home permissions modified by ssh module
08c715
08c715
    In #956, we updated the file and directory permissions for keys not in
08c715
    the user's home directory. We also unintentionally modified the
08c715
    permissions within the home directory as well. These should not change,
08c715
    and this commit changes that back.
08c715
08c715
    LP: #1940233
08c715
08c715
Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
08c715
---
08c715
 cloudinit/ssh_util.py                         |  35 ++++-
08c715
 .../modules/test_ssh_keysfile.py              | 132 +++++++++++++++---
08c715
 2 files changed, 146 insertions(+), 21 deletions(-)
08c715
08c715
diff --git a/cloudinit/ssh_util.py b/cloudinit/ssh_util.py
08c715
index b8a3c8f7..9ccadf09 100644
08c715
--- a/cloudinit/ssh_util.py
08c715
+++ b/cloudinit/ssh_util.py
08c715
@@ -321,23 +321,48 @@ def check_create_path(username, filename, strictmodes):
08c715
         home_folder = os.path.dirname(user_pwent.pw_dir)
08c715
         for directory in directories:
08c715
             parent_folder += "/" + directory
08c715
-            if home_folder.startswith(parent_folder):
08c715
+
08c715
+            # security check, disallow symlinks in the AuthorizedKeysFile path.
08c715
+            if os.path.islink(parent_folder):
08c715
+                LOG.debug(
08c715
+                    "Invalid directory. Symlink exists in path: %s",
08c715
+                    parent_folder)
08c715
+                return False
08c715
+
08c715
+            if os.path.isfile(parent_folder):
08c715
+                LOG.debug(
08c715
+                    "Invalid directory. File exists in path: %s",
08c715
+                    parent_folder)
08c715
+                return False
08c715
+
08c715
+            if (home_folder.startswith(parent_folder) or
08c715
+                    parent_folder == user_pwent.pw_dir):
08c715
                 continue
08c715
 
08c715
-            if not os.path.isdir(parent_folder):
08c715
+            if not os.path.exists(parent_folder):
08c715
                 # directory does not exist, and permission so far are good:
08c715
                 # create the directory, and make it accessible by everyone
08c715
                 # but owned by root, as it might be used by many users.
08c715
                 with util.SeLinuxGuard(parent_folder):
08c715
-                    os.makedirs(parent_folder, mode=0o755, exist_ok=True)
08c715
-                    util.chownbyid(parent_folder, root_pwent.pw_uid,
08c715
-                                   root_pwent.pw_gid)
08c715
+                    mode = 0o755
08c715
+                    uid = root_pwent.pw_uid
08c715
+                    gid = root_pwent.pw_gid
08c715
+                    if parent_folder.startswith(user_pwent.pw_dir):
08c715
+                        mode = 0o700
08c715
+                        uid = user_pwent.pw_uid
08c715
+                        gid = user_pwent.pw_gid
08c715
+                    os.makedirs(parent_folder, mode=mode, exist_ok=True)
08c715
+                    util.chownbyid(parent_folder, uid, gid)
08c715
 
08c715
             permissions = check_permissions(username, parent_folder,
08c715
                                             filename, False, strictmodes)
08c715
             if not permissions:
08c715
                 return False
08c715
 
08c715
+        if os.path.islink(filename) or os.path.isdir(filename):
08c715
+            LOG.debug("%s is not a file!", filename)
08c715
+            return False
08c715
+
08c715
         # check the file
08c715
         if not os.path.exists(filename):
08c715
             # if file does not exist: we need to create it, since the
08c715
diff --git a/tests/integration_tests/modules/test_ssh_keysfile.py b/tests/integration_tests/modules/test_ssh_keysfile.py
08c715
index f82d7649..3159feb9 100644
08c715
--- a/tests/integration_tests/modules/test_ssh_keysfile.py
08c715
+++ b/tests/integration_tests/modules/test_ssh_keysfile.py
08c715
@@ -10,10 +10,10 @@ 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
+_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
+ - {bootcmd}
08c715
 ssh_authorized_keys:
08c715
  - {default}
08c715
 users:
08c715
@@ -24,27 +24,17 @@ users:
08c715
 - name: test_user2
08c715
   ssh_authorized_keys:
08c715
     - {user2}
08c715
-""".format(  # noqa: E501
08c715
+""".format(
08c715
+    bootcmd='{bootcmd}',
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
+def common_verify(client, expected_keys):
08c715
     for user, filename, keys in expected_keys:
08c715
+        # Ensure key is in the key file
08c715
         contents = client.read_from_file(filename)
08c715
         if user in ['ubuntu', 'root']:
08c715
             # Our personal public key gets added by pycloudlib
08c715
@@ -83,3 +73,113 @@ def test_authorized_keys(client: IntegrationInstance):
08c715
                     look_for_keys=False,
08c715
                     allow_agent=False,
08c715
                 )
08c715
+
08c715
+        # Ensure we haven't messed with any /home permissions
08c715
+        # See LP: #1940233
08c715
+        home_dir = '/home/{}'.format(user)
08c715
+        home_perms = '755'
08c715
+        if user == 'root':
08c715
+            home_dir = '/root'
08c715
+            home_perms = '700'
08c715
+        assert '{} {}'.format(user, home_perms) == client.execute(
08c715
+            'stat -c "%U %a" {}'.format(home_dir)
08c715
+        )
08c715
+        if client.execute("test -d {}/.ssh".format(home_dir)).ok:
08c715
+            assert '{} 700'.format(user) == client.execute(
08c715
+                'stat -c "%U %a" {}/.ssh'.format(home_dir)
08c715
+            )
08c715
+        assert '{} 600'.format(user) == client.execute(
08c715
+            'stat -c "%U %a" {}'.format(filename)
08c715
+        )
08c715
+
08c715
+        # Also ensure ssh-keygen works as expected
08c715
+        client.execute('mkdir {}/.ssh'.format(home_dir))
08c715
+        assert client.execute(
08c715
+            "ssh-keygen -b 2048 -t rsa -f {}/.ssh/id_rsa -q -N ''".format(
08c715
+                home_dir)
08c715
+        ).ok
08c715
+        assert client.execute('test -f {}/.ssh/id_rsa'.format(home_dir))
08c715
+        assert client.execute('test -f {}/.ssh/id_rsa.pub'.format(home_dir))
08c715
+
08c715
+    assert 'root 755' == client.execute('stat -c "%U %a" /home')
08c715
+
08c715
+
08c715
+DEFAULT_KEYS_USERDATA = _USERDATA.format(bootcmd='""')
08c715
+
08c715
+
08c715
+@pytest.mark.ubuntu
08c715
+@pytest.mark.user_data(DEFAULT_KEYS_USERDATA)
08c715
+def test_authorized_keys_default(client: IntegrationInstance):
08c715
+    expected_keys = [
08c715
+        ('test_user1', '/home/test_user1/.ssh/authorized_keys',
08c715
+         TEST_USER1_KEYS),
08c715
+        ('test_user2', '/home/test_user2/.ssh/authorized_keys',
08c715
+         TEST_USER2_KEYS),
08c715
+        ('ubuntu', '/home/ubuntu/.ssh/authorized_keys',
08c715
+         TEST_DEFAULT_KEYS),
08c715
+        ('root', '/root/.ssh/authorized_keys', TEST_DEFAULT_KEYS),
08c715
+    ]
08c715
+    common_verify(client, expected_keys)
08c715
+
08c715
+
08c715
+AUTHORIZED_KEYS2_USERDATA = _USERDATA.format(bootcmd=(
08c715
+    "sed -i 's;#AuthorizedKeysFile.*;AuthorizedKeysFile "
08c715
+    "/etc/ssh/authorized_keys %h/.ssh/authorized_keys2;' "
08c715
+    "/etc/ssh/sshd_config"))
08c715
+
08c715
+
08c715
+@pytest.mark.ubuntu
08c715
+@pytest.mark.user_data(AUTHORIZED_KEYS2_USERDATA)
08c715
+def test_authorized_keys2(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
+    common_verify(client, expected_keys)
08c715
+
08c715
+
08c715
+NESTED_KEYS_USERDATA = _USERDATA.format(bootcmd=(
08c715
+    "sed -i 's;#AuthorizedKeysFile.*;AuthorizedKeysFile "
08c715
+    "/etc/ssh/authorized_keys %h/foo/bar/ssh/keys;' "
08c715
+    "/etc/ssh/sshd_config"))
08c715
+
08c715
+
08c715
+@pytest.mark.ubuntu
08c715
+@pytest.mark.user_data(NESTED_KEYS_USERDATA)
08c715
+def test_nested_keys(client: IntegrationInstance):
08c715
+    expected_keys = [
08c715
+        ('test_user1', '/home/test_user1/foo/bar/ssh/keys',
08c715
+         TEST_USER1_KEYS),
08c715
+        ('test_user2', '/home/test_user2/foo/bar/ssh/keys',
08c715
+         TEST_USER2_KEYS),
08c715
+        ('ubuntu', '/home/ubuntu/foo/bar/ssh/keys',
08c715
+         TEST_DEFAULT_KEYS),
08c715
+        ('root', '/root/foo/bar/ssh/keys', TEST_DEFAULT_KEYS),
08c715
+    ]
08c715
+    common_verify(client, expected_keys)
08c715
+
08c715
+
08c715
+EXTERNAL_KEYS_USERDATA = _USERDATA.format(bootcmd=(
08c715
+    "sed -i 's;#AuthorizedKeysFile.*;AuthorizedKeysFile "
08c715
+    "/etc/ssh/authorized_keys /etc/ssh/authorized_keys/%u/keys;' "
08c715
+    "/etc/ssh/sshd_config"))
08c715
+
08c715
+
08c715
+@pytest.mark.ubuntu
08c715
+@pytest.mark.user_data(EXTERNAL_KEYS_USERDATA)
08c715
+def test_external_keys(client: IntegrationInstance):
08c715
+    expected_keys = [
08c715
+        ('test_user1', '/etc/ssh/authorized_keys/test_user1/keys',
08c715
+         TEST_USER1_KEYS),
08c715
+        ('test_user2', '/etc/ssh/authorized_keys/test_user2/keys',
08c715
+         TEST_USER2_KEYS),
08c715
+        ('ubuntu', '/etc/ssh/authorized_keys/ubuntu/keys',
08c715
+         TEST_DEFAULT_KEYS),
08c715
+        ('root', '/etc/ssh/authorized_keys/root/keys', TEST_DEFAULT_KEYS),
08c715
+    ]
08c715
+    common_verify(client, expected_keys)
08c715
-- 
08c715
2.27.0
08c715