Projet

Général

Profil

Actions

Wiki » Historique » Révision 19

« Précédent | Révision 19/34 (diff) | Suivant »
sacha, 10/12/2023 05:10


DNS récursif Ouvert DoH DoT & haute disponibilité

45.67.81.23 - dns.aquilenet.fr

On propos un DNS récursif ouvert cf https://www.aquilenet.fr/services/dns/ https://dns.aquilenet.fr/
On a mis aussi un DNS ouvert sur l'IP 45.67.81.23 car facile à retenir, avec l'adresse de dns.aquilenet.fr (voir http://dns.aquilenet.fr) . Nous en avons profité pour ajouter DoH et DoT et d'avoir cette adresse en IP Virtuelle partagée entre pan1 et pan2.
Cette IP virtuelle est portée par Keepalived sur pan1 le primaire et pan2 le secondaire. Si le démon Keepalived est arrêté sur pan1, l'ip bascule sur pan2. Si on redémarre le démon sur pan1, ce dernier reprend l'IP: pratique pour les mises à jour, le service continue à fonctionner.

Pan[1|2] portent l'ip 45.67.81.23 gracieusement offerte par les copains de https://yaal.coop/fr un vlan est dédié sur ce réseau de l'hyperviseur.

Configurations communes à pan1 et pan2

Nginx

  • /etc/nginx/sites-enabled/default
server {
    listen       45.67.81.23:80 ;
    listen       [::]:80 ;

    location ~ /.well-known/acme-challenge {
        allow all;
        root /var/www/letsencrypt;
    }
    location / {
        return 301 https://dns.aquilenet.fr;
        }

    server_name dns.aquilenet.fr; # managed by Certbot

    location ~ /.well-known/acme-challenge {
        allow all;
        root /var/www/letsencrypt;
    }
}
server {
    listen       45.67.81.23:443 ssl http2;
    listen       [::]:443 ssl http2;
    root /var/www/dns.aquilenet.fr;
    index index.htm;

    ssl_certificate     /etc/unbound/ssl/dns.aquilenet.fr/fullchain.pem;
    ssl_certificate_key /etc/unbound/ssl/dns.aquilenet.fr/privkey.pem;
location /dns-query {
    grpc_pass grpc://127.0.0.1:443;
        }
}

root@pan1:/etc/unbound/ssl# vi /var/www/dns.aquilenet.fr/index.htm 
root@pan1:/etc/unbound/ssl# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host noprefixroute 
       valid_lft forever preferred_lft forever
2: enX0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:16:3e:01:01:21 brd ff:ff:ff:ff:ff:ff
    inet 45.67.81.21/24 brd 45.67.81.255 scope global enX0
       valid_lft forever preferred_lft forever
    inet 45.67.81.23/32 scope global enX0
       valid_lft forever preferred_lft forever
    inet6 fe80::216:3eff:fe01:121/64 scope link 
       valid_lft forever preferred_lft forever
root@pan1:/etc/unbound/ssl# vi /var/www/dns.aquilenet.fr/index.htm 
root@pan1:/etc/unbound/ssl# vi /var/www/dns.aquilenet.fr/index.htm 
root@pan1:/etc/unbound/ssl# vi /var/www/dns.aquilenet.fr/index.htm 
root@pan1:/etc/unbound/ssl# vi /var/www/dns.aquilenet.fr/index.htm 
root@pan1:/etc/unbound/ssl# cat /etc/nginx/sites-enabled/default 
server {
    listen       45.67.81.23:80 ;
    listen       [::]:80 ;

    location ~ /.well-known/acme-challenge {
        allow all;
        root /var/www/letsencrypt;
    }
    location / {
        return 301 https://dns.aquilenet.fr;
        }

    server_name dns.aquilenet.fr; # managed by Certbot

    location ~ /.well-known/acme-challenge {
        allow all;
        root /var/www/letsencrypt;
    }
}
server {
    listen       45.67.81.23:443 ssl http2;
    listen       [::]:443 ssl http2;
    root /var/www/dns.aquilenet.fr;
    index index.htm;

    ssl_certificate     /etc/unbound/ssl/dns.aquilenet.fr/fullchain.pem;
    ssl_certificate_key /etc/unbound/ssl/dns.aquilenet.fr/privkey.pem;
location /dns-query {
    grpc_pass grpc://127.0.0.1:443;
        }
}


Unbound

server {
    listen       45.67.81.23:80 ;
    listen       [::]:80 ;

    location ~ /.well-known/acme-challenge {
        allow all;
        root /var/www/letsencrypt;
    }
    location / {
        return 301 https://dns.aquilenet.fr;
        }

    server_name dns.aquilenet.fr; # managed by Certbot

    location ~ /.well-known/acme-challenge {
        allow all;
        root /var/www/letsencrypt;
    }
}
server {
    listen       45.67.81.23:443 ssl http2;
    listen       [::]:443 ssl http2;
    root /var/www/dns.aquilenet.fr;
    index index.htm;

    ssl_certificate     /etc/unbound/ssl/dns.aquilenet.fr/fullchain.pem;
    ssl_certificate_key /etc/unbound/ssl/dns.aquilenet.fr/privkey.pem;
location /dns-query {
    grpc_pass grpc://127.0.0.1:443;
        }
}

root@pan1:/etc/unbound/ssl# cat /etc/unbound/unbound
cat: /etc/unbound/unbound: Aucun fichier ou dossier de ce type
root@pan1:/etc/unbound/ssl# cat /etc/unbound/unbound.conf
include-toplevel: "/etc/unbound/unbound.conf.d/*.conf"

server:
    verbosity: 1 

    access-control: 0.0.0.0/0 allow 
    interface: 45.67.81.23
#    interface: 45.67.81.23@443
    interface: 45.67.81.23@853

http-notls-downstream: yes
    interface: 127.0.0.1@443

    port: 53
    tls-port: 853
    https-port: 443

    root-hints: "/usr/share/dns/root.hints"

    harden-glue: yes
    harden-large-queries: yes
    harden-dnssec-stripped: yes
    harden-below-nxdomain: yes
    harden-referral-path: yes
    harden-algo-downgrade: no # false positives with improperly configured zones
    use-caps-for-id: no # makes lots of queries fail
    edns-buffer-size: 1232
    rrset-roundrobin: yes
    cache-min-ttl: 300
    cache-max-ttl: 86400
    serve-expired: yes
    harden-algo-downgrade: yes
    harden-short-bufsize: yes
    hide-identity: yes
    identity: "dns.aquilenet.fr"
    hide-version: yes
    do-daemonize: no
    neg-cache-size: 4m
    qname-minimisation: yes
    deny-any: yes
    minimal-responses: yes
    prefetch: yes
    prefetch-key: yes
    num-threads: 2 
    msg-cache-size: 50m
    rrset-cache-size: 100m
    so-reuseport: yes
    so-rcvbuf: 4m
    so-sndbuf: 4m
    unwanted-reply-threshold: 100000
    log-queries: no
    log-replies: no
    log-servfail: no
    log-local-actions: no
    logfile: /dev/null

    # imposer la QNAME minimisation (RFC 7816)
    qname-minimisation: yes
    # même si le serveur faisant autorité ne le veut pas
    #   après discussion, il est possible que cette option ne soit
    #   pas recommandée dans le cadre d'un résolveur ouvert
    qname-minimisation-strict: yes

   # DoH
   tls-service-key: "/etc/letsencrypt/live/dns.aquilenet.fr/privkey.pem"
   tls-service-pem: "/etc/letsencrypt/live/dns.aquilenet.fr/cert.pem"

AppArmor

  • /etc/apparmor.d/usr.sbin.unbound
    décomenter:
    include

  • /etc/apparmor.d/local/usr.sbin.unbound

    /etc/letsencrypt/archive/** r,
    /etc/letsencrypt/live/** r,
    

    appliquer avec: apparmor_parser -r /etc/apparmor.d/usr.sbin.unbound

Iptables

*filter
:INPUT DROP
:FORWARD DROP
:OUTPUT ACCEPT

-A INPUT ! -i lo -s 127.0.0.0/8 -j DROP
-A INPUT -s 0.0.0.0 -i eth0 -j DROP
-A INPUT -d 0.0.0.0 -i eth0 -j DROP
-A INPUT -s 255.255.255.255 -i eth0 -j DROP
-A INPUT -d 255.255.255.255 -i eth0 -j DROP
-A INPUT -m conntrack --ctstate INVALID -j DROP

-A INPUT -p tcp -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p udp ! --dport 53 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A OUTPUT -o lo -j ACCEPT

# SSH no more 10 attempts in 3 min
-A INPUT -i eth0 -p tcp --dport 55555 -m recent --name SSH --update --hitcount 10 --seconds 180 -j DROP
-A INPUT -i eth0 -p tcp --dport 55555 -m recent --name SSH --set -j ACCEPT

# Web
-A INPUT -p tcp --dport 80 -j ACCEPT

# smtp
-A INPUT -i eth0 -p tcp --dport 25 -j ACCEPT
# smtps
-A INPUT -i eth0 -p tcp --dport 465 -j ACCEPT
# submission
-A INPUT -i eth0 -p tcp --dport 587 -j ACCEPT

# DNS
# récursif, lisible par nos abonnés seulement
-N dns_limit
-A dns_limit -s 185.233.100.0/22 -j ACCEPT
-A INPUT -d 45.67.81.23 -p tcp -m tcp --dport 53 -j dns_limit
-A INPUT -d 45.67.81.23 -p udp -m udp --dport 53 -j dns_limit
-A INPUT -d 45.67.81.23 -p tcp -m tcp --dport 443 -j dns_limit
-A INPUT -d 45.67.81.23 -p tcp -m tcp --dport 853 -j dns_limit

# mais aussi par le reste d'Internet, mais en rate-limit
-A dns_limit -m string --algo bm --hex-string "|00 00 ff 00 01|" --from 28 -m hashlimit --hashlimit-above 1/sec --hashlimit-burst 2 --hashlimit-mode srcip --hashlimit-name RL-DNS-ANY-v4 --hashlimit-srcmask 28 -m comment --comment "RATE-LIMIT DNS ANY QTYPE 1/s burst 2" -j DROP
-A dns_limit -m string --algo bm --hex-string "|00 00 ff 00 01|" --from 28 -m hashlimit --hashlimit-above 20/min --hashlimit-mode srcip --hashlimit-name RL-DNS-ANY-v4-LOG --hashlimit-srcmask 28 -m comment --comment "RATE-LIMIT DNS ANY QTYPE 20/min" -j LOG --log-prefix "DNS-FLOOD "
-A dns_limit -m hashlimit --hashlimit-above 10/sec --hashlimit-burst 100 --hashlimit-mode srcip --hashlimit-name DNS --hashlimit-srcmask 28 -m comment --comment "RATE-LIMIT DNS 10/src burst 100" -j DROP
-A dns_limit -j ACCEPT

# ICMP
-A INPUT -p icmp -j ACCEPT
-A INPUT -p icmp --icmp-type echo-request -m limit --limit 1/s -j ACCEPT

# NRPE
-A INPUT -s 185.233.100.230 -p tcp --dport 5666 -j ACCEPT

# Keepalived
-A INPUT -s 45.67.81.0/24 -p vrrp -j ACCEPT

COMMIT


## Configurations spécifiques

### Génération du certificat SSL sur Hades

certbot --apache --agree-tos --email sysop@aquilenet.fr -d dns.aquilenet.fr
chgrp bind /etc/letsencrypt/live/dns.aquilenet.fr/privkey.pem
chmod 0640 /etc/letsencrypt/live/dns.aquilenet.fr/privkey.pem
openssl dhparam -out /etc/bind/dhparam.pem 4096

Copie du certificat sur gaia

* /etc/letsencrypt/renewal-hooks/post/sync-cert.sh

#!/bin/bash
rsync -aPHSA /etc/letsencrypt/archive/dns.aquilenet.fr gaia:/etc/letsencrypt/archive/

### Clée sdns (auth par épinglage de la clé / key pining)

* A partir de la clé

hades|19:21:08|:~# openssl rsa -in /etc/letsencrypt/live/dns.aquilenet.fr/privkey.pem -outform der -pubout | openssl dgst -sha256 -binary | openssl enc -base64
writing RSA key
lBk09CRIPJ+J4O9rmbvJkEiGYoH5r9rxOIQdxkYxyII=

* ou à partir du certificat

hades|19:22:21|:~# openssl x509 -in /etc/letsencrypt/live/dns.aquilenet.fr/cert.pem -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
writing RSA key
lBk09CRIPJ+J4O9rmbvJkEiGYoH5r9rxOIQdxkYxyII=

### Keepalived

#### Hades Master

* /etc/keepalived/keepalived.conf

global_defs {
notification_email {
sysop@aquilenet.fr
}
notification_email_from vrrp-dns@aquilenet.fr.fr
smtp_server localhost
smtp_connect_timeout 30
}

vrrp_instance DNS {
state MASTER
interface eth0
virtual_router_id 101
priority 100
advert_int 2
smtp_alert
unicast_src_ip 185.233.100.16
unicast_peer {
185.233.100.2
}
virtual_ipaddress {
45.67.81.23/32 dev eth1 scope global # VIP
}
}

#### Gaia Slave

* /etc/keepalived/keepalived.conf

global_defs {
notification_email {
sysop@aquilenet.fr
}
notification_email_from vrrp-dns@aquilenet.fr.fr
smtp_server localhost
smtp_connect_timeout 30
}

vrrp_instance DNS {
state BACKUP
interface eth0
virtual_router_id 101
priority 50
advert_int 2
smtp_alert
unicast_src_ip 185.233.100.2
unicast_peer {
185.233.100.16
}
virtual_ipaddress {
45.67.81.23/32 dev eth1 scope global # VIP
}
}

## Validation

* test du service

dig +short dns.aquilenet.fr @45.67.81.23
45.67.81.23
dig +https +short dns.aquilenet.fr @45.67.81.23

45.67.81.23
dig +tls +short dns.aquilenet.fr @45.67.81.23

45.67.81.23

* bascule keepalived

sur hades: `systemctl stop keepalived`
sur gaia: `ip a |grep /32`doit donner "inet 45.67.81.23/32 scope global eth1"
http://dns.aquilenet.fr/host permet de savoir quel est le serveur qui porte l'IP virtuelle

## Indexes de serveurs DNS ouverts:

https://github.com/curl/curl/wiki/DNS-over-HTTPS#publicly-available-servers <= fait
https://diyisp.org/dokuwiki/doku.php?id=technical:dnsresolver <= fait
https://diyisp.org/dokuwiki/doku.php?id=technical:dnsresolver <= fait
https://dnsprivacy.org/public_resolvers/

## Ressources

https://www.bortzmeyer.org/doh-bortzmeyer-fr-policy.html
https://www.bortzmeyer.org/7858.html
https://kb.isc.org/docs/aa-01386

Mis à jour par sacha il y a 5 mois · 19 révisions