mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-08-09 01:06:22 +00:00
173 lines
6.5 KiB
Python
173 lines
6.5 KiB
Python
import importlib.util
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest import mock
|
|
|
|
|
|
MODULE_PATH = Path(__file__).resolve().parents[1] / "security_manager.py"
|
|
SPEC = importlib.util.spec_from_file_location("security_manager_under_test", MODULE_PATH)
|
|
security_manager = importlib.util.module_from_spec(SPEC)
|
|
SPEC.loader.exec_module(security_manager)
|
|
|
|
|
|
class Fail2BanTrustedNetworksTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.temp_dir = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.temp_dir.cleanup)
|
|
root = Path(self.temp_dir.name)
|
|
self.managed_file = root / "jail.d" / "99-proxmenux-ignore.local"
|
|
self.legacy_file = root / "jail.local"
|
|
self.paths_patch = mock.patch.multiple(
|
|
security_manager,
|
|
FAIL2BAN_TRUSTED_NETWORKS_FILE=str(self.managed_file),
|
|
FAIL2BAN_LEGACY_GLOBAL_FILE=str(self.legacy_file),
|
|
)
|
|
self.paths_patch.start()
|
|
self.addCleanup(self.paths_patch.stop)
|
|
self.command_patch = mock.patch.object(
|
|
security_manager, "_run_cmd", return_value=(0, "OK", "")
|
|
)
|
|
self.run_command = self.command_patch.start()
|
|
self.addCleanup(self.command_patch.stop)
|
|
|
|
def write_legacy(self, content):
|
|
self.legacy_file.write_text(content)
|
|
|
|
def test_reads_and_normalises_existing_global_entries(self):
|
|
self.write_legacy(
|
|
"[DEFAULT]\nignoreip = 127.0.0.1/8, ::1 192.168.10.15 10.1.2.9/24\n"
|
|
"\n[sshd]\nenabled = true\n"
|
|
)
|
|
|
|
entries = security_manager.get_fail2ban_trusted_networks()
|
|
|
|
self.assertEqual(
|
|
[entry["value"] for entry in entries],
|
|
["127.0.0.0/8", "::1", "192.168.10.15", "10.1.2.0/24"],
|
|
)
|
|
self.assertTrue(entries[0]["protected"])
|
|
self.assertTrue(entries[1]["protected"])
|
|
self.assertFalse(entries[2]["protected"])
|
|
|
|
def test_adds_ipv4_network_and_reloads_fail2ban(self):
|
|
success, message, value = security_manager.add_fail2ban_trusted_network(
|
|
"192.168.50.123/24"
|
|
)
|
|
|
|
self.assertTrue(success, message)
|
|
self.assertEqual(value, "192.168.50.0/24")
|
|
content = self.managed_file.read_text()
|
|
self.assertIn("ignoreip = 127.0.0.0/8 ::1 192.168.50.0/24", content)
|
|
self.run_command.assert_called_once_with(["fail2ban-client", "reload"])
|
|
|
|
def test_adds_ipv6_network(self):
|
|
success, message, value = security_manager.add_fail2ban_trusted_network(
|
|
"fd12:3456:789a::42/64"
|
|
)
|
|
|
|
self.assertTrue(success, message)
|
|
self.assertEqual(value, "fd12:3456:789a::/64")
|
|
|
|
def test_rejects_invalid_or_multiple_values_without_writing(self):
|
|
for value in ("not-an-ip", "10.0.0.1 10.0.0.2", "10.0.0.0/99"):
|
|
with self.subTest(value=value):
|
|
success, _, normalised = security_manager.add_fail2ban_trusted_network(value)
|
|
self.assertFalse(success)
|
|
self.assertIsNone(normalised)
|
|
|
|
self.assertFalse(self.managed_file.exists())
|
|
self.run_command.assert_not_called()
|
|
|
|
def test_duplicate_is_rejected(self):
|
|
self.write_legacy("[DEFAULT]\nignoreip = 10.0.0.0/24\n")
|
|
|
|
success, message, value = security_manager.add_fail2ban_trusted_network("10.0.0.9/24")
|
|
|
|
self.assertFalse(success)
|
|
self.assertIn("already trusted", message)
|
|
self.assertEqual(value, "10.0.0.0/24")
|
|
self.run_command.assert_not_called()
|
|
|
|
def test_address_already_covered_by_network_is_rejected(self):
|
|
self.write_legacy("[DEFAULT]\nignoreip = 10.0.0.0/24\n")
|
|
|
|
success, message, value = security_manager.add_fail2ban_trusted_network("10.0.0.42")
|
|
|
|
self.assertFalse(success)
|
|
self.assertIn("already trusted", message)
|
|
self.assertEqual(value, "10.0.0.42")
|
|
|
|
def test_rejects_network_that_would_disable_all_bans(self):
|
|
for value in ("0.0.0.0/0", "::/0"):
|
|
with self.subTest(value=value):
|
|
success, _, normalised = security_manager.add_fail2ban_trusted_network(value)
|
|
self.assertFalse(success)
|
|
self.assertIsNone(normalised)
|
|
|
|
def test_protected_network_cannot_be_removed(self):
|
|
success, message = security_manager.remove_fail2ban_trusted_network("127.0.0.1/8")
|
|
|
|
self.assertFalse(success)
|
|
self.assertIn("cannot be removed", message)
|
|
self.run_command.assert_not_called()
|
|
|
|
def test_removes_user_network_and_keeps_other_entries(self):
|
|
self.managed_file.parent.mkdir(parents=True)
|
|
self.managed_file.write_text(
|
|
"[DEFAULT]\nignoreip = 127.0.0.0/8 ::1 10.0.0.0/24 192.168.1.5\n"
|
|
)
|
|
|
|
success, message = security_manager.remove_fail2ban_trusted_network("10.0.0.0/24")
|
|
|
|
self.assertTrue(success, message)
|
|
content = self.managed_file.read_text()
|
|
self.assertNotIn("10.0.0.0/24", content)
|
|
self.assertIn("192.168.1.5", content)
|
|
|
|
def test_updates_user_network_in_place(self):
|
|
self.managed_file.parent.mkdir(parents=True)
|
|
self.managed_file.write_text(
|
|
"[DEFAULT]\nignoreip = 127.0.0.0/8 ::1 10.0.0.0/24 192.168.1.5\n"
|
|
)
|
|
|
|
success, message, value = security_manager.update_fail2ban_trusted_network(
|
|
"10.0.0.0/24", "10.20.30.99/24"
|
|
)
|
|
|
|
self.assertTrue(success, message)
|
|
self.assertEqual(value, "10.20.30.0/24")
|
|
content = self.managed_file.read_text()
|
|
self.assertNotIn("10.0.0.0/24", content)
|
|
self.assertIn("10.20.30.0/24", content)
|
|
self.assertIn("192.168.1.5", content)
|
|
|
|
def test_protected_network_cannot_be_updated(self):
|
|
success, message, value = security_manager.update_fail2ban_trusted_network(
|
|
"::1", "::2"
|
|
)
|
|
|
|
self.assertFalse(success)
|
|
self.assertIn("cannot be changed", message)
|
|
self.assertIsNone(value)
|
|
|
|
def test_reload_failure_restores_previous_file(self):
|
|
self.managed_file.parent.mkdir(parents=True)
|
|
original = "[DEFAULT]\nignoreip = 127.0.0.0/8 ::1 10.0.0.0/24\n"
|
|
self.managed_file.write_text(original)
|
|
self.run_command.side_effect = [
|
|
(1, "", "configuration error"),
|
|
(0, "OK", ""),
|
|
]
|
|
|
|
success, message, _ = security_manager.add_fail2ban_trusted_network("192.168.1.0/24")
|
|
|
|
self.assertFalse(success)
|
|
self.assertIn("configuration error", message)
|
|
self.assertEqual(self.managed_file.read_text(), original)
|
|
self.assertEqual(self.run_command.call_count, 2)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|