From cd205027ad9dc6eb86abd58b63c82373f942d740 Mon Sep 17 00:00:00 2001 From: Mikhail Sandakov Date: Wed, 2 Oct 2024 16:48:26 +0300 Subject: [PATCH] Add support for reading DNS configuration file includes in a chroot environment --- pleskdistup/common/src/dns.py | 19 ++++- pleskdistup/common/tests/dnstests.py | 111 +++++++++++++++++++++++++-- 2 files changed, 118 insertions(+), 12 deletions(-) diff --git a/pleskdistup/common/src/dns.py b/pleskdistup/common/src/dns.py index 0c95826..12b033c 100644 --- a/pleskdistup/common/src/dns.py +++ b/pleskdistup/common/src/dns.py @@ -1,14 +1,19 @@ # Copyright 2023-2024. WebPros International GmbH. All rights reserved. import os +import typing from collections import deque +from . import log -def get_all_includes_from_bind_config(config_file: str) -> list: - includes: list[str] = [] + +def get_all_includes_from_bind_config(config_file: str, chroot: str = "") -> typing.List[str]: + includes: typing.List[str] = [] + + config_file = os.path.join(chroot, config_file) if chroot else config_file if not os.path.exists(config_file): return includes - queue: deque[str] = deque([config_file]) + queue: typing.Deque[str] = deque([config_file]) while queue: current_file = queue.popleft() @@ -19,7 +24,13 @@ def get_all_includes_from_bind_config(config_file: str) -> list: for line in f: line = line.strip() if line.startswith("include"): - include_file = line.split('"')[1].strip(";") + include_file = line.split('"')[1] + + if include_file.startswith("/"): + include_file = chroot + include_file + else: + include_file = os.path.join(os.path.dirname(current_file), include_file) + includes.append(include_file) queue.append(include_file) diff --git a/pleskdistup/common/tests/dnstests.py b/pleskdistup/common/tests/dnstests.py index 52f9799..a8f52b1 100644 --- a/pleskdistup/common/tests/dnstests.py +++ b/pleskdistup/common/tests/dnstests.py @@ -1,6 +1,7 @@ # Copyright 2023-2024. WebPros International GmbH. All rights reserved. -import unittest +import shutil import os +import unittest import src.dns as dns @@ -8,8 +9,16 @@ class TestGetIncludeFromBindConfiguration(unittest.TestCase): def setUp(self): - with open("test.conf", "w") as actions_data_file: - actions_data_file.write('{ "actions": [] }') + with open("test.conf", "w") as test_bind_config: + test_bind_config.write(''' +options { + allow-recursion { + localnets; + }; + directory "/var"; + pid-file "/var/run/named/named.pid"; +}; +''') def tearDown(self): os.remove("test.conf") @@ -19,21 +28,76 @@ def test_no_config_file(self): def test_one_include(self): with open("test.conf", "w") as test_file: - test_file.write('include "included.conf";') + test_file.write(''' +options { + include "included.conf"; + allow-recursion { + localnets; + }; + directory "/var"; + pid-file "/var/run/named/named.pid"; +}; +''') self.assertEqual(dns.get_all_includes_from_bind_config("test.conf"), ["included.conf"]) + def test_include_with_spaces(self): + with open("test.conf", "w") as test_file: + test_file.write(''' +options { + include "included .conf"; + allow-recursion { + localnets; + }; + directory "/var"; + pid-file "/var/run/named/named.pid"; +}; +''') + self.assertEqual(dns.get_all_includes_from_bind_config("test.conf"), ["included .conf"]) + + def test_include_with_tabs(self): + with open("test.conf", "w") as test_file: + test_file.write(''' +options { + include "included .conf"; + allow-recursion { + localnets; + }; + directory "/var"; + pid-file "/var/run/named/named.pid"; +}; +''') + self.assertEqual(dns.get_all_includes_from_bind_config("test.conf"), ["included .conf"]) + def test_multiple_includes(self): with open("test.conf", "w") as test_file: - test_file.write('include "included1.conf";\n') - test_file.write('include "included2.conf";\n') + test_file.write(''' +options { + include "included1.conf"; + include "included2.conf"; + allow-recursion { + localnets; + }; + directory "/var"; + pid-file "/var/run/named/named.pid"; +}; +''') self.assertEqual(dns.get_all_includes_from_bind_config("test.conf"), ["included1.conf", "included2.conf"]) def test_nested_includes(self): with open("test.conf", "w") as test_file: - test_file.write('include "included1.conf";\n') - test_file.write('include "included2.conf";\n') + test_file.write(''' +options { + include "included1.conf"; + include "included2.conf"; + allow-recursion { + localnets; + }; + directory "/var"; + pid-file "/var/run/named/named.pid"; +}; +''') with open("included1.conf", "w") as test_file: test_file.write('include "included3.conf";\n') @@ -46,3 +110,34 @@ def test_nested_includes(self): os.remove("included1.conf") os.remove("included2.conf") + + def test_nested_includes_with_chroot(self): + os.makedirs("chroot", exist_ok=True) + + with open("chroot/test.conf", "w") as test_file: + test_file.write(''' +options { + include "included1.conf"; + include "included2.conf"; + allow-recursion { + localnets; + }; + directory "/var"; + pid-file "/var/run/named/named.pid"; +}; +''') + + with open("chroot/included1.conf", "w") as test_file: + test_file.write('include "subdir/included3.conf";\n') + + with open("chroot/included2.conf", "w") as test_file: + test_file.write('include "/subdir/included4.conf";\n') + + self.assertEqual(dns.get_all_includes_from_bind_config("test.conf", chroot="chroot"), [ + "chroot/included1.conf", + "chroot/included2.conf", + "chroot/subdir/included3.conf", + "chroot/subdir/included4.conf" + ]) + + shutil.rmtree("chroot")