Enhance UVM installation and configuration for Ubuntu 22.04 root filesystem
- Update README and USAGE documentation to reflect changes in rootfs handling, including the default local build from Ubuntu 22.04 packages. - Modify the installer to support building the rootfs locally with authenticated packages and to require SHA-256 checksums for prebuilt images. - Implement checks for setuid permissions on sudo and update the provisioning logic for guest users to ensure proper sudo access. - Adjust tests to validate the new rootfs building process and ensure that existing functionality is preserved. - Refactor code to improve clarity and maintainability, including the introduction of constants for Ubuntu release and guest packages.
This commit is contained in:
+42
-1
@@ -130,6 +130,19 @@ class ImageStoreTests(unittest.TestCase):
|
||||
self.assertIn("Match User service\n PasswordAuthentication yes", updated)
|
||||
self.assertNotIn("ChallengeResponseAuthentication yes", updated)
|
||||
|
||||
def test_full_ubuntu_sshd_global_directives_stay_outside_match_blocks(self) -> None:
|
||||
updated = _enable_ssh_password_authentication(
|
||||
"Include /etc/ssh/sshd_config.d/*.conf\n"
|
||||
"UsePAM yes\nSubsystem sftp /usr/lib/openssh/sftp-server\n"
|
||||
"Match Group restricted\n X11Forwarding no\n PasswordAuthentication no\n",
|
||||
"codemate-user",
|
||||
)
|
||||
|
||||
self.assertLess(updated.index("UsePAM yes"), updated.index("Match User codemate-user"))
|
||||
self.assertLess(updated.index("Subsystem sftp"), updated.index("Match User codemate-user"))
|
||||
self.assertLess(updated.index("Match User codemate-user"), updated.index("Match Group restricted"))
|
||||
self.assertIn("Match Group restricted\n X11Forwarding no", updated)
|
||||
|
||||
def test_removes_only_the_public_firecracker_demo_key(self) -> None:
|
||||
own_key = "ssh-ed25519 AAAA-own-key developer@example"
|
||||
|
||||
@@ -241,6 +254,8 @@ class OfflineGuestCredentialsTests(unittest.TestCase):
|
||||
),
|
||||
"/etc/group": "root:x:0:\nshadow:x:42:\nubuntu:x:1000:\nreserved:x:1001:\nnogroup:x:65534:\n",
|
||||
"/etc/gshadow": "root:*::\nshadow:*::\nubuntu:!::\nreserved:!::\nnogroup:*::\n",
|
||||
"/etc/sudoers": "Defaults env_reset\nroot ALL=(ALL:ALL) ALL\n%sudo ALL=(ALL:ALL) ALL\n",
|
||||
"/usr/bin/sudo": "sudo binary fixture\n",
|
||||
"/etc/ssh/sshd_config": "PasswordAuthentication no\nPermitRootLogin no\n",
|
||||
"/root/.ssh/authorized_keys": f"{_FIRECRACKER_DEMO_PUBLIC_KEY} demo\nssh-ed25519 AAAA-own-key\n",
|
||||
}
|
||||
@@ -251,7 +266,8 @@ class OfflineGuestCredentialsTests(unittest.TestCase):
|
||||
path = root / guest_path.lstrip("/")
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(contents, encoding="utf-8")
|
||||
path.chmod(0o640 if guest_path in ("/etc/shadow", "/etc/gshadow") else 0o644)
|
||||
mode = {"/etc/shadow": 0o640, "/etc/gshadow": 0o640, "/etc/sudoers": 0o440, "/usr/bin/sudo": 0o4755}
|
||||
path.chmod(mode.get(guest_path, 0o644))
|
||||
with self.template.open("wb") as image:
|
||||
image.truncate(16 * 1024 * 1024)
|
||||
self.runner.run(("mkfs.ext4", "-q", "-F", "-d", root, self.template), capture=True)
|
||||
@@ -305,6 +321,10 @@ class OfflineGuestCredentialsTests(unittest.TestCase):
|
||||
self.assertLessEqual(int(shadow[2]), int(time.time() // 86400))
|
||||
self.assertEqual(shadow[3:], ["0", "99999", "7", "", "", ""])
|
||||
self.assert_password("codemate-user", "custom-password")
|
||||
self.assertIn("codemate-user ALL=(ALL:ALL) PASSWD: ALL\n", self.contents("/etc/sudoers"))
|
||||
self.assertIn("Defaults:codemate-user !rootpw, !runaspw, !targetpw\n", self.contents("/etc/sudoers"))
|
||||
self.assert_metadata("/etc/sudoers", 0o440, 0, 0)
|
||||
self.assert_metadata("/usr/bin/sudo", 0o4755, 0, 0)
|
||||
for guest_path in ("/etc/passwd", "/etc/group"):
|
||||
self.assert_metadata(guest_path, 0o644, 0, 0)
|
||||
for guest_path in ("/etc/shadow", "/etc/gshadow"):
|
||||
@@ -332,6 +352,10 @@ class OfflineGuestCredentialsTests(unittest.TestCase):
|
||||
self.assertEqual(self.contents(guest_path), self.files[guest_path])
|
||||
self.assertEqual(self.entry("/etc/shadow", username)[2:], original_shadow[2:])
|
||||
self.assert_password(username, "changed-password")
|
||||
if username != "root":
|
||||
self.assertIn(f"{username} ALL=(ALL:ALL) PASSWD: ALL\n", self.contents("/etc/sudoers"))
|
||||
else:
|
||||
self.assertEqual(self.contents("/etc/sudoers"), self.files["/etc/sudoers"])
|
||||
for line in self.files["/etc/shadow"].splitlines():
|
||||
if line.split(":", 1)[0] != username:
|
||||
self.assertIn(line + "\n", self.contents("/etc/shadow"))
|
||||
@@ -375,3 +399,20 @@ class OfflineGuestCredentialsTests(unittest.TestCase):
|
||||
self.assertEqual(self.contents("/home/codemate-user"), self.files["/home/codemate-user"])
|
||||
self.assertEqual(self.contents("/etc/passwd"), self.files["/etc/passwd"])
|
||||
self.assertEqual(self.contents("/etc/shadow"), self.files["/etc/shadow"])
|
||||
|
||||
def test_custom_users_fail_with_an_actionable_error_on_stripped_images(self) -> None:
|
||||
del self.files["/usr/bin/sudo"]
|
||||
del self.files["/etc/sudoers"]
|
||||
self.make_disk()
|
||||
|
||||
with self.assertRaisesRegex(UvmError, "guest image needs setuid-root sudo.*uvm install --force"):
|
||||
self.store.provision_credentials(self.disk, "codemate-user", "custom-password")
|
||||
|
||||
self.assertEqual(self.contents("/etc/shadow"), self.files["/etc/shadow"])
|
||||
|
||||
def test_sudo_without_setuid_permission_does_not_produce_a_broken_administrator(self) -> None:
|
||||
self.make_disk()
|
||||
self.runner.run(("debugfs", "-w", "-R", "set_inode_field /usr/bin/sudo mode 0100755", self.disk), capture=True)
|
||||
|
||||
with self.assertRaisesRegex(UvmError, "guest image needs setuid-root sudo"):
|
||||
self.store.provision_credentials(self.disk, "codemate-user", "custom-password")
|
||||
|
||||
Reference in New Issue
Block a user