Авг 06 2015

dyndns_dyndns_2[1]

В связи с тем, что dyndns.org испортился и начал хотеть денег за свои услуги, пришлось таки озаботиться этим вопросом, изучить матчасть..

В общем-то, динамические апдейты в DNS реализованы уже много лет назад, но как обычно, есть некоторые тонкости. Под катом краткое руководство для тех, кому интересно.
Подразумевается, что у вас уже есть собственный домен, собственный первичный DNS-сервер для этого домена (BIND), и клиент под юниксом/линуксом. Вероятно, на роутере, работающем под openwrt/dd-wrt, такой метод тоже сработает с небольшими поправками, но я лично не пробовал.

Итак..

1. На клиенте генерируем секретный ключ (на самом деле, генерировать его можно где угодно, но поскольку он нужен на клиенте, то удобнее прямо там):
dnssec-keygen -a hmac-sha256 -b 128 -n HOST -r /dev/urandom dyndns

Тонкости:
-a: алгоритм можно выбирать из HMAC-MD5, HMAC-SHA1, HMAC-SHA224, HMAC-SHA256, HMAC-SHA384, HMAC-SHA512, но MD5 уже считается небезопасным, SHA1 тоже слабоват, поэтому ориентируемся на SHA256 и выше.

-b: длина ключа в битах, может быть от 1 до 512, но не более длины контрольной суммы, поэтому, например, для SHA256 ключ не может быть длиннее 256 бит.

-n: должно быть HOST, другие значения предназначены для других целей.

-r: устройство для получения случайных чисел. В принципе, его можно и не указывать, но с ним генерация ключей работает гораздо быстрее.

последний параметр (dyndns): название генерируемого ключа. Его можно выбирать достаточно произвольно.

2. В результате этой операции получаем два файла:
Knnnn.+aaa+iiiii.key и Knnnn.+aaa+iiiii.private где nnnn – название ключа (dyndns), aaa – номер алгоритма (например, hmac-sha256 – это 163, а hmac-sha512 – 165), iiiii – идентификатор (fingerprint) ключа.

Cкладываем оба файла куда-нибудь в /usr/local/etc/dyndns/, и на всякий случай даём доступ к ним только руту.

Внтури обоих файлов есть строка с ключом, закодированным в base64. Выглядит этот ключ примерно так: wVkp4d562LIR/h1JUm4fiulmkoijaF7lZTVp3YeEsVk=

3. Переходим к серверу. Предположим, у вас есть домен example.com. В принципе, можно настроить динамические изменения прямо в нём, но из соображений паранойи безопасности лучше завести отдельный поддомен и разрешить изменения только в нём.

Итак, на первичном DNS-сервере зоны example.com (будем считать, что его зовут ns1.example.com, а адрес у него 10.2.3.4) в файле зоны example.com заводим отдельный NS для поддомена:

    dyn IN NS ns1
; или так:
    dyn IN NS ns1.example.com. ; не забываем про точку в конце!

Можно также указать дополнительные NS’ы для поддомена. Они совершенно не обязательно должны совпадать с NS’ами самого домена example.com. В принципе, и первичный NS тоже вполне может быть на другом IP, тогда надо настроить записи в example.com точно так же, как это делается для обычного делегирования поддомена. Я этот вариант не рассматриваю, предполагая, что всё настраивается на одном и том же сервере, но вообще это не обязательно.

4. В конфиг сервера заносим ранее сгенерированный ключ:

key dyndns. {
    algorithm hmac-sha256;
    secret "wVkp4d562LIR/h1JUm4fiulmkoijaF7lZTVp3YeEsVk=";
};

Название ключа dyndns. не обязано совпадать c тем, что было указано при генерации, но лучше использовать то же самое, чтоб было понятно, что это за ключ.

И создаём запись про поддомен:

zone "dyn.example.com" {
  type master;
  file "dyn.example.com";
  allow-update { key dyndns. ; };
};

Название файла не обязано совпадать с именем зоны, но так яснее.

Тонкость: в процессе динамического изменения зоны BIND создаст журнал (файл с тем же именем и суффиксом .jnl) в той же директории, где лежит файл зоны. И время от времени будет переносить изменения из журнала в файл зоны. Соответственно, сам файл зоны и директория, где он лежит, должны быть писабельными для пользователя (или хотя бы группы), от которых запущен BIND (обычно bind:bind). Поэтому лучше положить файл зоны в стандартную директорию для временных файлов BIND’а (в линуксе обычно /var/cache/bind).

Хинт: если маршрут к файлу в директиве file не абсолютный, он отсчитывается от директории, указанной в конфиге директивой directory (которая по умолчанию и указывает на этот самый /var/cache/bind).
Либо можно указать в file абсолютный маршрут.

И не забыть проверить права на файл и директорию, чтоб BIND мог там читать и писать.

5. Создаём в выбранной директории начальный файл для зоны dyn.example.com примерно такого содержания:

$TTL 900	; TTL по умолчанию
@		IN SOA ns1.example.com. dnsmaster.example.com. (
				42         ; serial
				900        ; refresh
				900        ; retry
				691200     ; expire
				300        ; negative TTL
				)
  86400 IN 	NS ns1.example.com. ; ещё раз тот же NS

Не забываем поменять ему владельца на bind, или от кого там у вас BIND запускается..

Тонкости:
– Поскольку вся эта конструкция собирается для адресов, которые будут относительно часто меняться, TTL по умолчанию должен быть достаточно маленьким, чтобы предотвратить длительное кэширование записей в кэширующих NS’ах после того, как адреса уже поменялись в авторитетных.
– серийник будет автоматически увеличиваться BIND’ом при каждом изменении зоны, поэтому классический вариант YYYYMMDDNN не имеет смысла. Лучше использовать какое-нибудь маленькое число, хотя бы и 1.
– refresh и retry должны быть достаточно маленькими, сравнимыми с TTL, чтобы вторичные авторитетные серверы (если они предусмотрены) побыстрее синхронизировались с первичным, если посылаемые туда уведомления об изменении зоны случайно потеряются.
– expire — по желанию.
– negative TTL тоже должен быть маленьким, лучше даже меньше TTL по умолчанию, чтоб предотвратить длительное кэширование отрицательных ответов.
– а вот конкретно для записей NS можно явно указать большой TTL, они обычно редко меняются. Но это тоже по желанию.

6. Перезапускаем BIND, убеждаемся, что он поднялся и нормально отдаёт записи SOA и NS для поддомена (других там пока нет).

7. Возвращаемся на клиентскую машину и пробуем оттуда обновить зону:

# nsupdate -k /usr/local/etc/dyndns/Knnnn.+aaa+iiiii.private
> server 10.2.3.4
> zone dyn.example.com
> update add foo.dyn.example.com 900 A 127.1.2.3
> send

Затем запрашиваем у авторитетного сервера адрес хоста foo.dyn.example.com, должно получиться 127.1.2.3.
Если не получилось, добавляем перед send команды debug yes и show, читаем логи, и т.п.

8. Автоматизируем процесс узнавания клиентского IP и отправки его на сервер примерно таким скриптиком /usr/local/sbin/dyndns_update.sh:

#!/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin

KEYFILE=/usr/local/etc/dyndns/Knnnn.+aaa+iiiii.private
SERVER=1.2.3.4
ZONE=dyn.example.com
FQDN=foo.$ZONE
TTL=900
IFACE=eth0
LOG_FACILITY=syslog
LOG_TAG=dyndns

IP=`ip addr sh $IFACE 2>/dev/null | sed -n 's|^\s\+inet \([0-9.]\+\)/.*|\1|p' | grep -v 192.168.100.` 
# последний grep не обязателен
# это у меня на eth0 одновременно и DHCP и статический адрес 192.168.100
# для общения с модемом. Вот его и надо исключить
if [ "X$IP" == "X" ]; then
  logger -t $LOG_TAG -p ${LOG_FACILITY}.err "Failed to detect external IP on $IFACE"
  exit 1
fi

OLDIP=`host $FQDN $SERVER | sed -n 's|^.* has address \([0-9.]\+\).*|\1|p'`

if [ "X$OLDIP" == "X$IP" ]; then
  logger -t $LOG_TAG -p ${LOG_FACILITY}.info "$FQDN is $OLDIP, no need to update"
  exit 0
fi

# echo -e "server $SERVER\nzone $ZONE\nupdate delete $FQDN\nupdate add $FQDN $TTL A $IP\nsend"
MSG=`echo -e "server $SERVER\nzone $ZONE\nupdate delete $FQDN\nupdate add $FQDN $TTL A $IP\nsend" | nsupdate -k $KEYFILE 2>&1`
R=$?
if [ $R -eq 0 -a "X$MSG" == "X" ]; then
  logger -t $LOG_TAG -p ${LOG_FACILITY}.info "Successfully updated $FQDN with $IP"
else
  logger -t $LOG_TAG -p ${LOG_FACILITY}.err "$FQDN DNS update failed: $MSG"
  exit $R
fi

9. И засовываем его в крон, скажем, раз в 15 минут. Можно ещё приделать непосредственно к DHCP-клиенту в /etc/dhcp/dhclient-exit-hooks.d/, тогда он после обновления адреса будет срабатывать оперативнее

Оригинал статьи

Автор: Johnny Тэги: , , , , ,

Блог саратовского админа

При использовании материалов сайта на других ресурсах прошу оставлять прямую ссылку на оригинал статьи.