Novinky v Knot Resolver 6.x

V tomto příspěvku bych rád představil nadcházející hlavní verzi projektu Knot Resolver, která je v tuto chvíli ve fázi testování a ladění a proto velmi oceníme, když si ji vyzkoušíte a poskytnete nám k ní jakoukoliv zpětnou vazbu.

Architektura

Knot Resolver se od ostatních open-source implementací DNS resolverů liší převážně svou modularitou, a to v několika aspektech. Kompletní Knot Resolver je tvořen několika komponentami: kresd (samostatný resolvující daemon), cache databáze s DNS záznamy a cache garbage collector (proces starající se o periodické čištění záznamů v cache). Resolvující daemon je navíc jednovláknový proces, což znamená, že je velmi jednoduchý a přímočarý, pokud ale chceme plně využít zdroje procesoru, musí být spuštěno více jeho samostatných instancí. Jejich počet zpravidla odpovídá počtu jader procesoru.

Jednotlivé základní i doplňující funkce resolvujícího daemona (kresd) jsou zajišťovány samostatnými moduly, které je možné připojovat a odpojovat a mít tak „štíhlý“ resolver přizpůsobený vlastním potřebám bez nadbytečných funkcí. Další neobvyklostí byla až dosud konfigurace, která se píše v plnohodnotném programovacím jazyce Lua. Společně s možností psaní vlastních modulů v C nebo Lua to přináší takřka neomezené možnosti, jak Knot Resolver a jeho funkce doplňovat nebo konfigurovat.

S tím, jakou má Knot Resolver architekturu, se váže hned několik problémů:

  • Jednovláknové procesy neumožňují tak jednoduchou škálovatelnost na vícejádrových strojích. Musí se spustit více procesů, o které je pak třeba se jednotlivě starat. K tomu je zpravidla využíváno systemd, které ale není možné nasadit v Docker kontejnerech nebo některých distribucích Linuxu (např. Turris OS a OpenWrt).
  • Jednotlivé moduly je před použitím třeba manuálně načíst, jinak jejich funkce nebudou dostupné. U některých modulů je také potřeba zajistit správné pořadí jejich načtení.
  • Konfigurace v jazyce Lua neumožňuje jakýmkoli způsobem ověřit její správnost před spuštěním v resolveru. V lepším případě se chyba projeví hned při spuštění, v horším případě může resolver běžet bez problému a chyba se projeví až při pokusu o aplikaci špatné konfigurace nebo spuštění chybného kódu.

V nové nadcházející verzi vše zmíněné zůstává, ale přibývá nová komponenta zvaná manažer (manager), která se snaží nevýhody tohoto přístupu zredukovat a usnadnit interakci uživatele s Knot Resolverem.

Manažer a řízení procesů

Manažer je nová komponenta napsána v Pythonu, která spravuje zbylé části resolveru na základě konfigurace:

  • Dává pokyn ke spuštění nebo zastavení ostatních procesů. K tomu používá supervisord, který funguje i v Docker kontejneru a Linux distribucích nepodporujících systemd. Díky manažerovi je tedy mnohem snadnější spravovat procesy resolveru na systémech s absencí systemd.
  • Používá deklarativní formu konfigurace v YAML formátu, která je výrazně přehlednější než Lua. Navíc je možné ji validovat a odchytit tak chyby v konfiguraci ještě před spuštěním resolveru. Na pozadí se pak postará o správné načtení potřebných modulů v případě použití jejich funkcí.
  • Poskytuje HTTP API, které umožňuje změnu konfigurace za běhu nebo načtení agregovaných metrik ze všech kresd procesů.

V Knot Resolveru existují dvě různé řídící struktury, viz následující diagram. Sémanticky manažer řídí všechny zbylé části resolveru. Manažer však zároveň není kořenem procesní hierarchie, tím je supervisord, který je na vrcholu procesního stromu a řídí všechny procesy.

Procesní hierarchie lehce komplikuje proceduru spuštění resolveru. Je možné si toho všimnout i při náhledu do logů hned po spuštění.

Co se děje v při studeném startu:

  1. Spustí se manažer a přečte si deklarativní konfiguraci. Pokud je konfigurace validní, tak vygeneruje konfiguraci pro supervisord.
  2. Poté se spustí supervisord a dojde k nahrazení běžícího procesu manažera novým supervisord procesem.
  3. Supervisord načte svou konfiguraci a spustí novou instanci manažera. Nově spuštěný manažer běží jako podřízený proces pod supervisord, což je žádoucí stav.
  4. Manažer si znovu načte konfigurační soubor a na základě toho vytvoří konfiguraci pro zbylé procesy resolveru.
  5. Nakonec vydá manažer pokyn pro supervisord, aby spustil vyžadovaný počet procesů, které si následně načtou pro ně vytvořenou konfiguraci.

Díky tomu, že procesy řídí supervisord, je možné vyřešit selhání jednotlivých procesů resolveru bez zásahu uživatele. Vše mimo supervisord se při selhání automaticky restartuje. Pokud se ze selhání není možné zotavit automaticky, procesy se zastaví a nezanechají za sebou žádné smetí.

Zpracování konfigurace

Následující diagram detailněji popisuje průběh načtení konfiguračního souboru při studeném startu nebo při konfiguraci resolveru za běhu pomocí HTTP API. K jednodušší komunikaci s HTTP API je možné použít CLI nástroj kresctl. Konfigurační soubor je jediný autoritativní zdroj konfigurace a na rozdíl od konfigurování prostřednictvím HTTP API je perzistentní napříč restarty resolveru nebo i celého stroje. Omezením konfigurace přes HTTP API je také to, že z ní není možné generovat konfiguraci pro supervisord, která se sestavuje pouze při studeném startu. HTTP API proto neumožňuje konfigurovat naprosto vše co umí  konfigurační soubor.

Po načtení ze souboru nebo přijetí přes HTTP API je konfigurace naparsována z YAML nebo JSON řetězce do jednotného slovníkového formátu. Následně proběhne validace dat oproti vzorovému schématu a jejich normalizace. Dojde například k přiřazení defaultních hodnot nebo přiřazení hodnot na základě kontextu v konfiguračních datech. Je to možné díky vrstvení vzorového schématu konfigurace, které mezi jednotlivými vrstvami umožňuje transformace na základě kontextu z vrstvy předchozí nebo získání nové hodnoty ze systému. Tím vzniknou nová konfigurační data, která jsou validována oproti vrstvě následující. Uživatel tedy pracuje s lehce odlišnou strukturou konfigurace než resolver interně po validaci.

Po úspěšné validaci dojde při studeném startu k vygenerování konfigurace pro supervisord, v ostatních případech mimo studený start dojde pouze k vytvoření konfigurace pro jednotlivé procesy resolveru.

Instalace

Kompletní dokumentaci k testovací verzi lze nalézt na https://knot.pages.nic.cz/knot-resolver. Jak nainstalovat testovací verzi pro vybrané linuxové distribuce je popsané v kapitole Getting Started.

Spuštění

Naše upstream balíčky pro jednotlivé linuxové distribuce jsou standardně dodávány se systemd integrací. V nové verzi již není nutné spouštět jednotlivé instance kresd (kresd@1, kresd@2, …), stačí pouze spustit manažera pomocí nové systemd služby.

$ systemctl start knot-resolver # stop, reload, restart

Počet kresd instancí, kterým má manažer zajistit spuštění, se určuje číselnou hodnotou v konfiguračním souboru pod novým názvem workers. Hodnotu je také možné nastavit na auto, kdy manažer nastaví počet workerů automaticky na základě počtu jader procesoru.

# /etc/knot-resolver/config.yaml

workers: 4

Knot Resolver standardně využívá logování přes systemd, kam je možné se podívat pomocí systemd-journald služby, například při neúspěšném startu resolveru.

$ journalctl -eu knot-resolver

Konfigurace

Konfigurace je nově deklarativní ve formátu YAML nebo JSON (pro HTTP API). Je možné ji před použitím validovat a zachytit tak případné chyby ještě před spuštěním Knot Resolveru.

Validace

Validace konfigurace vždy probíhá automaticky při spuštění Knot Resolveru. V případě vadné konfigurace skončí jeho spuštění chybou.

Konfiguraci je možné validovat nezávisle na spuštění resolveru pomocí CLI nástroje kresctl.

kresctl validate /etc/knot-resolver/config.yaml

Pokud validace selže, objeví se detailní chybová hláška s informacemi, kde a co je špatně. Stejný výpis se objeví v logu Knot Resolveru, pokud selže validace konfigurace při startu.

Configuration validation errors detected:
    [/network/listen[0]/port] value 65536 is higher than the maximum 65535
    [/logging/level] degugg does not match any of the expected values (crit, ..., debug)

Dynamická změna konfigurace

Změnit konfiguraci resolveru za běhu je možné dvěma způsoby.
Knot Resolver je schopný dynamicky změnit vlastní konfiguraci znovunačtením konfiguračního souboru nebo prostřednictvím HTTP API.

Po úpravě konfiguračního souboru stačí následně zavolat reload systemd služby.

$ systemctl reload knot-resolver

K zaslání nové konfigurace na HTTP API je opět možné využít CLI nástroje kresctl nebo například standardní nástroj curl.

$ kresctl config set /workers 8

Při standardním použití si kresctl sám zjistí konfiguraci HTTP API z konfiguračního souboru. Mimo operace s konfigurací je možné prostřednictvím HTTP API získat metriky resolveru nebo kompletní JSON schéma konfigurace. Vše je detailněji popsáno v management sekci dokumentace.

HTTP API je ve výchozím nastavení nakonfigurované, aby poslouchalo na unixovém socketu. API je také možné nakonfigurovat na jedno ze síťových rozhraní a port.

# /etc/knot-resolver/config.yaml

management:
  interface: 127.0.0.1@5000
  # nebo použijte místo rozhraní unix-socket
  # unix-socket: /my/new/socket.sock

Obdobná změna konfigurace s pomocí curl nástroje bude vypadat následovně.

$ curl -H 'Content-Type: application/json' \
  -X PUT -d '8' http://127.0.0.1:5000/v1/config/workers

Rekonfigurace probíhá následujícím způsobem:

  1. Nová konfigurace získaná ze souboru nebo přes HTTP API je validována.
  2. Manažer zkontroluje, zda v nové konfiguraci nejsou nastavení, která by vyžadovala kompletní restart resolveru včetně manažera a vytvoření nové supervisord konfigurace. Pokud změny v konfiguraci nevyhovují, operace tímto končí chybovou hláškou a změnu konfigurace je potřeba provést pomocí restartu celého resolveru pomocí systemd.
  3. Manažer vytvoří novou konfiguraci pro jednotlivé procesy a s touto konfigurací spustí takzvaný “canary” proces, díky kterému se zjistí případné chyby.
  4. Pokud v “canary” procesu vše funguje jak má, následuje spuštění vyžadovaného počtu procesů s novou konfigurací.
  5. Nově spuštěné procesy v daný moment nahradí původní již běžící a tím je rekonfigurace dokončena bez výpadku služby.

Příklady konfigurace

První věc, kterou budete pravděpodobně chtít nakonfigurovat, jsou síťová rozhraní pro poslech. Následující příklad instruuje resolver, aby přijímal standardní nezašifrované DNS dotazy na localhost adresách. Zabezpečené DNS lze nastavit pomocí protokolů DNS-over-TLS nebo DNS-over-HTTPS. Nastavení nestandardního portu pro protokol je možné explicitně určit pomocí parametru port.

YAML také umožňuje vytvářet proměnné (&interfaces) a obsah následně replikovat v dalších částech konfigurace (*interfaces). Díky tomu není v příkladu třeba opakovaně vypisovat síťová rozhraní.

# /etc/knot-resolver/config.yaml

network:
  listen:
    # Nezabezpečené DNS na portu 53 (default).
    - interface: &interfaces
        - 127.0.0.1 
        - "::1"
        # Pro IPv6 adresy a jiné řetězce začínající
        # dvojtečkou je potřeba použít uvozovky.
    # DNS-over-TLS na portu 853 (default).
    - interface: *interfaces
      kind: dot 
    # DNS-over-HTTPS (port 443 je default). 
    - interface: *interfaces
      kind: doh2
      # Port je také možné nastavit explicitně.
      port: 5000

Dalším výrazným zlepšením nejen v konfiguraci, ale v chování resolveru obecně, je změna přístupu k vyhodnocování a aplikaci policy pravidel. Deklarativní přístup v konfiguraci zajišťuje, že je vždy vybráno pravidlo, které přicházejícímu dotazu odpovídá nejlépe. Starý přístup v předchozích verzích vybral první pravidlo, které odpovídalo dotazu. Bylo proto třeba dbát velké opatrnosti s pořadím pravidel, jinak se často nechtěným způsobem vzájemně „zastiňovala“.

U pravidlel pro třídění uživatelů na základě zdrojové podsítě bude mít vždy přednost menší odpovídající podsíť před větší. To je možné vidět v následujícím příkladu. Jedním pravidlem jsou uživatelé ze všech podsítí odmítnutí a dalšími pravidly s menšími podsítěmi jsou povoleny.

# /etc/knot-resolver/config.yaml

views:
  # Podsítě, které jsou povoleny.
  - subnets: [ 10.0.10.0/24, 127.0.0.1, "::1" ]
    answer: allow
  # Podsíť, pro kterou jsou přiřazena další pravidla
  # pomocí štítků (tags).
  - subnets: [  192.168.1.0/24 ]
    tags: [ malware, localnames ]
  # Vše ostatní je odmítnuto.
  - subnets: [ 0.0.0.0/0, "::/0" ]
    answer: refused

Jednotlivá pravidla je možné mezi sebou kombinovat. K tomu slouží štítky (tags), které stačí přiřadit k pravidlům, které chceme zkombinovat. Kombinace různých pravidel se chovají výrazně předvídatelněji než dříve. Je tak možné filtrovat uživatele na základě podsítě, ze které dotaz přichází, a pomocí tagu přiřadit pro tuto podsíť další pravidla zpracování dotazu. Mohou to být například úpravy v lokálních DNS záznamech, blokovací seznamy a další.

# /etc/knot-resolver/config.yaml

local-data:
  # Místo pro statické DNS záznamy.
  records: |
    www.google.com CNAME forcesafesearch.google.com
  rpz:
    # Malware seznam pro blokování.
    - file: /tmp/malware.rpz
      # Je použito pouze pro "malware" štítek (tag).
      # Například v kombinaci s podsítěmi ve "views".
      tags: [ malware ]
# /etc/knot-resolver/config.yaml

local-data:
  # Statické páry domén a adres.
  addresses:
    a1.example.com: 2001:db8::1
    a2.example.org: 
      - 192.0.2.2
      - 192.0.2.3
      - 2001:db8::4
  # Import souboru ve formát "/etc/hosts".
  addresses-files:
    - /etc/hosts

Deklarativní přístup dále umožnil pokrýt další části použití v oblasti přeposílání dotazů, kde je nově možné přeposlání na více míst i rámci jednoho dotazu. Cílem dotazu pak může být i autoritativní server. Přeposílání nyní nerozbije CNAME a resolver dotaz vyřeší správným způsobem.

# /etc/knot-resolver/config.yaml

forward:
  # Veškeré DNS dotazy budou přeposlány na ODVR servery.
  - subtree: '.'
    servers:
      - address:
          - 2001:148f:fffe::1
          - 193.17.47.1
        # Komunikace je zabezpečena pomocí TLS.
        transport: tls
        hostname: odvr.nic.cz
# /etc/knot-resolver/config.yaml

forward:
  # Přeposlání dotazu pro interní doménu.
  - subtree: internal.example.com
    servers: [ 10.0.0.53 ]
    options:
      # Cílem je interní autoritativní DNS server.
      authoritative: true
      dnssec: false

Závěrem

Na závěr bych rád poděkoval všem, kteří si nový Knot Resolver vyzkoušeli nebo teprve vyzkouší. Více o nové verzi naleznete v dokumentaci. Případnou zpětnou vazbu nám můžete poskytnout nejlépe v GitLabu nebo zaslat na e-mailovou adresu projektu. Pokud vše půjde podle plánu, na první oficiální vydání se můžete těšit začátkem příštího roku s označením verze 6.1.0.

Autor:

Zanechte komentář

Všechny údaje jsou povinné. E-mail nebude zobrazen.

Tato stránka používá Akismet k omezení spamu. Podívejte se, jak vaše data z komentářů zpracováváme..