Tõstame esile oma DNS-over-HTTPS serveri

DNS-i kasutamise erinevad aspektid on autori poolt korduvalt käsitletud mitmetes artiklites blogi raames. Peamine rõhk on alati pandud selle olulise internetiteenuse turvalisuse parandamisele.

Tõstame esile oma DNS-over-HTTPS serveri

Viimase ajani, vaatamata DNS-i liikluse haavatavuse ilmsele olemusele, mis suuresti edastatakse endiselt avatud kujul, on olnud keeruline kaitse tugevdamise protsess. See on tingitud pahatahtlikest toimingutest teenusepakkujate poolt, kes püüavad oma tulu tõsta reklaami sissetoomise kaudu sisusse, riiklike jõuorganite ja tsensuuri ning lihtsalt kurjategijate tegevusest. Kaitse tugevdamine, vaatamata erinevatele tehnoloogiatele nagu DNSSEC/DANE, DNScrypt, DNS-over-TLS ja DNS-over-HTTPS, on jäänud sageli tegemata. Kuigi serverilahendused, millest mõned on olnud juba mõnda aega laialdaselt tuntud ja saadaval, ei ole kliendipoolne tarkvara nende toetuses sama heal tasemel.

Õnneks on olukord muutumas. Eelkõige on populaarse brauseri Firefox arendajad teatanud plaane, et vaikimisi toetada DNS-over-HTTPS režiimi.

1. DNS-over-HTTPS probleemid

Esmapilgul näib, et DNS-over-HTTPS laialdane kasutuselevõtt Interneti-tarkvaras toob kaasa ainult positiivseid reaktsioone. Siiski, nagu öeldakse, on diabolus detailides.

Esimene probleem, mis piirab DoH-i laiemat kasutamist, on selle keskendumine ainult veebiliiklusele. Tõepoolest, protokoll HTTP ja selle praegune versioon HTTP/2, millele DoH toetub, on WWW alus. Kuid internet ei ole ainult veeb. Eksisteerib palju populaarseid teenuseid, nagu e-post, erinevad sõnumirakendused, failide edastamise süsteemid, multimeedia voogedastus jne, mis ei kasuta HTTP-d. Seega, vaatamata paljude DoH-i kui imerohi tajumisele, osutub see teiste, mitte ainult brauseritehnoloogiate jaoks kasutamatuks, nõudes tarbetuid lisajõupingutusi. Ütleme nii, et DNS-over-TLS näib selle rolli jaoks palju sobivam kandidaat, kuna see rakendab standardse DNS-i liikluse kapseldamist kaitstud standardse protokolli TLS.

Teine probleem, mis võib olla potentsiaalselt veelgi olulisem kui esimene, on tegelik loobumine DNS-i loomulikust detsentraliseeritusest, et kasutada brauseri seadetest määratud ühte DoH-serverit. Eriti soovitab Mozilla kasutada Cloudflare'i teenust. Sarnase teenuse käivitasid ka teised tuntud Interneti tegelased, sealhulgas Google. Tundub, et DNS-over-HTTPS-i rakendamine sellisel kujul, nagu see praegu ette nähakse, suurendab lõppkasutajate sõltuvust suurimatest teenustest. Pole saladus, et DNS-päringute analüüs võib koguda veelgi rohkem teavet ning parandada andmete täpsust ja asjakohasust.

Sellega seoses on autor olnud ja jääb massilise rakendamise pooldaja mitte DNS-over-HTTPS, vaid DNS-over-TLS koos DNSSEC/DANE-ga kui universaalse, turvalise ja Interneti edasise tsentraliseerimisele mitte soodustava vahendi jaoks DNS-i liikluse turvamiseks. Kahjuks ei saa arvestada kiiret massilise toe saamist DoH alternatiivide jaoks kliendirakendustes nähtavatel põhjustel, ja nende saatuseks jäävad hetkel turvatehnoloogiate entusiastid.

Aga kuna me nüüd saame DoH, siis miks mitte kasutada seda, liikudes potentsiaalse jälgimise vältimiseks ettevõtete serverite kaudu enda DNS-over-HTTPS serverisse?

2. Protokoll DNS-over-HTTPS

Kui vaadata standardit RFC8484 , mis kirjeldab DNS-over-HTTPS protokolli, võib näha, et see on põhimõtteliselt veeb API, mis võimaldab tavat DNS-paketi kapseldamist HTTP/2 protokolli. See teostatakse spetsiaalsete HTTP-pealkirjade kaudu ning edastatavate DNS-andmete binaarformaadi (vt. RFC1035 ja järgmised dokumendid) konverteerimise kaudu kujule, mis võimaldab nende edastamist ja vastuvõtmist ning vajalikku metainformatsiooni töötlust.

Standardi kohaselt toetatakse ainult HTTP/2 ja turvalist TLS-ühendust.

DNS-päringu saatmine võib toimuda tavapäraste GET ja POST meetodite kaudu. Esimesel juhul muudetakse päring base64URL-kodeeritud stringiks, teisel juhul aga POST-päringu kehaga binaarses vormis. Samuti kasutatakse DNS-päringu ja vastuse puhul spetsiaalset MIME-tüüpi andmete jaoks. application/dns-message.

root@eprove:~ # curl -H 'accept: application/dns-message' 'https://my.domaint/dns-query?dns=q80BAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE' -v
*   Üritus 2001:100:200:300::400:443...
* TCP_NODELAY seadistatud
* Ühendatud eprove.net (2001:100:200:300::400) sadamasse 443 (#0)
* ALPN, pakub h2
* ALPN, pakub http/1.1
* sertifikaadi kontrollimise asukohad on edukalt seadistatud:
*   CAfail: /usr/local/share/certs/ca-root-nss.crt
  CAtee: ei ole
* TLSv1.3 (VÄLJAS), TLS-käeulatus, Klient tere (1):
* TLSv1.3 (SISSE), TLS-käeulatus, Serveri tere (2):
* TLSv1.3 (SISSE), TLS-käeulatus, Krüpteeritud laiendused (8):
* TLSv1.3 (SISSE), TLS-käeulatus, Tunnistus (11):
* TLSv1.3 (SISSE), TLS-käeulatus, TUNNISTUS kontrollida (15):
* TLSv1.3 (SISSE), TLS-käeulatus, Lõpetatud (20):
* TLSv1.3 (VÄLJAS), TLS muutus krüpteerimiseks, Muuta krüpteerimist (1):
* TLSv1.3 (VÄLJAS), TLS-käeulatus, Lõpetatud (20):
* SSL-ühendus kasutades TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server aktsepteeris h2 kasutamist
* Serveri sertifikaat:
*  teema: CN=my.domain
*  alguskuupäev: Juul 22 00:07:13 2019 GMT
*  lõppkuupäev: Okt 20 00:07:13 2019 GMT
*  subjectAltName: host "my.domain" vastab sertifikaadi "my.domain"
*  väljastaja: C=US; O=Let's Encrypt; CN=Let's Encrypt Authority X3
*  SSL-sertifikaat on õigustatud.
* Kasutatakse HTTP2, server toetab mitme kasutuse korral
* Ühenduse olek muutus (HTTP/2 kinnitatud)
* Kopeeritakse HTTP/2 andmed voos olevasse puhvriühendusse pärast uuendamist: len=0
* Kasutades voolu ID: 1 (lihtne käepide 0x801441000)
> GET /dns-query?dns=q80BAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE HTTP/2
> Host: eprove.net
> User-Agent: curl/7.65.3
> accept: application/dns-message
>
* TLSv1.3 (SISSE), TLS-käeulatus, Uus sessiooni pilet (4):
* Ühenduse olek muutus (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
<
Hoiatus: Binaarne väljund võib häirida teie terminali. Kasutage "--output -" , et öelda
Hoiatus: curl alustada väljundi edastamist teie terminalisse, või kaaluge "--output
Hoiatus: <FILE>" salvestamiseks faili.
* Keha kirjutamine ebaõnnestus (0 != 45)
* peatatud peatus voog!
* Ühendus #0 hostiga eprove.net jäi puutumatuks

Pöörake tähelepanu ka pealkirjale cache-control: veebiserveri vastuses. Parameeter max-age sisaldab TTL väärtust tagastatud DNS-kirjetele (või minimaalset väärtust, kui tagastatakse mitu).

Tuginedes eeltoodule, koosneb DoH serveri toimimine mitmest etapist.

  • Saada HTTP päring. Kui see on GET, siis dekodeeri pakett base64URL kodeeringust.
  • Saada see pakett DNS serverile.
  • Saada vastus DNS serverilt.
  • Leida minimaalne TTL väärtus saadud kirjetes.
  • Tagastada kliendile vastus HTTP kaudu.

3. Oma DNS-over-HTTPS server

Oma isikliku DNS-over-HTTPS serveri kõige lihtsam, kiire ja tõhusam käivitamine tundub olevat HTTP/2 veebiserveri kasutamine H2O, millest autor on juba lühidalt kirjutanud (vt 'Kõrge jõudlusega veebiserver H2O«).

Selle valiku kasuks räägib see, et kogu oma DoH serveri kood võib olla täielikult rakendatud H2O integreeritud tõlkija kaudu mruby. Lisaks standardraamatukogudele on vajalik DNS serveriga andmete vahetamiseks biblioteek (mrbgem) Socket, mis on õnneks juba kaasatud praegusesse arenduse versiooni H2O 2.3.0-beta2 olemasolev FreeBSD sadamad. Kuid ei ole raske seda lisada ka mistahes varasemale versioonile, kloonides hoidla Socketi teegid kausta /deps enne kompileerimist.

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
=== License MIT BSD2CLAUSE accepted by the user
=== h2o-2.2.6 depends on file: /usr/local/sbin/pkg - found
=== Fetching all distfiles required by h2o-2.2.6 for building
=== Extracting for h2o-2.2.6.
=> SHA256 Checksum OK for h2o-h2o-v2.2.6_GH0.tar.gz.
=== h2o-2.2.6 depends on file: /usr/local/bin/ruby26 - found
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
Klonimine 'mruby-socket'…
remote: Enumerating objects: 385, done.
remote: Total 385 (delta 0), reused 0 (delta 0), pack-reused 385
Obtaining objects: 100% (385/385), 98.02 KiB | 647.00 KiB/s, ready.
Determining changes: 100% (208/208), ready.
root@beta:/usr/ports/www/h2o/work/h2o-2.2.6/deps # ll
total 181
drwxr-xr-x   9 root  wheel  18 12 aug.  16:09 brotli/
drwxr-xr-x   2 root  wheel   4 12 aug.  16:09 cloexec/
drwxr-xr-x   2 root  wheel   5 12 aug.  16:09 golombset/
drwxr-xr-x   4 root  wheel  35 12 aug.  16:09 klib/
drwxr-xr-x   2 root  wheel   5 12 aug.  16:09 libgkc/
drwxr-xr-x   4 root  wheel  26 12 aug.  16:09 libyrmcds/
drwxr-xr-x  13 root  wheel  32 12 aug.  16:09 mruby/
drwxr-xr-x   5 root  wheel  11 12 aug.  16:09 mruby-digest/
drwxr-xr-x   5 root  wheel  10 12 aug.  16:09 mruby-dir/
drwxr-xr-x   5 root  wheel  10 12 aug.  16:09 mruby-env/
drwxr-xr-x   4 root  wheel   9 12 aug.  16:09 mruby-errno/
drwxr-xr-x   5 root  wheel  14 12 aug.  16:09 mruby-file-stat/
drwxr-xr-x   5 root  wheel  10 12 aug.  16:09 mruby-iijson/
drwxr-xr-x   5 root  wheel  11 12 aug.  16:09 mruby-input-stream/
drwxr-xr-x   6 root  wheel  11 12 aug.  16:09 mruby-io/
drwxr-xr-x   5 root  wheel  10 12 aug.  16:09 mruby-onig-regexp/
drwxr-xr-x   4 root  wheel  10 12 aug.  16:09 mruby-pack/
drwxr-xr-x   5 root  wheel  10 12 aug.  16:09 mruby-require/
drwxr-xr-x   6 root  wheel  10 12 sept. 16:10 mruby-socket/
drwxr-xr-x   2 root  wheel   9 12 aug.  16:09 neverbleed/
drwxr-xr-x   2 root  wheel  13 12 aug.  16:09 picohttpparser/
drwxr-xr-x   2 root  wheel   4 12 aug.  16:09 picotest/
drwxr-xr-x   9 root  wheel  16 12 aug.  16:09 picotls/
drwxr-xr-x   4 root  wheel   8 12 aug.  16:09 ssl-conservatory/
drwxr-xr-x   8 root  wheel  18 12 aug.  16:09 yaml/
drwxr-xr-x   2 root  wheel   8 12 aug.  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
...

Veebiserveri konfiguratsioon on üldiselt standardne.

root@beta:/usr/ports/www/h2o #  cd /usr/local/etc/h2o/
root@beta:/usr/local/etc/h2o # cat h2o.conf
# see this sample config to understand how h2o is set up
# and a secure configuration for TLS and HTTP headers
# visit https://h2o.examp1e.net/ for comprehensive documentation
# and refer to h2o --help for command-line options and settings

# v.20180207 (c)2018 by Max Kostikov 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

Erandiks on vaid URL-i töötleja /dns-query , mille eest vastutab meie DNS-over-HTTPS server, kirjutatud mruby keeles ja kutsutud välja töötleja valiku kaudu mruby.handler-file.

root@beta:/usr/local/etc/h2o # cat h2odoh.rb
# H2O HTTP/2 veebiserver DNS-over-HTTP teenusena
# v.20190908 (c)2018-2019 Max Kostikov https://kostikov.co e-post: 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 dekodeerimine
                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' }, [ "Vale nõue" ]]
        else
            # --- küsi DNS serverilt
            sock = UDPSocket.new
            sock.connect("localhost", 53)
            sock.send(req, 0)
            str = sock.recv(4096)
            sock.close
            # --- leia madalaim TTL vastuses
            nans = str[6, 2].unpack1('n') # vastuste arv
            if nans > 0 # DNS ebaõnnestumist pole
                shift = 12
                ttl = 0
                while nans > 0
                    # töötle domeeninime kompressioon
                    if str[shift].unpack1("C") < 192
                        shift = str.index("x00", shift) + 5
                        if ttl == 0 # hüppa küsimuse ossa
                            next
                        end
                    end
                    shift += 6
                    curttl = str[shift, 4].unpack1('N')
                    shift += str[shift + 4, 2].unpack1('n') + 6 # vastuse andmete suurus
                    if ttl == 0 or ttl > 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' }, [ "Toetamata meediumitüüp" ]]
    end
}

Pöörake tähelepanu, et DNS-paketid töödeldakse kohalikus vahemälu serveris, antud juhul Unbound FreeBSD standardpaketist. Turvalisuse seisukohalt on see optimaalne lahendus. Siiski ei takista miski teise DNS-i aadressi asendamist, mida kavatsete kasutada. localhost Teise DNS-i aadressiga.

root@beta:/usr/local/etc/h2o # local-unbound versioon
kasutamine: local-unbound [valikud]
        käivita unbound teenuse DNS resolvija.
-h      see abi
-c fail konfigureerimisfail, mida lugeda selle asemel, et /var/unbound/unbound.conf
        failivormingut kirjeldatakse unbound.conf(5) dokumendis.
-d      ei forkita taustale.
-p      ei loo pidifaili.
-v      detailne (rohkem kordi, et suurendada detailitaset)
Versioon 1.8.1
seotud teegid: mini-event internal (kasutab select), OpenSSL 1.1.1a-freebsd 20 Nov 2018
seotud moodulid: dns64 respip validator iterator
BSD litsents, vaata LITSENTS allika pakendis üksikasjade jaoks.
Teata vigadest aadressil 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          *:*

Nüüd tuleb H2O taas käivitada ja vaadata, mis sellest välja tuleb.

root@beta:/usr/local/etc/h2o # service h2o restart
H2O peatamine.
Ootamine PIDS: 69871.
H2O käivitamine.
start_server (pid:70532) käivitamine ...

4. Testimine

Nüüd kontrollime tulemusi, saates uuesti proovipäringu ja vaadates võrgu liiklust utiliidiga tcpdump.

root@beta/usr/local/etc/h2o # curl -H 'accept: application/dns-message' 'https://my.domain/dns-query?dns=q80BAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE'
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.
...
root@beta:~ # tcpdump -n -i lo0 udp port 53 -xx -XX -vv
tcpdump: listening on lo0, link-type NULL (BSD loopback), capture size 262144 bytes
16:32:40.420831 IP (tos 0x0, ttl 64, id 37575, offset 0, flags [none], proto UDP (17), length 57, bad cksum 0 (->e9ea)!)
    127.0.0.1.21070 > 127.0.0.1.53: [bad 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), length 73, bad cksum 0 (->e9cb)!)
    127.0.0.1.53 > 127.0.0.1.21070: [bad 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 packets captured
23 packets received by filter
0 packets dropped by kernel

Väljundis on näha, kuidas päring aadressi lahendamiseks example.com oli vastu võetud ja DNS-serveri poolt edukalt töödeldud.

Nüüd on jäänud vaid aktiveerida meie server brauseris Firefox. Selleks tuleb konfiguratsioonilehelt muuta mõningaid seadeid. about:config.

Tõstame esile oma DNS-over-HTTPS serveri

Esiteks, see on meie API aadress, mille kaudu brauser küsib DNS teavet. network.trr.uri. Soovitav on ka määrata selle URL-i domeeni IP, et turvaliselt lahendada IP ilma DNS-i päringuteta. network.trr.bootstrapAddress. Ja lõpuks, ise see parameeter. network.trr.mode millel on DoH kasutamine. Väärtuse seadmine ‘3’ sundib brauserit kasutama ainult DNS-over-HTTPS nimede lahendamiseks, kuid usaldusväärsem ja turvalisem ‘2’ eelistab DoH, jättes standardse DNS-päringu varuvariantideks.

5. TULU!

Kas artikkel oli kasulik? Siis palun ärge häbenege ja toetage meid rahaga, kasutades allolevat annetamise vormi.

Allikas: habr.com

Osta usaldusväärne veebihosting DDoS kaitsega, VPS VDS serverid 🔥 Osta usaldusväärne veebihosting DDoS kaitsega, VPS VDS serverid | ProHoster