-
Notifications
You must be signed in to change notification settings - Fork 0
refactor: DNS backend #876
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR refactors the DNS backend from Bind9 to PowerDNS, implementing a two-component architecture with PowerDNS Authoritative Server and Recursor. The refactoring involves significant changes to the DNS management layer, API endpoints, and configuration.
Key changes:
- Migration from Bind9 to PowerDNS with custom Dockerfile for LMDB backend support
- Removal of self-hosted Bind9 manager and custom DNS API
- Introduction of new DTOs and enums aligned with PowerDNS data structures
- API restructuring with zone_id as path parameters and separate endpoints for forward zones
- Removal of DNS server-level configuration endpoints (DNSSEC now zone-specific)
Reviewed changes
Copilot reviewed 33 out of 33 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| pdns.conf | PowerDNS Authoritative Server configuration with LMDB backend |
| recursor.conf | PowerDNS Recursor configuration with API access |
| .docker/pdns_auth.Dockerfile | Alpine-based build for PowerDNS with LMDB support |
| docker-compose.yml | Replaced bind_dns service with pdns_auth and pdns_recursor services |
| app/ldap_protocol/dns/powerdns.py | New PowerDNS manager implementing AbstractDNSManager interface |
| app/ldap_protocol/dns/dto.py | Extended DTOs for PowerDNS zones, records, and RRSets |
| app/ldap_protocol/dns/enums.py | New enums for DNS record types, zone types, and change types |
| app/api/main/dns_router.py | Restructured routes with zone_id path params and forward zone endpoints |
| app/api/main/adapters/dns.py | Adapter updated to construct DTOs from request schemas |
| app/ldap_protocol/dns/use_cases.py | Simplified use cases with removed server options functionality |
| app/config.py | New config for PDNS hosts and API key |
| app/ioc.py | Updated DI container for PowerDNS client provisioning |
| tests/test_api/test_main/test_dns.py | Comprehensive test updates for new API structure |
Comments suppressed due to low confidence (1)
app/ldap_protocol/dns/stub.py:77
- This method requires 2 positional arguments, whereas overridden AbstractDNSManager.check_forward_dns_server requires 3.
@logger_wraps(is_stub=True)
async def check_forward_dns_server(
self,
dns_server_ip: str,
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
app/ldap_protocol/dns/use_cases.py
Outdated
| async def delete_zones(self, zone_ids: list[str]) -> None: | ||
| """Delete DNS zones.""" | ||
| for zone_id in zone_ids: | ||
| await self._dns_manager.delete_zone(zone_id) |
Copilot
AI
Dec 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The use_cases.py delete_zones method iterates through zone_ids and calls delete_zone for each one. If any of these calls fail, the remaining zones won't be deleted. Consider implementing error handling to either collect errors and raise them at the end, or use a transaction-like mechanism to ensure all-or-nothing deletion.
| @@ -0,0 +1,10 @@ | |||
| local-address=0.0.0.0 | |||
| webserver-allow-from=0.0.0.0/0 | |||
Copilot
AI
Dec 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The webserver is configured to allow connections from any IP address (0.0.0.0/0). This is a security risk in production environments. Consider restricting access to specific IP ranges or implementing additional authentication mechanisms.
| webserver-allow-from=0.0.0.0/0 | |
| webserver-allow-from=127.0.0.1/32 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Пока сделано так, в дальнейшем, при добавлении PowerDNS DNSdist будет переделано.
| local-port=53 | ||
| api=yes | ||
| api-key=supersecretapikey | ||
| webserver-allow-from=0.0.0.0/0 |
Copilot
AI
Dec 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The webserver is configured to allow connections from any IP address (0.0.0.0/0). This is a security risk in production environments. Consider restricting access to specific IP ranges or implementing additional authentication mechanisms.
| webserver-allow-from=0.0.0.0/0 | |
| webserver-allow-from=127.0.0.1,::1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Здесь то же самое
recursor.conf
Outdated
| webserver=yes | ||
| webserver-address=0.0.0.0 | ||
| webserver-port=8083 | ||
| api-key=supersecretapikey |
Copilot
AI
Dec 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The API key "supersecretapikey" is hardcoded in the configuration file. This is a security risk as it exposes the API key in version control. The API key should be passed as an environment variable or mounted from a secrets management system.
| api-key=supersecretapikey | |
| api-key=changeme # real value must be injected from environment/secrets at deploy time |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
То же самое, что и выше.
| async def delete_forward_zones(self, zone_ids: list[str]) -> None: | ||
| """Delete DNS forward zones.""" | ||
| for zone_id in zone_ids: | ||
| await self._dns_manager.delete_forward_zone(zone_id) |
Copilot
AI
Dec 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same issue exists in delete_forward_zones - if any deletion fails, the remaining zones won't be deleted. Consider implementing error handling to either collect errors and raise them at the end, or use a transaction-like mechanism to ensure all-or-nothing deletion.
| async def create(self, data: CatalogueSetting) -> None: | ||
| """Create DNS.""" | ||
| self._session.add(data) | ||
| await self._session.commit() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
на уровне gateway мы можем делать commit? по моему на таком низком уровне мы договаривались без коммита, а только flush
т.к. если тут сделать коммит, то транзакция закроется и мы не сможем собрать пайплайн из двух небольших методов гейтвея (если в каждом из них будет по коммиту), тк первый метод закроет транзакцию
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Нужна помощь второго ревьювера, не берусь утверждать
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
в manager/use_case/service есть коммит
гейтвей как и dao как и репозиторий примерно одно и то же с нюансами, но все инкапсулирует доступ к данным, поэтому там коммита нет, чтобы можно было легко собрать любой пайплайн из этих методов, как из кирпичиков и транзакция не закрылась по середине
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Дима прав. Егор говорил, что мы управляем сессией на уровне use_case не gateway. Т.к. задача gateway работать с данными, а когда их отнести до БД, решается выше уровнем
| code = ErrorCodes.DNS_NOT_IMPLEMENTED_ERROR | ||
|
|
||
|
|
||
| class DNSUnavailableError(DNSError): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
если какая-то из ошибок идет на фронт, нужно добавить код в enum выше. так же добавить в error_map ее, чтобы она обрабатывалась в роуте
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Эта ошибка не идет на фронт, но добавил другие
| base_retort = Retort() | ||
|
|
||
|
|
||
| class PowerDNSManager(AbstractDNSManager): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
как по мне, получился уберкомбайнер, который шлет\валидирует запросы(работа с http), логика самого днс, вычисление и агрегация данных. ИМХО слишком много всего, я бы декомпозировал
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Согласен, но предлагаю отложить пока
|
а мы от адаптикса отказались, почему все дто вручную? |
Потому что пытался по минимуму менять схемы фронтовые, чтобы меньше фронтам переделывать |
так адаптикс просто код перевода объектов убирает из логики, схему фронтовые можно не менять |
milov-dmitriy
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
от души
…agement and error handling
…andling in PowerDNSManager
…nt and authorization rules
…by_zone_kind method
…d updating DTO import
… in workflows and docker-compose
49664af to
8bd9a0e
Compare
milov-dmitriy
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ничего существенного только нейминг
я бы в будущем посмотрел в сторону рефакторинга app/ldap_protocol/dns, хотелось бы сгруппировать логические блоки кода между собой, например всех менеджеров в одну кучу, все Enum положить в одно место, нужно уменьшить кол-во разных сущностей в base, по итогу получится в лучших традициях high cohesion/low coupling
| - ./recursor.conf:/etc/powerdns/recursor.conf | ||
| - forward_zones:/etc/powerdns/recursor.d/ | ||
|
|
||
| pdnsdist: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pdns_dist ? стоит в едином стиле делать?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
или так принято
| """Get DNS service status.""" | ||
| return await self._service.get_dns_status() | ||
|
|
||
| async def set_dns_state( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
можно просто set_state, т.к. тут и так бизнес домен dns
+одноименный метод сервиса
| ) | ||
|
|
||
| async def get_forward_dns_zones(self) -> list[DNSForwardZone]: | ||
| async def get_dns_forward_zones(self) -> list[DNSForwardZoneDTO]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
get_forward_zones (аналогично случаю с set_dns_state предлагаю сделать)
| async def get_dns_master_zones(self) -> list[DNSMasterZoneDTO]: | ||
| """Get all DNS master zones.""" | ||
| return await self._service.get_master_zones() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
аналогично ( get_dns_master_zones - get_master_zones )
| async def check_dns_forward_zone( | ||
| self, | ||
| data: DNSServiceForwardZoneCheckRequest, | ||
| ) -> list[DNSForwardServerStatus]: | ||
| """Check DNS forward zone for availability.""" | ||
| return await self._service.check_dns_forward_zone(data.dns_server_ips) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
можем удалить ..dns.. ?
т.е. check_forward_zone и метод у сервиса соответствующе
| class DNSServiceSetStateRequest(BaseModel): | ||
| """DNS set state request schema.""" | ||
|
|
||
| state: DNSManagerState | ||
|
|
||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
может быть нам лучше сделать отдельный модуль для dns? и разместить там схемы только для днс, роуты только для днс и так далее, чтобы не миксовать, а то получается что main - это у нас и dns и requests и kerberos.. могут появиться вопросы почему 3 этих компонента это main а остальные не относятся к main
| base_retort = Retort() | ||
|
|
||
|
|
||
| class PowerDNSDistClient: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-
предлагаю согласовать названия файла и класса:
power_dns_manager.py< - >PowerDNSDistClient -
а что значит Dist?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- думаю, что было бы яснее, если мы бы смогли более явно указать, что значит remote_dns_manager. указать его отличие от других dns менеджеров если они есть (судя по названию вроде как должны быть)
т.е. предполагается что
- есть не только remote? но и local?
или - remote client и local client?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
для наглядности dns менеджеров можно этот файл назвать stub_dns_manager, чтобы разработчику было понятно что тут к чему по менеджерам, или мб даже отдельную папку им сделать (:
| PDNSAuthServerClient = NewType("PDNSAuthServerClient", httpx.AsyncClient) | ||
| PDNSRecursorServerClient = NewType( | ||
| "PDNSRecursorServerClient", | ||
| httpx.AsyncClient, | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
а где это используется? и названия странными выглядят: серверный клиент?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+, навести порядок в ioc, обсуждали
| @@ -0,0 +1,6 @@ | |||
| setLocal('0.0.0.0:53') | |||
| controlSocket('0.0.0.0:8084') | |||
| setKey('PSAag0AEziPZuBB7kdcfIEkVJOyQInRcBRAhadWDpU0=') | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
поменять на supersecretapikey, эту фигню скопировать на уровень выше и именно ее юзать в композах для разработки
| PDNS_AUTH_SERVER_HOST: str = "pdns_auth" | ||
| PDNS_AUTH_SERVER_IP: str = "172.20.0.4" | ||
| PDNS_AUTH_SERVER_PORT: int = 8082 | ||
| PDNS_RECURSOR_SERVER_HOST: str = "pdns_recursor" | ||
| PDNS_RECURSOR_SERVER_IP: str = "172.20.0.2" | ||
| PDNS_RECURSOR_SERVER_PORT: int = 8083 | ||
| PDNS_DIST_HOST: str = "172.20.0.3" | ||
| PDNS_DIST_PORT: int = 8084 | ||
| PDNS_DIST_CONFIG_PATH: str = "/dnsdist/delta.conf" | ||
| PDNS_DIST_KEY: str | ||
| PDNS_API_KEY: str | ||
| DEFAULT_NAMESERVER: str |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
добавить описание в вики
| PDNSAuthServerClient = NewType("PDNSAuthServerClient", httpx.AsyncClient) | ||
| PDNSRecursorServerClient = NewType( | ||
| "PDNSRecursorServerClient", | ||
| httpx.AsyncClient, | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+, навести порядок в ioc, обсуждали
| async def get_dns_mngr( | ||
| self, | ||
| settings: DNSManagerSettings, | ||
| dns_settings: DNSManagerSettings, | ||
| app_settings: Settings, | ||
| dns_manager_class: type[AbstractDNSManager], | ||
| http_client: DNSManagerHTTPClient, | ||
| ) -> AbstractDNSManager: | ||
| ) -> AsyncIterator[AbstractDNSManager]: | ||
| """Get DNSManager class.""" | ||
| return dns_manager_class(settings=settings, http_client=http_client) | ||
| yield dns_manager_class( | ||
| settings=dns_settings, | ||
| app_settings=app_settings, | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
зачем async, тут ничего не происходит?
| self, | ||
| settings: DNSManagerSettings, | ||
| http_client: httpx.AsyncClient, | ||
| app_settings: Settings, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
убрать отсюда app_settings, модифицировать DNSManagerSettings
| async def send( | ||
| self, | ||
| method: str, | ||
| url: str, | ||
| payload: dict | None = None, | ||
| ) -> httpx.Response: | ||
| """Get the recursor DNS HTTP client.""" | ||
| response = await self._http_client.request( | ||
| method=method, | ||
| url=url, | ||
| json=payload, | ||
| ) | ||
|
|
||
| await self._validate_response(response) | ||
|
|
||
| return response | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
выпилить метод, добавить более явные, чтобы в них падала dto, внутри каждого добавить обработку ошибок или придумать более изящное решение. Не забыть про логгирование
| _power_dns_auth_client: AbstractDNSHTTPClient | ||
| _power_dns_recursor_client: AbstractDNSHTTPClient | ||
| _dnsdist_client: PowerDNSDistClient | ||
|
|
||
| def __init__( | ||
| self, | ||
| settings: DNSManagerSettings, | ||
| app_settings: Settings, | ||
| ) -> None: | ||
| """Initialize the PowerDNS API repository.""" | ||
| super().__init__(settings, app_settings) | ||
| self._power_dns_auth_client = self._setup_client( | ||
| self._app_settings.PDNS_AUTH_SERVER_HOST, | ||
| self._app_settings.PDNS_AUTH_SERVER_PORT, | ||
| self._app_settings.PDNS_API_KEY, | ||
| ) | ||
| self._power_dns_recursor_client = self._setup_client( | ||
| self._app_settings.PDNS_RECURSOR_SERVER_HOST, | ||
| self._app_settings.PDNS_RECURSOR_SERVER_PORT, | ||
| self._app_settings.PDNS_API_KEY, | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
через ioc
| def _setup_dnsdist( | ||
| self, | ||
| dnsdist_host: str, | ||
| dnsdist_port: int, | ||
| dnsdist_key: str, | ||
| config_path: str, | ||
| ) -> PowerDNSDistClient: | ||
| """Set up dnsdist controller.""" | ||
| return PowerDNSDistClient( | ||
| dnsdist_host=dnsdist_host, | ||
| dnsdist_port=dnsdist_port, | ||
| dnsdist_key=dnsdist_key, | ||
| config_path=config_path, | ||
| ) | ||
|
|
||
| def _setup_client( | ||
| self, | ||
| server_host: str, | ||
| server_port: int, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
выпилить
| await self._dns_manager.delete_zone(zone_names) | ||
| async def delete_forward_zones(self, zone_ids: list[str]) -> None: | ||
| """Delete DNS forward zones.""" | ||
| for zone_id in zone_ids: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
как и договаривались, добавить обработку ошибки удаления, до конца проитерироваться, если хотя бы у 1 ошибка возникла, то вывести коды фронту
| @@ -0,0 +1,16 @@ | |||
| # | |||
| # macOS Notice | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
шо то надо решить с этим файлом
Задачи: 1057, 1058
Произведена замена DNS сервера с Bind9 на PowerDNS, состоящий из двух компонентов: Authoritative Server и Recursor. Произведен рефакторинг компонентов DNS сервера. Вся логика, относящаяся к Bind9, была удалена, за исключением некоторым датаклассов по просьбе Егора. Вся логика была воспроизведена в соответствии с новым DNS сервером за исключением пары фич: включение/отключение DNSSEC опции DNS сервера - теперь это опция относится к зоне, соответственно, временно удален функционал получения и изменения параметров DNS сервера, удалена возможность перезагружать DNS сервер и отдельную зону, так как в этом больше нет необходимости и на фронт этот функционал так и не был выведен.
Сделано:
UPD: Добавлен dnsdist