Задача: наладить подстановку поддельных сопоставлений FQDN/IP серверу обслуживания рекурсивных DNS-запросов "Unbound", используя в качестве источника "списка блокировки" сервер сервиса фильтрации пользовательского web-трафика "Carbon Reductor".
В России считается возможным блокировать жителям доступ к информации по усмотрению непорядка. Специально для этого сочинили законы, выдали привилегию тотально поражать всех в правах федеральной организации "Роскомнадзор" и предписали провайдерам не пускать клиентов туда, куда нельзя. Наиболее полный запретительный список поддерживает "Роскомнадзор", но он не единственный - есть ещё запреты от министерства юстиции, а также блокировки по региональным и даже локальным судебным решениям. В итоге каждый провайдер вынужден эти списки регулярно (раз в пару-тройку часов) актуализировать, просматривать трафик своих клиентов и пресекать обращения к ресурсам-фигурантам (IP-адресам, FQDN и URL). Понятно, что это та ещё непродуктивная возня, да ещё и по меняющимся правилам игры, так что провайдеры по большей части предпочли бы купить готовое решение в виде сопровождаемого сервиса - один из которых как раз сервер "Carbon Reductor" от московской компании "Carbon Soft".
На самом деле трафик никто не фильтрует, конечно же - исходящие запросы к web-ресурсам просто дублируют (зеркалируют) серверу, который вылавливает среди них вхождения в запретительный список и предпринимает посильные меры к блокированию доступа. Распространённые методики блокирования:
1. Фальшивый ответ (DNS Local Zones) с подставным IP-адресом запрашиваемого ресурса.
2. Фальшивый ответ (DNS Spoofing) с подставным IP-адресом запрашиваемого ресурса.
3. Фальшивый ответ (TCP:SYN/RST/FIN) об отсутствии запрашиваемого ресурса.
4. Фальшивый маршрут (OSPF, BGR) с подставным шлюзом к запрашиваемому узлу.
2. Фальшивый ответ (DNS Spoofing) с подставным IP-адресом запрашиваемого ресурса.
3. Фальшивый ответ (TCP:SYN/RST/FIN) об отсутствии запрашиваемого ресурса.
4. Фальшивый маршрут (OSPF, BGR) с подставным шлюзом к запрашиваемому узлу.
Первый способ, интеграцию сервиса "Carbon Reductor" с обслуживающим клиентские запросы DNS-сервером, мы здесь и рассмотрим.
Принцип работы следующий:
1. Выгружаем запретительный список всех блокируемых FQDN, уже сформированный внутри "Carbon Reductor".
2. На основе списка генерируем набор подставных сопоставлений FQDN/IP в формате используемого DNS-сервера.
3. Тестируем файл описания подставных "локальных зон" и применяем её.
4. Повторяем процедуру по расписанию.
2. На основе списка генерируем набор подставных сопоставлений FQDN/IP в формате используемого DNS-сервера.
3. Тестируем файл описания подставных "локальных зон" и применяем её.
4. Повторяем процедуру по расписанию.
Запросы наших клиентов обрабатываются DNS-сервером "Unbound" (пример базовой настройки). Не смотря на то, что этот сервер предназначен для обслуживания рекурсивных запросов и кеширования таковых, у него всё же есть примитивный функционал для сопоставления доменных имён и IP-адресов, реализованная через параметры "local-zone" и "stub-zone" - нам нужно лишь подставить свои значения и они будут отданы клиенту вместо настоящих IP-адресов запрашиваемых сайтов:
server:
....
local-zone: "example.net." transparent
local-data: "www.example.net. 300 IN A 1.2.3.4"
local-data: "ftp.example.net. 300 IN A 1.2.3.5"
....
....
local-zone: "example.net." transparent
local-data: "www.example.net. 300 IN A 1.2.3.4"
local-data: "ftp.example.net. 300 IN A 1.2.3.5"
....
Конечно, программное решение этой задачи разработчики сервиса уже предложили (https://github.com/carbonsoft/named_fakezone_generator), но меня оно не устроило, хотя бы и потому, что не заработало. Пришлось изучить код и переписать его, в результате чего получился простенький Bash-скрипт, делающий всё, что нужно в рамках решения поставленной задачи.
Итак, прежде всего нужно забрать файл со списком доменных имён с сервера фильтрации.
Месторасположение перечня блокируемых доменов в "Carbon Reductor v7":
/usr/local/Reductor/lists/https.resolv
Месторасположение перечня блокируемых доменов в "Carbon Reductor v8":
/app/reductor/var/lib/reductor/lists/tmp/domains.all
Для того, чтобы просто забирать с сервера файлы, генерируем специально для этого предназначенный SSH-ключ и добавляем его в набор разрешённых на сервере "Carbon Reductor":
root@ns:# mkdir -p /etc/unbound/.ssh/
root@ns:# ssh-keygen -t rsa -b 4096 -f /etc/unbound/.ssh/id_rsa_root_ns.example.net -C "root@ns.example.net"
root@ns:# ssh-copy-id -i /etc/unbound/.ssh/id_rsa_root_ns.example.net root@reductor.example.net
root@ns:# chown -R root:root /etc/unbound/.ssh/
root@ns:# chmod -R go-rwx /etc/unbound/.ssh/
root@ns:# ssh-keygen -t rsa -b 4096 -f /etc/unbound/.ssh/id_rsa_root_ns.example.net -C "root@ns.example.net"
root@ns:# ssh-copy-id -i /etc/unbound/.ssh/id_rsa_root_ns.example.net root@reductor.example.net
root@ns:# chown -R root:root /etc/unbound/.ssh/
root@ns:# chmod -R go-rwx /etc/unbound/.ssh/
В дальнейшем в скриптах будем использовать команды SSH с явным указанием идентификационного файла "-i /etc/unbound/.ssh/id_rsa_root_ns.example.net".
Современные доменные имена могут содержать символы национальных алфавитов, закодированные в соответствии со спецификацией IDN (Internationalized Domain Names). Устанавливаем утилиту перекодировки:
# aptitude install idn
Пишем рабочий скрипт как таковой:
# mkdir -p /etc/unbound/scripts
# vi /etc/unbound/scripts/crb-reductor-generator.sh
# chmod ug+x /etc/unbound/scripts/crb-reductor-generator.sh
# vi /etc/unbound/scripts/crb-reductor-generator.sh
# chmod ug+x /etc/unbound/scripts/crb-reductor-generator.sh
#!/bin/bash
###
### Скрипт выгрузки перечня FQDN блокируемых "Carbon Reductor",
### генерирования конфигурации с набором "фейковых локальных зон"
### и применения такового в "Unbound".
###
# IP-адрес сервера "Carbon Reductor", предоставляющего перечня блокируемых FQDN
SRC_SRV="1.2.3.4"
# Имя исходного файла с перечнем блокируемых FQDN
SRC_LIST="/app/reductor/var/lib/reductor/lists/tmp/domains.all"
# IP-адрес web-сервера со страницей-заглушкой, показываемой вместо ресурса с блокируемым FQDN
PEG_IP="1.2.3.4"
# Имя финального файла набора подставных "локальных зон" DNS-сервера "Unbound"
TRG_CONF="/etc/unbound/conf.d/crb-reductor-fakezones.conf"
# Месторасположение журнала событий этапов работы скрипта
LOG="/var/log/crb-reductor-fakezones.log"
# Создаём и запоминаем временные файлы, используемые для последовательной обработки списков
TMP_F1=$(mktemp --tmpdir=/tmp); TMP_F2=$(mktemp --tmpdir=/tmp); TMP_F3=$(mktemp --tmpdir=/tmp)
# Пресекаем конкурентный запуск этого же скрипта
if [ `pgrep -f -c $(basename "$0")` -ne 1 ]
then
echo "Обнаружен уже запущенный экземпляр этого генератора. Процедура прервана."
exit 1
fi
# Загружаем перечень FQDN, предназначенных для блокировки
scp -q -i /etc/unbound/.ssh/id_rsa_root_ns.example.net root@${SRC_SRV}:${SRC_LIST} ${TMP_F1}
# Очищаем данные от пробельных символов, сортируем список, попутно исключая дубликаты, и конвертируем национальные символы IDN в "Punycode"
cat "${TMP_F1}" | sed 's/\.$//' | tr -d ' ' | sed -e 's/^www\.//' | idn | sort -u > "${TMP_F2}"
# Генерируем конфигурационный файл в формате Unbound, подставляя адрес сервера со страницей-заглушкой
echo "server:" >> "${TMP_F3}"
while read FQDN; do
echo " domain-insecure: \"${FQDN}\"" >> "${TMP_F3}"
echo " local-zone: \"${FQDN}\" redirect" >> "${TMP_F3}"
echo " local-data: \"${FQDN} 300 IN A ${PEG_IP}\"" >> "${TMP_F3}"
done < "${TMP_F2}"
# Если итоговый файл не пустой, то копируем его в рабочую область, с последующим применением
if [ -s "${TMP_F3}" ]
then
cat "${TMP_F3}" > "${TRG_CONF}"
unbound-checkconf -f "${TRG_CONF}" > /dev/null 2>&1
if [ "$?" -eq "0" ]
then
# Перезагружаем конфигурацию Unbound
/etc/init.d/unbound reload > /dev/null 2>&1
[ "$?" -eq "0" ] && echo "`date`: Ok" >> "${LOG}" || echo "`date`: Error reload!" >> "${LOG}"
else
# Ввиду обнаружения некорректной конфигурации удаляем таковую и прерываем процедуру
echo "Зафиксирован сбой работы генератора: некорректное содержимое итогового конфигурационного файла. Процедура прервана."
echo "`date`: Error generation!" >> "${LOG}"
rm -f "${TRG_CONF}"
exit 1
fi
else
echo "Зафиксирован сбой работы генератора: отсутствует содержимое итогового конфигурационного файла. Процедура прервана."
exit 1
fi
# Удаляем временные файлы
rm -f "${TMP_F1}" "${TMP_F2}" "${TMP_F3}"
exit 0
###
### Скрипт выгрузки перечня FQDN блокируемых "Carbon Reductor",
### генерирования конфигурации с набором "фейковых локальных зон"
### и применения такового в "Unbound".
###
# IP-адрес сервера "Carbon Reductor", предоставляющего перечня блокируемых FQDN
SRC_SRV="1.2.3.4"
# Имя исходного файла с перечнем блокируемых FQDN
SRC_LIST="/app/reductor/var/lib/reductor/lists/tmp/domains.all"
# IP-адрес web-сервера со страницей-заглушкой, показываемой вместо ресурса с блокируемым FQDN
PEG_IP="1.2.3.4"
# Имя финального файла набора подставных "локальных зон" DNS-сервера "Unbound"
TRG_CONF="/etc/unbound/conf.d/crb-reductor-fakezones.conf"
# Месторасположение журнала событий этапов работы скрипта
LOG="/var/log/crb-reductor-fakezones.log"
# Создаём и запоминаем временные файлы, используемые для последовательной обработки списков
TMP_F1=$(mktemp --tmpdir=/tmp); TMP_F2=$(mktemp --tmpdir=/tmp); TMP_F3=$(mktemp --tmpdir=/tmp)
# Пресекаем конкурентный запуск этого же скрипта
if [ `pgrep -f -c $(basename "$0")` -ne 1 ]
then
echo "Обнаружен уже запущенный экземпляр этого генератора. Процедура прервана."
exit 1
fi
# Загружаем перечень FQDN, предназначенных для блокировки
scp -q -i /etc/unbound/.ssh/id_rsa_root_ns.example.net root@${SRC_SRV}:${SRC_LIST} ${TMP_F1}
# Очищаем данные от пробельных символов, сортируем список, попутно исключая дубликаты, и конвертируем национальные символы IDN в "Punycode"
cat "${TMP_F1}" | sed 's/\.$//' | tr -d ' ' | sed -e 's/^www\.//' | idn | sort -u > "${TMP_F2}"
# Генерируем конфигурационный файл в формате Unbound, подставляя адрес сервера со страницей-заглушкой
echo "server:" >> "${TMP_F3}"
while read FQDN; do
echo " domain-insecure: \"${FQDN}\"" >> "${TMP_F3}"
echo " local-zone: \"${FQDN}\" redirect" >> "${TMP_F3}"
echo " local-data: \"${FQDN} 300 IN A ${PEG_IP}\"" >> "${TMP_F3}"
done < "${TMP_F2}"
# Если итоговый файл не пустой, то копируем его в рабочую область, с последующим применением
if [ -s "${TMP_F3}" ]
then
cat "${TMP_F3}" > "${TRG_CONF}"
unbound-checkconf -f "${TRG_CONF}" > /dev/null 2>&1
if [ "$?" -eq "0" ]
then
# Перезагружаем конфигурацию Unbound
/etc/init.d/unbound reload > /dev/null 2>&1
[ "$?" -eq "0" ] && echo "`date`: Ok" >> "${LOG}" || echo "`date`: Error reload!" >> "${LOG}"
else
# Ввиду обнаружения некорректной конфигурации удаляем таковую и прерываем процедуру
echo "Зафиксирован сбой работы генератора: некорректное содержимое итогового конфигурационного файла. Процедура прервана."
echo "`date`: Error generation!" >> "${LOG}"
rm -f "${TRG_CONF}"
exit 1
fi
else
echo "Зафиксирован сбой работы генератора: отсутствует содержимое итогового конфигурационного файла. Процедура прервана."
exit 1
fi
# Удаляем временные файлы
rm -f "${TMP_F1}" "${TMP_F2}" "${TMP_F3}"
exit 0
# chown -R root:root /etc/unbound/scripts/
# chmod -R go-rwx /etc/unbound/scripts/
# chmod -R go-rwx /etc/unbound/scripts/
На практике 40000 (сорок тысяч) FQDN, спускаемых ныне "Роскомнадзор"-ом, обрабатываются скриптом менее чем за полминуты. Самим DNS-сервером "Unbound" конфигурация принимается в работу вообще мгновенно.
Ясное дело, что списки нужно регулярно обновлять. Если DNS-серверов несколько, то удобно сделать непересекающееся расписание, чтобы минимизировать возможность простоя сервисов (для своих двух серверов я делаю это каждые 15 и 23 минуты):
# vi /etc/crontab
....
# Run the "Carbon Reductor" fake-zone generator script
*/23 * * * * root /etc/unbound/scripts/crb-reductor-generator.sh &
# Run the "Carbon Reductor" fake-zone generator script
*/23 * * * * root /etc/unbound/scripts/crb-reductor-generator.sh &
В журналы событий пишется немного, но для порядка всё же включим для них ротацию:
# vim /etc/logrotate.d/crb-reductor-generator
/var/log/crb-reductor-fakezones.log {
monthly
rotate 3
size 10M
missingok
notifempty
compress
delaycompress
copytruncate
su root root
}
monthly
rotate 3
size 10M
missingok
notifempty
compress
delaycompress
copytruncate
su root root
}
Вот так мы делаем мир чище.