Como configurar seu servidor DNS-over-HTTPS

Vários aspectos da exploração do DNS já foram abordados pelo autor em diversas de artigos publicações no blog. Nesse contexto, o foco sempre esteve na melhoria da segurança deste serviço chave para toda a Internet.

Como configurar seu servidor DNS-over-HTTPS

Até recentemente, apesar da evidente vulnerabilidade do tráfego DNS, que, até hoje, na maior parte, é transmitido de forma aberta, para ações mal-intencionadas por parte de provedores que buscam aumentar sua receita através da inserção de anúncios no conteúdo, autoridades governamentais e censura, além de criminosos, o processo de fortalecimento de sua proteção, apesar da existência de várias tecnologias como DNSSEC/DANE, DNScrypt, DNS-over-TLS e DNS-over-HTTPS, estava estagnado. E embora as soluções de servidor, algumas das quais existem há bastante tempo, sejam amplamente conhecidas e acessíveis, o suporte por parte do software cliente deixa muito a desejar.

Felizmente, a situação está mudando. Em particular, os desenvolvedores do popular navegador Firefox declararam anunciaram planos para incluir o modo DNS-over-HTTPS (DoH) por padrão em breve. Isso deve ajudar a proteger o tráfego DNS dos usuários da WWW contra as ameaças mencionadas acima, mas potencialmente poderá causar novas.

1. Problemas do DNS-over-HTTPS

À primeira vista, a adoção em massa do DNS-over-HTTPS em softwares que operam na Internet gera apenas reações positivas. Contudo, como se diz, o diabo está nos detalhes.

O primeiro problema que limita a adoção em massa do DoH é sua orientação exclusivamente ao tráfego da web. De fato, o protocolo HTTP e sua versão atual, HTTP/2, na qual o DoH se baseia, são a base da WWW. Mas a Internet não é apenas web. Existem muitos serviços populares, como e-mail, diversos mensageiros, sistemas de transferência de arquivos, streaming de multimídia, etc., que não utilizam HTTP. Assim, apesar de muitos enxergarem o DoH como uma panaceia, ele se torna inaplicável sem esforços adicionais (e desnecessários) para qualquer coisa além das tecnologias de navegador. A propósito, o DNS-over-TLS surge como um candidato muito mais digno para esse papel, que encapsula o tráfego DNS padrão em um protocolo TLS seguro.

O segundo problema, que pode ser significativamente mais importante do que o primeiro, é a efetiva renúncia à descentralização inerente ao DNS por design em favor do uso de um único servidor DoH especificado nas configurações do navegador. Em particular, a Mozilla sugere o uso do serviço da Cloudflare. Serviços semelhantes também foram lançados por outras figuras notáveis da Internet, em particular o Google. Assim, a implementação do DNS-over-HTTPS na forma como está proposta atualmente apenas aumenta a dependência dos usuários finais em relação aos maiores serviços. Não é segredo que as informações que podem ser extraídas da análise das requisições DNS podem coletar ainda mais dados sobre o usuário, além de aumentar a precisão e a relevância desses dados.

Nesse contexto, o autor foi e continua sendo um defensor da implementação em massa não do DNS-over-HTTPS, mas do DNS-over-TLS junto com DNSSEC/DANE como um meio universal, seguro e que não promove uma maior centralização da Internet para garantir a segurança do tráfego DNS. Infelizmente, não se pode esperar uma rápida implementação de suporte em massa para alternativas ao DoH em software cliente devido a razões compreensíveis, e sua sorte atualmente recai sobre os entusiastas de tecnologias seguras.

Mas, uma vez que agora temos DoH, por que não usá-lo, afastando a potencial vigilância por parte das corporações através de seus servidores em um servidor DNS-over-HTTPS próprio?

2. Protocolo DNS-over-HTTPS

Ao olhar para o padrão RFC8484 que descreve o protocolo DNS-over-HTTPS, pode-se ver que ele representa, essencialmente, uma API web que permite encapsular um pacote DNS padrão em protocolo HTTP/2. Isso é realizado através de cabeçalhos HTTP especiais, bem como da conversão do formato binário dos dados DNS transmitidos (veja RFC1035 e documentos subsequentes) em uma forma que permite transmiti-los e recebê-los, bem como trabalhar com os metadados necessários.

O padrão suporta apenas HTTP/2 e conexão segura TLS.

O envio de requisições DNS pode ser feito pelos métodos padrão GET e POST. No primeiro caso, a requisição é transformada em uma string base64URL-encoded, enquanto no segundo — através do corpo da requisição POST em formato binário. Durante isso, um tipo MIME especial de dados é usado na requisição e na resposta DNS 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

Observe também o cabeçalho cache-control: na resposta do servidor web. O parâmetro max-age contém o valor TTL para o registro DNS retornado (ou o valor mínimo se um conjunto for retornado).

Com base no que foi dito acima, o funcionamento do servidor DoH ocorre em várias etapas.

  • Receber a solicitação HTTP. Se for um GET, decodifique o pacote a partir da codificação base64URL.
  • Enviar esse pacote para o servidor DNS.
  • Receber a resposta do servidor DNS
  • Encontrar o valor mínimo de TTL nos registros recebidos.
  • Devolver a resposta ao cliente via HTTP.

3. Seu próprio servidor DNS-over-HTTPS

A maneira mais simples, rápida e eficaz de executar seu próprio servidor DNS-over-HTTPS é usar um servidor web HTTP/2 H2O, sobre o qual o autor já escreveu brevemente (veja "Servidor web de alto desempenho H2O«).

A favor dessa escolha está o fato de que todo o código do próprio servidor DoH pode ser implementado completamente utilizando o interpretador integrado do H2O mruby. Além das bibliotecas padrão, é necessária a biblioteca (mrbgem) Socket para a troca de dados com o servidor DNS, que, felizmente, já está incluída na versão de desenvolvedor atual do H2O 2.3.0-beta2 presente nos ports do FreeBSD. No entanto, não é difícil adicioná-la em qualquer versão anterior clonando o repositório biblioteca Socket no diretório /deps antes da compilação.

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
===>  Licença MIT BSD2CLAUSE aceita pelo usuário
===>   h2o-2.2.6 depende do arquivo: /usr/local/sbin/pkg - encontrado
===> Buscando todos os arquivos de distribuição necessários pelo h2o-2.2.6 para construção
===>  Extraindo para h2o-2.2.6.
=> Verificação de SHA256 OK para h2o-h2o-v2.2.6_GH0.tar.gz.
===>   h2o-2.2.6 depende do arquivo: /usr/local/bin/ruby26 - encontrado
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
Clonando em "mruby-socket"…
remoto: Enumerando objetos: 385, feito.
remoto: Total 385 (delta 0), reutilizados 0 (delta 0), pack-reused 385
Recebendo objetos: 100% (385/385), 98.02 KiB | 647.00 KiB/s, pronto.
Determinação de mudanças: 100% (208/208), pronto.
root@beta:/usr/ports/www/h2o/work/h2o-2.2.6/deps # ll
total 181
drwxr-xr-x   9 root  wheel  18 12 ago  16:09 brotli/
drwxr-xr-x   2 root  wheel   4 12 ago  16:09 cloexec/
drwxr-xr-x   2 root  wheel   5 12 ago  16:09 golombset/
drwxr-xr-x   4 root  wheel  35 12 ago  16:09 klib/
drwxr-xr-x   2 root  wheel   5 12 ago  16:09 libgkc/
drwxr-xr-x   4 root  wheel  26 12 ago  16:09 libyrmcds/
drwxr-xr-x  13 root  wheel  32 12 ago  16:09 mruby/
drwxr-xr-x   5 root  wheel  11 12 ago  16:09 mruby-digest/
drwxr-xr-x   5 root  wheel  10 12 ago  16:09 mruby-dir/
drwxr-xr-x   5 root  wheel  10 12 ago  16:09 mruby-env/
drwxr-xr-x   4 root  wheel   9 12 ago  16:09 mruby-errno/
drwxr-xr-x   5 root  wheel  14 12 ago  16:09 mruby-file-stat/
drwxr-xr-x   5 root  wheel  10 12 ago  16:09 mruby-iijson/
drwxr-xr-x   5 root  wheel  11 12 ago  16:09 mruby-input-stream/
drwxr-xr-x   6 root  wheel  11 12 ago  16:09 mruby-io/
drwxr-xr-x   5 root  wheel  10 12 ago  16:09 mruby-onig-regexp/
drwxr-xr-x   4 root  wheel  10 12 ago  16:09 mruby-pack/
drwxr-xr-x   5 root  wheel  10 12 ago  16:09 mruby-require/
drwxr-xr-x   6 root  wheel  10 12 set 16:10 mruby-socket/
drwxr-xr-x   2 root  wheel   9 12 ago  16:09 neverbleed/
drwxr-xr-x   2 root  wheel  13 12 ago  16:09 picohttpparser/
drwxr-xr-x   2 root  wheel   4 12 ago  16:09 picotest/
drwxr-xr-x   9 root  wheel  16 12 ago  16:09 picotls/
drwxr-xr-x   4 root  wheel   8 12 ago  16:09 ssl-conservatory/
drwxr-xr-x   8 root  wheel  18 12 ago  16:09 yaml/
drwxr-xr-x   2 root  wheel   8 12 ago  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
...

A configuração do servidor web, em geral, é padrão.

root@beta: /usr/ports/www/h2o # cd /usr/local/etc/h2o/
root@beta: /usr/local/etc/h2o # cat h2o.conf
# esta configuração de exemplo dá uma ideia de como h2o pode ser usado
# e uma configuração de alta segurança para TLS e cabeçalhos HTTP
# veja https://h2o.examp1e.net/ para documentação detalhada
# e h2o --help para opções e configurações de linha de comando

# v.20180207 (c)2018 por Max Kostikov http://kostikov.co e-mail: max@kostikov.co

usuário: www
arquivopid: /var/run/h2o.pid
log-de-acesso:
    caminho: /var/log/h2o/h2o-access.log
    formato: "%h %v %l %u %t "%r" %s %b "%{Referer}i" "%{User-agent}i""
log-de-erro: /var/log/h2o/h2o-error.log

expira: off
compress: on
arquivo.listagem: off
arquivo.enviar-comprimido: on

arquivo.indice: [ 'index.html', 'index.php' ]

ouvir:
    porta: 80
ouvir:
    porta: 443
    ssl:
        conjunto-de-cifras: 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
        preferência-de-cifra: servidor
        arquivo-dh: /etc/ssl/dhparams.pem
        arquivo-certificado: /usr/local/etc/letsencrypt/live/eprove.net/fullchain.pem
        arquivo-chave: /usr/local/etc/letsencrypt/live/meu.dominio/privkey.pem

hosts:
    "*.meu.dominio":
        caminhos: &go_tls
            "/":
                redirecionar:
                    status: 301
                    url: https://meu.dominio/
    "meu.dominio:80":
        caminhos: *go_tls
    "meu.dominio:443":
        header.adicionar: "Strict-Transport-Security: max-age=15768000; includeSubDomains; preload"
        caminhos:
            "/dns-query":
               mruby.handler-file: /usr/local/etc/h2o/h2odoh.rb

A única exceção é o manipulador de URL /dns-query pelo qual é responsável, na verdade, nosso servidor DNS-over-HTTPS, escrito em mruby e chamado através da opção de manipulador mruby.handler-file.

root@beta:/usr/local/etc/h2o # cat h2odoh.rb
# H2O HTTP/2 servidor web como serviço DNS-over-HTTP
# 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=/,'')
                # decodificar base64URL
                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' }, [ "Pedido Inválido" ]]
        else
            # --- solicitar ao servidor DNS
            sock = UDPSocket.new
            sock.connect("localhost", 53)
            sock.send(req, 0)
            str = sock.recv(4096)
            sock.close
            # --- encontrar o menor TTL na resposta
            nans = str[6, 2].unpack1('n') # número de respostas
            if nans > 0 # nenhuma falha de DNS
                shift = 12
                ttl = 0
                while nans > 0
                    # processar a compressão do nome de domínio
                    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' }, [ "Tipo de Mídia Não Suportado" ]]
    end
}

Observe que o tratamento dos pacotes DNS é feito pelo servidor local de cache, neste caso Unbound da entrega padrão do FreeBSD. Em termos de segurança, esta é a solução ideal. No entanto, nada impede que você substitua localhost pelo endereço de outro DNS que você pretende usar.

root@beta:/usr/local/etc/h2o # versão local-unbound
uso: local-unbound [opções]
        iniciar o daemon do unbound resolver DNS.
-h      esta ajuda
-c arquivo do arquivo de configuração a ser lido em vez de /var/unbound/unbound.conf
        o formato do arquivo é descrito em unbound.conf(5).
-d      não criar processo em segundo plano.
-p      não criar um arquivo de PID.
-v      verbose (mais vezes para aumentar a verbosidade)
Versão 1.8.1
bibliotecas vinculadas: mini-event interno (usa select), OpenSSL 1.1.1a-freebsd 20 Nov 2018
módulos vinculados: dns64 respip validator iterator
licenciado sob BSD, veja a LICENÇA no pacote de origem para detalhes.
Relate bugs para 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          *:*

Agora é necessário reiniciar o H2O e verificar o que resultou disso.

root@beta:/usr/local/etc/h2o # service h2o restart
Parando o h2o.
Aguardando PIDS: 69871.
Iniciando o h2o.
start_server (pid:70532) iniciando agora...

4. Testando

Então, vamos testar os resultados reenviando uma solicitação de teste e observando o tráfego de rede usando a ferramenta tcpdump.

root@beta/usr/local/etc/h2o # curl -H 'accept: application/dns-message' 'https://my.domain/dns-query?dns=q80BAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE'
Aviso: A saída binária pode bagunçar seu terminal. Use "--output -" para dizer
Aviso: curl para exibi-la no seu terminal mesmo assim, ou considere "--output
Aviso: " para salvar em um arquivo.
...
root@beta:~ # tcpdump -n -i lo0 udp port 53 -xx -XX -vv
tcpdump: escutando em lo0, tipo de link NULL (loopback BSD), tamanho de captura 262144 bytes
16:32:40.420831 IP (tos 0x0, ttl 64, id 37575, offset 0, flags [none], proto UDP (17), length 57, checksum ruim 0 (->e9ea)!)
    127.0.0.1.21070 > 127.0.0.1.53: [checksum udp ruim 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, checksum ruim 0 (->e9cb)!)
    127.0.0.1.53 > 127.0.0.1.21070: [checksum udp ruim 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 pacotes capturados
23 pacotes recebidos pelo filtro
0 pacotes descartados pelo kernel

Na saída, é visível como a solicitação de resolução de endereço example.com foi recebida e processada com sucesso pelo servidor DNS.

Agora, resta ativar nosso servidor no navegador Firefox. Para isso, precisamos alterar algumas configurações na página de configuração about:config.

Como configurar seu servidor DNS-over-HTTPS

Primeiro, este é o endereço da nossa API pela qual o navegador vai solicitar as informações DNS em network.trr.uri. Também é recomendável especificar o IP do domínio desse URL para a resolução segura em IP através do próprio navegador, sem chamar o DNS em network.trr.bootstrapAddress. E, finalmente, o próprio parâmetro network.trr.mode que ativa o uso de DoH. Definir o valor como "3" fará com que o navegador utilize exclusivamente DNS-over-HTTPS para a resolução de nomes, enquanto que o mais confiável e seguro "2" priorizará DoH, mantendo a chamada padrão ao DNS como uma opção de fallback.

5. LUCRO!

O artigo foi útil? Então, sinta-se à vontade para apoiar com doações através do formulário (abaixo).

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster