Wy ferheegje ús DNS-over-HTTPS-tsjinner

Ferskate aspekten fan DNS-operaasje binne al ferskate kearen oanrekke troch de auteur yn in oantal artikels publisearre as ûnderdiel fan it blog. Tagelyk hat de wichtichste klam altyd west op it ferbetterjen fan de feiligens fan dizze kaai ynternettsjinst.

Wy ferheegje ús DNS-over-HTTPS-tsjinner

Oant koartlyn, nettsjinsteande de foar de hân lizzende kwetsberens fan DNS-ferkear, dy't noch altyd, foar it grutste part, yn 'e dúdlike oerdroegen wurdt, nei kweade aksjes fan' e kant fan providers dy't besykje har ynkommen te ferheegjen troch advertinsjes yn ynhâld yn te setten, ynstânsjes foar oerheidsfeiligens en sensuer, likegoed as gewoan kriminelen, it proses it fersterkjen fan har beskerming, nettsjinsteande de oanwêzigens fan ferskate technologyen lykas DNSSEC / DANE, DNScrypt, DNS-over-TLS en DNS-over-HTTPS, stoppe. En as serveroplossingen, en guon fan har bestean al in lange tiid, wiid bekend en beskikber binne, lit har stipe fan kliïntsoftware folle te winskjen oer.

Gelokkich feroaret de situaasje. Benammen de ûntwikkelders fan 'e populêre Firefox-blêder ferklearre oer plannen om standert stipemodus yn te skeakeljen DNS-oer-HTTPS (DoH) gau. Dit soe helpe om it DNS-ferkear fan 'e WWW-brûker te beskermjen tsjin de boppesteande bedrigingen, mar kin mooglik nije yntrodusearje.

1. DNS-over-HTTPS problemen

Op it earste each feroarsaket de begjinnende massale yntroduksje fan DNS-over-HTTPS yn ynternetsoftware allinich in positive reaksje. De duvel, lykas se sizze, is lykwols yn 'e details.

It earste probleem dat de omfang fan it wiidferspraat gebrûk fan DoH beheint is har fokus allinich op webferkear. Yndied, it HTTP-protokol en syn hjoeddeistige ferzje HTTP/2, wêrop DoH is basearre, binne de basis fan it WWW. Mar it ynternet is net allinich it web. D'r binne in protte populêre tsjinsten, lykas e-post, ferskate instant messengers, triemferfiersystemen, multimediastreaming, ensfh., dy't gjin HTTP brûke. Sa, nettsjinsteande de waarnimming troch in protte fan DoH as in panacea, it blykt te wêzen net fan tapassing sûnder ekstra (en ûnnedige) ynspannings foar wat oars as browser technologyen. Trouwens, DNS-over-TLS liket in folle mear wurdich kandidaat foar dizze rol, dy't de ynkapseling fan standert DNS-ferkear ymplementearret yn it feilige standert TLS-protokol.

It twadde probleem, dat potinsjeel folle wichtiger is as it earste, is it feitlike ferlitten fan 'e ynherinte desintralisaasje fan DNS troch ûntwerp yn it foardiel fan it brûken fan in inkele DoH-tsjinner opjûn yn' e browserynstellingen. Mozilla suggerearret benammen in tsjinst fan Cloudflare te brûken. In ferlykbere tsjinst waard ek lansearre troch oare promininte ynternetfigueren, benammen Google. It docht bliken dat de ymplemintaasje fan DNS-over-HTTPS yn 'e foarm wêryn't it op it stuit foarsteld wurdt allinich de ôfhinklikens fan ein brûkers op' e grutste tsjinsten fergruttet. It is gjin geheime dat de ynformaasje dy't analyse fan DNS-fragen kin leverje kin noch mear gegevens deroer sammelje, en ek de krektens en relevânsje dêrfan ferheegje.

Yn dit ferbân wie en bliuwt de skriuwer in oanhinger fan 'e massale ymplemintaasje net fan DNS-over-HTTPS, mar fan DNS-over-TLS tegearre mei DNSSEC / DANE as in universele, feilige en net befoarderlik foar fierdere sintralisaasje fan it ynternetmiddels foar it garandearjen fan de feiligens fan DNS-ferkear. Spitigernôch, foar fansels redenen, kin men net ferwachtsje in rappe yntroduksje fan massa-stipe foar DoH-alternativen yn kliïntsoftware, en it is noch altyd it domein fan befeiligingstechnology-entûsjasters.

Mar om't wy no DoH hawwe, wêrom net brûke nei it ûntkommen fan mooglike tafersjoch troch bedriuwen fia har servers nei ús eigen DNS-over-HTTPS-tsjinner?

2. DNS-over-HTTPS protokol

As jo ​​sjogge nei de standert RFC8484 it beskriuwen fan it DNS-over-HTTPS-protokol, kinne jo sjen dat it yn feite in web-API is wêrmei jo in standert DNS-pakket yn it HTTP/2-protokol ynkapselje kinne. Dit wurdt ymplementearre fia spesjale HTTP-headers, lykas ek konverzje fan it binêre formaat fan oerdroegen DNS-gegevens (sjoch. RFC1035 en folgjende dokuminten) yn in formulier wêrmei jo se kinne ferstjoere en ûntfange, en ek wurkje mei de nedige metadata.

Neffens de standert wurde allinich HTTP/2 en in feilige TLS-ferbining stipe.

It ferstjoeren fan in DNS-fersyk kin dien wurde mei de standert GET- en POST-metoaden. Yn it earste gefal wurdt it fersyk omfoarme ta in base64URL-kodearre tekenrige, en yn it twadde, troch it lichem fan it POST-fersyk yn binêre foarm. Yn dit gefal wurdt in spesjale MIME-gegevenstype brûkt tidens it DNS-oanfraach en antwurd applikaasje/dns-berjocht.

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: <FILE>" to save to a file.
* Failed writing body (0 != 45)
* stopped the pause stream!
* Connection #0 to host eprove.net left intact

Let ek op de titel cache-kontrôle: yn it antwurd fan de webtsjinner. Yn de parameter max-leeftyd befettet de TTL-wearde foar it DNS-record dat wurdt weromjûn (of de minimale wearde as in set fan har wurdt weromjûn).

Op grûn fan it boppesteande bestiet it funksjonearjen fan in DoH-tsjinner út ferskate stadia.

  • Untfang in HTTP-fersyk. As dit in GET is, dekodearje dan it pakket fan base64URL-kodearring.
  • Stjoer dit pakket nei de DNS-tsjinner.
  • Krij in antwurd fan 'e DNS-tsjinner
  • Fyn de minimale TTL-wearde yn 'e ûntfongen records.
  • Jou in antwurd werom nei de kliïnt fia HTTP.

3. Jo eigen DNS-over-HTTPS-tsjinner

De ienfâldichste, rapste en meast effektive manier om jo eigen DNS-over-HTTPS-tsjinner út te fieren is in HTTP/2-webserver te brûken H2O, dêr't de skriuwer al koart oer skreaun hat (sjoch "High Performance H2O Web Server").

Dizze kar wurdt stipe troch it feit dat alle koade fan jo eigen DoH-tsjinner folslein kin wurde ymplementearre mei de tolk yntegreare yn H2O sels mruby. Neist de standert biblioteken, om gegevens út te wikseljen mei de DNS-tsjinner, hawwe jo de (mrbgem) Socket-bibleteek nedich, dy't, gelokkich, al is opnommen yn 'e hjoeddeistige ûntwikkelingferzje fan H2O 2.3.0-beta2 oanwêzich yn FreeBSD-poarten. It is lykwols net dreech om it ta te foegjen oan elke foarige ferzje troch it repository te klonen Socket biblioteken nei de katalogus /deps foar kompilaasje.

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
Клонирование в «mruby-socket»…
remote: Enumerating objects: 385, done.
remote: Total 385 (delta 0), reused 0 (delta 0), pack-reused 385
Получение объектов: 100% (385/385), 98.02 KiB | 647.00 KiB/s, готово.
Определение изменений: 100% (208/208), готово.
root@beta:/usr/ports/www/h2o/work/h2o-2.2.6/deps # ll
total 181
drwxr-xr-x   9 root  wheel  18 12 авг.  16:09 brotli/
drwxr-xr-x   2 root  wheel   4 12 авг.  16:09 cloexec/
drwxr-xr-x   2 root  wheel   5 12 авг.  16:09 golombset/
drwxr-xr-x   4 root  wheel  35 12 авг.  16:09 klib/
drwxr-xr-x   2 root  wheel   5 12 авг.  16:09 libgkc/
drwxr-xr-x   4 root  wheel  26 12 авг.  16:09 libyrmcds/
drwxr-xr-x  13 root  wheel  32 12 авг.  16:09 mruby/
drwxr-xr-x   5 root  wheel  11 12 авг.  16:09 mruby-digest/
drwxr-xr-x   5 root  wheel  10 12 авг.  16:09 mruby-dir/
drwxr-xr-x   5 root  wheel  10 12 авг.  16:09 mruby-env/
drwxr-xr-x   4 root  wheel   9 12 авг.  16:09 mruby-errno/
drwxr-xr-x   5 root  wheel  14 12 авг.  16:09 mruby-file-stat/
drwxr-xr-x   5 root  wheel  10 12 авг.  16:09 mruby-iijson/
drwxr-xr-x   5 root  wheel  11 12 авг.  16:09 mruby-input-stream/
drwxr-xr-x   6 root  wheel  11 12 авг.  16:09 mruby-io/
drwxr-xr-x   5 root  wheel  10 12 авг.  16:09 mruby-onig-regexp/
drwxr-xr-x   4 root  wheel  10 12 авг.  16:09 mruby-pack/
drwxr-xr-x   5 root  wheel  10 12 авг.  16:09 mruby-require/
drwxr-xr-x   6 root  wheel  10 12 сент. 16:10 mruby-socket/
drwxr-xr-x   2 root  wheel   9 12 авг.  16:09 neverbleed/
drwxr-xr-x   2 root  wheel  13 12 авг.  16:09 picohttpparser/
drwxr-xr-x   2 root  wheel   4 12 авг.  16:09 picotest/
drwxr-xr-x   9 root  wheel  16 12 авг.  16:09 picotls/
drwxr-xr-x   4 root  wheel   8 12 авг.  16:09 ssl-conservatory/
drwxr-xr-x   8 root  wheel  18 12 авг.  16:09 yaml/
drwxr-xr-x   2 root  wheel   8 12 авг.  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
...

De webserverkonfiguraasje is oer it algemien standert.

root@beta:/usr/ports/www/h2o #  cd /usr/local/etc/h2o/
root@beta:/usr/local/etc/h2o # cat h2o.conf
# this sample config gives you a feel for how h2o can be used
# and a high-security configuration for TLS and HTTP headers
# see https://h2o.examp1e.net/ for detailed documentation
# and h2o --help for command-line options and settings

# v.20180207 (c)2018 by Max Kostikov http://kostikov.co e-mail: [email protected]

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

De ienige útsûndering is de URL handler /dns-query wêrfoar ús DNS-over-HTTPS-tsjinner, skreaun yn mruby en oproppen fia de handleropsje, eins ferantwurdlik is 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: [email protected]

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") < 192
                        shift = str.index("x00", shift) + 5
                        if ttl == 0 # skip question section
                            next
                        end
                    end
                    shift += 6
                    curttl = str[shift, 4].unpack1('N')
                    shift += str[shift + 4, 2].unpack1('n') + 6 # responce data size
                    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' }, [ "Unsupported Media Type" ]]
    end
}

Tink derom dat de lokale caching-tsjinner ferantwurdlik is foar it ferwurkjen fan DNS-pakketten, yn dit gefal Fallen fan 'e standert FreeBSD-distribúsje. Ut in feiligens eachpunt is dit de optimale oplossing. Neat foarkomt jo lykwols om te ferfangen localhost nei in oar DNS-adres dat jo fan doel binne te brûken.

root@beta:/usr/local/etc/h2o # local-unbound verison
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 [email protected]
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          *:*

Alles wat oerbliuwt is H2O opnij te starten en te sjen wat der fan komt.

root@beta:/usr/local/etc/h2o # service h2o restart
Stopping h2o.
Waiting for PIDS: 69871.
Starting h2o.
start_server (pid:70532) starting now...

4. Testing

Dat, lit ús de resultaten kontrolearje troch opnij in testfersyk te stjoeren en te sjen nei it netwurkferkear mei it nut 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: <FILE>" 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

De útfier lit sjen hoe't it fersyk om it adres op te lossen example.com waard ûntfongen en mei súkses ferwurke troch de DNS-tsjinner.

No bliuwt it allinich om ús server te aktivearjen yn 'e Firefox-blêder. Om dit te dwaan, moatte jo ferskate ynstellings feroarje op 'e konfiguraasjesiden oer: config.

Wy ferheegje ús DNS-over-HTTPS-tsjinner

As earste is dit it adres fan ús API wêryn de browser DNS-ynformaasje sil freegje network.trr.uri. It is ek oan te rieden om de domein-IP fan dizze URL op te jaan foar feilige IP-resolúsje mei de browser sels sûnder tagong te krijen ta DNS yn network.trr.bootstrapAddress. En as lêste, de parameter sels netwurk.trr.modus ynklusyf it brûken fan DoH. It ynstellen fan de wearde op "3" sil de blêder twinge om eksklusyf DNS-over-HTTPS te brûken foar nammeresolúsje, wylst de betrouberder en feiliger "2" prioriteit sil jaan oan DoH, wêrtroch de standert DNS-opsykjen as in fallback-opsje bliuwt.

5. WINST!

Wie it artikel nuttich? Wês dan asjebleaft net ferlegen en stypje mei jild fia it donaasjeformulier (hjirûnder).

Boarne: www.habr.com

Add a comment