LINUX.ORG.RU

Настраиваем DNS от PowerDNS dnsdist, скрипт автоустановки

 , dnsdist


0

2

Внезапно выяснил, что на ЛОР нет тега dnsdist, решил сразу это исправить, а заодно написать скрипт для автоустановки и ввода в работу данного DNS.

Скрипт ставит ванильный dnsdist из официального PowerDNS-репозитория (где это возможно), настраивает его как локальный резолвер по умолчанию, добавляет 8 DoH-провайдеров + DoH Яндекс, включает строгую проверку TLS-сертификатов, и принимает DNS-запросы только с localhost.


Поддержка официальных пакетов PowerDNS для dnsdist есть для Debian/Ubuntu и Enterprise Linux (RHEL/Alma/Rocky/CentOS Stream). Для openSUSE/Fedora/Arch скрипт ставит системный пакет из дистрибутива (это уже не “ваниль” от PowerDNS, но работает).

DoH endpoints: Cloudflare, Google, Quad9, AdGuard, CleanBrowsing (security filter), OpenDNS (Umbrella/FamilyShield), DNS.SB, Mullvad.

Что такое dnsdist

dnsdist — это балансировщик/прокси с функциями защиты от DoS/abuse и гибкими правилами маршрутизации. Он принимает DNS-запросы, выбирает лучший downstream-сервер, отправляет туда запрос и возвращает ответ клиенту. Конфигурация динамически меняется через консоль; метрики доступны через HTTP-API/Prometheus/Carbon. По умолчанию политика выбора — leastOutstanding (сервер с наименьшим числом «висящих» запросов).

Ключевое отличие от «кеширующих DNS» (Unbound, PowerDNS Recursor, Knot Resolver, BIND в режиме рекурсии): dnsdist сам не выполняет рекурсию и не «знает» зону — он лишь распределяет трафик и может опционально кэшировать пакетные ответы, стоя «перед» рекурсорами/авторитативными серверами. Данный скрипт задействует возможности кеширования dnsdist.

При этом dnsdist умеет терминировать зашифрованные протоколы на входе: DoT, DoH, DoQ/DoH3, а также говорить DoH к бэкендам (исходящие). Это удобно для приватности и как точка политики/фильтрации перед вашими рекурсорами.

Где уместен dnsdist

  • ISP/Enterprise: фронтенд перед кластером рекурсоров (Unbound/PDNS Recursor), балансировка, rate-limit, фильтрация, терминация DoT/DoH/DoQ.
  • Авторитативный DNS-сервер/DDoS: «щит» перед ns-серверами, гибкие правила дропа/замедления по источникам/именам/типам.
  • Наблюдаемость: метрики, веб-панель (в коммерческой версии), удалённая консоль (не настроена в этом скрипте, при попытке вызова выдает ответ об отсутствии конфигурации, данной конфигурацией допускаются только локальные вызовы).
  • В моем случае я поднял его на VPS, полёт нормальный, скрипт как раз разворачивает его для такого случая, но подойдёт и для локальной машины, в качестве замены DNS провайдера. Мне показалось немного «жирноват».

install-dnsdist-doh.sh

#!/usr/bin/env bash
# Installs dnsdist (vanilla from PowerDNS where supported), configures it
# as localhost-only resolver that forwards over DoH to 8 major providers + Yandex
# with strict TLS validation, and sets the OS to use 127.0.0.1 as default DNS.
set -euo pipefail

say(){ printf "\n\033[1;32m[*]\033[0m %s\n" "$*"; }
warn(){ printf "\n\033[1;33m[!]\033[0m %s\n" "$*"; }
die(){ printf "\n\033[1;31m[x]\033[0m %s\n" "$*"; exit 1; }
reqroot(){ [[ $EUID -eq 0 ]] || die "Запустите как root"; }

reqroot

OS_ID=""; OS_VER=""; OS_CODE=""
if [[ -r /etc/os-release ]]; then
  # shellcheck disable=SC1091
  . /etc/os-release
  OS_ID="${ID:-}"; OS_VER="${VERSION_ID:-}"; OS_CODE="${VERSION_CODENAME:-}"
else
  die "Не найден /etc/os-release"
fi

say "Определён дистрибутив: ID=${OS_ID}, VERSION=${OS_VER}, CODENAME=${OS_CODE:-n/a}"

# ---- Install dnsdist (vanilla repo where possible) ---------------------------
install_dnsdist(){
  case "$OS_ID" in
    debian)
      # map VERSION_ID -> codename if VERSION_CODENAME absent
      if [[ -z "${OS_CODE:-}" ]]; then
        case "$OS_VER" in
          12*) OS_CODE="bookworm" ;;
          13*) OS_CODE="trixie" ;;
          *) die "Неизвестная версия Debian: $OS_VER";;
        esac
      fi
      say "Добавляю репозиторий PowerDNS (dnsdist 2.0) для Debian ${OS_CODE}"
      install -d /etc/apt/keyrings
      curl -fsSL https://repo.powerdns.com/FD380FBB-pub.asc -o /etc/apt/keyrings/dnsdist-20-pub.asc
      echo "deb [signed-by=/etc/apt/keyrings/dnsdist-20-pub.asc] http://repo.powerdns.com/debian ${OS_CODE}-dnsdist-20 main" \
        > /etc/apt/sources.list.d/pdns-dnsdist.list
      cat > /etc/apt/preferences.d/dnsdist-20 <<'EOF'
Package: dnsdist*
Pin: origin repo.powerdns.com
Pin-Priority: 600
EOF
      apt-get update
      apt-get install -y dnsdist ca-certificates curl
      ;;
    ubuntu)
      if [[ -z "${OS_CODE:-}" ]]; then
        case "$OS_VER" in
          22.04*) OS_CODE="jammy" ;;
          24.04*) OS_CODE="noble" ;;
          *) die "Неизвестная версия Ubuntu: $OS_VER";;
        esac
      fi
      say "Добавляю репозиторий PowerDNS (dnsdist 2.0) для Ubuntu ${OS_CODE}"
      install -d /etc/apt/keyrings
      curl -fsSL https://repo.powerdns.com/FD380FBB-pub.asc -o /etc/apt/keyrings/dnsdist-20-pub.asc
      echo "deb [signed-by=/etc/apt/keyrings/dnsdist-20-pub.asc] http://repo.powerdns.com/ubuntu ${OS_CODE}-dnsdist-20 main" \
        > /etc/apt/sources.list.d/pdns-dnsdist.list
      cat > /etc/apt/preferences.d/dnsdist-20 <<'EOF'
Package: dnsdist*
Pin: origin repo.powerdns.com
Pin-Priority: 600
EOF
      apt-get update
      apt-get install -y dnsdist ca-certificates curl
      ;;
    rhel|centos|rocky|almalinux|ol)
      say "Добавляю репозиторий PowerDNS (dnsdist 2.0) для Enterprise Linux"
      # EPEL нужен для зависимостей
      dnf install -y epel-release curl ca-certificates
      curl -fsSL -o /etc/yum.repos.d/powerdns-dnsdist-20.repo \
        https://repo.powerdns.com/repo-files/el-dnsdist-20.repo
      dnf -y install dnsdist
      ;;
    fedora)
      warn "Для Fedora официального репо PowerDNS нет — ставлю пакет из дистрибутива"
      dnf -y install dnsdist ca-certificates curl || die "Не удалось установить dnsdist"
      ;;
    opensuse*|sles|suse)
      warn "Для openSUSE/SLES официального репо PowerDNS нет — ставлю пакет из дистрибутива"
      zypper -n in dnsdist ca-certificates curl || die "Не удалось установить dnsdist"
      ;;
    arch)
      warn "Для Arch официального репо PowerDNS нет — ставлю пакет из дистрибутива"
      pacman -Sy --noconfirm ca-certificates curl dnsdist || die "Не удалось установить dnsdist"
      ;;
    *)
      die "Дистрибутив ${OS_ID} пока не поддержан автоматически"
      ;;
  esac
}

install_dnsdist

# ---- Ensure dnsdist supports outgoing DoH ------------------------------------
if ! dnsdist --version 2>/dev/null | grep -q 'dns-over-https'; then
  dnsdist --version || true
  die "Сборка dnsdist без поддержки исходящего DoH (nghttp2). Установите пакет из поддерживаемого репозитория PowerDNS."
fi

# ---- Detect CA bundle --------------------------------------------------------
detect_ca(){
  for p in \
    /etc/ssl/certs/ca-certificates.crt \
    /etc/pki/tls/certs/ca-bundle.crt \
    /etc/ssl/ca-bundle.pem \
    /etc/ssl/cert.pem \
    ; do [[ -r "$p" ]] && { echo "$p"; return; }; done
  die "Не найден файл корневых сертификатов"
}
CA_BUNDLE="$(detect_ca)"
say "Использую CA bundle: $CA_BUNDLE"

# ---- Write dnsdist.conf (localhost-only + DoH upstreams) ---------------------
CONF_DIR="/etc/dnsdist"
CONF_FILE="${CONF_DIR}/dnsdist.conf"
install -d -m 0755 "$CONF_DIR"
if [[ -f "$CONF_FILE" ]]; then cp -a "$CONF_FILE" "${CONF_FILE}.bak.$(date +%s)"; fi

cat >"$CONF_FILE"<<EOF
-- Autogenerated by install-dnsdist-doh.sh
-- Listen ONLY on loopback:
setLocal("127.0.0.1:53")
setACL({"127.0.0.0/8"})

-- Be conservative and pick the least busy upstream
setServerPolicy(leastOutstanding)

-- Packet cache: 50k records
pc = newPacketCache(50000, {maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60, dontAge=false})
getPool(""):setCache(pc)

-- Strict TLS verification for DoH backends
local CAB = "${CA_BUNDLE}"

-- Cloudflare DoH (1.1.1.1)
newServer({address="1.1.1.1:443",   tls="openssl", subjectName="cloudflare-dns.com", dohPath="/dns-query", validateCertificates=true, caStore=CAB})
newServer({address="1.0.0.1:443",   tls="openssl", subjectName="cloudflare-dns.com", dohPath="/dns-query", validateCertificates=true, caStore=CAB})

-- Google Public DNS DoH
newServer({address="8.8.8.8:443",   tls="openssl", subjectName="dns.google",         dohPath="/dns-query", validateCertificates=true, caStore=CAB})
newServer({address="8.8.4.4:443",   tls="openssl", subjectName="dns.google",         dohPath="/dns-query", validateCertificates=true, caStore=CAB})

-- Quad9 DoH
newServer({address="9.9.9.9:443",         tls="openssl", subjectName="dns.quad9.net", dohPath="/dns-query", validateCertificates=true, caStore=CAB})
newServer({address="149.112.112.112:443", tls="openssl", subjectName="dns.quad9.net", dohPath="/dns-query", validateCertificates=true, caStore=CAB})

-- AdGuard DoH
newServer({address="94.140.14.14:443", tls="openssl", subjectName="dns.adguard-dns.com", dohPath="/dns-query", validateCertificates=true, caStore=CAB})
newServer({address="94.140.15.15:443", tls="openssl", subjectName="dns.adguard-dns.com", dohPath="/dns-query", validateCertificates=true, caStore=CAB})

-- CleanBrowsing DoH (Security filter)
newServer({address="57.129.49.47:443", tls="openssl", subjectName="doh.cleanbrowsing.org", dohPath="/doh/security-filter/", validateCertificates=true, caStore=CAB})
newServer({address="46.105.222.254:443", tls="openssl", subjectName="doh.cleanbrowsing.org", dohPath="/doh/security-filter/", validateCertificates=true, caStore=CAB})

-- OpenDNS / Cisco Umbrella DoH
newServer({address="208.67.222.222:443", tls="openssl", subjectName="doh.opendns.com", dohPath="/dns-query", validateCertificates=true, caStore=CAB})
newServer({address="208.67.220.220:443", tls="openssl", subjectName="doh.opendns.com", dohPath="/dns-query", validateCertificates=true, caStore=CAB})

-- DNS.SB DoH
newServer({address="45.11.45.11:443", tls="openssl", subjectName="doh.dns.sb", dohPath="/dns-query", validateCertificates=true, caStore=CAB})

-- Mullvad DoH
newServer({address="194.242.2.3:443", tls="openssl", subjectName="adblock.dns.mullvad.net", dohPath="/dns-query", validateCertificates=true, caStore=CAB})

-- Yandex DoH (basic)
newServer({address="77.88.8.1:443", tls="openssl", subjectName="common.dot.dns.yandex.net", dohPath="/dns-query", validateCertificates=true, caStore=CAB})
newServer({address="77.88.8.8:443", tls="openssl", subjectName="common.dot.dns.yandex.net", dohPath="/dns-query", validateCertificates=true, caStore=CAB})

EOF

# ---- enable + start ----------------------------------------------------------
say "Проверяю конфигурацию dnsdist..."
dnsdist --check-config -C "$CONF_FILE" || die "Проверка конфигурации провалилась"

say "Включаю и запускаю сервис dnsdist"
systemctl enable --now dnsdist

# ---- Make dnsdist the default system resolver --------------------------------
make_default_dns(){
  # If systemd-resolved is present, point it to dnsdist (127.0.0.1) and keep stub 127.0.0.53
  if systemctl is-enabled systemd-resolved >/dev/null 2>&1 || systemctl is-active systemd-resolved >/dev/null 2>&1; then
    say "Настраиваю systemd-resolved -> 127.0.0.1"
    mkdir -p /etc/systemd
    if [[ -f /etc/systemd/resolved.conf ]]; then cp -a /etc/systemd/resolved.conf /etc/systemd/resolved.conf.bak.$(date +%s); fi
    awk '
      BEGIN{foundDNS=0; foundStub=0}
      /^#?DNS=/ {print "DNS=127.0.0.1"; foundDNS=1; next}
      /^#?FallbackDNS=/ {print "FallbackDNS="; next}
      /^#?DNSStubListener=/ {print "DNSStubListener=yes"; foundStub=1; next}
      {print}
      END{
        if(!foundDNS) print "DNS=127.0.0.1";
        if(!foundStub) print "DNSStubListener=yes";
      }
    ' /etc/systemd/resolved.conf 2>/dev/null > /etc/systemd/resolved.conf.tmp || true
    [[ -s /etc/systemd/resolved.conf.tmp ]] && mv /etc/systemd/resolved.conf.tmp /etc/systemd/resolved.conf
    systemctl restart systemd-resolved || true

    # Ensure resolv.conf points to the stub which forwards to dnsdist
    if [[ -L /etc/resolv.conf ]]; then
      ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
    else
      rm -f /etc/resolv.conf
      ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
    fi
    return
  fi

  # openSUSE netconfig path
  if command -v netconfig >/dev/null 2>&1; then
    say "Настраиваю netconfig (openSUSE) -> 127.0.0.1"
    CFG=/etc/sysconfig/network/config
    cp -a "$CFG" "${CFG}.bak.$(date +%s)"
    if grep -q '^NETCONFIG_DNS_STATIC_SERVERS=' "$CFG"; then
      sed -i 's/^NETCONFIG_DNS_STATIC_SERVERS=.*/NETCONFIG_DNS_STATIC_SERVERS="127.0.0.1"/' "$CFG"
    else
      echo 'NETCONFIG_DNS_STATIC_SERVERS="127.0.0.1"' >> "$CFG"
    fi
    netconfig update -f
    return
  fi

  # Fallback: write resolv.conf directly
  say "Переключаю /etc/resolv.conf -> 127.0.0.1"
  printf "nameserver 127.0.0.1\noptions edns0 trust-ad\n" > /etc/resolv.conf
}

make_default_dns

say "Готово. Пример проверки: dig +short powerdns.com @127.0.0.1"

Скрипт протестирован на Debian 12, написан не без помощи ИИ, но правки коснулись не самого процесса установки, а, в основном, параметров конфигурации сервера.



Проверено: hobbit ()
Последнее исправление: hobbit (всего исправлений: 4)

Если можно, добавьте тег dnsdist

unclestephen
() автор топика
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.