Опыты в домашней лаборатории: динамически обновляем записи приватной зоны DNS в OpenWRT

от автора

Моя домашняя лаборатория подключена к интернету через маршрутизатор с прошивкой OpenWRT. Развертывая локальный ACME сервер, я понял, что, независимо от применяемого типа валидации запросов, ACME должен найти в DNS полное доменное имя сервера, для которого запрошен сертификат.

В размышлениях, где же стоит хостить свою приватную DNS зону, меня озарило: «Но у нас уже есть дома DNS-сервер в OpenWRT. Наверняка можно удаленно обновлять записи в его локальной зоне».
TL;DR: В итоге пришлось поставить BIND.

Увы, мне не удалось найти хороший способ удаленно обновлять записи в dnsmasq (DNS-форвардер в OpenWRT по умолчанию).

Для моих целей нужно было что-то простое, авторитетное и с поддержкой TSIG. При сравнении матрицы функциональности DNS серверов и репозитория OpenWRT взгляд зацепился за такие варианты:

  • BIND 9.18.24-1

  • PowerDNS 4.7.4-1

  • Knot DNS 3.3.5-1

  • NSD 4.6.1-1

Здесь они отсортированы по принципу «чем больше зеленого в таблице, тем выше в списке».

На тот момент при выборе варианта я руководствовался отсутствием личных предпочтений и понимания, какие нюансы нужно учитывать. Поэтому отважно начал с BIND, который стоит первым в списке.

План:

  1. Установить BIND;

  2. Отключить DNS-forwarding в dnsmasq, оставив его обслуживать DHCP запросы;

  3. Настроить базовые вещи;

  4. Включить и протестировать динамическое обновление зон;

  5. Автоматизировать обновление в DNS записей для хостов, инициализирующихся по DHCP.

Настройки роутера:

  • .lan — суффикс для имен в локальной сети,

  • CIDR локальной сети: 192.168.1.0/24

Версии:

  • OpenWrt 23.05.3 arm64

  • BIND 9.18.24

Дисклеймер: cледуя инструкциям ниже, вы рискуете поломать разрешение имен в своей сети, уменьшить ресурс флэш-памяти роутера или лишить себя интернета. Прежде чем повторять эти шаги на реальном роутере, сначала попробуйте на эмуляторе.

Установка

Перед началом эксперимента взглянем на активные сетевые сервисы:

$ netstat -tulpn Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address    Foreign Address  State    PID/Program name tcp        0      0 0.0.0.0:22       0.0.0.0:*        LISTEN   1637/dropbear tcp        0      0 0.0.0.0:80       0.0.0.0:*        LISTEN   2187/uhttpd tcp        0      0 192.168.1.1:53   0.0.0.0:*        LISTEN   2510/dnsmasq tcp        0      0 10.1.2.144:53    0.0.0.0:*        LISTEN   2510/dnsmasq tcp        0      0 127.0.0.1:53     0.0.0.0:*        LISTEN   2510/dnsmasq tcp        0      0 0.0.0.0:443      0.0.0.0:*        LISTEN   2187/uhttpd udp        0      0 127.0.0.1:53     0.0.0.0:*                 2510/dnsmasq udp        0      0 10.1.2.144:53    0.0.0.0:*                 2510/dnsmasq udp        0      0 192.168.1.1:53   0.0.0.0:*                 2510/dnsmasq udp        0      0 0.0.0.0:67       0.0.0.0:*                 2510/dnsmasq ...

Как и ожидалось, порты 53 (DNS) и 67 (DHCP) обслуживаются dnsmasq.

Устанавливаем сервер bind и набор утилит к нему:

$ opkg update $ opkg install bind-server bind-tools bind-client

Затем отключаем в dnsmasq часть, ответственную за DNS, указываем наш локальный домен и конфигурируем DHCP серевер отсылать клиентам 192.168.1.1 в качестве адреса DNS:

$ uci set dhcp.@dnsmasq[0].port=0 $ uci set dhcp.@dnsmasq[0].domain='lan' $ uci add_list dhcp.@dnsmasq[0].dhcp_option='6,192.168.1.1' $ uci commit dhcp $ /etc/init.d/dnsmasq restart $ /etc/init.d/named restart

После перезагрузки сервисов активные порты должны выглядеть примерно так:

$ netstat -tulpn Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address   Foreign Address   State   PID/Program name tcp        0      0 0.0.0.0:22      0.0.0.0:*         LISTEN  1637/dropbear tcp        0      0 0.0.0.0:80      0.0.0.0:*         LISTEN  2187/uhttpd tcp        0      0 127.0.0.1:953   0.0.0.0:*         LISTEN  8511/named tcp        0      0 127.0.0.1:953   0.0.0.0:*         LISTEN  8511/named tcp        0      0 0.0.0.0:443     0.0.0.0:*         LISTEN  2187/uhttpd udp        0      0 192.168.1.1:53  0.0.0.0:*                 8511/named udp        0      0 192.168.1.1:53  0.0.0.0:*                 8511/named udp        0      0 10.1.2.144:53   0.0.0.0:*                 8511/named udp        0      0 10.1.2.144:53   0.0.0.0:*                 8511/named udp        0      0 127.0.0.1:53    0.0.0.0:*                 8511/named udp        0      0 127.0.0.1:53    0.0.0.0:*                 8511/named udp        0      0 0.0.0.0:67      0.0.0.0:*                 8767/dnsmasq ...

Как видим, dnsmasq слушает DHCP, а named (BIND) слушает порты 53 и 953.

Давайте проверим, что у нас есть доступ к DNS:

$ dig +dnssec +multi . DNSKEY  ; <<>> DiG 9.18.24 <<>> +dnssec +multi . DNSKEY ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 19567 ;; flags: qr rd ra ad; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags: do; udp: 1232 ; COOKIE: a398f023571fde5701000000667c7bbbd959a05085315b53 (good) ;; QUESTION SECTION: ;.                      IN DNSKEY ;; ANSWER SECTION: .                       171554 IN DNSKEY 256 3 8 (                                 AwEAAZBALoOFImwcJJg9Iu7Vy7ZyLjhtXfvO1c9k4vHj                                 Opf9i7U1kKtrBvhnwsOni1sb50gkUayRtMDTUQqvljMM                                 f4bpkyEtcE5evCzhHbFLq1coL5QOix3mfJm++FvIMaAt                                 52nOvAdqR/luuI11bA1AmSCIJKAUx147DcfOHYKg3as+                                 dznn3Iah4cWBMVzDe7PPsFS1AO6gU8EpmiRJ9VMNA09f                                 OyDuq9+d6sw8UUnJRMAFAuPLhUFjUAOuWOw74BC9lOtM                                 QpbLMz8pX0CDKdOXDHjyj61nxSSWxPdUjeoxI17lQTpS                                 PRtqRHFn5Fgj2e+9BVwhhWGDQN8kUVSJHZtQiI0=                                 ) ; ZSK; alg = RSASHA256 ; key id = 5613 .                       171554 IN DNSKEY 256 3 8 (                                 AwEAAdSiy6sslYrcZSGcuMEK4DtE8DZZY1A08kAsviAD                                 49tocYO5m37AvIOyzeiKBWuPuJ4m9u5HonCM/ntxklZK                                 YFyMftv8XoRwbiXdpSjfdpNHiMYTTV2oDUNMjdLFnF6H                                 YSY48xrPbevQOYbAFGHpxqcXAQT0+BaBiAx3Ls6lXBQ3                                 /hSVOprvDWJCQiI2OT+9+saKLddSIX6DwTVy0S5T4YY4                                 EGg5R3c/eKUb2/8XgKWUzlOIZsVAZZUSTKW0tX54ccAA                                 LO7Grvsx/NW62jc1xv6wWAXocOEVgB7+4Lzb7q9p5o30                                 +sYoGpOsKgFvMSy4oCZTQMQx2Sjd/NG2bMMw6nM=                                 ) ; ZSK; alg = RSASHA256 ; key id = 20038 .                       171554 IN DNSKEY 257 3 8 (                                 AwEAAaz/tAm8yTn4Mfeh5eyI96WSVexTBAvkMgJzkKTO                                 iW1vkIbzxeF3+/4RgWOq7HrxRixHlFlExOLAJr5emLvN                                 7SWXgnLh4+B5xQlNVz8Og8kvArMtNROxVQuCaSnIDdD5                                 LKyWbRd2n9WGe2R8PzgCmr3EgVLrjyBxWezF0jLHwVN8                                 efS3rCj/EWgvIWgb9tarpVUDK/b58Da+sqqls3eNbuv7                                 pr+eoZG+SrDK6nWeL3c6H5Apxz7LjVc1uTIdsIXxuOLY                                 A4/ilBmSVIzuDWfdRUfhHdY6+cn8HFRm+2hM8AnXGXws                                 9555KrUB5qihylGa8subX2Nn6UwNR1AkUTV74bU=                                 ) ; KSK; alg = RSASHA256 ; key id = 20326 .                       171554 IN RRSIG DNSKEY 8 0 172800 (                                 20240711000000 20240620000000 20326 .                                 k7Tz3FFlPySd/LF69we2WyDwnqf+JTTpJ3sriFGLkq26                                 MGBD/fioXO4xqcCrnWVF50nKs8CaEQpdI9N0N2rW3fZh                                 9sVryGEvPiNnxfv8JC9MiMlt5pnVWYyOzDWpt9OAznmv                                 JVvqhZIi19MvmkEj+S/WQCuJwZUx+0r1Nv8mBrN0dbms                                 LpH3sjgs8pw8SSL4QCLFlJzmqomt1ncM5ocoWqvOU7Hb                                 Xgt40Gg0ZiZFqs9IebA62pbu5GAVzJEMoANUqxIo3lAg                                 2JIEWTpo/+hF3QpaB/SFJ0obrJMi4OULOfY2DCx1jjlq                                 C4qaiS7c/IaGux2bMwQV1zfRDpu4AA5eSw== ) ;; Query time: 9 msec ;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP) ;; WHEN: Wed Jun 26 20:36:11 UTC 2024 ;; MSG SIZE  rcvd: 1169

Доступ есть, можем переходить к настройке.

Настройки по умолчанию

Сразу после установки каталог /etc/bind выглядит так:

$ ls -lah /etc/bind  drwxr-xr-x  2 root root  3.4K Jun 26 20:15 . drwxr-xr-x  1 root root  3.4K Jun 26 20:15 .. -rw-r--r--  1 root root  3.8K Feb 16 18:24 bind.keys -rw-r--r--  1 root root   237 Feb 16 18:24 db.0 -rw-r--r--  1 root root   271 Feb 16 18:24 db.127 -rw-r--r--  1 root root   237 Feb 16 18:24 db.255 -rw-r--r--  1 root root   237 Feb 16 18:24 db.empty -rw-r--r--  1 root root   256 Feb 16 18:24 db.local -rw-r--r--  1 root root  3.1K Feb 16 18:24 db.root -rw-r--r--  1 root root   281 Jun 26 20:15 named-rndc.conf -rw-r--r--  1 root root   982 Feb 16 18:24 named.conf -rw-r--r--  1 root root   225 Jun 26 20:15 rndc.conf

Здесь:

  • bind.keys — якоря доверия для корневой зоны DNS(.). Если есть желание, то можно сравнить содержимое файла с тем, что нам выше вернул dig ;

  • db.root — информация о корневых серверах для предзаполнения нашего кэша;

  • db.0, db.255, db.empty — обратные (reverse lookup) зоны для широковещательных запросов;

  • db.local — прямая (forward lookup) зона для localhost;

  • db.127 — обратная зона для loopback адресов;

  • named-rndc.conf — ключ и полиси, позволяющие утилите rndc локально управлять нашим сервером;

  • rndc.conf — настройки для самой rndc ;

  • named.conf — основной файл конфигурации BIND.

Прямо «из коробки» /etc/bind/named.conf в OpenWRT выглядит примерно так:

// base named.conf file // Recommended that you always maintain a change log in this file  // options clause defining the server-wide properties options {         // all relative paths use this directory as a base         directory "/tmp";          // If your ISP provided one or more IP addresses for stable         // nameservers, you probably want to use them as forwarders.         // Uncomment the following block, and insert the addresses replacing         // the all-0's placeholder.         // forwarders {         //      0.0.0.0;         // };          auth-nxdomain no;    # conform to RFC1035          // this ensures that any reverse map for private IPs         // not defined in a zone file will *not* be passed          // to the public network         empty-zones-enable yes; };  include "/etc/bind/named-rndc.conf"; include "/tmp/bind/named.conf.local";  // prime the server with knowledge of the root servers zone "." {         type hint;         file "/etc/bind/db.root"; };  // Provide forward mapping zone for localhost (optional) zone "localhost" {         type primary;         file "/etc/bind/db.local"; };  // Provide reverse mapping zone for the loopback address 127.0.0.1 // zone "0.0.127.in-addr.arpa"  zone "127.in-addr.arpa" {         type primary;         file "/etc/bind/db.127"; };  zone "0.in-addr.arpa" {         type primary;         file "/etc/bind/db.0"; };  zone "255.in-addr.arpa" {         type primary;         file "/etc/bind/db.255"; }; 

Настройка локальных зон

Для начала добавим файл /etc/bind/db.lan локальной зоны прямого просмотра lan.:

; forward zone file for lan. $ORIGIN . $TTL 0  ; 0 seconds lan                     IN SOA  ns1.lan. root.lan. (                                 1719490275 ; serial                                 43200      ; refresh (12 hours)                                 900        ; retry (15 minutes)                                 1814400    ; expire (3 weeks)                                 7200       ; minimum (2 hours)                                 ) $TTL 900        ; 15 minutes                         NS      ns1.lan. $ORIGIN lan. ns1                     A       192.168.1.1 openwrt                 A       192.168.1.1 router                  CNAME   openwrt acme                    CNAME   openwrt 

и файл /etc/bind/db.1.168.192 зоны обратного просмотра для нашей подсети 192.168.1.0/24:

; reverse zone file for lan. $ORIGIN . $TTL 0  ; 0 seconds 1.168.192.in-addr.arpa  IN SOA  ns1.lan. root.lan. (                                 1719490269 ; serial                                 43200      ; refresh (12 hours)                                 900        ; retry (15 minutes)                                 1814400    ; expire (3 weeks)                                 7200       ; minimum (2 hours)                                 ) $TTL 900        ; 15 minutes                         NS      ns1.lan. $ORIGIN 1.168.192.in-addr.arpa. 1                       PTR     ns1.lan.                         PTR     openwrt.lan. 

Обзор синтаксиса файлов зон можно найти здесь,

но если вкратце:
  • Парсинг происходит сверху вниз.

  • ; начинает комментарий. Все, что в строке после него, игнорируется.

  • ()скобки используются для разделения длинных строк на несколько более коротких.

  • При парсинге к любому доменному имени, которое не заканчивается точкой «.«, добавляется текущее значение переменной $ORIGIN. Например, запись acme CNAME openwrt интерпретируется как acme.lan. CNAME openwrt.lan., поскольку четырьмя строками выше мы задали lan. как текущее значение $ORIGIN.

  • После того, как класс IN определили в записи SOA, в ресурсных записях его можно не писать.

  • Если в записях не писать значение TTL, то оно будет принято равным текущему значению переменной $TTL.

  • В записи SOA значение ns1.lan. — это имя DNS сервера, который эту зону обслуживает, root.lan. транслируется в e-mail адрес администратора зоны root@lan .

  • Если в ресурсной записи самое первое поле (оно называется OWNER) пустое, то оно считается равным значению этого поля на предыдущей строке.

  • Имена в обратной зоне in-addr.arpa., соответствующие IP адресам, записываются в обратном порядке, то есть IP адрес 10.1.2.3 превращается в 3.2.1.10.in-addr.arpa.

Правила парсинга, приведенные выше, позволяют значительно сократить ресурсную запись 1.1.168.192.in-addr.arpa. 900 IN PTR openwrt.lan. до простого PTR openwrt.lan.

Убедимся, что мы не напутали с синтаксисом:

$ named-checkzone lan /etc/bind/db.lan  zone lan/IN: loaded serial 1719490275 OK  $ named-checkzone 1.168.192.in-addr.arpa /etc/bind/db.1.168.192  zone 1.168.192.in-addr.arpa/IN: loaded serial 1719490269 OK

Теперь можем добавлять сами зоны в /etc/bind/named.conf:

$ cat <<EOF>> /etc/bind/named.conf zone "lan" {         type primary;         file "/etc/bind/db.lan"; }; zone "1.168.192.in-addr.arpa" {         type primary;         file "/etc/bind/db.1.168.192"; }; EOF 

Проверяем синтаксис файла конфигурации:

$ named-checkconf -pzx

Перезагружаем конфигурацию bind и смотрим, подгрузились ли зоны:

$ rndc reload  $ rndc zonestatus lan  name: lan type: primary files: /etc/bind/db.lan serial: 1719490275 nodes: 5 last loaded: Thu, 27 Jun 2024 13:01:34 GMT secure: no dynamic: no reconfigurable via modzone: no  $ rndc zonestatus 1.168.192.in-addr.arpa  name: 1.168.192.in-addr.arpa type: primary files: /etc/bind/db.1.168.192 serial: 1719490269 nodes: 2 last loaded: Thu, 27 Jun 2024 13:13:03 GMT secure: no dynamic: no reconfigurable via modzone: no

И, наконец, проверяем, как работает разрешение имен:

$ nslookup acme.lan && nslookup 192.168.1.1  Server:         127.0.0.1 Address:        127.0.0.1#53 acme.lan        canonical name = openwrt.lan. Name:   openwrt.lan Address: 192.168.1.1 1.1.168.192.in-addr.arpa        name = ns1.lan. 1.1.168.192.in-addr.arpa        name = openwrt.lan.

Настраиваем forwarders{}

Теперь у нас есть прямое подключение к DNS, а это значит, что любой, кто видит наш траффик, будет больше нас знать о том, на какие сайты мы ходим. Попробуем исправить это с помощью перенаправления DNS запросов.

Дисклеймер: сокрытие DNS-трафика приведенным ниже способом на самом деле представляет собой выбор между Провайдер и БольшойБрат или же Доктор Зло с Иллюминатами. Конечно, если пересылаемые запросы покидают вашу сеть через порт 53 незашифрованными, интернет-провайдер все равно видит 🙂

Начиная с версии 9.19.10, в BIND можно настроить использование TLS для перенаправленных запросов. Увы, в репозитории пакетов OpenWRT на данный момент только версия 9.18.24. Для принуждения к DNS-over-TLS (DOT) запустим DNS прокси stubby, который «из коробки» перенаправляет запросы на сервера CloudFlare:

$ opkg install ca-certificates $ opkg install stubby

Проверяем, какой порт он слушает:

$ uci get stubby.global.listen_address  127.0.0.1@5453 0::1@5453

Прописываем 127.0.0.1 port 5453 в /etc/bind/named.conf и запрещаем прямые запросы к DNS:

options { ...    forward only;    forwarders {       127.0.0.1 port 5453;     };  ... }; 

Проверяем и перезагружаем конфигурацию:

$ named-checkconf $ rndc reload $ rndc flush
Если после включения переадресации разрешение имен перестало работать или хочется не CloudFlare

вот несколько советов, которые помогли мне:

  • Заменить CloudFlare можно в /etc/config/stubby

  • Там же можно раскомментить option log_level '7', выполнить /etc/init.d/stubby reload, что выдаст больше информации в syslog. Просмотреть его можно, запустив logread -f во второй сессии ssh

  • Установка рут сертификатов opkg install ca-certificates помогла побороть ошибку TLS - *Failure* - (20) "unable to get local issuer certificate"

Настраиваем динамическое обновление зон

На данный момент наш сервер DNS полностью статичен и новые записи в файлах зон сами по себе не появляются. Мы можем вручную редактировать файлы прямой и обратной зон с последующей перезагрузкой конфигурации, но есть способ лучше: использовать утилиту nsupdate или любой другой инструмент для криптографически подписанных обновлений DNS.

Для этого нам понадобится сгенерировать ключ TSIG, подправить конфигурацю зон в /etc/bind/named.conf и сменить владельца файлов.

Примечание: во многих примерах из интернета для генерации ключей TSIG используется утилита dnssec-keygen. В текущей версии BIND она не годится для этой цели, поскольку функция генерации алгоритмов HMAC для использования в качестве ключей TSIG через dnssec-keygen была удалена в BIND 9.13.0. Вместо этого мы будем использовать tsig-keygen.

Поскольку вывод tsig-keygen уже отформатирован для включения в файл конфигурации, нам просто нужно его запустить:

$ tsig-keygen | tee /etc/bind/keys.conf key "tsig-key" {         algorithm hmac-sha256;         secret "HAyLN66//YxVF2lrZ6kSZK4TZEpV7WMvzYnNUQ0BvEo="; };

Затем сослаться на сгенерированный файл в /etc/bind/named.conf :

$ cat<<EOF>> /etc/bind/named.conf include "/etc/bind/keys.conf"; EOF

В /etc/bind/named.conf добавляем allow-update{key tsig-key;} в конфигурацию зон, что разрешит нашему ключу любые изменения:

zone "lan" {         type primary;         file "/etc/bind/db.lan";         allow-update {           key tsig-key;         }; }; zone "1.168.192.in-addr.arpa" {         type primary;         file "/etc/bind/db.1.168.192";         allow-update {           key tsig-key;         }; };

Примечание: если неограниченные права в вашем случае избыточны, то можно вместо allow-update{} задать более сложный набор правил через update-policy{}.

Проверяем и перезагружаем конфигурацию:

$ named-checkconf $ rndc reload

Маленькая, но очень важная деталь: в данный момент каталог /etc/bind со всеми файлами принадлежит root:root. Нам нужно поменять владельца на bind:bind, чтобы named мог создавать и изменять файлы. Заодно подправим права доступа для остальных системных пользователей:

$ chown -R bind:bind /etc/bind $ chmod 600 -R /etc/bind/*

Тестируем обновление записей через nsupdate

Давайте добавим пару записей в наши зоны. Для этого создадим файл nsupdate.cmd со следующим содержимым:

server 127.0.0.1 53 zone lan. update delete host2.lan. update add host2.lan. 900 A 192.168.1.2 show send zone 1.168.192.in-addr.arpa. update delete 2.1.168.192.in-addr.arpa. update add 2.1.168.192.in-addr.arpa. 900 PTR host2.lan. show send

Этой последовательностью команд мы просим DNS сервер удалить все записи (если таковые существуют) для host2.lan. в зоне lan., а затем добавить A запись с TTL=900 для host2.lan., ссылающуюся на 192.168.1.2. Вторая последовательность команд повторяет алгоритм для PTR записи в обратной зоне 1.168.192.in-addr.arpa.

Запускаем nsupdate, указывая путь к файлу с ключом и к файлу со списком команд:

$ nsupdate -k /etc/bind/keys.conf nsupdate.cmd Outgoing update query: ;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id:      0 ;; flags:; ZONE: 0, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0 ;; ZONE SECTION: ;lan.                           IN      SOA  ;; UPDATE SECTION: host2.lan.              0       ANY     ANY host2.lan.              900     IN      A       192.168.1.2  Outgoing update query: ;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id:      0 ;; flags:; ZONE: 0, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0 ;; ZONE SECTION: ;1.168.192.in-addr.arpa.                IN      SOA  ;; UPDATE SECTION: 2.1.168.192.in-addr.arpa. 0     ANY     ANY 2.1.168.192.in-addr.arpa. 900   IN      PTR     host2.lan.

Проверяем результат:

$ host 192.168.1.2 && host host2.lan  2.1.168.192.in-addr.arpa domain name pointer host2.lan. host2.lan has address 192.168.1.2

Если заглянем в каталог с конфигами, то увидим два новых файла с расширением .jnl, в которых хранится журнал изменений записей в зонах:

$ ls /etc/bind/*jnl  /etc/bind/db.1.168.192.jnl  /etc/bind/db.lan.jnl

Дисклеймер: в контексте OpenWRT это был для меня неприятный сюрприз, так как запись происходит во flash память роутера и тем самым ускоряет ее деградацию. Рекомендую ознакомиться с этим разделом документации для оценки частоты дампов в вашем случае. Я же просто принял как неизбежное зло то, что раз в 15 минут может произойти запись во flash. По крайней мере, до тех пор, пока не найдется надежный способ держать эти журналы на разделе /tmp, который находится в оперативной памяти роутера (принимая все риски, связанные с потерей электроснабжения). Возможно, толковый совет будет в комментах.

А пока давайте просто сделаем дамп журналов в файлы зон и посмотрим на изменения:

$ rndc sync && cat /etc/bind/db.lan 
$ORIGIN . $TTL 0  ; 0 seconds lan                     IN SOA  ns1.lan. root.lan. (                                 1719490277 ; serial                                 43200      ; refresh (12 hours)                                 900        ; retry (15 minutes)                                 1814400    ; expire (3 weeks)                                 7200       ; minimum (2 hours)                                 ) $TTL 900        ; 15 minutes                         NS      ns1.lan. $ORIGIN lan. acme                    CNAME   openwrt host2                   A       192.168.1.2 ns1                     A       192.168.1.1 openwrt                 A       192.168.1.1 router                  CNAME   openwrt
$ cat /etc/bind/db.1.168.192 
$ORIGIN . $TTL 0  ; 0 seconds 1.168.192.in-addr.arpa  IN SOA  ns1.lan. root.lan. (                                 1719490271 ; serial                                 43200      ; refresh (12 hours)                                 900        ; retry (15 minutes)                                 1814400    ; expire (3 weeks)                                 7200       ; minimum (2 hours)                                 ) $TTL 900        ; 15 minutes                         NS      ns1.lan. $ORIGIN 1.168.192.in-addr.arpa. 1                       PTR     ns1.lan.                         PTR     openwrt.lan. 2                       PTR     host2.lan.

Как видим, записи для host2.lan. появились в обеих зонах.

Запускаем nsupdate на другом хосте

Теперь проверим, как можно изменить запись с другой машины в сети. Для этого перенесем содержимое /etc/bind/keys.conf на хост, подключенный к роутеру со стороны LAN. Также сгенерируем файл nsupdate.cmd со списком команд, указав, что сервер находится по адресу 192.168.1.1:

server 192.168.1.1 53 zone lan. update delete host2.lan. show send zone 1.168.192.in-addr.arpa. update delete 2.1.168.192.in-addr.arpa. show send

Запускаем nsupdate:

[rocky@test ~]$ nsupdate -k keys.conf nsupdate.cmd  Outgoing update query: ;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id:      0 ;; flags:; ZONE: 0, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0 ;; ZONE SECTION: ;lan.                           IN      SOA  ;; UPDATE SECTION: host2.lan.              0       ANY     ANY  Outgoing update query: ;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id:      0 ;; flags:; ZONE: 0, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0 ;; ZONE SECTION: ;1.168.192.in-addr.arpa.                IN      SOA  ;; UPDATE SECTION: 2.1.168.192.in-addr.arpa. 0     ANY     ANY

Ошибок нет — получилось!

Управляем зоной через Terraform

В принцие, не имеет значения, какой утилитой мы управляем зоной. Нам нужны лишь имя и материал ключа TSIG.
Создаем main.tf :

# Disclaimer: Storing secrets in plain text within Terraform's configuration # and state files is strongly discouraged due to the inevitable security risks. # It is crucial to familiarize yourself with techniques to avoid those.  terraform {   required_providers {     dns = {       source = "hashicorp/dns"       version = "3.4.1"     }   } }  provider "dns" {   update {     server        = "192.168.1.1"     key_name      = "tsig-key."     key_algorithm = "hmac-sha256"     key_secret    = "HAyLN66//YxVF2lrZ6kSZK4TZEpV7WMvzYnNUQ0BvEo="   } }  resource "dns_a_record_set" "host100" {   zone = "lan."   name = "host100"   addresses = [     "192.168.1.100"   ]   ttl = 900 }  resource "dns_ptr_record" "ptr_192_168_1_100" {   zone = "1.168.192.in-addr.arpa."   name = "100"   ptr  = "host100.lan."   ttl = 900 }

Обратите внимание на точку (.) в содержимом key_name. Провайдер hashicorp/dns требует имена ключей оформлять как FQDN. При этом значение поля name в конфигурации ресурса нужно задавать в укороченной форме.

Устанавливаем terraform/tofu и запускаем:

[rocky@test ~]$ terraform init [rocky@test ~]$ terraform plan [rocky@test ~]$ terraform apply Terraform will perform the following actions:   # dns_a_record_set.host100 will be created   + resource "dns_a_record_set" "host100" {       + addresses = [           + "192.168.1.100",         ]       + id        = (known after apply)       + name      = "host100"       + ttl       = 900       + zone      = "lan."     }   # dns_ptr_record.ptr_192_168_1_100 will be created   + resource "dns_ptr_record" "ptr_192_168_1_100" {       + id   = (known after apply)       + name = "100"       + ptr  = "host100.lan."       + ttl  = 900       + zone = "1.168.192.in-addr.arpa."     } Plan: 2 to add, 0 to change, 0 to destroy. Do you want to perform these actions?   Terraform will perform the actions described above.   Only 'yes' will be accepted to approve.   Enter a value: yes dns_ptr_record.ptr_192_168_1_100: Creating... dns_a_record_set.host100: Creating... dns_a_record_set.host100: Creation complete after 0s [id=host100.lan.] dns_ptr_record.ptr_192_168_1_100: Creation complete after 0s [id=100.1.168.192.in-addr.arpa.] Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

Вернемся в шелл OpenWRT и посмотрим на изменения в зонах:

$ rndc sync && cat /etc/bind/db.lan 
$ORIGIN . $TTL 0  ; 0 seconds lan                     IN SOA  ns1.lan. root.lan. (                                 1719490287 ; serial                                 43200      ; refresh (12 hours)                                 900        ; retry (15 minutes)                                 1814400    ; expire (3 weeks)                                 7200       ; minimum (2 hours)                                 ) $TTL 900        ; 15 minutes                         NS      ns1.lan. $ORIGIN lan. acme                    CNAME   openwrt host100                 A       192.168.1.100 ns1                     A       192.168.1.1 openwrt                 A       192.168.1.1 router                  CNAME   openwrt
$ cat /etc/bind/db.1.168.192 
$ORIGIN . $TTL 0  ; 0 seconds 1.168.192.in-addr.arpa  IN SOA  ns1.lan. root.lan. (                                 1719490281 ; serial                                 43200      ; refresh (12 hours)                                 900        ; retry (15 minutes)                                 1814400    ; expire (3 weeks)                                 7200       ; minimum (2 hours)                                 ) $TTL 900        ; 15 minutes                         NS      ns1.lan. $ORIGIN 1.168.192.in-addr.arpa. 1                       PTR     ns1.lan.                         PTR     openwrt.lan. 100                     PTR     host100.lan.

Записи для host100.lan. появились.

Теперь удалим их:

[rocky@test ~]$ terraform destroy Terraform will perform the following actions:   # dns_a_record_set.host100 will be destroyed   - resource "dns_a_record_set" "host100" {       - addresses = [           - "192.168.1.100",         ] -> null       - id        = "host100.lan." -> null       - name      = "host100" -> null       - ttl       = 900 -> null       - zone      = "lan." -> null     }   # dns_ptr_record.ptr_192_168_1_100 will be destroyed   - resource "dns_ptr_record" "ptr_192_168_1_100" {       - id   = "100.1.168.192.in-addr.arpa." -> null       - name = "100" -> null       - ptr  = "host100.lan." -> null       - ttl  = 900 -> null       - zone = "1.168.192.in-addr.arpa." -> null     } Plan: 0 to add, 0 to change, 2 to destroy. Do you really want to destroy all resources?   Terraform will destroy all your managed infrastructure, as shown above.   There is no undo. Only 'yes' will be accepted to confirm.   Enter a value: yes dns_ptr_record.ptr_192_168_1_100: Destroying... [id=100.1.168.192.in-addr.arpa.] dns_a_record_set.host100: Destroying... [id=host100.lan.] dns_a_record_set.host100: Destruction complete after 0s dns_ptr_record.ptr_192_168_1_100: Destruction complete after 0s Destroy complete! Resources: 2 destroyed.

Автоматическая регистрация ресурсных записей для хостов, инициализирующихся по DHCP

До сих пор изменение записей в DNS инициировалось вручную. Давайте добавим немного автоматизации для клиентов DHCP.

В OpenWRT есть механизм hotplug, который позволяет запускать пользовательские скрипты при возникновении определенных событий в различных сервисах. Нас интересуют события DHCP, поступающие от dnsmasq, и для запуска нашего скрипта нам нужно просто поместить его в каталог /etc/hotplug.d/dhcp/. Размещенный в этом каталоге скрипт будет вызываться из /usr/lib/dnsmasq/dhcp-script.sh, который, в свою очередь, вызывается самим dnsmasq.

Нашему скрипту будет передана информация о событии DHCP в переменных: $MACADDR, $IPADDR, $HOSTNAME и $ACTION=("add"|"remove"|"update")

Примечание: В случае, когда клиент не передает свое имя DHCP-серверу, есть нюанс с переменной $HOSTNAME

Создадим тестовый скрипт /etc/hotplug.d/dhcp/00-hello:

logger "=======================" logger "I've been supplied with this number of arguments: ${#}" logger "There they are: ${@}" logger "Here're variables available to me:" for var_passed in $(set); do     logger "${var_passed}" done logger "======================="

Инициируем событие DHCP с хоста на стороне LAN, но не будем отправлять имя хоста:

[rocky@test ~]$ hostname test  [rocky@test ~]$ sudo dhclient -r eth0 -v Listening on LPF/eth0/bc:24:11:2a:92:e3 Sending on   LPF/eth0/bc:24:11:2a:92:e3 Sending on   Socket/fallback DHCPRELEASE of 192.168.1.122 on eth0 to 192.168.1.1 port 67 (xid=0x303d9b4c)

Заглянем в вывод команды logread на OpenWRT:

user.notice root: ======================= user.notice root: I've been supplied with this number of arguments: 0 user.notice root: There they are: user.notice root: Here're variables available to me: user.notice root: ACTION='remove' ... user.notice root: HOSTNAME='OpenWrt' ... user.notice root: IPADDR='192.168.1.122' ... user.notice root: MACADDR='bc:24:11:2a:92:e3' ... user.notice root: =======================

Как видим, наш скрипт вместо пустой строки видит 'OpenWrt' в переменной $HOSTNAME.

В то же время, когда клиент отправляет имя хоста, системная переменная $HOSTNAME правильно переопределяется значением 'test'.

[rocky@test ~]$ hostname test  [rocky@test ~]$ sudo dhclient -v -H $(hostname) eth0 Listening on LPF/eth0/bc:24:11:2a:92:e3 Sending on   LPF/eth0/bc:24:11:2a:92:e3 Sending on   Socket/fallback DHCPDISCOVER on eth0 to 255.255.255.255 port 67 interval 6 (xid=0x4c02d471) DHCPOFFER of 192.168.1.122 from 192.168.1.1 DHCPREQUEST for 192.168.1.122 on eth0 to 255.255.255.255 port 67 (xid=0x4c02d471) DHCPACK of 192.168.1.122 from 192.168.1.1 (xid=0x4c02d471) bound to 192.168.1.122 -- renewal in 17215 seconds.

Вывод комманды logread:

user.notice root: ======================= user.notice root: I've been supplied with this number of arguments: 0 user.notice root: There they are: user.notice root: Here're variables available to me: user.notice root: ACTION='add' ... user.notice root: HOSTNAME='test' ... user.notice root: IPADDR='192.168.1.122' ... user.notice root: MACADDR='bc:24:11:2a:92:e3' user.notice root: =======================

Предположу, что такое поведение задано намеренно, так как можно довольно легко отфильтровать клиентские DHCP запросы самого роутера.

Создадим файл /etc/hotplug.d/dhcp/00-hello со следующим содержимым:

#!/bin/sh #set -x   if [ "${HOSTNAME}" = "$(uci get system.@system[0].hostname)" ] || [ "${HOSTNAME}" = "" ] || [ "${IPADDR}" = "" ]; then     exit 0 fi  if [ "${ACTION}" != 'remove' ] && [ "${ACTION}" != 'add' ]; then     exit 0 fi  br_lan_ip=$(ip addr show dev br-lan | awk '/inet / {print $2}' | cut -d'/' -f1) br_lan_ptr_zone=$(dig -x ${br_lan_ip} SOA | awk '/AUTHORITY SECTION:/ {getline; print $1}') domain=$(uci get dhcp.@dnsmasq[0].domain) REVERSE_IP=$(echo "${IPADDR}" | awk -F. '{print $4"."$3"."$2"."$1}') TTL=900  case "${ACTION}" in         add)                 logger -p daemon.info -t hotplug.dhcp "Processing \"DHCPAC ${IPADDR} ${MACADDR} ${HOSTNAME}\": Adding RRs for \"${HOSTNAME}.${domain}.\" and \"${REVERSE_IP}.in-addr.arpa.\""                 nsupdate -k /etc/bind/keys.conf <<EOL | logger -p daemon.info -t hotplug.dhcp                 server 127.0.0.1 53                 zone ${domain}.                 update delete ${HOSTNAME}.${domain}.                 update add ${HOSTNAME}.${domain}. ${TTL} A ${IPADDR}                 show                 send                 zone ${br_lan_ptr_zone}                 update delete ${REVERSE_IP}.in-addr.arpa.                 update add ${REVERSE_IP}.in-addr.arpa. ${TTL} PTR ${HOSTNAME}.${domain}.                 show                 send EOL #                rndc sync         ;;         remove)                 logger -p daemon.info -t hotplug.dhcp "Processing \"DHCPRELEASE ${IPADDR} ${MACADDR}\": Removing RRs for \"${HOSTNAME}.${domain}.\" and \"${REVERSE_IP}.in-addr.arpa.\""                 nsupdate -k /etc/bind/keys.conf <<EOL | logger -p daemon.info -t hotplug.dhcp                 server 127.0.0.1 53                 zone ${domain}.                 update delete ${HOSTNAME}.${domain}.                 show                 send                 zone ${br_lan_ptr_zone}                 update delete ${REVERSE_IP}.in-addr.arpa.                 show                 send EOL #                rndc sync         ;; esac exit 0 

Перезапускаем dnsmasq :

$ /etc/init.d/dnsmasq restart

Проверяем выполнение скрипта, запуская следующие команды на хосте, подключенном к LAN интерфейсу роутера:

[rocky@test ~]$ hostname test  [rocky@test ~]$ sudo dhclient -v -H $(hostname) eth0 -r Listening on LPF/eth0/bc:24:11:2a:92:e3 Sending on   LPF/eth0/bc:24:11:2a:92:e3 Sending on   Socket/fallback DHCPRELEASE of 192.168.1.122 on eth0 to 192.168.1.1 port 67 (xid=0x6f71f069)  [rocky@test ~]$ sudo dhclient -v -H $(hostname) eth0  Listening on LPF/eth0/bc:24:11:2a:92:e3 Sending on   LPF/eth0/bc:24:11:2a:92:e3 Sending on   Socket/fallback DHCPDISCOVER on eth0 to 255.255.255.255 port 67 interval 7 (xid=0x46faa10b) DHCPOFFER of 192.168.1.122 from 192.168.1.1 DHCPREQUEST for 192.168.1.122 on eth0 to 255.255.255.255 port 67 (xid=0x46faa10b) DHCPACK of 192.168.1.122 from 192.168.1.1 (xid=0x46faa10b) bound to 192.168.1.122 -- renewal in 20468 seconds.

наблюдая при этом за системными логами в OpenWRT:

$ logread -f  ... dnsmasq-dhcp[1]: DHCPRELEASE(br-lan) 192.168.1.122 bc:24:11:2a:92:e3 hotplug.dhcp: Processing "DHCPRELEASE 192.168.1.122 bc:24:11:2a:92:e3": Adding RRs for "test.lan." and "122.1.168.192.in-addr.arpa." hotplug.dhcp: Outgoing update query: hotplug.dhcp: ;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id:      0 hotplug.dhcp: ;; flags:; ZONE: 0, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0 hotplug.dhcp: ;; ZONE SECTION: hotplug.dhcp: ;lan.                                IN      SOA hotplug.dhcp: ;; UPDATE SECTION: hotplug.dhcp: test.lan.            0       ANY     ANY named[1681]: client @0xffff95f64280 127.0.0.1#44123/key tsig-key: signer "tsig-key" approved named[1681]: client @0xffff95f64280 127.0.0.1#44123/key tsig-key: updating zone 'lan/IN': delete all rrsets from name 'test.lan' hotplug.dhcp: Outgoing update query: hotplug.dhcp: ;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id:      0 hotplug.dhcp: ;; flags:; ZONE: 0, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0 hotplug.dhcp: ;; ZONE SECTION: hotplug.dhcp: ;1.168.192.in-addr.arpa.             IN      SOA hotplug.dhcp: ;; UPDATE SECTION: hotplug.dhcp: 122.1.168.192.in-addr.arpa. 0        ANY     ANY named[1681]: client @0xffff95de81d0 127.0.0.1#58206/key tsig-key: signer "tsig-key" approved named[1681]: client @0xffff95de81d0 127.0.0.1#58206/key tsig-key: updating zone '1.168.192.in-addr.arpa/IN': delete all rrsets from name '122.1.168.192.in-addr.arpa' ... dnsmasq-dhcp[1]: DHCPDISCOVER(br-lan) 192.168.1.122 bc:24:11:2a:92:e3 dnsmasq-dhcp[1]: DHCPOFFER(br-lan) 192.168.1.122 bc:24:11:2a:92:e3 dnsmasq-dhcp[1]: DHCPREQUEST(br-lan) 192.168.1.122 bc:24:11:2a:92:e3 dnsmasq-dhcp[1]: DHCPACK(br-lan) 192.168.1.122 bc:24:11:2a:92:e3 test hotplug.dhcp: Processing "DHCPAC 192.168.1.122 bc:24:11:2a:92:e3 test": Adding RRs for "test.lan." and "122.1.168.192.in-addr.arpa." hotplug.dhcp: Outgoing update query: hotplug.dhcp: ;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id:      0 hotplug.dhcp: ;; flags:; ZONE: 0, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0 hotplug.dhcp: ;; ZONE SECTION: hotplug.dhcp: ;lan.                                IN      SOA hotplug.dhcp: ;; UPDATE SECTION: hotplug.dhcp: test.lan.            0       ANY     ANY hotplug.dhcp: test.lan.            900     IN      A       192.168.1.122 named[1681]: client @0xffff95f64280 127.0.0.1#46151/key tsig-key: signer "tsig-key" approved named[1681]: client @0xffff95f64280 127.0.0.1#46151/key tsig-key: updating zone 'lan/IN': delete all rrsets from name 'test.lan' named[1681]: client @0xffff95f64280 127.0.0.1#46151/key tsig-key: updating zone 'lan/IN': adding an RR at 'test.lan' A 192.168.1.122 hotplug.dhcp: Outgoing update query: hotplug.dhcp: ;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id:      0 hotplug.dhcp: ;; flags:; ZONE: 0, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0 hotplug.dhcp: ;; ZONE SECTION: hotplug.dhcp: ;1.168.192.in-addr.arpa.             IN      SOA hotplug.dhcp: ;; UPDATE SECTION: hotplug.dhcp: 122.1.168.192.in-addr.arpa. 0        ANY     ANY hotplug.dhcp: 122.1.168.192.in-addr.arpa. 900      IN      PTR     test.lan. named[1681]: client @0xffff95f64280 127.0.0.1#39719/key tsig-key: signer "tsig-key" approved named[1681]: client @0xffff95f64280 127.0.0.1#39719/key tsig-key: updating zone '1.168.192.in-addr.arpa/IN': delete all rrsets from name '122.1.168.192.in-addr.arpa' named[1681]: client @0xffff95f64280 127.0.0.1#39719/key tsig-key: updating zone '1.168.192.in-addr.arpa/IN': adding an RR at '122.1.168.192.in-addr.arpa' PTR test.lan. ...

Судя по логам, в DNS должны были появиться прямая и обратная записи для хоста test.lan с адресом 192.168.1.122. Проверяем:

[rocky@test ~]$ nslookup test.lan Server:         192.168.1.1 Address:        192.168.1.1#53 Name:   test.lan Address: 192.168.1.122

На этом и завершим настройку.

Вместо заключения

Окей, теперь у нас есть свой DNS сервер с поддержкой динамических зон и аутентификацией TSIG. И что дальше?

А дальше мы построим в OpenWRT свой Let’s Encrypt c TLS-ALPN-01 и DNS-01.


ссылка на оригинал статьи https://habr.com/ru/articles/826826/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *