Моя домашняя лаборатория подключена к интернету через маршрутизатор с прошивкой 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, который стоит первым в списке.
План:
-
Установить BIND;
-
Отключить DNS-forwarding в
dnsmasq
, оставив его обслуживать DHCP запросы; -
Настроить базовые вещи;
-
Включить и протестировать динамическое обновление зон;
-
Автоматизировать обновление в 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/
Добавить комментарий