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