Собственный DNS-over-HTTPS сервер. Как хоббиту шифровать DNS запросы

Все настройки выполнены в VPS от hostsailor.com. У других хостеров наверняка также, но могут быть нюансы.

Всё, что написано ниже, написано только для маленьких хоббитов волею случая проживающих в Мордоре и находящихся под надзором злобного Саурона. Это просто сказка, любые совпадения с реальностью случайны.

Представим себе совершенно гипотетическую ситуацию. Какому-то хоббиту не повезло оказаться на ПМЖ в Мордоре — стране, где каждый его шаг в Интернете хочет видеть и контролировать злодей Саурон. В нашей-то стране такого конечно нет. Только в волшебном Мордоре. Хоббит в целом, наверное, не дурак и давно уже сделал собственный 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


# 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-centos-7-0.el7.ngx.noarch.rpm
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/master.zip
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. Чтобы переделать файл под другого хоббита и другое доменное имя:

  1. Найти строчки, начинающиеся на «server_name», и заменить в них доменное имя ns1.theshire.ru на своё.
  2. Найти строчки, начинающиеся на «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 адрес. У нашего хоббита он такой:

https://ns1.theshire.ru/dns-query

Другим хоббитам вместо 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

 

 

 

 

 

 
 

 

 

Вы можите оставить комментарий, или поставить трэкбек со своего сайта.

Написать комментарий

XHTML: Вы можете использовать эти теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>