From e0f2280ac4e723fe74f2670e1a78c422a3115d7a Mon Sep 17 00:00:00 2001 From: Miika Alikirri Date: Mon, 27 May 2024 18:44:49 +0300 Subject: [PATCH 01/59] spec: Allow building cockpit for tumbleweed testing --- tools/cockpit.spec | 49 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/tools/cockpit.spec b/tools/cockpit.spec index 07ed05eae1e8..aed5baa6854b 100644 --- a/tools/cockpit.spec +++ b/tools/cockpit.spec @@ -155,7 +155,7 @@ BuildRequires: python3-pytest-timeout %check make -j$(nproc) check -%if 0%{?rhel} == 0 +%if 0%{?rhel} == 0 && 0%{?suse_version} == 0 export NO_QUNIT=1 %pytest %endif @@ -163,8 +163,13 @@ export NO_QUNIT=1 %install %make_install make install-tests DESTDIR=%{buildroot} +%if 0%{?suse_version} > 1500 +mkdir -p $RPM_BUILD_ROOT%{_pam_vendordir} +install -p -m 644 tools/cockpit.pam $RPM_BUILD_ROOT%{_pam_vendordir}/cockpit +%else mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/pam.d install -p -m 644 tools/cockpit.pam $RPM_BUILD_ROOT%{_sysconfdir}/pam.d/cockpit +%endif rm -f %{buildroot}/%{_libdir}/cockpit/*.so install -D -p -m 644 AUTHORS COPYING README.md %{buildroot}%{_docdir}/cockpit/ @@ -218,7 +223,22 @@ find %{buildroot}%{_datadir}/cockpit/static -type f >> static.list sed -i "s|%{buildroot}||" *.list -%if ! 0%{?suse_version} +%if 0%{?suse_version} +# remove brandings with stale symlinks. Means they don't match +# the distro. +pushd %{buildroot}/%{_datadir}/cockpit/branding +ls --hide={default,kubernetes,opensuse,registry,sle-micro,suse} | xargs rm -rv +popd +# need this in SUSE as post build checks dislike stale symlinks +install -m 644 -D /dev/null %{buildroot}/run/cockpit/motd +test -e %{buildroot}/usr/share/cockpit/branding/opensuse/default-1920x1200.jpg || install -m 644 -D /dev/null %{buildroot}/usr/share/cockpit/branding/opensuse/default-1920x1200.jpg +test -e %{buildroot}/usr/share/cockpit/branding/sle-micro/apple-touch-icon.png || install -m 644 -D /dev/null %{buildroot}/usr/share/cockpit/branding/sle-micro/apple-touch-icon.png +test -e %{buildroot}/usr/share/cockpit/branding/sle-micro/default-1920x1200.png || install -m 644 -D /dev/null %{buildroot}/usr/share/cockpit/branding/sle-micro/default-1920x1200.png +# remove files of not installable packages +rm -r %{buildroot}%{_datadir}/cockpit/sosreport +rm -f %{buildroot}/%{_prefix}/share/metainfo/org.cockpit_project.cockpit_sosreport.metainfo.xml +rm -f %{buildroot}%{_datadir}/icons/hicolor/64x64/apps/cockpit-sosreport.png +%else %global _debugsource_packages 1 %global _debuginfo_subpackages 0 @@ -348,6 +368,12 @@ Recommends: system-logos Suggests: sssd-dbus >= 2.6.2 # for cockpit-desktop Suggests: python3 +%if 0%{?suse_version} +Provides: group(cockpit-ws) +Provides: group(cockpit-wsinstance) +Provides: user(cockpit-ws) +Provides: user(cockpit-wsinstance) +%endif # prevent hard python3 dependency for cockpit-desktop, it falls back to other browsers %global __requires_exclude_from ^%{_libexecdir}/cockpit-client$ @@ -366,11 +392,18 @@ authentication via sssd/FreeIPA. %doc %{_mandir}/man8/pam_ssh_add.8.gz %dir %{_sysconfdir}/cockpit %config(noreplace) %{_sysconfdir}/cockpit/ws-certs.d +%if 0%{?suse_version} > 1500 +%{_pam_vendordir}/cockpit +%else %config(noreplace) %{_sysconfdir}/pam.d/cockpit +%endif # created in %post, so that users can rm the files %ghost %{_sysconfdir}/issue.d/cockpit.issue %ghost %{_sysconfdir}/motd.d/cockpit %ghost %attr(0644, root, root) %{_sysconfdir}/cockpit/disallowed-users +%if 0%{?suse_version} +%ghost /run/cockpit/motd +%endif %dir %{_datadir}/cockpit/motd %{_datadir}/cockpit/motd/update-motd %{_datadir}/cockpit/motd/inactive.motd @@ -465,7 +498,11 @@ fi Summary: Cockpit user interface for kernel crash dumping Requires: cockpit-bridge >= %{required_base} Requires: cockpit-shell >= %{required_base} +%if 0%{?suse_version} +Requires: kexec-tools +%else Requires: /usr/bin/kdumpctl +%endif BuildArch: noarch %description kdump @@ -474,6 +511,8 @@ The Cockpit component for configuring kernel crash dumping. %files kdump -f kdump.list %{_datadir}/metainfo/org.cockpit_project.cockpit_kdump.metainfo.xml +# sosreport is not supported on opensuse yet +%if !0%{?suse_version} %package sosreport Summary: Cockpit user interface for diagnostic reports Requires: cockpit-bridge >= %{required_base} @@ -488,6 +527,7 @@ sosreport tool. %files sosreport -f sosreport.list %{_datadir}/metainfo/org.cockpit_project.cockpit_sosreport.metainfo.xml %{_datadir}/icons/hicolor/64x64/apps/cockpit-sosreport.png +%endif %package networkmanager Summary: Cockpit user interface for networking, using NetworkManager @@ -512,7 +552,10 @@ The Cockpit component for managing networking. This package uses NetworkManager Summary: Cockpit SELinux package Requires: cockpit-bridge >= %{required_base} Requires: cockpit-shell >= %{required_base} -Requires: setroubleshoot-server >= 3.3.3 +# setroubleshoot is available on SLE Micro starting with 5.5 +%if !0%{?is_smo} || ( 0%{?is_smo} && 0%{?sle_version} >= 150500 ) +Requires: setroubleshoot-server >= 3.3.3 +%endif BuildArch: noarch %description selinux From ab4302e9027b3e088e0a3a0b9d24ea43973f819b Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Wed, 13 Nov 2024 14:39:06 +0000 Subject: [PATCH 02/59] test: Set opensuse specific cockpit.pam file --- tools/cockpit.spec | 2 +- tools/cockpit.suse.pam | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 tools/cockpit.suse.pam diff --git a/tools/cockpit.spec b/tools/cockpit.spec index aed5baa6854b..1f71e563c84c 100644 --- a/tools/cockpit.spec +++ b/tools/cockpit.spec @@ -165,7 +165,7 @@ export NO_QUNIT=1 make install-tests DESTDIR=%{buildroot} %if 0%{?suse_version} > 1500 mkdir -p $RPM_BUILD_ROOT%{_pam_vendordir} -install -p -m 644 tools/cockpit.pam $RPM_BUILD_ROOT%{_pam_vendordir}/cockpit +install -p -m 644 tools/cockpit.suse.pam $RPM_BUILD_ROOT%{_pam_vendordir}/cockpit %else mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/pam.d install -p -m 644 tools/cockpit.pam $RPM_BUILD_ROOT%{_sysconfdir}/pam.d/cockpit diff --git a/tools/cockpit.suse.pam b/tools/cockpit.suse.pam new file mode 100644 index 000000000000..ffd1729508c0 --- /dev/null +++ b/tools/cockpit.suse.pam @@ -0,0 +1,11 @@ +#%PAM-1.0 +auth substack common-auth +# List of users to deny access to Cockpit, by default root is included. +auth required pam_listfile.so item=user sense=deny file=/etc/cockpit/disallowed-users onerr=succeed +account required pam_nologin.so +account include common-account +password include common-password +session required pam_loginuid.so +session optional pam_keyinit.so force revoke +session include common-session +auth [user_unknown=ignore success=ok] pam_oath.so usersfile=${HOME}/.pam_oath_usersfile no_usersfile_okay window=20 digits=6 From 4cc003ef640f7ea1b8a3ce2a837ba171e066a997 Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Wed, 29 May 2024 11:21:10 +0100 Subject: [PATCH 03/59] .gitignore: Ignore test results --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b6d108ec306e..4bc3a773a00c 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,7 @@ Makefile.in /tags /test-* /test_rsa_key +/Test[A-Z]* /tmp-dist /tmp/ /tsconfig.tsbuildinfo From eecfb6a27aada3339039f7ed93b45f1d482b69ab Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Wed, 13 Nov 2024 12:13:30 +0000 Subject: [PATCH 04/59] test: Add helper function for system admin user --- test/common/testlib.py | 6 +++++- test/verify/check-superuser | 25 ++++++++++++++++--------- test/verify/check-system-info | 3 ++- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/test/common/testlib.py b/test/common/testlib.py index f6a96951940e..265883341b9c 100644 --- a/test/common/testlib.py +++ b/test/common/testlib.py @@ -1094,7 +1094,7 @@ def become_superuser( # In (open)SUSE images, superuser access always requires the root password if user is None: - user = "root" if "suse" in self.machine.image else "admin" + user = get_superuser(self.machine.image) if passwordless: self.wait_in_text("div[role=dialog]", "Administrative access") @@ -2467,6 +2467,10 @@ def get_decorator(method: object, _class: object, name: str, default: Any = None return getattr(method, attr, getattr(_class, attr, default)) +def get_superuser(image: str) -> str: + # In (open)SUSE images, superuser access always requires the root password + return "root" if "suse" in image else "admin" + ########################### # Test decorators # diff --git a/test/verify/check-superuser b/test/verify/check-superuser index 1538f7cb1103..f91d7a50ad6d 100755 --- a/test/verify/check-superuser +++ b/test/verify/check-superuser @@ -70,7 +70,9 @@ class TestSuperuser(testlib.MachineCase): if "ubuntu" not in m.image and "debian" not in m.image: b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", "We trust you have received the usual lecture") - b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", "Password for admin:") + + user = testlib.get_superuser(m.image) + b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", f"Password for {user}:") b.set_input_text(".pf-v5-c-modal-box:contains('Switch to administrative access') input", "foobar") b.focus(".pf-v5-c-modal-box button:contains('Cancel')") b.assert_pixels(".pf-v5-c-modal-box:contains('Switch to administrative access')", "superuser-modal") @@ -151,7 +153,8 @@ session include system-auth """) b.open_superuser_dialog() - b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", "Password for admin:") + user = testlib.get_superuser(m.image) + b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", f"Password for {user}:") # Let the dialog sit there for 45 seconds, to test that this doesn't trigger a D-Bus timeout. time.sleep(45) b.set_input_text(".pf-v5-c-modal-box:contains('Switch to administrative access') input", "foobar") @@ -170,14 +173,15 @@ session include system-auth b.check_superuser_indicator("Limited access") b.open_superuser_dialog() - b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", "Password for admin:") + user = testlib.get_superuser(self.machine.image) + b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", f"Password for {user}:") b.set_input_text(".pf-v5-c-modal-box:contains('Switch to administrative access') input", "wrong") b.click(".pf-v5-c-modal-box button:contains('Authenticate')") - b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", "Password for admin:") + b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", f"Password for {user}:") b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", "Sorry, try again") b.set_input_text(".pf-v5-c-modal-box:contains('Switch to administrative access') input", "wronger") b.click(".pf-v5-c-modal-box button:contains('Authenticate')") - b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", "Password for admin:") + b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", f"Password for {user}:") b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", "Sorry, try again") b.set_input_text(".pf-v5-c-modal-box:contains('Switch to administrative access') input", "wrongest") b.click(".pf-v5-c-modal-box button:contains('Authenticate')") @@ -215,7 +219,8 @@ session include system-auth # cancelling auth dialog stops sudo b.open_superuser_dialog() b.wait_in_text(".pf-v5-c-modal-box", "Switch to administrative access") - b.wait_in_text(".pf-v5-c-modal-box", "Password for admin") + user = testlib.get_superuser(m.image) + b.wait_in_text(".pf-v5-c-modal-box", f"Password for {user}") status = m.execute("loginctl --lines=0 user-status admin") self.assertIn("sudo", status) if not m.ws_container: @@ -228,7 +233,7 @@ session include system-auth # logging out cleans up pending sudo auth; user should either go to "State: closing" or disappear completely b.open_superuser_dialog() - b.wait_in_text(".pf-v5-c-modal-box", "Password for admin") + b.wait_in_text(".pf-v5-c-modal-box", f"Password for {user}") if not m.ws_container: self.assertIn("cockpit-askpass", m.execute("loginctl --lines=0 user-status admin")) b.click(".pf-v5-c-modal-box button:contains('Cancel')") @@ -391,7 +396,8 @@ session include system-auth b.set_val(".pf-v5-c-modal-box:contains('Switch to administrative access') select", "Sudo") b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access') select", "Sudo") b.click(".pf-v5-c-modal-box button:contains('Authenticate')") - b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", "Password for admin:") + user = testlib.get_superuser(self.machine.image) + b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", f"Password for {user}:") b.set_input_text(".pf-v5-c-modal-box:contains('Switch to administrative access') input", "foobar") b.click(".pf-v5-c-modal-box button:contains('Authenticate')") b.wait_not_present(".pf-v5-c-modal-box:contains('Switch to administrative access')") @@ -403,7 +409,8 @@ session include system-auth self.login_and_go("/system", superuser=False) b.wait_visible(".pf-v5-c-alert:contains('Web console is running in limited access mode.')") b.click(".pf-v5-c-alert:contains('Web console is running in limited access mode.') button:contains('Turn on')") - b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", "Password for admin:") + user = testlib.get_superuser(self.machine.image) + b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", f"Password for {user}:") b.set_input_text(".pf-v5-c-modal-box:contains('Switch to administrative access') input", "foobar") b.click(".pf-v5-c-modal-box button:contains('Authenticate')") b.wait_not_present(".pf-v5-c-modal-box:contains('Switch to administrative access')") diff --git a/test/verify/check-system-info b/test/verify/check-system-info index 5ce5000a0abe..431508503be1 100755 --- a/test/verify/check-system-info +++ b/test/verify/check-system-info @@ -933,7 +933,8 @@ class TestSystemInfoTime(packagelib.PackageCase): # Gain admin access b.click(".pf-v5-c-alert:contains('Web console is running in limited access mode.') button:contains('Turn on')") - b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", "Password for admin:") + user = testlib.get_superuser(m.image) + b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", f"Password for {user}:") b.set_input_text(".pf-v5-c-modal-box:contains('Switch to administrative access') input", "foobar") b.click(".pf-v5-c-modal-box button:contains('Authenticate')") b.wait_not_present(".pf-v5-c-modal-box:contains('Switch to administrative access')") From 0a8d2871b74e38603f510f54a2d7c1d032c22bdd Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Tue, 26 Nov 2024 12:35:12 +0000 Subject: [PATCH 05/59] test: fix existing RUF031: Avoid parentheses for tuples in subscripts --- test/common/packagelib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/common/packagelib.py b/test/common/packagelib.py index 02877b40d36e..5a2f22ec2592 100644 --- a/test/common/packagelib.py +++ b/test/common/packagelib.py @@ -157,7 +157,7 @@ def createPackage(self, name, version, release, install=False, else: self.createRpm(name, version, release, depends, postinst, install, content, arch, provides) if updateinfo: - self.updateInfo[(name, version, release)] = updateinfo + self.updateInfo[name, version, release] = updateinfo def createDeb(self, name, version, depends, postinst, install, content, arch, provides): """Create a dummy deb in repo_dir on self.machine From ec698d4ea63f9098142a0e8134a26308176d195d Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Wed, 29 May 2024 11:27:31 +0100 Subject: [PATCH 06/59] test: Add SUSE to package cases --- test/common/packagelib.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/common/packagelib.py b/test/common/packagelib.py index 5a2f22ec2592..40b163177a5c 100644 --- a/test/common/packagelib.py +++ b/test/common/packagelib.py @@ -46,6 +46,10 @@ def setUp(self): self.backend = "dnf5" self.primary_arch = "noarch" self.secondary_arch = "x86_64" + elif "suse" in self.machine.image: + self.backend = "zypper" + self.primary_arch = "noarch" + self.secondary_arch = "x86_64" elif self.machine.image == "arch": self.backend = "alpm" self.primary_arch = "any" From 0924383c1308f22d7a68430bbf594538dcd4f9ee Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Thu, 30 May 2024 08:47:27 +0100 Subject: [PATCH 07/59] test: Set the lease path to /run/dnsmasq due to apparmour policy on TW --- test/common/netlib.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/common/netlib.py b/test/common/netlib.py index 053830211d0a..e6ded64d0104 100644 --- a/test/common/netlib.py +++ b/test/common/netlib.py @@ -44,8 +44,11 @@ def add_veth(self, name, dhcp_cidr=None, dhcp_range=None): if dhcp_cidr: # up the remote end, give it an IP, and start DHCP server self.machine.execute(f"ip a add {dhcp_cidr} dev v_{name}; ip link set v_{name} up") - server = self.machine.spawn("dnsmasq --keep-in-foreground --log-queries --log-facility=- " - f"--conf-file=/dev/null --dhcp-leasefile=/tmp/leases.{name} --no-resolv " + + # TODO: Consider running all env's through /run/dnsmasq + lease_path = "/run/dnsmasq" if "suse" in self.machine.image else "/tmp" + server = self.machine.spawn(f"mkdir -p {lease_path}; dnsmasq --keep-in-foreground --log-queries --log-facility=- " + f"--conf-file=/dev/null --dhcp-leasefile={lease_path}/leases.{name} --no-resolv " f"--bind-interfaces --except-interface=lo --interface=v_{name} --dhcp-range={dhcp_range[0]},{dhcp_range[1]},4h", f"dhcp-{name}.log") self.addCleanup(self.machine.execute, "kill %i" % server) From 5c1232fc02e52974b6bdc15f426926d9a85f9aca Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Thu, 30 May 2024 09:18:25 +0100 Subject: [PATCH 08/59] test: Update sshd_path logic to be global --- test/common/testlib.py | 5 +++++ test/verify/check-connection | 2 +- test/verify/check-loopback | 3 ++- test/verify/check-shell-multi-machine | 8 +++++--- test/verify/check-shell-multi-machine-key | 7 ++++++- test/verify/check-static-login | 6 ++++-- test/verify/check-system-info | 3 ++- test/verify/check-system-realms | 3 ++- test/verify/check-users | 3 ++- 9 files changed, 29 insertions(+), 11 deletions(-) diff --git a/test/common/testlib.py b/test/common/testlib.py index 265883341b9c..afd789fb2a7b 100644 --- a/test/common/testlib.py +++ b/test/common/testlib.py @@ -2471,6 +2471,11 @@ def get_superuser(image: str) -> str: # In (open)SUSE images, superuser access always requires the root password return "root" if "suse" in image else "admin" + +def get_sshd_config_path(image: str) -> str: + # In (open)SUSE images, superuser access always requires the root password + return "/usr/etc/ssh/sshd_config" if "suse" in image else "/etc/ssh/sshd_config" + ########################### # Test decorators # diff --git a/test/verify/check-connection b/test/verify/check-connection index 9c7906e55091..12b4c1cb8acf 100755 --- a/test/verify/check-connection +++ b/test/verify/check-connection @@ -603,7 +603,7 @@ class TestConnection(testlib.MachineCase): m.execute("passwd -d admin") m.execute("passwd -d user") - self.sed_file('$ a\\\nPermitEmptyPasswords yes', '/etc/ssh/sshd_config', + self.sed_file('$ a\\\nPermitEmptyPasswords yes', testlib.get_sshd_config_path(m.image), self.restart_sshd) def assertInOrNot(string: str, result: str, *, expected: bool) -> None: diff --git a/test/verify/check-loopback b/test/verify/check-loopback index ee5e343c3af8..d44ba81dd53b 100755 --- a/test/verify/check-loopback +++ b/test/verify/check-loopback @@ -40,7 +40,8 @@ class TestLoopback(testlib.MachineCase): m.disconnect() self.restore_dir("/etc/ssh", restart_unit=self.sshd_service) - m.execute("sed -i 's/.*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config $(ls /etc/ssh/sshd_config.d/* 2>/dev/null || true)") + sshd_path = testlib.get_sshd_config_path(m.image) + m.execute(f"sed -i 's/.*PasswordAuthentication.*/PasswordAuthentication no/' {sshd_path} $(ls {sshd_path}.d/* 2>/dev/null || true)") m.execute(self.restart_sshd) m.wait_execute() diff --git a/test/verify/check-shell-multi-machine b/test/verify/check-shell-multi-machine index d0c4650cc21b..54f92dbe31d1 100755 --- a/test/verify/check-shell-multi-machine +++ b/test/verify/check-shell-multi-machine @@ -92,8 +92,9 @@ def change_ssh_port(machine, address, sshd_socket, sshd_service, port=None, time machine.execute("systemctl daemon-reload") machine.execute("systemctl restart ssh.socket") else: - machine.execute("sed -i 's/.*Port .*/#\\0/' /etc/ssh/sshd_config") - machine.write("/etc/ssh/sshd_config", + sshd_path = testlib.get_sshd_config_path(machine.image) + machine.execute(f"sed -i 's/.*Port .*/#\\0/' {sshd_path}") + machine.write(sshd_path, f"ListenAddress 127.27.0.15:22\nListenAddress {address}:{port}\n", append=True) # We stop the sshd.socket unit and just go with a regular @@ -659,7 +660,8 @@ class TestMultiMachine(testlib.MachineCase): m2 = self.machine2 # Logging in as root is no longer allowed by default by sshd - m2.execute("sed -ri 's/#?PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config") + sshd_path = testlib.get_sshd_config_path(m1.image) + m2.execute(f"sed -ri 's/#?PermitRootLogin prohibit-password/PermitRootLogin yes/' {sshd_path}") m2.execute(self.restart_sshd) machine_path = "/@10.111.113.2" diff --git a/test/verify/check-shell-multi-machine-key b/test/verify/check-shell-multi-machine-key index 3acb385c0669..a770f0229255 100755 --- a/test/verify/check-shell-multi-machine-key +++ b/test/verify/check-shell-multi-machine-key @@ -101,8 +101,13 @@ class TestMultiMachineKeyAuth(testlib.MachineCase): # Add user self.machine2.disconnect() self.machine2.execute("useradd user -c User", direct=True) + sshd_path = testlib.get_sshd_config_path(self.machine.image) self.machine2.execute( - "sed -i 's/.*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config $(ls /etc/ssh/sshd_config.d/* 2>/dev/null || true)", direct=True) + f"sed -i 's/.*PasswordAuthentication.*/PasswordAuthentication no/' {sshd_path} $(ls {sshd_path}.d/* 2>/dev/null || true)", direct=True) + # HACK - Increase MaxAuthTries because of + # https://bugs.libssh.org/T233 and because we have a lot of + # keys and cockpit-ssh tries them all, and many of them twice. + self.machine2.write(sshd_path, "MaxAuthTries 100", append=True) self.machine2.execute(self.restart_sshd, direct=True) self.machine2.wait_execute() diff --git a/test/verify/check-static-login b/test/verify/check-static-login index 6717e6cd7154..8500277aeae1 100755 --- a/test/verify/check-static-login +++ b/test/verify/check-static-login @@ -276,8 +276,9 @@ account required pam_succeed_if.so user ingroup %s""" % m.get_admin_group # with the ws container this happens over ssh if m.ws_container: self.restore_dir("/etc/ssh", restart_unit=self.sshd_service) + sshd_path = testlib.get_sshd_config_path(m.image) m.execute("sed -i 's/.*ChallengeResponseAuthentication.*/ChallengeResponseAuthentication yes/' " - "/etc/ssh/sshd_config $(ls /etc/ssh/sshd_config.d/* 2>/dev/null || true)") + f"{sshd_path} $(ls {sshd_path}.d/* 2>/dev/null || true)") m.execute(self.restart_sshd) # test steps below assume a pam_pwquality config with retry > 1; on some images authselect drops that setting @@ -377,8 +378,9 @@ account required pam_succeed_if.so user ingroup %s""" % m.get_admin_group if m.ostree_image: conf = "/etc/pam.d/sshd" self.restore_dir("/etc/ssh", restart_unit=self.sshd_service) + sshd_path = testlib.get_sshd_config_path(m.image) m.execute("sed -i 's/.*ChallengeResponseAuthentication.*/ChallengeResponseAuthentication yes/' " - "/etc/ssh/sshd_config $(ls /etc/ssh/sshd_config.d/* 2>/dev/null || true)") + f"{sshd_path} $(ls {sshd_path}.d/* 2>/dev/null || true)") m.execute(self.restart_sshd) # On Arch Linux the ordering matters due to an auth include for system-remote-login diff --git a/test/verify/check-system-info b/test/verify/check-system-info index 431508503be1..3813fd7ac468 100755 --- a/test/verify/check-system-info +++ b/test/verify/check-system-info @@ -143,7 +143,8 @@ class TestSystemInfo(testlib.MachineCase): b.wait_not_present("#system_information_ssh_keys") # Change ssh config and restart - self.sed_file(r"s,.*HostKey *,#,; $ a HostKey /etc/ssh/weirdname", "/etc/ssh/sshd_config", + sshd_path = testlib.get_sshd_config_path(m.image) + self.sed_file(r"s,.*HostKey *,#,; $ a HostKey /etc/ssh/weirdname", sshd_path, self.restart_sshd) ssh_reconnect(m) diff --git a/test/verify/check-system-realms b/test/verify/check-system-realms index 721292468f45..d80fd1ac18d0 100755 --- a/test/verify/check-system-realms +++ b/test/verify/check-system-realms @@ -740,7 +740,8 @@ ipa-advise enable-admins-sudo | sh -ex """) # enable ssh GSSAPI authentication - m.execute("sed -ri 's/#GSSAPIAuthentication.*/GSSAPIAuthentication yes/' /etc/ssh/sshd_config") + sshd_path = testlib.get_sshd_config_path(m.image) + m.execute(f"sed -ri 's/#GSSAPIAuthentication.*/GSSAPIAuthentication yes/' {sshd_path}") m.execute(self.restart_sshd) # avoid "unknown host" error in SSH diff --git a/test/verify/check-users b/test/verify/check-users index d1cae819d916..e256f47518d8 100755 --- a/test/verify/check-users +++ b/test/verify/check-users @@ -662,7 +662,8 @@ class TestAccounts(testlib.MachineCase): # with container this happens over ssh if m.ws_container: self.restore_dir("/etc/ssh", restart_unit=self.sshd_service) - m.execute("sed -i 's/.*ChallengeResponseAuthentication.*/ChallengeResponseAuthentication yes/' /etc/ssh/sshd_config $(ls /etc/ssh/sshd_config.d/* 2>/dev/null) || true") + sshd_path = testlib.get_sshd_config_path(m.image) + m.execute(f"sed -i 's/.*ChallengeResponseAuthentication.*/ChallengeResponseAuthentication yes/' {sshd_path} $(ls {sshd_path}.d/* 2>/dev/null) || true") m.execute(self.restart_sshd) b.wait_visible("#login") From d1dcb7f1d7f883575761fa24a363c932b423b318 Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Thu, 30 May 2024 11:13:10 +0100 Subject: [PATCH 09/59] system: Add support for suse platforms --- pkg/lib/kernelopt.sh | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pkg/lib/kernelopt.sh b/pkg/lib/kernelopt.sh index 7f319ad277aa..65d5c01b8c2e 100755 --- a/pkg/lib/kernelopt.sh +++ b/pkg/lib/kernelopt.sh @@ -14,8 +14,8 @@ error() { grub() { key="${2%=*}" # split off optional =value - # For the non-BLS case, or if someone overrides those with grub2-mkconfig - # or update-grub, change it in /etc/default/grub + # For the non-BLS case, or if someone overrides those with grub2-mkconfig, + # update-grub or update-bootloader, change it in /etc/default/grub if [ -e /etc/default/grub ]; then if [ "$1" = set ]; then # replace existing argument, otherwise append it @@ -37,6 +37,11 @@ grub() { elif [ -e /etc/default/grub ] && type update-grub >/dev/null 2>&1; then update-grub + # on Suse platforms, use update-bootloader + # Since earlier we updated /etc/default/grub we can just run update-bootloader + elif type update-bootloader >/dev/null 2>&1; then + update-bootloader + # on OSTree, the kernel config is inside the image elif cur=$(rpm-ostree kargs 2>&1); then if [ "$1" = set ]; then @@ -50,7 +55,7 @@ grub() { rpm-ostree kargs --delete="$key" fi else - error "No supported grub update mechanism found (grubby, update-grub, or rpm-ostree)" + error "No supported grub update mechanism found (grubby, update-grub, update-bootloader or rpm-ostree)" fi } From 575f84b2126891ec47dfac2a1997f0bb79fc7a69 Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Thu, 30 May 2024 11:14:14 +0100 Subject: [PATCH 10/59] test: Add support for suse to cpu migitgation tests --- test/verify/check-system-info | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/verify/check-system-info b/test/verify/check-system-info index 3813fd7ac468..46b3ef1fa847 100755 --- a/test/verify/check-system-info +++ b/test/verify/check-system-info @@ -557,6 +557,9 @@ echo dummy > /boot/vmlinuz-42.0.0; mkdir -p /lib/modules/42.0.0/ if type update-grub >/dev/null 2>&1; then update-grub # Debian/Ubuntu grep -q 'linux.*/vmlinuz-42.0.0.*nosmt' /boot/grub*/grub.cfg +elif type update-bootloader >/dev/null 2>&1; then + update-bootloader # suse + grep -q 'linux.*/vmlinuz-42.0.0.*nosmt' /boot/grub*/grub.cfg else cp -a /boot/grub2/grubenv /boot/grub2/grubenv.prev kernel-install add 42.0.0 /boot/vmlinuz-42.0.0 2>/dev/null @@ -570,6 +573,8 @@ fi rm /boot/vmlinuz-42.0.0 if type update-grub >/dev/null 2>&1; then update-grub # Debian/Ubuntu +elif type update-bootloader >/dev/null 2>&1; then + update-bootloader # suse else kernel-install remove 42.0.0 /boot/vmlinuz-42.0.0 # HACK: https://bugzilla.redhat.com/show_bug.cgi?id=2078359 and https://bugzilla.redhat.com/show_bug.cgi?id=2078379 @@ -621,6 +626,8 @@ fi m.execute('mv /etc/default/grub /etc/default/grub.bak || true') m.write('/tmp/grubby', '#!/bin/sh\necho 0') m.execute('[ ! -f /usr/sbin/grubby ] || mount --bind /tmp/grubby /usr/sbin/grubby') + m.write('/tmp/update-bootloader', '#!/bin/sh\necho 0') + m.execute('[ ! -f /usr/sbin/update-bootloader ] || mount --bind /tmp/update-bootloader /usr/sbin/update-bootloader') m.execute('systemctl stop rpm-ostreed.service || true; systemctl mask rpm-ostreed.service') self.login_and_go('/system/hwinfo') b.click('#hwinfo button:contains(Mitigations)') From d1089aef5cef6a4e2e04147441ef5a1fb2e4ae39 Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Thu, 30 May 2024 14:05:51 +0100 Subject: [PATCH 11/59] test: Add in login.defs path for tumbleweed --- pkg/users/users.js | 9 ++++++++- test/verify/check-system-info | 5 ++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/pkg/users/users.js b/pkg/users/users.js index 28a8a2bee36b..329c1167809a 100755 --- a/pkg/users/users.js +++ b/pkg/users/users.js @@ -91,7 +91,14 @@ function AccountsPage() { const handleShadow = cockpit.file("/etc/shadow", { superuser: "try" }); handleShadow.watch(() => debouncedGetLoginDetails(), { read: false }); - const handleLogindef = cockpit.file("/etc/login.defs"); + let handleLogindef; + try { + await cockpit.spawn(["test", "-e", "/etc/login.defs"], { err: "ignore" }); + handleLogindef = cockpit.file("/etc/login.defs"); + } catch (err1) { + handleLogindef = cockpit.file("/usr/etc/login.defs"); + } + handleLogindef.watch((logindef) => { if (logindef === null) return; diff --git a/test/verify/check-system-info b/test/verify/check-system-info index 46b3ef1fa847..5955bec8775d 100755 --- a/test/verify/check-system-info +++ b/test/verify/check-system-info @@ -221,7 +221,10 @@ class TestSystemInfo(testlib.MachineCase): self.restore_file("/etc/motd") # run this test with a tight umask to check preserving file permissions - self.sed_file(r"/^UMASK/ s/0../077/", "/etc/login.defs") + if "suse" in m.image: + self.sed_file(r"/^UMASK/ s/0../077/", "/usr/etc/login.defs") + else: + self.sed_file(r"/^UMASK/ s/0../077/", "/etc/login.defs") m.execute("rm -f /etc/motd") self.login_and_go("/system") From 85237385f39c665d5b905ac22372d4d5adcaca5c Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Mon, 3 Jun 2024 09:47:14 +0100 Subject: [PATCH 12/59] test: Remove password access for all users in sudoers for SUSE --- test/verify/check-superuser | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/verify/check-superuser b/test/verify/check-superuser index f91d7a50ad6d..267f78bc7446 100755 --- a/test/verify/check-superuser +++ b/test/verify/check-superuser @@ -201,6 +201,11 @@ session include system-auth if "ubuntu" in m.image: self.sed_file("/^%admin/d", "/etc/sudoers") + # The default in tumbleweed is to allow passworded sudo to root + # We need to remove this before we actually run this test + if "suse" in m.image: + self.sed_file("/^ALL/d", "/usr/etc/sudoers") + m.execute(f"gpasswd -d admin {m.get_admin_group()}") self.login_and_go() From bf240b5593de6e71cb079a539fee0c2b446fc8f9 Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Mon, 3 Jun 2024 10:43:08 +0100 Subject: [PATCH 13/59] test: Add suse specific configuration for pam test --- test/verify/check-superuser | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/verify/check-superuser b/test/verify/check-superuser index 267f78bc7446..26d512d2efaa 100755 --- a/test/verify/check-superuser +++ b/test/verify/check-superuser @@ -142,6 +142,14 @@ auth required pam_unix.so auth required mock-pam-conv-mod.so @include common-account @include common-session-noninteractive +""") + elif "suse" in m.image: + self.write_file("/etc/pam.d/sudo", """ +auth required pam_unix.so +auth required mock-pam-conv-mod.so +account include common-account +password include common-password +session include common-session """) else: self.write_file("/etc/pam.d/sudo", """ From 8b21fe45e2bdcd986a4d6ba377e4972131e8dbc7 Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Mon, 3 Jun 2024 14:42:28 +0100 Subject: [PATCH 14/59] storage: fallback to starting multipathd if mpathconf doesn't exist --- pkg/storaged/multipath.jsx | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/pkg/storaged/multipath.jsx b/pkg/storaged/multipath.jsx index 4179941aa287..cdc2c33dfb74 100644 --- a/pkg/storaged/multipath.jsx +++ b/pkg/storaged/multipath.jsx @@ -50,17 +50,29 @@ export class MultipathAlert extends React.Component { const multipathd_running = !this.multipathd_service.state || this.multipathd_service.state === "running"; const multipath_broken = client.broken_multipath_present === true; - function activate(event) { + async function activate(event) { if (!event || event.button !== 0) return; - cockpit.spawn(["mpathconf", "--enable", "--with_multipathd", "y"], - { superuser: "try" }) - .catch(function (error) { - dialog_open({ - Title: _("Error"), - Body: error.toString() + try { + await cockpit.spawn(["type", "mpathconf"], { err: "ignore" }); + cockpit.spawn(["mpathconf", "--enable", "--with_multipathd", "y"], + { superuser: "try" }) + .catch(function (error) { + dialog_open({ + Title: _("Error"), + Body: error.toString() + }); }); - }); + } catch (err1) { + cockpit.spawn(["systemctl", "enable", "--now", "multipathd.service"], + { superuser: "try" }) + .catch(function (error) { + dialog_open({ + Title: _("Error"), + Body: error.toString() + }); + }); + } } if (multipath_broken && !multipathd_running) From 4d8c10606ce2ea1fa3df02049794d77d361dab56 Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Mon, 3 Jun 2024 16:06:49 +0100 Subject: [PATCH 15/59] test: Skip a few unsupported tests --- test/verify/check-storage-anaconda | 2 +- test/verify/check-storage-iscsi | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/verify/check-storage-anaconda b/test/verify/check-storage-anaconda index a188d704c53e..956f95707adb 100755 --- a/test/verify/check-storage-anaconda +++ b/test/verify/check-storage-anaconda @@ -166,7 +166,7 @@ class TestStorageAnaconda(storagelib.StorageCase): b.wait(lambda: b.call_js_func('ph_count', self.card("Storage") + " tbody tr") == 1) b.wait_not_present(self.card_row("Storage", location="/var")) - @testlib.skipImage("No Stratis", "debian-*", "ubuntu-*") + @testlib.skipImage("No Stratis", "debian-*", "ubuntu-*", "*suse*") @testlib.skipImage("commit 817c957899a4 removed Statis 2 support", "rhel-8-*") def testStratis(self): m = self.machine diff --git a/test/verify/check-storage-iscsi b/test/verify/check-storage-iscsi index a81c162beb7a..18d7ae7825e6 100755 --- a/test/verify/check-storage-iscsi +++ b/test/verify/check-storage-iscsi @@ -21,7 +21,7 @@ import storagelib import testlib -@testlib.skipImage("UDisks doesn't have support for iSCSI", "debian-*", "ubuntu-*", "arch") +@testlib.skipImage("UDisks doesn't have support for iSCSI", "debian-*", "ubuntu-*", "arch", "*suse*") @testlib.nondestructive class TestStorageISCSI(storagelib.StorageCase): From 9be58b2b095d5a9bf8a57e64bc9020faa4582518 Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Tue, 4 Jun 2024 08:24:30 +0100 Subject: [PATCH 16/59] test: Exclude suse images from sos report tests --- test/verify/check-sosreport | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/verify/check-sosreport b/test/verify/check-sosreport index 0d44bdc8dd2b..457ab6cda876 100755 --- a/test/verify/check-sosreport +++ b/test/verify/check-sosreport @@ -25,7 +25,7 @@ import testlib @testlib.skipOstree("No sosreport") -@testlib.skipImage("No sosreport", "arch") +@testlib.skipImage("No sosreport", "arch", "*suse*") @testlib.nondestructive class TestSOS(testlib.MachineCase): From 25173418df739516fe63ca65977b7adaeb83b2dc Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Tue, 4 Jun 2024 10:37:03 +0100 Subject: [PATCH 17/59] test: Overwrite default tumbleweed cgroup and memoryaccounting settings --- test/vm.install | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/vm.install b/test/vm.install index e954ecfa4c79..7c84f7464367 100644 --- a/test/vm.install +++ b/test/vm.install @@ -45,6 +45,15 @@ if [ "$ID" = "debian" ]; then echo '* soft core unlimited' >> /etc/security/limits.conf fi +if [ "$ID" = "opensuse-tumbleweed" ]; then + # Force allowing memory accounting and cgroup delegation in suse + # This is because by default suse images disable both of these + # so we can't run any tests that rely on checking memory usage + printf '[Manager]\nDefaultMemoryAccounting=yes\n' > /usr/lib/systemd/system.conf.d/80-memory-accounting.conf + mkdir -p /etc/systemd/system/user@.service.d + printf '[Service]\nDelegate=cpu io memory\n' > /etc/systemd/system/user@.service.d/80-cgroups-delegation.conf +fi + PLATFORM_ID="${PLATFORM_ID:-}" if [ "${PLATFORM_ID#platform:el}" != "$PLATFORM_ID" ]; then # allow /usr/local/bin/ in sudo shells, to use our installed tools like the Python bridge From 796b08734f1532cea09088f1210af2e716ddf8cc Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Tue, 4 Jun 2024 13:00:12 +0100 Subject: [PATCH 18/59] test: Skip selinux tests for suse --- test/verify/check-selinux | 4 ++-- test/verify/check-static-login | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/verify/check-selinux b/test/verify/check-selinux index 45736af00141..a7966bc7c008 100755 --- a/test/verify/check-selinux +++ b/test/verify/check-selinux @@ -54,7 +54,7 @@ class TestSelinux(testlib.MachineCase): "ansible_machine": {"image": TEST_OS_DEFAULT, "memory_mb": 650} } - @testlib.skipImage("No setroubleshoot", "debian-*", "ubuntu-*", "arch") + @testlib.skipImage("No setroubleshoot", "debian-*", "ubuntu-*", "arch", "*suse*") @testlib.skipOstree("No setroubleshoot") @testlib.skipBrowser("Firefox fails to recognize 'clipboard-read' permission", "firefox") def testTroubleshootAlerts(self): @@ -292,7 +292,7 @@ class TestSelinux(testlib.MachineCase): b.wait_text("ul[aria-label='System modifications']", "The logged in user is not permitted to view system modifications") -@testlib.skipImage("No cockpit-selinux", "debian-*", "ubuntu-*", "arch") +@testlib.skipImage("No cockpit-selinux", "debian-*", "ubuntu-*", "arch", "*suse*") @testlib.skipOstree("No cockpit-selinux") @testlib.nondestructive class TestSelinuxEnforcing(testlib.MachineCase): diff --git a/test/verify/check-static-login b/test/verify/check-static-login index 8500277aeae1..e82f1c2ced5c 100755 --- a/test/verify/check-static-login +++ b/test/verify/check-static-login @@ -476,7 +476,7 @@ account required pam_succeed_if.so user ingroup %s""" % m.get_admin_group "couldn't parse login input: Malformed input", "couldn't parse login input: Authentication failed") - @testlib.skipImage("No SELinux", "debian-*", "ubuntu-*", "arch") + @testlib.skipImage("No SELinux", "debian-*", "ubuntu-*", "arch", "*suse*") @testlib.skipOstree("No semanage") def testSELinuxRestrictedUser(self): m = self.machine From 36eee2b538a621dbca04eef8a005a3933db1cac7 Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Wed, 5 Jun 2024 12:28:34 +0100 Subject: [PATCH 19/59] test: run this without superuser since tw assumes it as su --- test/verify/check-reauthorize | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/verify/check-reauthorize b/test/verify/check-reauthorize index 0d35f6d1e91e..b96f315d8da6 100755 --- a/test/verify/check-reauthorize +++ b/test/verify/check-reauthorize @@ -89,7 +89,7 @@ class TestReauthorize(testlib.MachineCase): m.execute("echo user:foobar | chpasswd") b.default_user = "user" - self.login_and_go("/playground/test") + self.login_and_go("/playground/test", superuser=False) b.click(".super-channel button") b.wait_in_text(".super-channel span", 'result: ') From d7ec9fdf3bae9c4c19691bf679c252dfffeb1745 Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Thu, 6 Jun 2024 09:11:31 +0100 Subject: [PATCH 20/59] test: Add check docs case for Tumbleweed --- test/verify/check-pages | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/verify/check-pages b/test/verify/check-pages index 9f57345fa63a..4960d8973383 100755 --- a/test/verify/check-pages +++ b/test/verify/check-pages @@ -46,6 +46,8 @@ class TestPages(testlib.MachineCase): expected = "Fedora Linux documentation" + expected elif m.image.startswith("rhel"): expected = "Red Hat Enterprise Linux documentation" + expected + elif "suse" in m.image: + expected = "openSUSE Tumbleweed documentation" + expected elif m.image == "arch": expected = "Arch Linux documentation" + expected From 8bd5ad4d33ee9ae0a4c7fe8320d4545d8c17aae6 Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Fri, 7 Jun 2024 12:19:12 +0100 Subject: [PATCH 21/59] tests: Tumbleweed doesn't have sshd.socket --- test/common/testlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/common/testlib.py b/test/common/testlib.py index afd789fb2a7b..0e25f8866b10 100644 --- a/test/common/testlib.py +++ b/test/common/testlib.py @@ -1776,7 +1776,7 @@ def setUp(self, restrict: bool = True) -> None: self.sshd_socket = 'ssh.socket' else: self.sshd_service = 'sshd.service' - if image == 'arch': + if image == 'arch' or "suse" in image: self.sshd_socket = None else: self.sshd_socket = 'sshd.socket' From 6e78bd544133ebac821ab562c0657d7a3f893931 Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Fri, 7 Jun 2024 12:19:56 +0100 Subject: [PATCH 22/59] test: ChallengeResponseAuthentication to no on tumbleweed otherwise pam succeeds --- test/verify/check-loopback | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/verify/check-loopback b/test/verify/check-loopback index d44ba81dd53b..ab2c1522f917 100755 --- a/test/verify/check-loopback +++ b/test/verify/check-loopback @@ -42,6 +42,8 @@ class TestLoopback(testlib.MachineCase): self.restore_dir("/etc/ssh", restart_unit=self.sshd_service) sshd_path = testlib.get_sshd_config_path(m.image) m.execute(f"sed -i 's/.*PasswordAuthentication.*/PasswordAuthentication no/' {sshd_path} $(ls {sshd_path}.d/* 2>/dev/null || true)") + if "suse" in m.image: + m.execute(f"echo 'ChallengeResponseAuthentication no' >> {sshd_path}") m.execute(self.restart_sshd) m.wait_execute() From e71a3e2a439cee652d0433f8b4c755dea44d4a4b Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Fri, 7 Jun 2024 12:32:29 +0100 Subject: [PATCH 23/59] test: Don't run tlog tests on Tumbleweed --- test/verify/check-static-login | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/verify/check-static-login b/test/verify/check-static-login index e82f1c2ced5c..01b149900188 100755 --- a/test/verify/check-static-login +++ b/test/verify/check-static-login @@ -420,7 +420,7 @@ account required pam_succeed_if.so user ingroup %s""" % m.get_admin_group self.allow_restart_journal_messages() - @testlib.skipImage("No tlog", "debian-*", "ubuntu-*", "arch") + @testlib.skipImage("No tlog", "debian-*", "ubuntu-*", "arch", "*suse*") @testlib.skipOstree("No tlog") @testlib.skipImage("FIXME: tlog messes up bridge frame protocol", "rhel-8-10") def testSessionRecordingShell(self): From ec0b8efd8534875d8c4865ee8e0f8a1312105d4c Mon Sep 17 00:00:00 2001 From: Miika Alikirri Date: Mon, 10 Jun 2024 10:19:38 +0300 Subject: [PATCH 24/59] test: set useradd defaults on suse images --- test/verify/check-users | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/test/verify/check-users b/test/verify/check-users index e256f47518d8..68eb8de52e27 100755 --- a/test/verify/check-users +++ b/test/verify/check-users @@ -197,7 +197,12 @@ class TestAccounts(testlib.MachineCase): b.wait_visible("#accounts-create") # Create a user from the UI - self.sed_file('s@^SHELL=.*$@SHELL=/bin/true@', '/etc/default/useradd') + if "suse" in m.image: + # suse doesn't have /etc/default/useradd + m.execute("echo 'SHELL=/bin/true' > /etc/default/useradd") + else: + self.sed_file('s@^SHELL=.*$@SHELL=/bin/true@', '/etc/default/useradd') + b.click('#accounts-create') b.wait_visible('#accounts-create-dialog') b.set_input_text('#accounts-create-user-name', "Berta") @@ -415,7 +420,11 @@ class TestAccounts(testlib.MachineCase): self.login_and_go("/users") # Create a locked user with weak password - self.sed_file('s/^SHELL=.*$/SHELL=/', '/etc/default/useradd') + if "suse" in m.image: + # suse doesn't have /etc/default/useradd + m.execute("echo 'SHELL=' > /etc/default/useradd") + else: + self.sed_file('s/^SHELL=.*$/SHELL=/', '/etc/default/useradd') self.allow_journal_messages(".*required to change your password immediately.*") self.allow_journal_messages(".*user account or password has expired.*") From 87976359cc6a79cd4c15515fc53bcf97c419eb29 Mon Sep 17 00:00:00 2001 From: Miika Alikirri Date: Mon, 10 Jun 2024 10:20:07 +0300 Subject: [PATCH 25/59] test: temporarily unlock root on suse images --- test/verify/check-users | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/verify/check-users b/test/verify/check-users index 68eb8de52e27..89f1b846a431 100755 --- a/test/verify/check-users +++ b/test/verify/check-users @@ -268,6 +268,11 @@ class TestAccounts(testlib.MachineCase): b.logout() b.login_and_go("/users") + + # in suse images, Administrative requires root to be unlocked + if "suse" in m.image: + m.execute("usermod root --unlock") + createUser( browser=b, machine=m, From 3a04ac516d71cd81c3324f3a63e8e3f33e62e8e1 Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Tue, 11 Jun 2024 08:04:44 +0100 Subject: [PATCH 26/59] test: SUSE Images have different path for password complexity requirements --- test/verify/check-static-login | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/verify/check-static-login b/test/verify/check-static-login index 01b149900188..34ba552ebd79 100755 --- a/test/verify/check-static-login +++ b/test/verify/check-static-login @@ -282,8 +282,10 @@ account required pam_succeed_if.so user ingroup %s""" % m.get_admin_group m.execute(self.restart_sshd) # test steps below assume a pam_pwquality config with retry > 1; on some images authselect drops that setting - if not m.image.startswith('debian') and not m.image.startswith('ubuntu') and not m.image.startswith("arch"): + if not m.image.startswith('debian') and not m.image.startswith('ubuntu') and not m.image.startswith("arch") and "suse" not in m.image: self.sed_file("/password.*requisite.*pam_pwquality/ s/$/ retry=3/", "/etc/pam.d/password-auth") + elif "suse" in m.image: + self.sed_file("/password.*requisite.*pam_pwquality/ s/$/ retry=3/", "/etc/pam.d/common-password-pc") m.execute("chage -d 0 admin") m.start_cockpit() From b1f4327614ed065181e0cdc8f2813574d5203fc7 Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Tue, 11 Jun 2024 08:05:34 +0100 Subject: [PATCH 27/59] test: Add faillock configuration for SUSE images --- test/verify/check-static-login | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/verify/check-static-login b/test/verify/check-static-login index 34ba552ebd79..211369802f58 100755 --- a/test/verify/check-static-login +++ b/test/verify/check-static-login @@ -704,6 +704,16 @@ account required pam_succeed_if.so user ingroup %s""" % m.get_admin_group "/etc/pam.d/common-auth") elif m.image == "arch": self.sed_file("s/# deny = 3/deny = 4/", "/etc/security/faillock.conf") + elif "suse" in m.image: + m.execute("""cat < /etc/security/faillock.conf +deny = 4 +EOF""") + m.execute("""cat <> /etc/pam.d/common-auth +auth [success=1 default=bad] pam_unix.so +auth [default=die] pam_faillock.so authfail +auth sufficient pam_faillock.so authsucc +auth required pam_deny.so +EOF""") else: # see https://access.redhat.com/solutions/62949 self.sed_file("""/pam_unix/ { From d4fcabbc078ba374cc0b23cca533c3dddddefac2 Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Tue, 11 Jun 2024 08:06:49 +0100 Subject: [PATCH 28/59] test: Make lastlog silent to prevent unexpected journal messages --- test/vm.install | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/vm.install b/test/vm.install index 7c84f7464367..364ff8ec03ab 100644 --- a/test/vm.install +++ b/test/vm.install @@ -46,6 +46,11 @@ if [ "$ID" = "debian" ]; then fi if [ "$ID" = "opensuse-tumbleweed" ]; then + # Default pam policy notes pam last login date due to lastlog.so + # This will add cockpit's pam config to the silent list so we + # Don't fail due to unexpected journal lines + sed -i 's/sddm/sddm,cockpit/' /etc/pam.d/postlogin-session + # Force allowing memory accounting and cgroup delegation in suse # This is because by default suse images disable both of these # so we can't run any tests that rely on checking memory usage From adc139a6964ab25d05b2cc33c09e30a1bbf4e89f Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Tue, 11 Jun 2024 09:23:32 +0100 Subject: [PATCH 29/59] test: SUSE images use a different path for default pam files --- test/verify/check-static-login | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/verify/check-static-login b/test/verify/check-static-login index 211369802f58..c19aa09b0f81 100755 --- a/test/verify/check-static-login +++ b/test/verify/check-static-login @@ -385,6 +385,9 @@ account required pam_succeed_if.so user ingroup %s""" % m.get_admin_group f"{sshd_path} $(ls {sshd_path}.d/* 2>/dev/null || true)") m.execute(self.restart_sshd) + if "suse" in m.image: + conf = "/usr/lib/pam.d/cockpit" + # On Arch Linux the ordering matters due to an auth include for system-remote-login if self.machine.image == "arch": self.sed_file('1 a auth required mock-pam-conv-mod.so', conf) From e5319511207da06a89042733d25b76cce05c5d3e Mon Sep 17 00:00:00 2001 From: Miika Alikirri Date: Tue, 11 Jun 2024 11:56:02 +0300 Subject: [PATCH 30/59] test: ignore missing btmp file journal messages --- test/common/testlib.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/common/testlib.py b/test/common/testlib.py index 0e25f8866b10..b47324ff3747 100644 --- a/test/common/testlib.py +++ b/test/common/testlib.py @@ -2052,6 +2052,12 @@ def login_and_go( # timedatex.service shuts down after timeout, runs into race condition with property watching ".*org.freedesktop.timedate1: couldn't get all properties.*Error:org.freedesktop.DBus.Error.NoReply.*", + + # https://github.com/cockpit-project/cockpit/issues/19235 + "invalid non-UTF8 @data passed as text to web_socket_connection_send.*", + + # Noise from btmp file missing systems where it doesn't exists + r"cockpit-session: open\(\/var\/log\/btmp\) failed: No such file or directory", ] default_allowed_messages += os.environ.get("TEST_ALLOW_JOURNAL_MESSAGES", "").split(",") From 89abe03390e7903338667a23eac7ad6092034b33 Mon Sep 17 00:00:00 2001 From: Miika Alikirri Date: Wed, 19 Jun 2024 11:05:46 +0300 Subject: [PATCH 31/59] tests: add specific login logic for tumbleweed --- test/verify/check-users | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/test/verify/check-users b/test/verify/check-users index 89f1b846a431..4e1103321673 100755 --- a/test/verify/check-users +++ b/test/verify/check-users @@ -544,7 +544,13 @@ class TestAccounts(testlib.MachineCase): # Login as jussi and change role admin for itself b.logout() - b.login_and_go("/users", user="jussi", password="bar") + if "suse" in m.image: + # on suse images, you cannot use login_and_go hackery to automatigically get admin rights + # when the user doesn't have the same password as root + b.login_and_go("/users", user="jussi", password="bar", superuser=False) + b.become_superuser() + else: + b.login_and_go("/users", user="jussi", password="bar") # There is only one badge and it is for jussi b.wait_text('#current-account-badge', 'Your account') @@ -620,7 +626,13 @@ class TestAccounts(testlib.MachineCase): m.execute('echo "damaged:x:1234:1234:Damaged" >> /etc/passwd') # Logout and login with the new password - b.relogin(path="/users", user="jussi", password=good_password_2) + if "suse" in m.image: + # on suse images, you cannot use login_and_go hackery to automatigically get admin rights + # when the user doesn't have the same password as root + b.relogin(path="/users", user="jussi", password=good_password_2, superuser=False) + b.become_superuser() + else: + b.relogin(path="/users", user="jussi", password=good_password_2) b.go("/users") b.enter_page("/users") @@ -1066,6 +1078,7 @@ class TestAccounts(testlib.MachineCase): b.wait_not_present("#password-expiration") b.logout() + # HACK: https://github.com/cockpit-project/cockpit/issues/20262 self.login_and_go("/users#/scruffy", user="scruffy", superuser=False) b.wait_text("#account-user-name", "scruffy") From 37ed3bd1f586f992e5f7dc650f8c67d77d6b1011 Mon Sep 17 00:00:00 2001 From: Miika Alikirri Date: Wed, 19 Jun 2024 11:07:13 +0300 Subject: [PATCH 32/59] test: Skip TestAD for suse images --- test/verify/check-system-realms | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/verify/check-system-realms b/test/verify/check-system-realms index d80fd1ac18d0..7b20ba416a99 100755 --- a/test/verify/check-system-realms +++ b/test/verify/check-system-realms @@ -836,7 +836,7 @@ ipa-advise enable-admins-sudo | sh -ex m.execute(f"! su -c '{ccache_env} klist' alice") -@testlib.skipImage("adcli not on test images", "debian-*", "ubuntu-*") +@testlib.skipImage("adcli not on test images", "debian-*", "ubuntu-*", "*suse*") @testlib.no_retry_when_changed class TestAD(TestRealms, CommonTests): def setUp(self): From d43386dca172211fb5d440accae08dc1c2d5ed63 Mon Sep 17 00:00:00 2001 From: Miika Alikirri Date: Wed, 19 Jun 2024 11:08:15 +0300 Subject: [PATCH 33/59] test: Parse comments out of os-release file --- test/verify/check-connection | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/test/verify/check-connection b/test/verify/check-connection index 12b4c1cb8acf..070b75da9621 100755 --- a/test/verify/check-connection +++ b/test/verify/check-connection @@ -1107,7 +1107,24 @@ until pgrep -f '^(/usr/[^ ]+/[^ /]*python[^ /]* )?/usr/bin/cockpit-bridge'; do s # branding.css undergoes variable substitution based on the content of # /usr/lib/os-release. Perform the substitution for ourselves for # validation. envsubst comes from gettext. - os_release_vars = m.execute("cat /usr/lib/os-release").replace('\n', ' ') + os_release_vars = m.execute("cat /usr/lib/os-release") + # Sometimes os-release has lines containing comments which messes up the shell + if "#" in os_release_vars: + valid_lines = [] + for line in os_release_vars.split('\n'): + line = line.strip() + # Ignore commented out lines + if line.startswith('#'): + continue + + # remove the comment at the end of line if it exists + line = line.split('#')[0] + valid_lines.append(line) + + os_release_vars = ' '.join(valid_lines) + else: + os_release_vars = os_release_vars.replace('\n', ' ') + m.execute(f'{os_release_vars} envsubst < {branddir}/branding.css > /tmp/branding.ref') self.addCleanup(m.execute, "rm /tmp/branding.ref") From fd91467b9adbd1d8ff05973bf92cc4722e18ca75 Mon Sep 17 00:00:00 2001 From: Miika Alikirri Date: Wed, 19 Jun 2024 11:09:09 +0300 Subject: [PATCH 34/59] tests: Check right image on suse images --- test/verify/check-connection | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/verify/check-connection b/test/verify/check-connection index 070b75da9621..a46a4546fcb4 100755 --- a/test/verify/check-connection +++ b/test/verify/check-connection @@ -1147,7 +1147,10 @@ until pgrep -f '^(/usr/[^ ]+/[^ /]*python[^ /]* )?/usr/bin/cockpit-bridge'; do s # some brands miss the images, but the OSes we CI have them all curl_and_compare('branding.css', 'text/css', reference='/tmp/branding.ref') - curl_and_compare('logo.png', 'image/png') + if "suse" in m.image: + curl_and_compare('square-hicolor.svg', 'image/svg') + else: + curl_and_compare('logo.png', 'image/png') curl_and_compare('favicon.ico') # do a pixel test to make sure everything looks like we expect From bc781e43b64c7d26e8825d03812b96015e3f825b Mon Sep 17 00:00:00 2001 From: Miika Alikirri Date: Wed, 19 Jun 2024 11:10:27 +0300 Subject: [PATCH 35/59] test: Check the right shell indicator on suse images --- test/verify/check-shell-active-pages | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/verify/check-shell-active-pages b/test/verify/check-shell-active-pages index ffa0d96f9757..c73fdbb1dca3 100755 --- a/test/verify/check-shell-active-pages +++ b/test/verify/check-shell-active-pages @@ -65,7 +65,10 @@ class TestActivePages(testlib.MachineCase): # wait for prompt in first line b.wait_visible(".terminal .xterm-accessibility-tree") - b.wait_in_text(line_sel(1), '$') + shell_indicator = '$' + if "suse" in m.image: + shell_indicator = '>' + b.wait_in_text(line_sel(1), shell_indicator) # run a command that we can easily identify, and which will die with the terminal b.input_text("bash -c 'exec -a kitten cat'\n") From 78beade2f345b9381c64d0fd7fe8dda4558e17bc Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Tue, 11 Jun 2024 11:27:18 +0100 Subject: [PATCH 36/59] test: prefix values with = so comments don't get matched --- test/verify/check-kdump | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/verify/check-kdump b/test/verify/check-kdump index b35cc0d47364..cd3dc2f1507e 100755 --- a/test/verify/check-kdump +++ b/test/verify/check-kdump @@ -407,7 +407,7 @@ class TestKdump(KdumpHelpers): b.wait_text("#kdump-target-info", "Remote over NFS, someserver:/srv/var/crash") conf = m.execute("cat /etc/sysconfig/kdump") self.assertIn('KDUMP_SAVEDIR=nfs://someserver/srv', conf) - self.assertNotIn("ssh://", conf) + self.assertNotIn("=ssh://", conf) # NFS with custom path b.click("#kdump-change-target") @@ -429,7 +429,7 @@ class TestKdump(KdumpHelpers): b.wait_text("#kdump-target-info", "Local, /var/tmp") conf = m.execute("cat /etc/sysconfig/kdump") self.assertIn('KDUMP_SAVEDIR=file:///var/tmp', conf) - self.assertNotIn("nfs://", conf) + self.assertNotIn("=nfs://", conf) # Check compression conf = m.execute("cat /etc/sysconfig/kdump") From a55f6f4dfa7b2d475a4af9fac9b57853bc2f974f Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Tue, 18 Jun 2024 13:54:49 +0100 Subject: [PATCH 37/59] firewall: Improve handling of service retrieval for Tumbleweed --- pkg/networkmanager/firewall.jsx | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/pkg/networkmanager/firewall.jsx b/pkg/networkmanager/firewall.jsx index 8ff8945371f1..4ecdab43341f 100644 --- a/pkg/networkmanager/firewall.jsx +++ b/pkg/networkmanager/firewall.jsx @@ -556,10 +556,24 @@ class AddEditServicesModal extends React.Component { componentDidMount() { firewall.getAvailableServices() .then(services => this.setState({ services })); - cockpit.file('/etc/services').read() - .then(content => this.setState({ - avail_services: this.parseServices(content) - })); + async function fetchServices(component) { + try { + await cockpit.spawn(["test", "-e", "/usr/etc/services"], { err: "ignore" }); + cockpit.file('/usr/etc/services').read() + .then(content => component.setState({ + avail_services: component.parseServices(content) + })); + } catch (err1) { + cockpit.file('/etc/services').read() + .then(content => component.setState({ + avail_services: component.parseServices(content) + })); + } + } + + if (this.state.avail_services === null) { + fetchServices(this); + } } onFilterChanged(value) { From 1cee618553ae062f2eb583980be7dc051a55c4a4 Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Tue, 18 Jun 2024 13:56:47 +0100 Subject: [PATCH 38/59] test: Amend mapping for Tumbleweed as 50000 is mrt --- test/verify/check-networkmanager-firewall | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/verify/check-networkmanager-firewall b/test/verify/check-networkmanager-firewall index 7f549fd23edb..5bb87082aa53 100755 --- a/test/verify/check-networkmanager-firewall +++ b/test/verify/check-networkmanager-firewall @@ -390,10 +390,13 @@ class TestFirewall(netlib.NetworkCase): set_field("#tcp-ports", "", "") set_field("#tcp-ports", "80,7", "custom--http-echo") set_field("#udp-ports", "123", "custom--http-echo-ntp") - set_field("#udp-ports", "123, 50000", "custom--http-echo-ntp-50000") - set_field("#tcp-ports", "", "custom--ntp-50000") - set_field("#tcp-ports", "https", "custom--https-ntp-50000") - save("custom--https-ntp-50000", "443", "123, 50000") + + # SUSE has port 50000 listed as mrt + mrt_port = "mrt" if "suse" in m.image else "50000" + set_field("#udp-ports", "123, 50000", f"custom--http-echo-ntp-{mrt_port}") + set_field("#tcp-ports", "", f"custom--ntp-{mrt_port}") + set_field("#tcp-ports", "https", f"custom--https-ntp-{mrt_port}") + save(f"custom--https-ntp-{mrt_port}", "443", "123, 50000") open_dialog() set_field("#tcp-ports", "80-82", "custom--80-82") From e8408c36eeebe9b71a9313062de67fdb8dfc6da2 Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Wed, 26 Jun 2024 08:55:32 +0100 Subject: [PATCH 39/59] cockpitconf: Always check against system config directories --- src/common/cockpitconf.c | 22 +++++++++++++++++++--- src/common/test-config.c | 2 +- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/common/cockpitconf.c b/src/common/cockpitconf.c index 7e070346e8a9..e809f1785ca7 100644 --- a/src/common/cockpitconf.c +++ b/src/common/cockpitconf.c @@ -76,7 +76,7 @@ regcompx (regex_t *preg, const char *regex, int cflags) /* For optimization, this modifies string; the returned array has pointers into string * The array itself gets allocated and must be freed after use. */ static const char ** -strsplit (char *string, char delimiter) +strsplit (char *string, char delimiter, unsigned *len_out) { const char ** parts = reallocarrayx (NULL, 2, sizeof (char*)); char *cur = string; @@ -104,6 +104,10 @@ strsplit (char *string, char delimiter) } parts[len] = NULL; + + if (len_out) + *len_out = len; + return parts; } @@ -299,9 +303,21 @@ cockpit_conf_get_dirs (void) env = getenv ("XDG_CONFIG_DIRS"); if (env && env[0]) { + unsigned len_out; + + const char ** xdg_config_dirs = NULL; + unsigned len = sizeof (cockpit_config_dirs) / sizeof (char*); + system_config_dirs = reallocarrayx (NULL, len, sizeof (char*)); + memcpy (system_config_dirs, cockpit_config_dirs, sizeof (cockpit_config_dirs)); + /* strsplit() modifies the string inline, so copy and keep a ref */ env = strdup (env); - system_config_dirs = strsplit (env, ':'); + xdg_config_dirs = strsplit (env, ':', &len_out); + + system_config_dirs = reallocarrayx (system_config_dirs, len + len_out + 1, sizeof (char*)); + memcpy (system_config_dirs + (len-1), xdg_config_dirs, len_out * sizeof (char*)); + system_config_dirs[len - 1 + len_out] = NULL; + free (xdg_config_dirs); } } @@ -348,7 +364,7 @@ cockpit_conf_strv (const char *section, entry->strv_value = strdupx (entry->value); for (char *c = entry->strv_value + strlen (entry->strv_value) - 1; c >= entry->strv_value && isspace (*c); --c) *c = '\0'; - entry->strv_cache = strsplit (entry->strv_value, delimiter); + entry->strv_cache = strsplit (entry->strv_value, delimiter, NULL); entry->strv_delimiter = delimiter; } diff --git a/src/common/test-config.c b/src/common/test-config.c index b6d8a31d0fef..675405e2fccf 100644 --- a/src/common/test-config.c +++ b/src/common/test-config.c @@ -139,7 +139,7 @@ test_load_dir (void) cockpit_config_file = "cockpit.conf"; g_assert_cmpstr (cockpit_conf_string ("Section2", "value1"), ==, "string"); - g_assert_cmpstr (cockpit_conf_get_dirs ()[1], ==, SRCDIR "/src/ws/mock-config"); + g_assert_cmpstr (cockpit_conf_get_dirs ()[2], ==, SRCDIR "/src/ws/mock-config"); cockpit_conf_cleanup (); } From 7c04921091afb207ab668a6df3a6409fc401c4f7 Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Wed, 26 Jun 2024 08:58:01 +0100 Subject: [PATCH 40/59] test: Add Tumbleweed workaround for passwordless ssh --- test/verify/check-connection | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/test/verify/check-connection b/test/verify/check-connection index a46a4546fcb4..56dd344f4af5 100755 --- a/test/verify/check-connection +++ b/test/verify/check-connection @@ -597,12 +597,27 @@ class TestConnection(testlib.MachineCase): m = self.machine # non-admin user - m.execute("useradd user") + m.execute("useradd -m user") + m.execute("groupadd user") + m.execute("usermod -a -G user user") # enable no-password login for 'admin' and 'user' m.execute("passwd -d admin") m.execute("passwd -d user") + # HACK: For SUSE images, PAM for ssh is setup in a way that disallowed passwordless ssh + # In order to get around this lets generate an ssh key and handle auth that way + if "suse" in m.image: + m.execute("ssh-keygen -t ed25519 -f /root/.ssh/id_ed25519 -N ''") + m.execute("""cat /root/.ssh/id_ed25519.pub > /home/admin/.ssh/authorized_keys + chown admin:admin /home/admin/.ssh/authorized_keys + chmod 600 /home/admin/.ssh/authorized_keys""") + m.execute("""mkdir -p /home/user/.ssh + cat /root/.ssh/id_ed25519.pub > /home/user/.ssh/authorized_keys + chown -R user:user /home/user/.ssh + chmod 700 /home/user/.ssh + chmod 600 /home/user/.ssh/authorized_keys""") + self.sed_file('$ a\\\nPermitEmptyPasswords yes', testlib.get_sshd_config_path(m.image), self.restart_sshd) From e57f1fb8a4f1dcc3a14ae64d06c6eea63df06172 Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Wed, 26 Jun 2024 09:07:42 +0100 Subject: [PATCH 41/59] test: Add kdump support for tumbleweed Tumbleweed's file doesn't correctly recognise this as compressed kdump but instead just as regular data, so this check would always fail It also uses a date format for the file paths --- test/verify/check-kdump | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/test/verify/check-kdump b/test/verify/check-kdump index cd3dc2f1507e..45439cd6d1db 100755 --- a/test/verify/check-kdump +++ b/test/verify/check-kdump @@ -40,6 +40,11 @@ class KdumpHelpers(testlib.MachineCase): browser.wait_visible(".pf-v5-c-switch__input" + (":checked" if active else ":not(:checked)")) def enableKdump(self): + if "suse" in self.machine.image: + self.machine.execute("sed -i -r 's/GRUB_CMDLINE_LINUX=\"(.*)\"/GRUB_CMDLINE_LINUX=\"\\1 crashkernel=192M,high crashkernel=0,low\"/g' /etc/default/grub") + self.machine.execute("update-bootloader; systemctl enable kdump") + self.machine.reboot() + if self.machine.image.startswith("fedora"): self.machine.execute("systemctl enable kdump; kdumpctl reset-crashkernel") self.machine.reboot() @@ -104,7 +109,7 @@ class TestKdump(KdumpHelpers): b.wait_visible("#app") - if m.image.startswith("fedora"): + if m.image.startswith("fedora") or "suse" in m.image: # crashkernel command line not set, needs to be explicitly enabled in Fedora b.wait_in_text(".pf-v5-c-alert__title", "Kernel did not boot with the crashkernel setting") # no service on/off button @@ -154,7 +159,10 @@ class TestKdump(KdumpHelpers): b.click(f"#kdump-settings-dialog button{self.primary_btn_class}") with b.wait_timeout(120): # needs to rebuild initrd b.wait_not_present(pathInput) - m.execute("cat /etc/kdump.conf | grep -qE 'makedumpfile.*-c.*'") + if "suse" in m.image: + m.execute("cat /etc/sysconfig/kdump | grep -qE 'KDUMP_SAVEDIR'") + else: + m.execute("cat /etc/kdump.conf | grep -qE 'makedumpfile.*-c.*'") # generate a valid kdump config with ssh target b.click("#kdump-change-target") @@ -202,13 +210,19 @@ class TestKdump(KdumpHelpers): # crash the kernel and make sure it wrote a report into the right directory self.crashKernel(f"stored in {customPath} as vmcore") - m.execute(f"until test -e {customPath}/127.0.0.1*/vmcore; do sleep 1; done", timeout=180) - self.assertIn("Kdump compressed dump", m.execute(f"file {customPath}/127.0.0.1*/vmcore")) + m.execute(f"until test -e {customPath}/*/vmcore; do sleep 1; done", timeout=180) + # HACK: Tumbleweed's file doesn't correctly recognise kdump's, they are only shown + # as data, lets skip this and just check the file exists + if "suse" not in m.image: + self.assertIn("Kdump compressed dump", m.execute(f"file {customPath}/*/vmcore")) self.login_and_go("/kdump") b.wait_visible("#app") - m.execute("cp /etc/kdump.conf /etc/kdump.conf.orig") + if "suse" in m.image: + m.execute("cp /etc/sysconfig/kdump /etc/sysconfig/kdump.orig") + else: + m.execute("cp /etc/kdump.conf /etc/kdump.conf.orig") b.click("#kdump-automation-script") b.click("button:contains('Shell script')") shell_script_sel = ".automation-script-modal .pf-v5-c-modal-box__body section:nth-child(3) textarea" @@ -217,7 +231,10 @@ class TestKdump(KdumpHelpers): m.execute(b.text(shell_script_sel)) b.click(".pf-v5-c-modal-box__footer button:contains('Close')") b.wait_not_present(".automation-script-modal") - m.execute("diff /etc/kdump.conf /etc/kdump.conf.orig", stdout=None) + if "suse" in m.image: + m.execute("diff /etc/sysconfig/kdump /etc/sysconfig/kdump.orig", stdout=None) + else: + m.execute("diff /etc/kdump.conf /etc/kdump.conf.orig", stdout=None) # service errors @@ -502,8 +519,12 @@ class TestKdumpNFS(KdumpHelpers): b.wait_not_present("#kdump-settings-dialog") # explicit nfs option, unset path - conf = m.execute("cat /etc/kdump.conf") - self.assertIn("\nnfs 10.111.113.2:/srv/kdump\n", conf) + if "suse" in m.image: + conf = m.execute("cat /etc/sysconfig/kdump") + self.assertIn("nfs://10.111.113.2/srv/kdump", conf) + else: + conf = m.execute("cat /etc/kdump.conf") + self.assertIn("\nnfs 10.111.113.2:/srv/kdump\n", conf) self.assertNotIn("\npath", conf) try: From dba53de80e6f367f9196f7f0e74026a67f5e9645 Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Wed, 26 Jun 2024 09:08:46 +0100 Subject: [PATCH 42/59] test: Add Tumbleweeds redis target --- test/verify/check-metrics | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/verify/check-metrics b/test/verify/check-metrics index c3b627f679b1..8c884a091dc4 100755 --- a/test/verify/check-metrics +++ b/test/verify/check-metrics @@ -70,6 +70,8 @@ def redisService(image): return "redis-server" if image.startswith(("fedora", "centos-10", "rhel-10")): return "valkey" + if "suse" in image: + return "redis.target" return "redis" From 67031370cc15e49bdf51b3772bad12fe3f5807ec Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Wed, 26 Jun 2024 09:09:11 +0100 Subject: [PATCH 43/59] test: Update Tumbleweeds pam path --- test/verify/check-static-login | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/verify/check-static-login b/test/verify/check-static-login index c19aa09b0f81..d1991db91bd1 100755 --- a/test/verify/check-static-login +++ b/test/verify/check-static-login @@ -59,7 +59,11 @@ account required pam_succeed_if.so user ingroup %s""" % m.get_admin_group if not m.ws_container: deny_non_root("/etc/pam.d/cockpit") - deny_non_root("/etc/pam.d/sshd") + + if "suse" in m.image: + deny_non_root("/usr/lib/pam.d/sshd") + else: + deny_non_root("/etc/pam.d/sshd") m.start_cockpit() b.open("/system") From d722fbc4083e92fd374b72cf9bdcb8fd1937939f Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Wed, 26 Jun 2024 09:11:07 +0100 Subject: [PATCH 44/59] test: Update config path for Tumbleweed --- test/verify/check-system-realms | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/verify/check-system-realms b/test/verify/check-system-realms index 7b20ba416a99..35eec4431b6e 100755 --- a/test/verify/check-system-realms +++ b/test/verify/check-system-realms @@ -983,7 +983,11 @@ fi ipa-getkeytab -p HTTP/x0.cockpit.lan -k %(keytab)s # HACK: due to sudo's "last rule wins", our /etc/sudoers rule becomes trumped by sssd's, so swap the order -sed -i '/^sudoers:/ s/files sss/sss files/' /etc/nsswitch.conf +if [ -f /etc/nsswitch.conf ]; then + sed -i '/^sudoers:/ s/files sss/sss files/' /etc/nsswitch.conf +else + sed -i '/^sudoers:/ s/files sss/sss files/' /usr/etc/nsswitch.conf +fi """ # This is here because our test framework can't run ipa VM's twice From 6b79abdd51759f7740312b090cc083498841598c Mon Sep 17 00:00:00 2001 From: Alice Brooks Date: Thu, 18 Jul 2024 06:32:17 +0100 Subject: [PATCH 45/59] test: Update suse images to use valkey --- pkg/metrics/manifest.json | 3 ++- pkg/metrics/metrics.jsx | 21 ++++++++++++++------- test/verify/check-metrics | 4 +--- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/pkg/metrics/manifest.json b/pkg/metrics/manifest.json index 4a868e38cc08..a22d5e2c79fa 100644 --- a/pkg/metrics/manifest.json +++ b/pkg/metrics/manifest.json @@ -12,7 +12,8 @@ "config": { "redis_package": { "fedora": "valkey", - "platform:el10": "valkey" + "platform:el10": "valkey", + "opensuse-tumbleweed": "valkey" } } } diff --git a/pkg/metrics/metrics.jsx b/pkg/metrics/metrics.jsx index dc217c25e070..923c992bc8e1 100644 --- a/pkg/metrics/metrics.jsx +++ b/pkg/metrics/metrics.jsx @@ -1323,12 +1323,12 @@ const wait_cond = (cond, objects) => { const PCPConfigDialog = ({ firewalldRequest, - s_pmlogger, s_pmproxy, s_redis, s_redis_server, s_valkey, - packageInstallCallback, + s_pmlogger, s_pmproxy, s_redis, s_redis_server, s_valkey, s_valkey_target, + packageInstallCallback }) => { const Dialogs = useDialogs(); const dialogInitialProxyValue = runningService(s_pmproxy) && ( - runningService(s_redis) || runningService(s_redis_server) || runningService(s_valkey)); + runningService(s_redis) || runningService(s_redis_server) || runningService(s_valkey) || runningService(s_valkey_target)); const [dialogError, setDialogError] = useState(null); const [dialogLoggerValue, setDialogLoggerValue] = useState(runningService(s_pmlogger)); const [dialogProxyValue, setDialogProxyValue] = useState(dialogInitialProxyValue); @@ -1343,7 +1343,7 @@ const PCPConfigDialog = ({ if (dialogLoggerValue && !s_pmlogger.exists) { missing.push(...await get_pcp_packages()); } - const redisExists = () => s_redis.exists || s_redis_server.exists || s_valkey.exists; + const redisExists = () => s_redis.exists || s_redis_server.exists || s_valkey.exists || s_valkey_target.exists; if (dialogProxyValue && !redisExists()) { const os_release = await read_os_release(); missing.push(get_manifest_config_matchlist("metrics", "redis_package", "redis", @@ -1357,7 +1357,7 @@ const PCPConfigDialog = ({ debug("PCPConfig: package installation successful"); await wait_cond(() => (s_pmlogger.exists && (!dialogProxyValue || (s_pmproxy.exists && redisExists()))), - [s_pmlogger, s_pmproxy, s_redis, s_redis_server, s_valkey]); + [s_pmlogger, s_pmproxy, s_redis, s_redis_server, s_valkey, s_valkey_target]); } }; @@ -1373,6 +1373,9 @@ const PCPConfigDialog = ({ if (s_valkey.exists && s_valkey.unit?.UnitFileState !== 'masked') { real_redis = s_valkey; redis_name = "valkey.service"; + } else if (s_valkey_target.exists && s_valkey_target.unit?.UnitFileState !== 'masked') { + real_redis = s_valkey_target; + redis_name = "valkey.target"; } else if (s_redis_server.exists && s_redis_server.unit?.UnitFileState !== 'masked') { real_redis = s_redis_server; redis_name = "redis-server.service"; @@ -1498,6 +1501,7 @@ const PCPConfig = ({ buttonVariant, firewalldRequest }) => { const s_redis = useObject(() => service.proxy("redis.service"), null, []); const s_redis_server = useObject(() => service.proxy("redis-server.service"), null, []); const s_valkey = useObject(() => service.proxy("valkey.service"), null, []); + const s_valkey_target = useObject(() => service.proxy("valkey.target"), null, []); useEvent(superuser, "changed"); useEvent(s_pmlogger, "changed"); @@ -1505,12 +1509,14 @@ const PCPConfig = ({ buttonVariant, firewalldRequest }) => { useEvent(s_redis, "changed"); useEvent(s_redis_server, "changed"); useEvent(s_valkey, "changed"); + useEvent(s_valkey_target, "changed"); debug("PCPConfig s_pmlogger.state", s_pmlogger.state); debug("PCPConfig s_pmproxy state", s_pmproxy.state, "redis exists", s_redis.exists, "state", s_redis.state, "redis-server exists", s_redis_server.exists, "state", s_redis_server.state, - "valkey exists", s_valkey.exists, "state", s_valkey.state); + "valkey exists", s_valkey.exists, "state", s_valkey.state, + "valkey-target exists", s_valkey_target.exists, "state", s_valkey_target.state); if (!superuser.allowed) return null; @@ -1521,13 +1527,14 @@ const PCPConfig = ({ buttonVariant, firewalldRequest }) => { s_pmlogger={s_pmlogger} s_pmproxy={s_pmproxy} s_redis={s_redis} s_redis_server={s_redis_server} s_valkey={s_valkey} + s_valkey_target={s_valkey_target} packageInstallCallback={() => setPackageInstallStatus("done")} />); } return (