Zvyšujeme svůj server DNS-over-HTTPS

Různé aspekty provozu DNS byly autorem již několikrát probrány v rámci článků publikovaných příspěvků v blogu. Přitom byl vždy kladen hlavní důraz na zvýšení bezpečnosti této klíčové služby pro celý internet.

Zvyšujeme svůj server DNS-over-HTTPS

Až do nedávné doby, i přes zřejmou zranitelnost DNS provozu, který je dosud převážně přenášen otevřeně, pro zlé úmysly ze strany poskytovatelů, kteří se snaží zvýšit své příjmy vložením reklam do obsahu, vládních orgánů a cenzury, stejně jako prostých zločinců, proces posílení jeho ochrany, i přes existenci různých technologií, jako jsou DNSSEC/DANE, DNScrypt, DNS-over-TLS a DNS-over-HTTPS, stagnoval. Zatímco serverové řešení, z nichž některé existují již poměrně dlouho, jsou široce známé a dostupné, podpora ze strany uživatelského softwaru zanechává mnoho co do přání.

Naštěstí se situace mění. Konkrétně vývojáři populárního prohlížeče Firefox prohlásili plánují brzy výchozí podporu pro DNS-over-HTTPS (DoH). To by mělo pomoci chránit DNS provoz uživatelů WWW před výše uvedenými hrozbami, avšak potenciálně může vyvolat nové.

1. Problémy s DNS-over-HTTPS

Na první pohled začínající masové zavádění DNS-over-HTTPS do softwaru na webu vyvolává pouze pozitivní reakce. Ale, jak se říká, ďábel se skrývá v detailech.

Prvním problémem, který omezuje oblast masového použití DoH, je jeho zaměření výhradně na webový provoz. Opravdu, protokol HTTP a jeho aktuální verze HTTP/2, na níž je DoH založen, je základnou WWW. Ale internet není jen web. Existuje celá řada populárních služeb, jako je e-mail, různé messengery, systémy pro přenos souborů, streaming multimédií a další, které HTTP nevyužívají. Tím pádem, navzdory tomu, že mnozí vnímají DoH jako panaceu, se ukazuje jako nepoužitelný bez dalších (a zbytečných) snah pro něco jiného než webové technologie. Pokud bych měl hovořit, pak by na tuto roli byl mnohem lepším kandidátem DNS-over-TLS, který realizuje enkapsulaci standardního DNS provozu do zabezpečeného standardního protokolu TLS.

Druhým problémem, který je potenciálně mnohem významnější než první, je faktické vzdání se inherentní decentralizace DNS kvůli používání jediného serveru DoH uvedeného v nastavení prohlížeče. Zejména Mozilla doporučuje používat službu od Cloudflare. Podobné služby spustily také jiné významné internetové společnosti, konkrétně Google. Vzniká tak situace, kdy zavedení DNS-over-HTTPS v podobě, v jaké je nyní navrhováno, pouze zvyšuje závislost koncových uživatelů na největších službách. Není žádným tajemstvím, že informace, které může poskytovat analýza DNS dotazů, může shromažďovat ještě více dat o uživatelském chování a také zvyšovat jejich přesnost a aktuálnost.

V této souvislosti byl a zůstává autor zastáncem masového zavedení ne DNS-over-HTTPS, ale DNS-over-TLS společně s DNSSEC/DANE jako univerzálního, bezpečného a neumožňujícího další centralizaci internetu prostředku pro zajištění bezpečnosti DNS trafik. Bohužel není reálné očekávat rychlé zavedení masové podpory alternativ DoH v klientském softwaru kvůli zřejmým důvodům a zbývá to zatím pouze na nadšence bezpečných technologií.

Ale pokud nyní máme DoH, proč ho nevyužít, přičemž se vyhneme možnosti sledování ze strany společností prostřednictvím jejich serverů a přejdeme na vlastní server DNS-over-HTTPS?

2. Protokol DNS-over-HTTPS

Pokud nahlédneme do standardu RFC8484 popisujícího protokol DNS-over-HTTPS, můžeme vidět, že v podstatě představuje webové API, které umožňuje enkapsulaci standardního DNS paketu do protokolu HTTP/2. To se realizuje prostřednictvím speciálních HTTP hlaviček a konverze binárního formátu přenášených DNS dat (viz RFC1035 a další dokumenty) do formátu, který umožňuje jejich přenos a příjem, a také práci s potřebnými metadaty.

Podle standardu je podporováno pouze HTTP/2 a zabezpečené připojení TLS.

Odeslání DNS dotazu může probíhat standardními metodami GET a POST. V prvním případě se dotaz transformuje na base64URL-encoded řetězec, a ve druhém — prostřednictvím těla POST dotazu v binární formě. Při tom se pro dotaz a odpověď DNS používá speciální MIME typ dat application/dns-message.

root@eprove:~ # curl -H 'accept: application/dns-message' 'https://my.domaint/dns-query?dns=q80BAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE' -v
*   Trying 2001:100:200:300::400:443...
* TCP_NODELAY set
* Connected to eprove.net (2001:100:200:300::400) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /usr/local/share/certs/ca-root-nss.crt
  CApath: none
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=my.domain
*  start date: Jul 22 00:07:13 2019 GMT
*  expire date: Oct 20 00:07:13 2019 GMT
*  subjectAltName: host "my.domain" matched cert's "my.domain"
*  issuer: C=US; O=Let's Encrypt; CN=Let's Encrypt Authority X3
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x801441000)
> GET /dns-query?dns=q80BAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE HTTP/2
> Host: eprove.net
> User-Agent: curl/7.65.3
> accept: application/dns-message
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* Connection state changed (MAX_CONCURRENT_STREAMS == 100)!
< HTTP/2 200
< server: h2o/2.3.0-beta2
< content-type: application/dns-message
< cache-control: max-age=86274
< date: Thu, 12 Sep 2019 13:07:25 GMT
< strict-transport-security: max-age=15768000; includeSubDomains; preload
< content-length: 45
<
Warning: Binary output can mess up your terminal. Use "--output -" to tell
Warning: curl to output it to your terminal anyway, or consider "--output
Warning: " to save to a file.
* Failed writing body (0 != 45)
* stopped the pause stream!
* Connection #0 to host eprove.net left intact

Věnujte také pozornost hlavičce cache-control: v odpovědi ze strany webového serveru. V parametru max-age je uvedena hodnota TTL pro vracený záznam DNS (nebo minimální hodnota, pokud se vrací jejich skupina).

Na základě výše uvedeného, fungování serveru DoH zahrnuje několik fází.

  • Získat HTTP požadavek. Pokud jde o GET, dekódovat paket z base64URL kódování.
  • Odeslat tento paket DNS serveru.
  • Získat odpověď od DNS serveru.
  • Najít minimální hodnotu TTL v získaných záznamech.
  • Vrátit klientovi odpověď přes HTTP.

3. Vlastní server DNS-over-HTTPS

Nejjednodušším, nejrychlejším a nejefektivnějším způsobem, jak spustit svůj vlastní server DNS-over-HTTPS, se zdá být použití HTTP/2 webového serveru H2O, o kterém autor již stručně psal (viz „Vysoce výkonný webový server H2O«).

V prospěch této volby hraje skutečnost, že celý kód vlastního serveru DoH může být kompletně implementován prostřednictvím integrovaného interpretu v samotném H2O mruby. Kromě standardních knihoven je pro výměnu dat se serverem DNS potřebná knihovna (mrbgem) Socket, která je, naštěstí, již zahrnuta v aktuální vývojové verzi H2O 2.3.0-beta2. přítomná v portech FreeBSD. Nicméně není těžké ji přidat ani do jakékoli předchozí verze klonováním repozitáře. knihovny Socket do adresáře /deps před kompilací.

root@beta:~ # uname -v
FreeBSD 12.0-RELEASE-p10 GENERIC
root@beta:~ # cd /usr/ports/www/h2o
root@beta:/usr/ports/www/h2o # make extract
===>  Uživatel přijal licenci MIT BSD2CLAUSE
===>   h2o-2.2.6 závisí na souboru: /usr/local/sbin/pkg - bylo nalezeno
===> Stahování všech distfile potřebných pro h2o-2.2.6 pro sestavení
===>  Extrakce pro h2o-2.2.6.
=> SHA256 kontrolní součet OK pro h2o-h2o-v2.2.6_GH0.tar.gz.
===>   h2o-2.2.6 závisí na souboru: /usr/local/bin/ruby26 - bylo nalezeno
root@beta:/usr/ports/www/h2o # cd work/h2o-2.2.6/deps/
root@beta:/usr/ports/www/h2o/work/h2o-2.2.6/deps # git clone https://github.com/iij/mruby-socket.git
Klonování do „mruby-socket“...
remote: Počítání objektů: 385, hotovo.
remote: Celkem 385 (delta 0), znovu použito 0 (delta 0), pack-reused 385
Přijímání objektů: 100% (385/385), 98.02 KiB | 647.00 KiB/s, hotovo.
Určování změn: 100% (208/208), hotovo.
root@beta:/usr/ports/www/h2o/work/h2o-2.2.6/deps # ll
total 181
drwxr-xr-x   9 root  wheel  18 12 srp.  16:09 brotli/
drwxr-xr-x   2 root  wheel   4 12 srp.  16:09 cloexec/
drwxr-xr-x   2 root  wheel   5 12 srp.  16:09 golombset/
drwxr-xr-x   4 root  wheel  35 12 srp.  16:09 klib/
drwxr-xr-x   2 root  wheel   5 12 srp.  16:09 libgkc/
drwxr-xr-x   4 root  wheel  26 12 srp.  16:09 libyrmcds/
drwxr-xr-x  13 root  wheel  32 12 srp.  16:09 mruby/
drwxr-xr-x   5 root  wheel  11 12 srp.  16:09 mruby-digest/
drwxr-xr-x   5 root  wheel  10 12 srp.  16:09 mruby-dir/
drwxr-xr-x   5 root  wheel  10 12 srp.  16:09 mruby-env/
drwxr-xr-x   4 root  wheel   9 12 srp.  16:09 mruby-errno/
drwxr-xr-x   5 root  wheel  14 12 srp.  16:09 mruby-file-stat/
drwxr-xr-x   5 root  wheel  10 12 srp.  16:09 mruby-iijson/
drwxr-xr-x   5 root  wheel  11 12 srp.  16:09 mruby-input-stream/
drwxr-xr-x   6 root  wheel  11 12 srp.  16:09 mruby-io/
drwxr-xr-x   5 root  wheel  10 12 srp.  16:09 mruby-onig-regexp/
drwxr-xr-x   4 root  wheel  10 12 srp.  16:09 mruby-pack/
drwxr-xr-x   5 root  wheel  10 12 srp.  16:09 mruby-require/
drwxr-xr-x   6 root  wheel  10 12 zář. 16:10 mruby-socket/
drwxr-xr-x   2 root  wheel   9 12 srp.  16:09 neverbleed/
drwxr-xr-x   2 root  wheel  13 12 srp.  16:09 picohttpparser/
drwxr-xr-x   2 root  wheel   4 12 srp.  16:09 picotest/
drwxr-xr-x   9 root  wheel  16 12 srp.  16:09 picotls/
drwxr-xr-x   4 root  wheel   8 12 srp.  16:09 ssl-conservatory/
drwxr-xr-x   8 root  wheel  18 12 srp.  16:09 yaml/
drwxr-xr-x   2 root  wheel   8 12 srp.  16:09 yoml/
root@beta:/usr/ports/www/h2o/work/h2o-2.2.6/deps # cd ..../../../..
root@beta:/usr/ports/www/h2o # make install clean
...

Konfigurace webového serveru je vcelku standardní.

root@beta:\/usr\/ports\/www\/h2o #  cd \/usr\/local\/etc\/h2o\/ 
root@beta:\/usr\/local\/etc\/h2o # cat h2o.conf
# Tato vzorová konfigurace vám ukáže, jak může být h2o použit
# a vysokou bezpečnostní konfiguraci pro TLS a HTTP hlavičky
# viz https:\/\/h2o.examp1e.net\/ pro podrobné dokumentace
# a h2o --help pro možnosti příkazového řádku a nastavení

# v.20180207 (c)2018 od Maxe Kostikova http:\/\/kostikov.co e-mail: max@kostikov.co

user: www
pid-file: \/var\/run\/h2o.pid
access-log:
    path: \/var\/log\/h2o\/h2o-access.log
    format: "%h %v %l %u %t "%r" %s %b "%{Referer}i" "%{User-agent}i""
error-log: \/var\/log\/h2o\/h2o-error.log

expires: off
compress: on
file.dirlisting: off
file.send-compressed: on

file.index: [ 'index.html', 'index.php' ]

listen:
    port: 80
listen:
    port: 443
    ssl:
        cipher-suite: ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS
        cipher-preference: server
        dh-file: \/etc\/ssl\/dhparams.pem
        certificate-file: \/usr\/local\/etc\/letsencrypt\/live\/eprove.net\/fullchain.pem
        key-file: \/usr\/local\/etc\/letsencrypt\/live\/my.domain\/privkey.pem

hosts:
    "*.my.domain":
        paths: &go_tls
            "\/":
                redirect:
                    status: 301
                    url: https:\/\/my.domain\/ 
    "my.domain:80":
        paths: *go_tls
    "my.domain:443":
        header.add: "Strict-Transport-Security: max-age=15768000; includeSubDomains; preload"
        paths:
            "\/dns-query":
               mruby.handler-file: \/usr\/local\/etc\/h2o\/h2odoh.rb

Výjimku tvoří pouze zpracovatel URL /dns-query za který je odpovědný náš server DNS-over-HTTPS, napsaný v mruby a vyvolávaný prostřednictvím možnosti zpracovatele mruby.handler-file.

root@beta:‍/usr/‍local/‍etc/‍h2o # cat h2odoh.rb
# H2O HTTP/‍2 web server as DNS-over-HTTP service
# v.20190908 (c)2018-2019 Max Kostikov https://kostikov.co e-mail: max@kostikov.co

proc {|env|
    if env['HTTP_ACCEPT'] == "application/‍dns-message"
        case env['REQUEST_METHOD']
            when "GET"
                req = env['QUERY_STRING'].gsub(/^dns=/,'')
                # base64URL decode
                req = req.tr("-_", "+/)
                if !req.end_with?("=") && req.length % 4 != 0
                    req = req.ljust((req.length + 3) & ~3, "=")
                end
                req = req.unpack1("m")
            when "POST"
                req = env['rack.input'].read
            else
                req = ""
        end
        if req.empty?
            [400, { 'content-type' => 'text/‍plain' }, [ "Bad Request" ]]
        else
            # --- ask DNS server
            sock = UDPSocket.new
            sock.connect("localhost", 53)
            sock.send(req, 0)
            str = sock.recv(4096)
            sock.close
            # --- find lowest TTL in response
            nans = str[6, 2].unpack1('n') # number of answers
            if nans > 0 # no DNS failure
                shift = 12
                ttl = 0
                while nans > 0
                    # process domain name compression
                    if str[shift].unpack1("C")  curttl
                        ttl = curttl
                    end
                    nans -= 1
                 end
                 cc = 'max-age=' + ttl.to_s
            else
                 cc = 'no-cache'
            end
            [200, { 'content-type' => 'application/‍dns-message', 'content-length' => str.size, 'cache-control' => cc }, [ str ] ]
        end
    else
        [415, { 'content-type' => 'text/‍plain' }, [ "Unsupported Media Type" ]]
    end
}

Všimněte si, že za zpracování DNS paketů odpovídá lokální caching server, v tomto případě Unbound z standardní verze FreeBSD. Z hlediska bezpečnosti je to optimální řešení. Nicméně nic nebrání tomu, abyste to nahradili localhost adresou jiného DNS, o kterém uvažujete, že ho budete používat.

root@beta:‍/usr/‍local/‍etc/‍h2o # local-unbound version
usage:  local-unbound [options]
        start unbound daemon DNS resolver.
-h      this help
-c file config file to read instead of /var/unbound/unbound.conf
        file format is described in unbound.conf(5).
-d      do not fork into the background.
-p      do not create a pidfile.
-v      verbose (more times to increase verbosity)
Version 1.8.1
linked libs: mini-event internal (it uses select), OpenSSL 1.1.1a-freebsd  20 Nov 2018
linked modules: dns64 respip validator iterator
BSD licensed, see LICENSE in source package for details.
Report bugs to unbound-bugs@nlnetlabs.nl
root@eprove:‍/usr/‍local/‍etc/‍h2o # sockstat -46 | grep unbound
unbound  local-unbo 69749 3  udp6   ::1:53                *:*
unbound  local-unbo 69749 4  tcp6   ::1:53                *:*
unbound  local-unbo 69749 5  udp4   127.0.0.1:53          *:*
unbound  local-unbo 69749 6  tcp4   127.0.0.1:53          *:*

Zbývá restartovat H2O a podívat se, co z toho vyšlo.

root@beta:/usr/local/etc/h2o # service h2o restart
Zastavuji h2o.
Čekám na PIDS: 69871.
Spouštím h2o.
start_server (pid:70532) nyní začíná...

4. Testování

Takže zkontrolujeme výsledky tím, že opět zašleme testovací požadavek a podíváme se na síťový provoz pomocí utility tcpdump.

root@beta/usr/local/etc/h2o # curl -H 'accept: application/dns-message' 'https://my.domain/dns-query?dns=q80BAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE'
Upozornění: Binární výstup může poškodit váš terminál. Použijte "--output -", aby
Upozornění: curl výstup odeslal do terminálu, nebo zvažte "--output
Upozornění: " pro uložení do souboru.
...
root@beta:~ # tcpdump -n -i lo0 udp port 53 -xx -XX -vv
tcpdump: naslouchá na lo0, typ odkazu NULL (BSD smyčka), velikost zachycení 262144 bytů
16:32:40.420831 IP (tos 0x0, ttl 64, id 37575, offset 0, flags [none], proto UDP (17), délka 57, špatný kontrolní součet 0 (->e9ea)!)
    127.0.0.1.21070 > 127.0.0.1.53: [špatný udp cksum 0xfe38 -> 0x33e3!] 43981+ A? example.com. (29)
        0x0000:  0200 0000 4500 0039 92c7 0000 4011 0000  ....E..9....@...
        0x0010:  7f00 0001 7f00 0001 524e 0035 0025 fe38  ........RN.5.%.8
        0x0020:  abcd 0100 0001 0000 0000 0000 0765 7861  .............exa
        0x0030:  6d70 6c65 0363 6f6d 0000 0100 01         mple.com.....
16:32:40.796507 IP (tos 0x0, ttl 64, id 37590, offset 0, flags [none], proto UDP (17), délka 73, špatný kontrolní součet 0 (->e9cb)!)
    127.0.0.1.53 > 127.0.0.1.21070: [špatný udp cksum 0xfe48 -> 0x43fa!] 43981 q: A? example.com. 1/0/0 example.com. A 93.184.216.34 (45)
        0x0000:  0200 0000 4500 0049 92d6 0000 4011 0000  ....E..I....@...
        0x0010:  7f00 0001 7f00 0001 0035 524e 0035 fe48  .........5RN.5.H
        0x0020:  abcd 8180 0001 0001 0000 0000 0765 7861  .............exa
        0x0030:  6d70 6c65 0363 6f6d 0000 0100 01c0 0c00  mple.com........
        0x0040:  0100 0100 0151 8000 045d b8d8 22         .....Q...].."
^C
2 pakety zachyceny
23 paketů přijato filtrem
0 paketů zahozeno jádrem

Ve výstupu je vidět, jak byl požadavek na rozřešení adresy example.com přijat a úspěšně zpracován DNS serverem.

Nyní zbývá aktivovat náš server v prohlížeči Firefox. K tomu na stránce konfigurace je třeba změnit několik nastavení about:config.

Zvyšujeme svůj server DNS-over-HTTPS

Za prvé, to je adresa našeho API, kam prohlížeč bude posílat žádosti o DNS informace v network.trr.uri. Doporučuje se také uvést IP domény z této URL pro bezpečné rozřešení do IP prostředky samotného prohlížeče bez volání k DNS v network.trr.bootstrapAddress. A nakonec vlastně samotný parametr network.trr.mode který zahrnuje použití DoH. Nastavení hodnoty na "3" donutí prohlížeč používat výhradně DNS-over-HTTPS pro rozřešení názvů, zatímco spolehlivější a bezpečnější "2" dá přednost DoH, přičemž standardní dotaz na DNS zůstane jako záložní možnost.

5. ZISK!

Byl článek užitečný? Pak se nebojte podpořit nás finančně přes formulář pro dary (níže).

Zdroj: habr.com

Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster