Skip to content

Stop storing system keys in user's authorized keys file and check for folder permissions#956

Merged
TheRealFalcon merged 4 commits into
canonical:mainfrom
esposem:pr_948
Aug 9, 2021
Merged

Stop storing system keys in user's authorized keys file and check for folder permissions#956
TheRealFalcon merged 4 commits into
canonical:mainfrom
esposem:pr_948

Conversation

@esposem
Copy link
Copy Markdown
Contributor

@esposem esposem commented Jul 30, 2021

Proposed Commit Message

This is the continuation of PR #948. In addition to the fix proposed by @TheRealFalcon, it contains the permission fix discussed in the PR, with additional test cases and the fix for the old ones

In /etc/ssh/sshd_config, it is possible to define a custom authorized_keys file
that will contain the keys allowed to access the machine via
the AuthorizedKeysFile option.
Cloudinit is able to add user-specific keys to the existing ones, but we need
to be careful on which of the authorized_keys files listed to pick.
Chosing for example a file that is shared by all user will cause security
issues, because the owner of that key can then access also other users.
    
We therefore pick an authorized_keys file only if it satisfies the following
conditions:
    1. it is not a "global" file, ie it must be defined in AuthorizedKeysFile
        with %u, %h or be in  /home/<user>. This avoids security issues.
    2. it must comply with ssh permission requirements, otherwise the ssh agent
        won't use that file.
    
We also need to consider the case when the chosen authorized_keys file
does not exist. In this case, the existing behavior of cloud-init is
to create the new file. We therefore need to be sure that the file complies
with ssh permissions too, by setting:
    - the actual file to permission 600, and owned by the user
    - the directories in the path that do not exist must be root owned and with
      permission 755.

Test Steps

See PR #948

Checklist:

  • My code follows the process laid out in the documentation
  • I have updated or added any unit tests accordingly
  • I have updated or added any documentation accordingly

@esposem esposem marked this pull request as draft July 30, 2021 12:03
@esposem esposem force-pushed the pr_948 branch 2 times, most recently from fe30e10 to 3424077 Compare August 2, 2021 20:46
@esposem esposem force-pushed the pr_948 branch 4 times, most recently from cfde209 to 73f2d04 Compare August 3, 2021 14:40
@esposem esposem marked this pull request as ready for review August 4, 2021 20:26
@esposem
Copy link
Copy Markdown
Contributor Author

esposem commented Aug 4, 2021

@TheRealFalcon I updated the PR with what I believe are the final changes for this fix.
The things that I added and are not mentioned in PR #948 are:

  • support to create the chosen user-specific authorized_keys file if it doesn't exist, with the right permissions.
  • we stop at the first user-specific authorized_keys file that we can use. If we don't do that, we would create folders and empty authorized_keys files for all user-specific options given in AuthorizedKeysFile.

The issue still lays in the unit tests, we have:

  1. Right now I noted that FakePwEnt does not support pw_gid, but even adding it creates problems when I try to fetch which group the fake user owns to
  2. the permission on the files for the fake users are wrong.
    I am clearly doing something wrong here, so please advise.

@esposem
Copy link
Copy Markdown
Contributor Author

esposem commented Aug 4, 2021

@blackboxsw since you approved my original PR #937, I thought about including you too to this PR.

@TheRealFalcon
Copy link
Copy Markdown
Contributor

@esposem , yes, I see the problem. Since we're dealing with files that get written to the local filesystem, I think we have no choice but to mock the permissions related functions.

It gets a bit more cumbersome, but something like this should work (save and apply with git apply <filename):

diff --git a/tests/unittests/test_sshutil.py b/tests/unittests/test_sshutil.py
index d1b64bd7..9cf971ec 100644
--- a/tests/unittests/test_sshutil.py
+++ b/tests/unittests/test_sshutil.py
@@ -1,6 +1,7 @@
 # This file is part of cloud-init. See LICENSE file for license information.
 
 from collections import namedtuple
+from functools import partial
 from unittest.mock import patch
 
 from cloudinit import ssh_util
@@ -8,13 +9,40 @@ from cloudinit.tests import helpers as test_helpers
 from cloudinit import util
 
 # https://stackoverflow.com/questions/11351032/
-FakePwEnt = namedtuple(
-    'FakePwEnt',
-    ['pw_dir', 'pw_gecos', 'pw_name', 'pw_passwd', 'pw_shell', 'pwd_uid'])
+FakePwEnt = namedtuple('FakePwEnt', [
+    'pw_name',
+    'pw_passwd',
+    'pw_uid',
+    'pw_gid',
+    'pw_gecos',
+    'pw_dir',
+    'pw_shell',
+])
 FakePwEnt.__new__.__defaults__ = tuple(
     "UNSET_%s" % n for n in FakePwEnt._fields)
 
 
+def mock_get_owner(updated_permissions, value):
+    try:
+        return updated_permissions[value][0]
+    except ValueError:
+        return util.get_owner(value)
+
+
+def mock_get_group(updated_permissions, value):
+    try:
+        return updated_permissions[value][1]
+    except ValueError:
+        return util.get_group(value)
+
+
+def mock_get_permissions(updated_permissions, value):
+    try:
+        return updated_permissions[value][2]
+    except ValueError:
+        return util.get_permissions(value)
+
+
 # Do not use these public keys, most of them are fetched from
 # the testdata for OpenSSH, and their private keys are available
 # https://github.com/openssh/openssh-portable/tree/master/regress/unittests/sshkey/testdata
@@ -658,8 +686,24 @@ class TestMultipleSshAuthorizedKeysFile(test_helpers.CiTestCase):
         self.assertTrue(VALID_CONTENT['dsa'] in content)
 
     @patch("cloudinit.ssh_util.pwd.getpwnam")
-    def test_single_user_local_global_files(self, m_getpwnam):
+    @patch("cloudinit.util.get_permissions")
+    @patch("cloudinit.util.get_owner")
+    @patch("cloudinit.util.get_group")
+    def test_single_user_local_global_files(
+        self, m_get_group, m_get_owner, m_get_permissions, m_getpwnam
+    ):
         fpw = FakePwEnt(pw_name='bobby', pw_dir='/tmp/home/bobby')
+        mock_permissions = {
+            '/tmp': ('bobby', 'bobby', 0o700),
+            '/tmp/home': ('bobby', 'bobby', 0o700),
+            '/tmp/home/bobby': ('bobby', 'bobby', 0o700),
+            '/tmp/home/bobby/.ssh': ('bobby', 'bobby', 0o700),
+            '/tmp/home/bobby/.ssh/user_keys': ('bobby', 'bobby', 0o600),
+        }
+        m_get_permissions.side_effect = partial(
+            mock_get_permissions, mock_permissions)
+        m_get_owner.side_effect = partial(mock_get_owner, mock_permissions)
+        m_get_group.side_effect = partial(mock_get_group, mock_permissions)
         m_getpwnam.return_value = fpw
         user_ssh_folder = "%s/.ssh" % fpw.pw_dir
 

I only applied it to one of the failing tests, but you can see how it works.

Does this help you?

@esposem
Copy link
Copy Markdown
Contributor Author

esposem commented Aug 6, 2021

@TheRealFalcon exactly what I needed, thank you! I extended your code a little bit to cover also get_user_groups and getpwnam, but the idea remains the same. I fixed all tests and added 5 new ones.
Let me know if you have any feedback!

@esposem esposem force-pushed the pr_948 branch 2 times, most recently from 1415046 to 4c89c40 Compare August 6, 2021 10:52
@esposem
Copy link
Copy Markdown
Contributor Author

esposem commented Aug 6, 2021

Once again same integration test error as in my original PR:
#937 (comment)

@otubo
Copy link
Copy Markdown
Contributor

otubo commented Aug 6, 2021

@esposem I believe that looks good me! Worth all the discussion and brain puzzle we had :-D

Copy link
Copy Markdown
Contributor

@TheRealFalcon TheRealFalcon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@esposem , this is looking really good. Thanks. I've added some inline comments, but all minor things.

Once again same integration test error as in my original PR

Dang, I thought I fixed that. I'm not sure what's causing it as I'm unable to reproduce it locally. I can trigger a re-run if it happens again.

Comment thread cloudinit/ssh_util.py Outdated
Comment thread cloudinit/ssh_util.py Outdated
Comment thread cloudinit/ssh_util.py Outdated
Comment thread cloudinit/util.py Outdated
Comment thread cloudinit/util.py Outdated
Comment thread cloudinit/util.py Outdated
Comment thread tests/unittests/test_sshutil.py Outdated
Comment thread tests/unittests/test_sshutil.py
esposem added 2 commits August 7, 2021 23:05
extract_authorized_keys

In /etc/ssh/sshd_config, it is possible to define a custom authorized_keys file
that will contain the keys allowed to access the machine via
the AuthorizedKeysFile option.
Cloudinit is able to add user-specific keys to the existing ones, but we need
to be careful on which of the authorized_keys files listed to pick.
Chosing for example a file that is shared by all user will cause security
issues, because the owner of that key can then access also other users.

We therefore pick an authorized_keys file only if it satisfies the following
conditions:
1. it is not a "global" file, ie it must be defined in AuthorizedKeysFile
	with %u, %h or be in  /home/<user>. This avoids security issues.
2. it must comply with ssh permission requirements, otherwise the ssh agent
	won't use that file.

We also need to consider the case when the chosen authorized_keys file
does not exist. In this case, the existing behavior of cloud-init is
to create the new file. We therefore need to be sure that the file complies
with ssh permissions too, by setting:
- the actual file to permission 600, and owned by the user
- the directories in the path that do not exist must be root owned and with
	permission 755.

Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
Copy link
Copy Markdown
Contributor

@TheRealFalcon TheRealFalcon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@esposem , This looks good. Thanks for driving this!

@TheRealFalcon TheRealFalcon merged commit 00dbaf1 into canonical:main Aug 9, 2021
@esposem esposem deleted the pr_948 branch August 9, 2021 21:34
TheRealFalcon pushed a commit to TheRealFalcon/cloud-init that referenced this pull request Aug 10, 2021
)

In /etc/ssh/sshd_config, it is possible to define a custom
authorized_keys file that will contain the keys allowed to access the
machine via the AuthorizedKeysFile option. Cloudinit is able to add
user-specific keys to the existing ones, but we need to be careful on
which of the authorized_keys files listed to pick.
Chosing a file that is shared by all user will cause security
issues, because the owner of that key can then access also other users.

We therefore pick an authorized_keys file only if it satisfies the
following conditions:
1. it is not a "global" file, ie it must be defined in
   AuthorizedKeysFile with %u, %h or be in  /home/<user>. This avoids
   security issues.
2. it must comply with ssh permission requirements, otherwise the ssh
   agent won't use that file.

If it doesn't meet either of those conditions, write to
~/.ssh/authorized_keys

We also need to consider the case when the chosen authorized_keys file
does not exist. In this case, the existing behavior of cloud-init is
to create the new file. We therefore need to be sure that the file
complies with ssh permissions too, by setting:
- the actual file to permission 600, and owned by the user
- the directories in the path that do not exist must be root owned and
  with permission 755.
TheRealFalcon added a commit to TheRealFalcon/cloud-init that referenced this pull request Aug 17, 2021
In canonical#956, we updated the file and directory permissions for keys not in
the user's home directory. We also unintentionally modified the
permissions within the home directory as well. These should not change,
and this commit changes that back.

LP: #1940233
TheRealFalcon added a commit to TheRealFalcon/cloud-init that referenced this pull request Aug 17, 2021
In canonical#956, we updated the file and directory permissions for keys not in
the user's home directory. We also unintentionally modified the
permissions within the home directory as well. These should not change,
and this commit changes that back.

LP: #1940233
TheRealFalcon added a commit to TheRealFalcon/cloud-init that referenced this pull request Aug 17, 2021
In canonical#956, we updated the file and directory permissions for keys not in
the user's home directory. We also unintentionally modified the
permissions within the home directory as well. These should not change,
and this commit changes that back.

LP: #1940233
esposem pushed a commit to esposem/cloud-init that referenced this pull request Aug 20, 2021
In canonical#956, we updated the file and directory permissions for keys not in
the user's home directory. We also unintentionally modified the
permissions within the home directory as well. These should not change,
and this commit changes that back.

LP: #1940233
TheRealFalcon added a commit that referenced this pull request Aug 20, 2021
Fix home permissions modified by ssh module

In #956, we updated the file and directory permissions for keys not in
the user's home directory. We also unintentionally modified the
permissions within the home directory as well. These should not change,
and this commit changes that back.

LP: #1940233
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants