diff --git a/.package/docker-compose.yml b/.package/docker-compose.yml index 64f8b4a7d..dc7db924b 100644 --- a/.package/docker-compose.yml +++ b/.package/docker-compose.yml @@ -411,7 +411,7 @@ services: - 53/tcp volumes: - dns_lmdb:/var/lib/pdns-lmdb - - dns_config:/etc/powerdns + - ./pdns.conf:/etc/powerdns/pdns.conf pdns_recursor: diff --git a/app/ioc.py b/app/ioc.py index fa3b85ee3..1a87389d4 100644 --- a/app/ioc.py +++ b/app/ioc.py @@ -307,10 +307,13 @@ async def get_dns_mngr_settings( self, dns_state_gateway: DNSStateGateway, settings: Settings, + root_dse_gw: DomainReadProtocol, ) -> AsyncIterator[DNSSettingsDTO]: """Get DNS manager's settings.""" + domain = await root_dse_gw.get_domain() dns_settings = await dns_state_gateway.get_dns_manager_settings( settings, + domain.name, ) yield dns_settings diff --git a/app/ldap_protocol/dns/constants.py b/app/ldap_protocol/dns/constants.py index 45c6e1073..3e7a33171 100644 --- a/app/ldap_protocol/dns/constants.py +++ b/app/ldap_protocol/dns/constants.py @@ -11,6 +11,42 @@ DNS_MANAGER_IP_ADDRESS_NAME = "DNSManagerIpAddress" DNS_MANAGER_TSIG_KEY_NAME = "DNSManagerTSIGKey" +DEFAULT_FORWARD_ZONE_NAMES: list[str] = [ + ".", + "b.e.f.ip6.arpa.", + "a.e.f.ip6.arpa.", + "23.172.in-addr.arpa.", + "21.172.in-addr.arpa.", + "254.169.in-addr.arpa.", + "20.172.in-addr.arpa.", + "17.172.in-addr.arpa.", + "31.172.in-addr.arpa.", + "22.172.in-addr.arpa.", + "16.172.in-addr.arpa.", + "19.172.in-addr.arpa.", + "24.172.in-addr.arpa.", + "168.192.in-addr.arpa.", + "10.in-addr.arpa.", + "8.e.f.ip6.arpa.", + "127.in-addr.arpa.", + "113.0.203.in-addr.arpa.", + "26.172.in-addr.arpa.", + "27.172.in-addr.arpa.", + "8.b.d.0.1.0.0.2.ip6.arpa.", + "28.172.in-addr.arpa.", + "d.f.ip6.arpa.", + "18.172.in-addr.arpa.", + "30.172.in-addr.arpa.", + "9.e.f.ip6.arpa.", + "100.51.198.in-addr.arpa.", + "255.255.255.255.in-addr.arpa.", + "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.", + "29.172.in-addr.arpa.", + "0.in-addr.arpa.", + "25.172.in-addr.arpa.", + "2.0.192.in-addr.arpa.", +] + DNS_FIRST_SETUP_RECORDS: list[dict[str, str | DNSRecordType]] = [ {"name": "_ldap._tcp.", "value": "0 0 389 ", "type": DNSRecordType.SRV}, {"name": "_ldaps._tcp.", "value": "0 0 636 ", "type": DNSRecordType.SRV}, diff --git a/app/ldap_protocol/dns/dns_gateway.py b/app/ldap_protocol/dns/dns_gateway.py index 068ae4c1e..f5fa6802d 100644 --- a/app/ldap_protocol/dns/dns_gateway.py +++ b/app/ldap_protocol/dns/dns_gateway.py @@ -125,6 +125,7 @@ async def create_settings( async def get_dns_manager_settings( self, app_settings: Settings, + domain: str, ) -> DNSSettingsDTO: """Get DNS manager settings.""" power_dns_settings = PowerDNSSettingsDTO( @@ -132,7 +133,7 @@ async def get_dns_manager_settings( recursor_server_ip=app_settings.PDNS_RECURSOR_SERVER_IP, ) dns_settings = DNSSettingsDTO( - domain=app_settings.DOMAIN, + domain=domain, dns_server_ip=None, tsig_key=None, default_nameserver=app_settings.DEFAULT_NAMESERVER, diff --git a/app/ldap_protocol/dns/managers/bind_to_pdns_migrations_manager.py b/app/ldap_protocol/dns/managers/bind_to_pdns_migrations_manager.py deleted file mode 100644 index 1d02531df..000000000 --- a/app/ldap_protocol/dns/managers/bind_to_pdns_migrations_manager.py +++ /dev/null @@ -1,162 +0,0 @@ -"""Manager for migrating from BIND to PowerDNS. - -Copyright (c) 2026 MultiFactor -License: https://github.com/MultiDirectoryLab/MultiDirectory/blob/main/LICENSE -""" - -import os - -import dns.zone -from loguru import logger - -from ldap_protocol.dns.dto import ( - DNSForwardZoneDTO, - DNSMasterZoneDTO, - DNSRecordDTO, - DNSRRSetDTO, - DNSSettingsDTO, -) -from ldap_protocol.dns.enums import DNSRecordType -from ldap_protocol.dns.managers.power_dns_manager import PowerDNSManager - - -class BindToPDNSMigrationManager: - bind_zone_file_dir: str = "/opt/" - bind_config_files_dir: str = "/etc/bind/" - - def __init__( - self, - pdns_manager: PowerDNSManager, - dns_settings: DNSSettingsDTO, - ) -> None: - self.pdns_manager = pdns_manager - self.dns_settings = dns_settings - - def parse_bind_config_file( - self, - ) -> tuple[list[DNSMasterZoneDTO], list[DNSForwardZoneDTO]]: - """Parse BIND configuration files to extract zone information.""" - master_zones: list[DNSMasterZoneDTO] = [] - forward_zones: list[DNSForwardZoneDTO] = [] - - with open( - os.path.join(self.bind_config_files_dir, "named.conf.local"), - ) as f: - for line in f: - line = line.strip() - if line.startswith("zone"): - parts = line.split() - if len(parts) >= 2: - zone_name = parts[1].strip('"') - continue - - if "type master" in line: - master_zones.append( - DNSMasterZoneDTO( - id=zone_name, - name=zone_name, - ), - ) - elif "type forward" in line: - forward_zones.append( - DNSForwardZoneDTO( - id=zone_name, - name=zone_name, - ), - ) - - return master_zones, forward_zones - - def parse_zones_records( - self, - master_zones: list[DNSMasterZoneDTO], - ) -> list[DNSMasterZoneDTO]: - """Parse zone files to extract DNS records.""" - for zone in master_zones: - zone_rrsets: list[DNSRRSetDTO] = [] - zone_file_path = os.path.join( - self.bind_zone_file_dir, - f"{zone.name}.zone", - ) - zone_obj = dns.zone.from_file( - zone_file_path, - origin=zone.name, - relativize=False, - ) - for name, ttl, rdata in zone_obj.iterate_rdatas(): - try: - DNSRecordType(rdata.rdtype.name) - except ValueError: - logger.warning( - f"Unsupported DNS record type {rdata.rdtype.name} in zone '{zone.name}'", # noqa: E501 - ) - continue - - zone_rrsets.append( - DNSRRSetDTO( - name=name.to_text(), - type=DNSRecordType(rdata.rdtype.name), - records=[ - DNSRecordDTO( - content=rdata.to_text(), - disabled=False, - ), - ], - ttl=ttl, - ), - ) - zone.rrsets = zone_rrsets - - return master_zones - - async def get_bind_zones( - self, - ) -> tuple[list[DNSMasterZoneDTO], list[DNSForwardZoneDTO]]: - """Get zones from BIND.""" - master_zones, forward_zones = self.parse_bind_config_file() - master_zones = self.parse_zones_records(master_zones) - - return master_zones, forward_zones - - async def migrate_from_bind(self) -> None: - """Migrate from BIND to PowerDNS.""" - master_zones, forward_zones = await self.get_bind_zones() - - for master_zone in master_zones: - await self.pdns_manager.create_master_zone( - master_zone, - is_empty=True, - ) - for rrset in master_zone.rrsets: - await self.pdns_manager.create_record( - master_zone.name, - rrset, - ) - - for forward_zone in forward_zones: - await self.pdns_manager.create_forward_zone(forward_zone) - - open(os.path.join(self.bind_zone_file_dir, "migrated"), "a").close() - open(os.path.join(self.bind_config_files_dir, "migrated"), "a").close() - - def is_migration_needed(self) -> bool: - """Check if migration is needed.""" - return not ( - os.path.exists(os.path.join(self.bind_zone_file_dir, "migrated")) - and os.path.exists( - os.path.join(self.bind_config_files_dir, "migrated"), - ) - ) and bool(os.listdir(self.bind_zone_file_dir)) - - async def migrate(self) -> None: - """Migrate from BIND to PowerDNS.""" - if not self.is_migration_needed(): - logger.info("BIND to PowerDNS migration is not needed, exiting...") - return - - logger.info("Starting BIND to PowerDNS migration...") - await self.pdns_manager.setup(self.dns_settings, is_migration=True) - - await self.migrate_from_bind() - logger.info("Migration successful") - return diff --git a/app/ldap_protocol/dns/managers/power_dns_manager.py b/app/ldap_protocol/dns/managers/power_dns_manager.py index 500c69d40..d42d478b7 100644 --- a/app/ldap_protocol/dns/managers/power_dns_manager.py +++ b/app/ldap_protocol/dns/managers/power_dns_manager.py @@ -14,7 +14,10 @@ PowerDNSDistClient, PowerDNSRecursorHTTPClient, ) -from ldap_protocol.dns.constants import DNS_FIRST_SETUP_RECORDS +from ldap_protocol.dns.constants import ( + DEFAULT_FORWARD_ZONE_NAMES, + DNS_FIRST_SETUP_RECORDS, +) from ldap_protocol.dns.dto import ( DNSForwardServerStatus, DNSForwardZoneDTO, @@ -225,7 +228,14 @@ async def get_master_zone_by_id(self, zone_id: str) -> DNSMasterZoneDTO: async def get_forward_zones(self) -> list[DNSForwardZoneDTO]: """Retrieve all forward DNS zones.""" try: - return await self._power_dns_recursor_client.get_forward_zones() + forward_zones = ( + await self._power_dns_recursor_client.get_forward_zones() + ) + return [ + zone + for zone in forward_zones + if zone.name not in DEFAULT_FORWARD_ZONE_NAMES + ] except DNSError as e: raise DNSZoneGetError(f"Failed to get DNS zones: {e}") diff --git a/tests/conftest.py b/tests/conftest.py index 364f086de..9be038db5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -286,9 +286,14 @@ async def get_dns_mngr_settings( self, dns_state_gateway: DNSStateGateway, settings: Settings, + root_dse_gw: DomainReadProtocol, ) -> AsyncIterator["DNSSettingsDTO"]: """Get DNS manager's settings.""" - yield await dns_state_gateway.get_dns_manager_settings(settings) + domain = await root_dse_gw.get_domain() + yield await dns_state_gateway.get_dns_manager_settings( + settings, + domain.name, + ) attribute_type_dao = provide(AttributeTypeDAO, scope=Scope.REQUEST) object_class_dao = provide(ObjectClassDAO, scope=Scope.REQUEST)