Как хоббиту шифровать DNS запросы. Собственный DNS-over-HTTPS сервер
Всё, что написано ниже, написано только для маленьких хоббитов волею случая проживающих в Мордоре и находящихся под надзором злобного Саурона. Это просто сказка, любые совпадения с реальностью случайны.
Представим себе совершенно гипотетическую ситуацию. Какому-то хоббиту не повезло оказаться на ПМЖ в Мордоре — стране, где каждый его шаг в Интернете хочет видеть и контролировать злодей Саурон. В нашей-то стране такого конечно нет. Только в волшебном Мордоре. Хоббит в целом, наверное, не дурак и давно уже сделал собственный VPN-сервер, как описано в этих статьях. Ну или хоббит уже преисполнился, и сделал, как написано здесь или здесь. Однако не всегда есть возможность/желание установить VPN-клиент. Например хоббит за рабочим компьютером или это компьютер бабушки хоббита, которая не умеет запускать VPN-клиенты. И до сих пор хоббита выручало то, что все сайты уже давно используют защищённый протокол HTTPS. Да вот беда: Саурон узнал про существование DNS. Это такая служба Domain Name System, которая позволяет хоббитопонятный интернет-адрес превратить в IP-адрес, понятный для интернет-браузеров. Без этой службы интернет-браузеры не работают. А к серверам службы DNS большинство компьютеров обычно ходят по не шифрованному DNS протоколу.
Саурон приказал своему орккомнадзору фильтровать эти запросы, записывать их и, в некоторых случаях, подменять. То есть Саурон не видит, что хоббит делал на сайте, ведь там протокол HTTPS, но вот какие сайты хоббит запрашивал — он видит. А если хоббит захотел открыть эльфийский сайт, то можно в DNS запросе подменить ответ и хоббит вместо эльфийского сайта увидит злобную картинку от орккомнадзора. Или просто обвинить хоббита в сочувствии и поддержке эльфов, признать его эльфоагентом. Тогда мудрые эльфы придумали протокол DNS-over-HTTPS (сокращенно DOH). Это когда DNS-сервер прикидывается обычным веб-сервером, работающим по протоколу HTTPS, и интернет-браузер хоббита запрашивает у этого сервера IP-адреса сайтов по зашифрованному протоколу.
Саурон сначала приуныл, но потом до него дошло, что DNS сервера эльфов, поддерживающие протокол DOH, это те же сервера, что и работающие по обычному DNS протоколу. Да и серверов таких совсем не много. Тогда Саурон приказал своим оркам блокировать любые запросы хоббитов к известным серверам, поддерживающим DOH. Это заставит хоббита пользоваться мордорскими DNS серверами и можно и дальше фильтровать/записывать/подменять его запросы. Что ж делать хоббиту? Ну, например, использовать личный VPN, как написано в начале статьи. Но у хоббита есть потребность обойтись без VPN. Хоббит хитрый и он подумал: обращения к серверу DOH всё равно выглядят как обычный HTTPS, выдаёт их в основном только то, что они идут к известным Саурону DNS-серверам. Что если сделать свой web-сервер, такой же, как тысячи других, работающий по HTTPS, но при этом способный выступать посредником между хоббитом и публичным DNS-сервером. Т.е. сервер хоббита будет принимать зашифрованные DNS запросы, пересылать их публичному эльфийскому DNS-серверу, принимать ответы, зашифровывать и отправлять обратно хоббиту. Получится реверс-прокси сервер для DOH. Расположить его конечно нужно там, куда не дотянуться беспредельничающие орки.
Для Саурона это будет выглядеть так, как будто хоббит ходит на очередной web-сайт. Конечно, если он начнёт вглядываться очень пристально в конкретного хоббита, то через некоторое время поймет что к чему. Но хобиттов в Мордоре миллионы и Саурон предпочитает бить по площадям. Кроме того DOH реверс-прокси лишит Саурона списка эльфийских сайтов, которые запрашивал хоббит. А это может помочь хоббиту в его не простой жизни.
Всё, что описано ниже, выглядит сложным. Но это не сложно. С этим справится любой хоббит, даже если у него лапки. Нужно просто верить в себя и следовать плану.
Ниже будут конфиги. Их форматирование будет безбожно убито. Они всё равно останутся рабочими, но будут некрасивенькими. Конфиги в нормальном виде можно найти в оригинале статьи.
Для начала хоббиту нужно заблаговременно озаботится арендой доменного имени. Как это сделать примерно описано в начале этой статьи. Предположим хоббит арендовал доменное имя theshire.ru. В принципе он мог его и использовать для реверс-прокси. Но можно добавить имя третьего уровня. Тогда сам домен можно будет потом использовать для чего-то ещё. Так больше гибкости. Хоббит решил сделать имя третьего уровня ns1.theshire.ru. У него получились такие записи домена:
В этих записях вписан IP арендованного хоббитом VPS. Думаю не нужно напоминать, что виртуальный сервер должен находится за пределами Мордора. Хоббит, используя эту статью, арендовал предельно дешёвую виртуалку с такими параметрами:
Как видно, на сервере установлена операционная система CentOS 7. Далее нужно настроить защищённый вход на сервер, как описано здесь, и файрволл сервера, как описано здесь. Только при настройке файрволла нужно в ipt-set записать другое содержимое:
#!/bin/sh
IF_EXT="venet0"
IPT="/sbin/iptables"
IPT6="/sbin/ip6tables"
# flush
$IPT --flush
$IPT -t nat --flush
$IPT -t mangle --flush
$IPT -X
$IPT6 --flush
# loopback
$IPT -A INPUT -i lo -j ACCEPT
$IPT -A OUTPUT -o lo -j ACCEPT
# default
$IPT -P INPUT DROP
$IPT -P OUTPUT DROP
$IPT -P FORWARD DROP
$IPT6 -P INPUT DROP
$IPT6 -P OUTPUT DROP
$IPT6 -P FORWARD DROP
# allow forwarding
echo 1 > /proc/sys/net/ipv4/ip_forward
# NAT
# #########################################
# SNAT - local users to out internet
$IPT -t nat -A POSTROUTING -o $IF_EXT -j MASQUERADE
# INPUT chain
# #########################################
$IPT -A INPUT -p tcp ! --syn -m state --state NEW -j DROP
$IPT -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# ssh
$IPT -A INPUT -i $IF_EXT -p tcp --dport 22 -j ACCEPT
# nginx
$IPT -A INPUT -i $IF_EXT -p udp --dport 53 -j ACCEPT
$IPT -A INPUT -i $IF_EXT -p tcp --dport 80 -j ACCEPT
$IPT -A INPUT -i $IF_EXT -p tcp --dport 443 -j ACCEPT
$IPT -A INPUT -i $IF_EXT -p tcp --dport 853 -j ACCEPT
# OUTPUT chain
# #########################################
$IPT -A OUTPUT -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
Не забудьте про правильное имя внешнего интерфейса в переменной IF_EXT (вторая строчка), как описано в этой статье.
Обновим сервер и установим необходимые утилиты:
yum -y update
yum -y install net-tools wget
Проверим, не сидит ли какая-то дефолтная http служба на нужном нам 80-ом порту:
netstat -tulnp | grep 80
Сидит, собака
Убиваем её
systemctl stop httpd
systemctl disable httpd
yum -y remove httpd
Теперь сервер готов. Реверс-прокси сделаем конечно же на nginx. Ранее уже была статья про использование его в такой роли. Ретранслировать DOH запросы не на столько простая задача, как кажется. Дабы не изобретать велосипед, используем уже готовый проект NGINX-DNS. Благодаря скриптам этого проекта мы не только сделает DOH между хоббитом и его реверс прокси, но и обмен по протоколу DOT между реверс-прокси и публичным DNS. Т.е даже хостинг VPS не сможет заглянуть в трафик хоббита! Кстати, для этого проекта подойдёт не каждый nginx. Тот, который в epel-release, НЕ подойдёт. Поэтому epel-release ни в коем случае не устанавливаем, он всё поломает. Накатим репозиторий с правильным nginx:
cd /tmp
wget http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release...
rpm -ivh nginx-release-centos-7-0.el7.ngx.noarch.rpm
Проверим, что операционная система теперь знает, где брать nginx и модули к нему:
yum list | grep nginx
Отлично. Устанавливаем nginx и нужный модуль:
yum -y install nginx.x86_64 nginx-module-njs.x86_64
Еще раз проверьте, что файрволл настроен и открытые порты выглядят так:
iptables -L -n
Теперь любым удобным способом откроем для редактирования файл /etc/nginx/conf.d/default.conf (хоббитам с лапаками рекомендую использовать WinSCP и Notepad++). В этом файле сразу после строчки
server {
нужно найти строчку
server_name localhost;
и вместо localhost вписать своё доменное имя. У хоббита это так (не удалите случайно точку с запятой!):
server_name ns1.theshire.ru;
Файл можно сохранить и закрыть. Запускаем nginx и смотрим, что всё в порядке:
systemctl start nginx
systemctl status nginx
Дальше в браузере на компьютере переходим по доменному адресу сервера. У хоббита это http://ns1.theshire.ru
Оно живое! Но только по протоколу HTTP, а нам надо по HTTPS. Для этого нужно сделать SSL сертификаты от letsencryp. Сертификаты весь прогрессивный эльфийский мир делает с помощью Certbot. Поскольку epel-release мы не используем, то ставить certbot хоббиту придётся через жопу Pip.
Накатим питона с либами:
yum -y install python3 augeas-libs
и подготовим виртуальную среду:
python3 -m venv /opt/certbot/
/opt/certbot/bin/pip install --upgrade pip
Отлично. Можно и накатить сам certbot:
/opt/certbot/bin/pip install certbot certbot-nginx
ln -s /opt/certbot/bin/certbot /usr/bin/certbot
Теперь сделаем сертификаты:
certbot --nginx
Соглашаемся с условиями использования, указываем почту (желательно настоящую, туда, если что, будут спамить о проблемах с сертификатами) и отказываемся от спама. На запрос доменного имени нужно выбрать своё. У хоббита получилось так:
Если на этом этапе не удаётся создать сертификаты, появляются ошибки, то:
-- ещё раз проверить настройки файрволла и доступность страницы приветствия по (у хоббита это http://ns1.theshire.ru);
-- возможно домен арендован и/или его записи внесены менее 24 часов назад и не все DNS серверы успели их синхронизировать. Тогда придётся подождать.
Современный certbot настолько преисполненный, что он сам вписал настройки HTTPS в nginx и сам применил их. Хоббитам остаётся только открыть свой доменный адрес по HTTPS:
SSL сертификаты выдаются всего на три месяца. Дабы они не прокисли, добавляем в планировщик задачу по обновлению:
echo "0 0,12 * * * root /opt/certbot/bin/python -c 'import random; import time; time.sleep(random.random() * 3600)' && certbot renew -q" | tee -a /var/spool/cron/root > /dev/null
и проверяем результат:
crontab -l
Теперь превратим наш безобидный nginx в боевой DOH сервер. Для начала остановим его:
systemctl stop nginx
Теперь из проекта NGINX-DNS с гитхаба скопируем нужные нам файлы и засунем в папку nginx. При этом старые конфиги nginx удалять не будем, а просто добавим к имени приставку .old:
cd /tmp
wget https://github.com/TuxInvader/nginx-dns/archive/refs/heads/m...
unzip master.zip
cp -r nginx-dns-master/njs.d /etc/nginx/
cd /etc/nginx/
mv nginx.conf nginx.conf.old
mv ./conf.d/default.conf default.conf.old
В папке /etc/nginx/ создадим файл nginx.conf с вот таким содержимым:
user nginx;
worker_processes auto;
load_module modules/ngx_stream_js_module.so;
error_log /var/log/nginx/error.log error;
#error_log off;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# logging directives
log_format doh '$remote_addr - $remote_user [$time_local] "$request" '
'[ $msec, $request_time, $upstream_response_time $pipe ] '
'$status $body_bytes_sent "$http_x_forwarded_for" '
'$upstream_http_x_dns_question $upstream_http_x_dns_type '
'$upstream_http_x_dns_result '
'$upstream_http_x_dns_ttl $upstream_http_x_dns_answers '
'$upstream_cache_status';
access_log /var/log/nginx/doh-access.log doh;
#access_log off;
resolver 1.1.1.1 valid=10s;
# This upstream connects to a local Stream service which converts HTTP -> DNS
upstream dohloop {
zone dohloop 64k;
server 127.0.0.1:8053;
keepalive_timeout 60s;
keepalive_requests 100;
keepalive 10;
}
# Proxy Cache storage - so we can cache the DoH response from the upstream
proxy_cache_path /var/cache/nginx/doh_cache levels=1:2 keys_zone=doh_cache:10m;
# The DoH server block
server {
server_name ns1.theshire.ru;
root /usr/share/nginx/html;
# Listen on standard HTTPS port, and accept HTTP2, with SSL termination
listen 443 ssl http2 default_server;
ssl_certificate /etc/letsencrypt/live/ns1.theshire.ru/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/ns1.theshire.ru/privkey.pem; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
ssl_session_cache shared:ssl_cache:10m;
ssl_session_timeout 10m;
# DoH may use GET or POST requests, Cache both
proxy_cache_methods GET POST;
# Return 404 to all responses, except for those using our published DoH URI
location / {
return 404 "404 Not Found\n";
}
# This is our published DoH URI
location /dns-query {
# Proxy HTTP/1.1, clear the connection header to enable Keep-Alive
proxy_http_version 1.1;
proxy_set_header Connection "";
# Enable Cache, and set the cache_key to include the request_body
proxy_cache doh_cache;
proxy_cache_key $scheme$proxy_host$uri$is_args$args$request_body;
# proxy pass to the dohloop upstream
proxy_pass http://dohloop;
}
}
server {
if ($host = ns1.theshire.ru) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
listen [::]:80;
server_name ns1.theshire.ru;
return 404; # managed by Certbot
}
}
# DNS Stream Services
stream {
# DNS logging
log_format dns '$remote_addr [$time_local] $protocol "$dns_qname"';
access_log /var/log/nginx/dns-access.log dns;
#access_log off;
# Include the NJS module
js_include /etc/nginx/njs.d/nginx_stream.js;
# The $dns_qname variable can be populated by preread calls, and can be used for DNS routing
js_set $dns_qname dns_get_qname;
#DNS upstream pool.
upstream dns {
zone dns 64k;
server 1.1.1.1:53;
}
# DNS over TLS upstream pool
upstream dot {
zone dot 64k;
server 1.1.1.1:853;
}
# DNS(TCP) and DNS over TLS (DoT) Server
# Upstream can be either DNS(TCP) or DoT. If upstream is DNS, proxy_ssl should be off.
server {
# DNS TCP
listen 53;
# DNS DoT
listen 853 ssl;
ssl_certificate /etc/letsencrypt/live/ns1.theshire.ru/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/ns1.theshire.ru/privkey.pem; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
# This is used to pull out question for logging
js_preread dns_preread_dns_request;
# Enable SSL re-encryption for DoT connection upstream
proxy_ssl on;
proxy_pass dot;
}
# DNS over HTTPS (gateway) Service
# Upstream can be either DNS(TCP) or DoT. If upstream is DNS, proxy_ssl should be off.
server {
listen 127.0.0.1:8053;
js_filter dns_filter_doh_request;
proxy_ssl on;
proxy_pass dot;
}
# DNS(UDP) Server
# DNS UDP proxy onto DNS UDP
server {
listen 53 udp;
proxy_responses 1;
js_preread dns_preread_dns_request;
proxy_pass dns;
}
}
Указанный выше конфиг сделан для нашего хоббита и его доменного имени ns1.theshire.ru. Чтобы переделать файл под другого хоббита и другое доменное имя:
Найти строчки, начинающиеся на «server_name», и заменить в них доменное имя ns1.theshire.ru на своё.
Найти строчки, начинающиеся на «ssl_certificate», и заменить в них путь на сертификат своим. Свой путь можно найти в аналогичных строчках файла /etc/nginx/default.conf.old
Сохраните и закройте файл. Выполните тест корректности конфига командой:
nginx -t
У хоббита хоть и лапки, но прямые. У него получилось с первого раза:
Если есть ошибки, то будут написаны номера строк с ними. Смотрим в эти строки и исправляем. Чаще всего хоббиты нечаяно удаляют «;» в конце строк.
Теперь nginx можно запустить и добавить в автозагрузку.
systemctl start nginx
systemctl enable nginx
Собственно всё. Хоббит сделал сервер который:
1. Может работать как обычный DNS сервер по порту 53/UDP. Если добавить его IP в настройки DNS, например, сетевой карты компьютера, то всё взлетит. Обратите внимание, что это не шифрованный обмен. Обычный DNS. Если этот функционал не нужен, то просто закомментируйте/удалите строку с портом 53 в файле /root/ipt-set и перезагрузите сервер.
2. Принимает запросы по протоколу DOH (по порту 443), расшифровывает их, разбирает, перенаправляет запросы на публичный DNS сервер по зашифрованному протоколу DOT, полученные ответы зашифровывает и отправляет хоббиту.
3. Принимает запросы по протоколу DOТ, расшифровывает их, разбирает, перенаправляет запросы на публичный DNS сервер по, опять же, протоколу DOT, полученные ответы зашифровывает и отправляет хоббиту. Протокол DOT на сегодня не особо распространён в браузерах. Если этот функционал не нужен, то просто закомментируйте/удалите строку с портом 853 в файле /root/ipt-set и перезагрузите сервер.
Хоббиту осталось настроить браузер.
В Mozilla Firefox нужно открыть Настройки -> Параметры сети -> Настроить, в самом низу поставить галочку «Включить DNS через HTTPS» и вписать свой DNS адрес. У нашего хоббита он такой:
Другим хоббитам вместо ns1.theshire.ru нужно вписать свой домен.
В Google Chrome открыть Настройки -> Конфиденциальность и безопасность -> Безопасность -> Использовать безопасный DNS-сервер вписать сервер также, как это указано выше для Firefox. У нашего хоббита это получилось так:
Для других браузеров хоббиту предлагается напрячь лапки и погуглить.
В качестве домашнего задания также предлагается нагуглить, как настроить Windows 10 для использования DOH. Необязательный пункт. Браузеры не зависят от этого и используют настройку, показанную выше.
Дальше хоббит пользуется браузером как обычно. Сайты должны без проблем открываться. Убедится, что сервер работает как надо, можно сходив в файл лога /var/log/nginx/doh-access.log. Там будут логгированы все DNS запросы хоббита по протоколу DOH.
Посмотрев логи умненький хоббит должен подумать, а нужны ли ему эти следы? Если нет, то он в файле /etc/nginx/nginx.conf:
-- находит строчку, начинающуюся на «error_log» и ставит в ее начале #. А вот из строчки «#error_log off;» наоборот символ # убирает.
-- находит строчки, начинающиеся на «access_log» и ставит в их начале #. Во всех строчках «#access_log off;» символ # убирает.
Дальше нужно сохранить файл. Остановить сервер командой:
systemctl stop nginx
удалить все файлы из папки /var/log/nginx/ и запустить сервер обратно:
systemctl start nginx
Теперь никаких следов.
За сим эта часть саги про хоббита заканчивается. Обсуждение в https://t.me/SecFAll_chat
Утаскивать в сообщества запрещаю.