-
Notifications
You must be signed in to change notification settings - Fork 881
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support sudoers in the "usr merge" location #5161
Conversation
@blackboxsw need some help with the test please, I do not understand why I am triggering a permission error in the mock for the file write. Thanks |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks pretty good for handling the case of /usr/etc/sudoers
vs an absent /etc/sudoers
file. Some general questions I have are:
- Is the a changeset represented in upstream sudo project that points to this behavior and/or /usr/etc/sudoers overrides?
- what documentation tells us we should ignore /usr/etc/sudoers when /etc/sudoers exits?
- What should be the behavior on a system with both /etc/sudoers.d and /usr/etc/sudoers.d? Do all the sudo config parts.d directory get aggregated?
cloudinit/distros/__init__.py
Outdated
if not isinstance(sudo_base, (list, tuple)): | ||
sudo_base = [sudo_base] | ||
for sudoers_base in sudo_base: | ||
if os.path.exists(sudoers_base): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will there be a case where /usr/etc/sudoers exists bit /etc/sudoers does not? It seems this is possible given this read-only FS note in SUSE docs which talk about visudo writing to /etc/sudoers if /usr/etc/sudoers exists but /etc/sudoers does not.
Do we want to completely ignore /usr/etc/sudoers in any environment which already has an /etc/sudoers file? There is no active merge of /usr/etc/sudoers content with primary /etc/sudoers anywhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will there be a case where /usr/etc/sudoers exists bit /etc/sudoers does not? It seems this is possible given this read-only FS note in SUSE docs which talk about visudo writing to /etc/sudoers if /usr/etc/sudoers exists but /etc/sudoers does not.
Yes, in newer SUSE distributions /etc/sudoers
will not exist by default.
Do we want to completely ignore /usr/etc/sudoers in any environment which already has an /etc/sudoers file? There is no active merge of /usr/etc/sudoers content with primary /etc/sudoers anywhere.
If /etc/sudoers
exists the underlying premise is that the user (sysadmin) has made the modifications they want to have in place. Should the include
directive be missing we add it to this file to match the current behavior. If /etc/usr/sudoers
exists and contains the include
directive and we would not add the include
directive to an existing /etc/sudoers
we would change the behavior compared to the current implementation because the include files would be processed at a different point in time.
cloudinit/distros/__init__.py
Outdated
if sudoers_base != admin_sudo_base: | ||
util.write_file( | ||
admin_sudo_base, sudoers_contents, 0o440) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the case where /etc/sudoers
doesn't exist and /usr/etc/sudoers
does exist . We fall into this conditional where we now create a root read-only /etc/sudoers
file with a copy of /usr/etc/soduers
(presumably the sudoers path of the read-only filesystem). At this point, permissions of 0o440 will disallow our calling of append_file below as a non-root user that is executing the unit tests. The reason other unit tests pass is because those unit tests actually performed the util.write_file("/etc/sudeors") with default mode 0o644 leaving the unittest able to append_file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is a unit-test workaround that avoids a non-root test-user getting hit by the read-only permission check on the test-created /etc/sudoers
diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py
index ab656ab5a..63b50b2fc 100644
--- a/cloudinit/distros/__init__.py
+++ b/cloudinit/distros/__init__.py
@@ -1048,6 +1048,11 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
util.write_file(admin_sudo_base, sudoers_contents, 0o440)
else:
if sudoers_base != admin_sudo_base:
+ LOG.debug(
+ "Copying content from %s to %s",
+ sudoers_base,
+ admin_sudo_base,
+ )
util.write_file(
admin_sudo_base, sudoers_contents, 0o440)
lines = [
diff --git a/tests/unittests/distros/test__init__.py b/tests/unittests/distros/test__init__.py
index e762f79f6..aa682acc5 100644
--- a/tests/unittests/distros/test__init__.py
+++ b/tests/unittests/distros/test__init__.py
@@ -251,9 +251,15 @@ class TestGenericDistro(helpers.FilesystemMockingTestCase):
self.patchOS(self.tmp)
self.patchUtils(self.tmp)
util.write_file("/usr/etc/sudoers", "josh, josh\n")
- d.ensure_sudo_dir("/b")
+ # This test creates missing /etc/sudoers with mode=0o440.
+ # Avoid append_file which raises a permissions error on 0o440.
+ with mock.patch("cloudinit.distros.util.append_file") as append:
+ d.ensure_sudo_dir("/b")
+ path, appended_content = append.call_args_list[0][0]
+ self.assertEqual("/etc/sudoers", path)
+ self.assertRegex(appended_content, r"#includedir \/b")
+
contents = util.load_text_file("/etc/sudoers")
- self.assertIn("includedir /b", contents)
self.assertTrue(os.path.isdir("/b"))
self.assertIn("josh", contents)
self.assertEqual(2, contents.count("josh"))
cloudinit/distros/__init__.py
Outdated
else: | ||
if sudoers_base != admin_sudo_base: | ||
util.write_file( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's log what we've done here, otherwise our write_files logging won't really tell us where we got the contents. "Copying content from /usr/etc/sudoers to /etc/sudoers"
With the usr merge effort many configuration files are moving from /etc to /usr/etc clearing the way for the /etc directory to be a space managed by the system administrator and the /usr/etc space used by the distribution vendor. Support the /usr/etc/sudoers location when managing the sudoers file.
There is no need to copy the content of the file. If we do not find the include directive in either the /etc/sudoers file or the /usr/etc/sudoers file we append the include directive to the /etc/sudoers file if it exists, if it does not exist we simply write the include directive into /etc/sudoers. This preserves the current behavior w.r.t. oredering of the read operation.
OK, thanks for the questions, made me realize that I had a logic error. There is no need to copy the content from |
Not that I can find. The existence of
The condition on https://build.opensuse.org/projects/openSUSE:Factory/packages/sudo/files/sudo.spec?expand=1
I changed that in the current update. I twisted myself into a knot with the first go around. The logic is more straight forwrad now, IMHO.
The default SUSE configuration is to include |
If the include directive we expect is found in the /usr/etc/sudoers file we shold not create a file in /etc.
When /etc/sudoers exists the content of /usr/etc/sudoers is ignored. Therefore, if it does not already exist we copy the content of /usr/etc/sudores to make sure we preserve the default settings and then add the include directive,
Log info when we use the content form /usr/etc/sudoers as the base to seed /etc/sudoers
Hello! Thank you for this proposed change to cloud-init. This pull request is now marked as stale as it has not seen any activity in 14 days. If no activity occurs within the next 7 days, this pull request will automatically close. If you are waiting for code review and you are seeing this message, apologies! Please reply, tagging TheRealFalcon, and he will ensure that someone takes a look soon. (If the pull request is closed and you would like to continue working on it, please do tag TheRealFalcon to reopen it.) |
@blackboxsw I think I addressed the requested changes. Anything else? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you @rjschwei this is much simpler and straight-forward.
I've added a couple of supplemental log checks to unittests here. but looks good to me.
commit 43d29f1350057ab60c992c340eda8cf2e0f227a2 (HEAD -> usrMerge)
Author: Chad Smith <[email protected]>
Date: Wed May 29 21:27:20 2024 -0600
test log additions
diff --git a/tests/unittests/distros/test__init__.py b/tests/unittests/distros/test__init__.py
index 6ce3d25bd..39583b137 100644
--- a/tests/unittests/distros/test__init__.py
+++ b/tests/unittests/distros/test__init__.py
@@ -55,6 +55,8 @@ gapmi = distros._get_arch_package_mirror_info
class TestGenericDistro(helpers.FilesystemMockingTestCase):
+ with_logs = True
+
def setUp(self):
super(TestGenericDistro, self).setUp()
# Make a temp directoy for tests to use.
@@ -231,6 +233,9 @@ class TestGenericDistro(helpers.FilesystemMockingTestCase):
self.assertTrue(os.path.isdir("/b"))
self.assertIn("josh", contents)
self.assertEqual(2, contents.count("josh"))
+ self.assertIn(
+ "Added '#includedir /b' to /etc/sudoers", self.logs.getvalue()
+ )
def test_sudoers_ensure_append_sudoer_file(self):
cls = distros.fetch("ubuntu")
@@ -257,8 +262,11 @@ class TestGenericDistro(helpers.FilesystemMockingTestCase):
self.assertEqual(2, contents.count("josh"))
self.assertIn("includedir /b", contents)
self.assertTrue(os.path.isdir("/b"))
+ self.assertIn(
+ "Using content from '/usr/etc/sudoers", self.logs.getvalue()
+ )
- def test_usr_sudoers_ensure_no_etc_creat(self):
+ def test_usr_sudoers_ensure_no_etc_create_when_include_in_usr_etc(self):
cls = distros.fetch("ubuntu")
d = cls("ubuntu", {}, None)
self.patchOS(self.tmp)
In a followup PR this cycle we'll be converting all this FilesystemMocking tests to pytest so look for that in the future.
When sudo is compiled with the secure-path option and the configuration file sudoers exists in /usr/etc and not in /etc take the configuration in /usr/etc into account. If the desired include directive exists in the system (/usr/etc) configuration there is nothing to do if there is no file in /etc. When /etc/sudoers exists, the content of /usr/etc/sudoers is ignored.
Proposed Commit Message
When sudo is compiled with the
secure-path
option and the configuration file exists in/usr/etc
and not in/etc
take the configuration in/usr/etc
into account. If the desired include directive exists in the system (/usr/etc
) configuration there is nothing to do if there is no file inetc
.Additional Context
Test Steps
Checklist
Merge type