Enhance security by ensuring unique SSH host keys for VMs and protecting registry credentials. Update documentation to reflect changes in SSH key management and improve state file permissions.
This commit is contained in:
+56
-3
@@ -4,6 +4,7 @@ import hashlib
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from uvm.config import Settings
|
||||
from uvm.errors import UvmError
|
||||
@@ -21,12 +22,15 @@ from uvm.system import CommandResult
|
||||
class PasswordHashRunner:
|
||||
def __init__(self) -> None:
|
||||
self.command: tuple[str, ...] | None = None
|
||||
self.calls: list[tuple[str, ...]] = []
|
||||
self.options: dict[str, object] = {}
|
||||
|
||||
def run(self, command, **options) -> CommandResult:
|
||||
self.command = tuple(str(part) for part in command)
|
||||
self.calls.append(self.command)
|
||||
self.options = options
|
||||
return CommandResult(self.command, 0, stdout="$6$salt$password-hash\n")
|
||||
stdout = "$6$salt$password-hash\n" if self.command[0] == "openssl" else ""
|
||||
return CommandResult(self.command, 0, stdout=stdout)
|
||||
|
||||
|
||||
class ImageStoreTests(unittest.TestCase):
|
||||
@@ -79,15 +83,27 @@ class ImageStoreTests(unittest.TestCase):
|
||||
|
||||
def test_enables_root_password_login_before_existing_sshd_settings(self) -> None:
|
||||
updated = _enable_ssh_password_authentication(
|
||||
"PasswordAuthentication no\nPermitRootLogin prohibit-password\n",
|
||||
"HostKey /insecure/shared-key\n"
|
||||
"PasswordAuthentication no\n"
|
||||
"PermitRootLogin prohibit-password\n",
|
||||
"root",
|
||||
)
|
||||
|
||||
self.assertTrue(
|
||||
updated.startswith(
|
||||
"# Managed by uvm\nPasswordAuthentication yes\nPermitRootLogin yes\n"
|
||||
"# Managed by uvm\n"
|
||||
"HostKey /etc/ssh/ssh_host_rsa_key\n"
|
||||
"HostKey /etc/ssh/ssh_host_ecdsa_key\n"
|
||||
"HostKey /etc/ssh/ssh_host_ed25519_key\n"
|
||||
"PasswordAuthentication no\n"
|
||||
"PermitRootLogin yes\n"
|
||||
"Match User root\n"
|
||||
" PasswordAuthentication yes\n"
|
||||
"Match all\n"
|
||||
)
|
||||
)
|
||||
self.assertNotIn("/insecure/shared-key", updated)
|
||||
self.assertNotIn("PermitRootLogin prohibit-password", updated)
|
||||
|
||||
def test_removes_only_the_public_firecracker_demo_key(self) -> None:
|
||||
own_key = "ssh-ed25519 AAAA-own-key developer@example"
|
||||
@@ -97,3 +113,40 @@ class ImageStoreTests(unittest.TestCase):
|
||||
)
|
||||
|
||||
self.assertEqual(updated, f"{own_key}\n")
|
||||
|
||||
def test_generates_three_unique_guest_host_key_types(self) -> None:
|
||||
runner = PasswordHashRunner()
|
||||
store = ImageStore(Settings(), runner=runner) # type: ignore[arg-type]
|
||||
with (
|
||||
tempfile.TemporaryDirectory() as temporary_directory,
|
||||
patch.object(store, "_read_guest_file", return_value=None),
|
||||
patch.object(store, "_write_guest_file") as write_guest_file,
|
||||
):
|
||||
store._replace_guest_host_keys(
|
||||
Path("/tmp/rootfs.ext4"),
|
||||
Path(temporary_directory),
|
||||
)
|
||||
|
||||
keygen_calls = [call for call in runner.calls if call[0] == "ssh-keygen"]
|
||||
self.assertEqual([call[3] for call in keygen_calls], ["rsa", "ecdsa", "ed25519"])
|
||||
self.assertEqual(write_guest_file.call_count, 6)
|
||||
|
||||
def test_optional_guest_file_inspection_fails_closed_on_debugfs_errors(self) -> None:
|
||||
class FailedRunner:
|
||||
@staticmethod
|
||||
def run(_command, **_options) -> CommandResult:
|
||||
return CommandResult(
|
||||
args=("debugfs",),
|
||||
returncode=0,
|
||||
stderr="filesystem checksum failure",
|
||||
)
|
||||
|
||||
store = ImageStore(Settings(), runner=FailedRunner()) # type: ignore[arg-type]
|
||||
with tempfile.TemporaryDirectory() as temporary_directory:
|
||||
with self.assertRaises(UvmError):
|
||||
store._read_guest_file(
|
||||
Path("/tmp/rootfs.ext4"),
|
||||
"/root/.ssh/authorized_keys",
|
||||
Path(temporary_directory) / "authorized_keys",
|
||||
required=False,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user