mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-01-21 07:55:00 +00:00
- As of bind 9.16, the option to enable DNSSEC 'dnssec-enable' is obsolete and has no effect[1]. The option 'dnssec-validation' controls DNSSEC validation and is set to 'auto' by default. 'auto' means that DNSSEC validation is enabled and default trust anchor is used for DNS root zone. DNSSEC signatures are also passed onto a client whenever available. Current stable, Debian Buster, has version 9.16[3]. - As of bind 9.18, the option to enable DNSSEC 'dnssec-enable' is not recognized and causes the daemon to fail to start[2]. Debian next, Debian Bookworm, has version 9.18[3]. Therefore, in testing and unstable, bind fails to start of installation from FreedomBox. - There is no use-case for changing the current default behavior. Links: 1) https://bind9.readthedocs.io/en/v9_16_32/reference.html#dnssec-validation-option 2) https://bind9.readthedocs.io/en/v9_18_6/reference.html 3) https://tracker.debian.org/pkg/bind9 Tests: - Run functional and unit tests. - Option to enable/disable DNSSEC is removed. - When bind is installed on testing without the patch, it fails to start. When the patch is applied, bind will be upgraded, the dnssec-enable option is removed from the configuration file /etc/bind/named.conf.options and bind is running. Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org> Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
180 lines
5.5 KiB
Python
180 lines
5.5 KiB
Python
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
"""Configuration helper for BIND server."""
|
|
|
|
import re
|
|
from collections import defaultdict
|
|
from pathlib import Path
|
|
|
|
import augeas
|
|
|
|
from plinth import action_utils
|
|
from plinth.actions import privileged
|
|
|
|
CONFIG_FILE = '/etc/bind/named.conf.options'
|
|
ZONES_DIR = '/var/bind/pri'
|
|
|
|
DEFAULT_CONFIG = '''
|
|
acl goodclients {
|
|
localnets;
|
|
};
|
|
options {
|
|
directory "/var/cache/bind";
|
|
|
|
recursion yes;
|
|
allow-query { goodclients; };
|
|
|
|
forwarders {
|
|
|
|
};
|
|
forward first;
|
|
|
|
auth-nxdomain no; # conform to RFC1035
|
|
listen-on-v6 { any; };
|
|
};
|
|
'''
|
|
|
|
|
|
@privileged
|
|
def setup(old_version: int):
|
|
"""Setup BIND configuration."""
|
|
if old_version == 0:
|
|
with open(CONFIG_FILE, 'w', encoding='utf-8') as conf_file:
|
|
conf_file.write(DEFAULT_CONFIG)
|
|
elif old_version < 3:
|
|
_remove_dnssec()
|
|
|
|
Path(ZONES_DIR).mkdir(exist_ok=True, parents=True)
|
|
|
|
action_utils.service_restart('named')
|
|
|
|
|
|
@privileged
|
|
def configure(forwarders: str):
|
|
"""Configure BIND."""
|
|
_set_forwarders(forwarders)
|
|
action_utils.service_restart('named')
|
|
|
|
|
|
def get_config():
|
|
"""Get current configuration."""
|
|
data = [line.strip() for line in open(CONFIG_FILE, 'r', encoding='utf-8')]
|
|
|
|
forwarders = ''
|
|
flag = False
|
|
for line in data:
|
|
if re.match(r'^\s*forwarders\s+{', line):
|
|
flag = True
|
|
elif flag and '//' not in line:
|
|
forwarders = re.sub('[;]', '', line)
|
|
flag = False
|
|
|
|
return {'forwarders': forwarders}
|
|
|
|
|
|
def _set_forwarders(forwarders):
|
|
"""Set DNS forwarders."""
|
|
flag = 0
|
|
data = [line.strip() for line in open(CONFIG_FILE, 'r', encoding='utf-8')]
|
|
conf_file = open(CONFIG_FILE, 'w', encoding='utf-8')
|
|
for line in data:
|
|
if re.match(r'^\s*forwarders\s+{', line):
|
|
conf_file.write(line + '\n')
|
|
for dns in forwarders.split():
|
|
conf_file.write(dns + '; ')
|
|
conf_file.write('\n')
|
|
flag = 1
|
|
elif '};' in line and flag == 1:
|
|
conf_file.write(line + '\n')
|
|
flag = 0
|
|
elif flag == 0:
|
|
conf_file.write(line + '\n')
|
|
conf_file.close()
|
|
|
|
|
|
def _remove_dnssec():
|
|
"""Remove DNSSEC options."""
|
|
data = [line.strip() for line in open(CONFIG_FILE, 'r', encoding='utf-8')]
|
|
|
|
with open(CONFIG_FILE, 'w', encoding='utf-8') as file_handle:
|
|
for line in data:
|
|
if not re.match(r'^\s*dnssec-enable\s+yes;', line):
|
|
file_handle.write(line + '\n')
|
|
|
|
|
|
def get_served_domains():
|
|
"""Return list of domains service handles.
|
|
|
|
Augeas path for zone files:
|
|
===========================
|
|
augtool> print /files/var/bind/pri/local.zone
|
|
/files/var/bind/pri/local.zone
|
|
/files/var/bind/pri/local.zone/$TTL = "604800"
|
|
/files/var/bind/pri/local.zone/@[1]
|
|
/files/var/bind/pri/local.zone/@[1]/1
|
|
/files/var/bind/pri/local.zone/@[1]/1/class = "IN"
|
|
/files/var/bind/pri/local.zone/@[1]/1/type = "SOA"
|
|
/files/var/bind/pri/local.zone/@[1]/1/mname = "localhost."
|
|
/files/var/bind/pri/local.zone/@[1]/1/rname = "root.localhost."
|
|
/files/var/bind/pri/local.zone/@[1]/1/serial = "2"
|
|
/files/var/bind/pri/local.zone/@[1]/1/refresh = "604800"
|
|
/files/var/bind/pri/local.zone/@[1]/1/retry = "86400"
|
|
/files/var/bind/pri/local.zone/@[1]/1/expiry = "2419200"
|
|
/files/var/bind/pri/local.zone/@[1]/1/minimum = "604800"
|
|
/files/var/bind/pri/local.zone/@[2]
|
|
/files/var/bind/pri/local.zone/@[2]/1
|
|
/files/var/bind/pri/local.zone/@[2]/1/class = "IN"
|
|
/files/var/bind/pri/local.zone/@[2]/1/type = "NS"
|
|
/files/var/bind/pri/local.zone/@[2]/1/rdata = "localhost."
|
|
/files/var/bind/pri/local.zone/@[3]
|
|
/files/var/bind/pri/local.zone/@[3]/1
|
|
/files/var/bind/pri/local.zone/@[3]/1/class = "IN"
|
|
/files/var/bind/pri/local.zone/@[3]/1/type = "A"
|
|
/files/var/bind/pri/local.zone/@[3]/1/rdata = "127.0.0.1"
|
|
/files/var/bind/pri/local.zone/@[4]
|
|
/files/var/bind/pri/local.zone/@[4]/1
|
|
/files/var/bind/pri/local.zone/@[4]/1/class = "IN"
|
|
/files/var/bind/pri/local.zone/@[4]/1/type = "AAAA"
|
|
/files/var/bind/pri/local.zone/@[4]/1/rdata = "::1"
|
|
|
|
Need to find the related functionality to parse the A records
|
|
|
|
Retrieve from /etc/bind/db* zone files all the configured A records.
|
|
Assuming zones files in ZONES_DIR are all used.
|
|
:return: dictionary in the form 'domain_name': ['ip_address', 'ipv6_addr']
|
|
"""
|
|
RECORD_TYPES = ('A', 'AAAA')
|
|
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
|
|
augeas.Augeas.NO_MODL_AUTOLOAD)
|
|
aug.set('/augeas/load/Dns_Zone/lens', 'Dns_Zone.lns')
|
|
|
|
zone_file_path = Path(ZONES_DIR)
|
|
zone_files = [zf for zf in zone_file_path.iterdir() if zf.is_file()]
|
|
|
|
# augeas load only required files
|
|
for zone_file in zone_files:
|
|
aug.set('/augeas/load/Dns_Zone/incl[last() + 1]', str(zone_file))
|
|
|
|
aug.load()
|
|
|
|
served_domains = defaultdict(list)
|
|
for zone_file in zone_files:
|
|
base_path = '/files/%s/@[{record_order}]/1/{field}' % zone_file
|
|
count = 1
|
|
mname = aug.get(base_path.format(record_order=count, field='mname'))
|
|
while True:
|
|
record_type = aug.get(
|
|
base_path.format(record_order=count, field='type'))
|
|
|
|
# no record type ends the search
|
|
if record_type is None:
|
|
break
|
|
|
|
if record_type in RECORD_TYPES:
|
|
served_domains[mname].append(
|
|
aug.get(base_path.format(record_order=count,
|
|
field='rdata')))
|
|
|
|
count += 1
|
|
|
|
return served_domains
|