339 lines
17 KiB
Markdown
339 lines
17 KiB
Markdown
+++
|
||
|
||
title = "Passerelles VPN redondantes avec OpenBSD et BIRD"
|
||
date = 2014-08-22
|
||
aliases = [ "post/2014/08/22/Passerelles-VPN-redondantes-avec-OpenBSD-et-BIRD"]
|
||
[taxonomies]
|
||
tags = [ "système", "rézo", "openbsd" ]
|
||
+++
|
||
|
||
BIRD, c'est bon, mangez-en !
|
||
|
||
<!-- more -->
|
||
|
||
OpenBSD, c'est le papa de tous les IPSEC (enfin presque) et surtout OpenBSD, c'est truffé de petits outils pour rendre IPSEC plus résistant et résilient. On va voir aujourd'hui comment construire une passerelle VPN avec des morceaux de routage dynamique dedans.
|
||
|
||
# L'hypothèse de départ
|
||
|
||
On a un site A avec deux passerelles VPN et un routeur OSPF et un site B avec seulement deux routeurs VPN. Pour cette exemple, on va supposer que chaque site à son propre opérateur (même si pour des raisons de simplicité, ce sera le même réseau pour la maquette) avec suffisamment d'adresses IPv4/v6 publiques (au moins 3 par opérateur).
|
||
|
||
Basiquement, ça va ressembler à ça :
|
||
|
||

|
||
|
||
Je ne détaillerai pas volontairement la configuration de `pf` et `pfsync` (c'est pas le but et vous êtes assez grands pour le faire tout seul, faut pas déconner non plus).
|
||
|
||
# Configuration réseau
|
||
|
||
On va donc connement commencer par la configuration réseau. Sur le site A rien de particulier, si ce n'est la configuration de `carp` pour la passerelle en elle-même sur la patte Internet :
|
||
|
||
```
|
||
inet 100.64.0.254 255.255.255.0 NONE advbase 1 advskew 0 carpdev vio0 vhid 20 pass labiteadudule
|
||
```
|
||
|
||
C'est en théorie inutile sur la patte LAN puisqu'OSPF qui va se charger d'annoncer les bonnes routes. En l'occurence, les passerelles étant actives/passives, il va falloir s'arranger pour que la passerelle active annonce la route et que la passerelle passive n'annonce rien du tout.
|
||
|
||
Côté site B, pas grand chose de plus que cette configuration là, même astuce pour le `carp` :
|
||
```
|
||
inet 100.64.0.100 255.255.255.0 NONE advbase 1 advskew 0 carpdev vio0 vhid 30 pass lateteatoto
|
||
```
|
||
Sauf qu'il faut aussi prévoir un `carp` côté LAN pour servir de passerelle aux machines distantes :
|
||
```
|
||
inet 192.168.1.254 255.255.255.0 NONE advbase 1 advskew 0 carpdev vio1 vhid 40 pass cocolasticot
|
||
inet6 alias fddc:a021:7c20:1::254 64
|
||
```
|
||
L'adresse IPv6 ici est totalement dispensable (`rtadvd` sur OpenBSD permet d'annoncer des routes sur une interface `carp` ou d'annoncer des priorités, donc aucun intérêt) mais ça peut simplifier le débogage.
|
||
|
||
# Oh oui !! Mets-la moi dans le tunnel !!
|
||
|
||
Dernier petit point de configuration réseau : on va monter un tunnel `gif` entre les deux paires de routeurs. Ça nous permettra de gérer le routage sans avoir à se prendre la tête avec les tables IPSEC d'OpenBSD, qui peuvent être particulièrement pénibles dans certains cas. Il est à noter également que les fameuses tables en question ne sont pas des tables de routage, parce qu'IPSEC n'est pas un protocole de routage. C'est duraille hein, mais c'est comme ça.
|
||
|
||
Du coup `gif` et on se prend pas le chou.
|
||
|
||
Bref, la configuration est exactement symétrique entre le site A et le site B. On a donc en A :
|
||
```
|
||
tunnel 100.64.0.254 100.64.0.100
|
||
inet 169.254.0.0 255.255.255.254 169.254.0.1
|
||
inet6 fddc:a021:7c20:ffff::0 128 fddc:a021:7c20:ffff::1
|
||
```
|
||
Et en B :
|
||
```
|
||
tunnel 100.64.0.100 100.64.0.254
|
||
inet 169.254.0.1 255.255.255.254 169.254.0.0
|
||
inet6 fddc:a021:7c20:ffff::1 128 fddc:a021:7c20:ffff::0
|
||
```
|
||
Subtilité supplémentaire : on ouvre le tunnel entre les adresses publiques des interfaces `carp`. Comme ça, le tunnel n'est pas vraiment ouvert sur le routeur passif (en théorie, en pratique, il peut arriver qu'il tente d'initier le tunnel, mais on a un moyen très simple de l'en empêcher).
|
||
|
||
Normalement avec cette configuration-là, on a déjà une communication possible au moins dans le tunnel. Il va simplement nous manquer les routes de part et d'autre du tunnel. Ainsi depuis *bvpn-1* :
|
||
|
||
```
|
||
$ ping -c3 -I 169.254.0.1 169.254.0.0
|
||
PING 169.254.0.0 (169.254.0.0): 56 data bytes
|
||
64 bytes from 169.254.0.0: icmp_seq=0 ttl=255 time=0.646 ms
|
||
64 bytes from 169.254.0.0: icmp_seq=1 ttl=255 time=0.698 ms
|
||
64 bytes from 169.254.0.0: icmp_seq=2 ttl=255 time=0.635 ms
|
||
--- 169.254.0.0 ping statistics ---
|
||
3 packets transmitted, 3 packets received, 0.0% packet loss
|
||
round-trip min/avg/max/std-dev = 0.635/0.659/0.698/0.040 ms
|
||
$ ping6 -c3 -S fddc:a021:7c20:ffff::1 fddc:a021:7c20:ffff::
|
||
PING6(56=40+8+8 bytes) fddc:a021:7c20:ffff::1 --fddc:a021:7c20:ffff::
|
||
16 bytes from fddc:a021:7c20:ffff::, icmp_seq=0 hlim=64 time=0.685 ms
|
||
16 bytes from fddc:a021:7c20:ffff::, icmp_seq=1 hlim=64 time=0.832 ms
|
||
16 bytes from fddc:a021:7c20:ffff::, icmp_seq=2 hlim=64 time=0.724 ms
|
||
--- fddc:a021:7c20:ffff:: ping6 statistics ---
|
||
3 packets transmitted, 3 packets received, 0.0% packet loss
|
||
round-trip min/avg/max/std-dev = 0.685/0.747/0.832/0.062 ms
|
||
```
|
||
# BIRD, le petit zozio sur la bran-cheuh
|
||
|
||
[BIRD](http://bird.network.cz/), c'est juste le meilleur routeur open source à ce jour. Si, si. Et c'est justement l'occasion d'en parler, parce que c'est vraiment un logiciel qui fait le café, la vaisselle, le ménage, qui te taille une petite pipe et qui se fait oublier après. ~~La femme parfaite en somme~~Le logiciel de routage préféré de Bibi ! Il fonctionne en IPv4 et en IPv6 avec une petite particularité : `bird` gère l'IPv4 et `bird6` l'IPv6.
|
||
|
||
[BIRD](http://bird.network.cz/) sait parler OSPF, BGP, RIP et probablement deux ou trois autres trucs dont tout le monde se fout. Son principe de fonctionnement est relativement simple : on peut créer autant de routeur virtuel que l'on souhaite par protocole ; chaque de ces routeurs virtuels (`protocols` dans BIRD) va échanger des routes avec une table interne (la *BIRD Internal Routing Table*). On peut décider ce qu'on injecte ou ce qu'on extrait de chaque protocole très facilement, via un (très) puissant système de filtre.
|
||
|
||
Si on prend un routeur OSPF standard, ça donnera à peu près ça :
|
||
|
||

|
||
|
||
Évidemment, chaque route dans la table interne va avoir tout un tas de propriétés associées (ici, je n'ai représenté que le `RTS` *Routing Table Source*, mais il y en a plein d'autres).
|
||
|
||
On va donc configurer `artr-1` pour qu'il annonce des routes statiques et ses propres interfaces connectées :
|
||
|
||
```
|
||
log syslog { warning, error };
|
||
router id 192.168.0.1;
|
||
|
||
## « direct » correspond aux routes connectées
|
||
protocol direct {
|
||
interface "vio*";
|
||
}
|
||
|
||
## « kernel » correspond aux routes du
|
||
## noyau (routes statiques ajoutées à la main par exemple)
|
||
## c'est aussi grâce à lui qu'on va exporter la table BIRD
|
||
## vers le système
|
||
protocol kernel {
|
||
persist; # les routes sont persistentes même si BIRD plante
|
||
scan time 20;
|
||
export all;
|
||
}
|
||
|
||
# pseudo-protocole permettant de surveiller les interfaces
|
||
protocol device {
|
||
scan time 10;
|
||
}
|
||
## pour les routes statiques gérées par BIRD
|
||
protocol static {
|
||
route 10.0.0.0/8 via "lo0";
|
||
route 172.16.0.0/12 via "lo0";
|
||
route 192.168.0.0/16 via "lo0";
|
||
}
|
||
|
||
## notre fameux routeur OSPF
|
||
protocol ospf {
|
||
import all;
|
||
export all;
|
||
area 0.0.0.0 {
|
||
interface "vio0" {
|
||
};
|
||
};
|
||
}
|
||
```
|
||
Oui, oui, c'est tout :). Le protocole `static` va permettre de créer des routes statiques un peu bidon histoire qu'on ait quelque chose à annoncer en OSPF (par défaut, il est en `import all` donc toutes les routes statiques seront envoyées dans BIRD). Le protocole `kernel` permet d'écrire les routes de la *BIRD Internal Routing Table* vers le système pour « réellement » router.
|
||
|
||
Pour la partie IPv6, c'est presque tout pareil : on prend le fichier `/etc/bird.conf` et le copie en `/etc/bird6.conf` en changeant simplement les routes statiques. C'est l'utilitaire `birdc` qui permet de communiquer directement avec BIRD, démonstration :
|
||
```
|
||
$ birdc
|
||
BIRD 1.4.0 ready.
|
||
birdshow ospf neighbors
|
||
ospf1:
|
||
Router ID Pri State DTime Interface Router IP
|
||
birdshow route
|
||
10.0.0.0/8 dev lo0 [static1 10:15:23] * (200)
|
||
192.168.0.0/24 dev vio0 [direct1 10:15:23] * (240)
|
||
dev vio0 [ospf1 10:15:24] I (150/10) [192.168.0.1]
|
||
192.168.0.0/16 dev lo0 [static1 10:15:23] * (200)
|
||
172.16.0.0/12 dev lo0 [static1 10:15:23] * (200)
|
||
```
|
||
Et toutes les routes se retrouvent bien dans le noyau :
|
||
```
|
||
$ route -n show -inet
|
||
Routing tables
|
||
|
||
Internet:
|
||
Destination Gateway Flags Refs Use Mtu Prio Iface
|
||
10/8 127.0.0.1 U1 0 0 33192 56 lo0
|
||
127/8 127.0.0.1 UGRS 0 0 33192 8 lo0
|
||
127.0.0.1 127.0.0.1 UH 1 0 33192 4 lo0
|
||
172.16/12 127.0.0.1 U1 0 0 33192 56 lo0
|
||
192.168.0/24 link#1 UC 1 0 - 4 vio0
|
||
192.168/16 127.0.0.1 U1 0 0 33192 56 lo0
|
||
192.168.0.1 52:54:00:1f:76:10 UHLc 0 4 - 4 lo0
|
||
224/4 127.0.0.1 URS 0 0 33192 8 lo0
|
||
```
|
||
## Configuration OSPF des VPN du site A
|
||
|
||
Pour les VPN du site A, nous allons avoir un petit souci. Si on annonce toutes les routes connectées (incluant donc le tunnel `gif`), ça ne va pas beaucoup nous aider à router. Il va donc falloir annoncer cela sous forme de route statique. Mais comme on ne peut pas annoncer la même route statique des deux côtés, il va falloir procéder autrement.
|
||
|
||
Côté site B tout d'abord, on va ajouter des routes statiques à la montée des interfaces `gif`. Pas très compliqué, il suffit de rajouter cela à la fin du fichier `/etc/hostname.gif0` :
|
||
```
|
||
!route -n add -inet 10/8 169.254.0.0
|
||
!route -n add -inet 172.16/12 169.254.0.0
|
||
!route -n add -inet 192.168/16 169.254.0.0
|
||
!route -n add -inet6 fc00::/7 fddc:a021:7c20:ffff::0
|
||
```
|
||
ême principe côté site A :
|
||
```
|
||
!route -n add -inet 192.168.1/24 169.254.0.1
|
||
!route -n add -inet6 fddc:a021:7c20:1::/64 fddc:a021:7c20:ffff::1
|
||
```
|
||
Ainsi au démarrage de l'interface, les routes sont montées automatiquement dans le noyau et il suffit de demander à BIRD de les apprendre. Pour empêcher les routes d'être annoncées sur le routeur de secours côté site A, on va se servir de `ifstated` pour faire monter/descendre les interfaces en fonction du maître. Ci-dessous `/etc/ifstated.conf` :
|
||
|
||
```
|
||
init-state auto ## état d'origine au démarrage de ifstated
|
||
## variable prenant true ou false en fonction de l'état de carp0
|
||
fw_carp_up = "carp0.link.up"
|
||
fw_carp_init = "carp0.link.unknown"
|
||
|
||
state auto {
|
||
if ($fw_carp_init)
|
||
run "sleep 10"
|
||
if ($fw_carp_up)
|
||
set-state fw_master
|
||
if (! $fw_carp_up)
|
||
set-state fw_slave
|
||
}
|
||
|
||
state fw_master { # si on devient master CARP
|
||
init {
|
||
run "ifconfig gif0 up"
|
||
}
|
||
if($fw_carp_init)
|
||
run "sleep 2"
|
||
if(! $fw_carp_up)
|
||
set-state fw_slave
|
||
}
|
||
|
||
state fw_slave { # si on devient slave CARP
|
||
init {
|
||
run "ifconfig gif0 down"
|
||
}
|
||
if($fw_carp_init)
|
||
run "sleep 2"
|
||
if($fw_carp_up)
|
||
set-state fw_master
|
||
}
|
||
```
|
||
Avec cette astuce, l'esclave ne peut jamais transmettre la route (puisque l'interface sous-jacente est *down* systématiquement). Passons maintenant à la configuration de BIRD pour les passerelles VPN en question. On va être obligé dans cet exemple d'apprendre les routes « aliens » venant du noyau (puisqu'on ajoute/supprime des routes à la volée via `ifstated`). Il va donc falloir filtrer ces routes sinon, ça va tourner au grand nawak très rapidement.
|
||
|
||
```
|
||
log syslog { warning, error };
|
||
router id 192.168.0.251;
|
||
|
||
protocol kernel {
|
||
learn; # on force l'apprentissage des routes, import ne suffit pas
|
||
persist;
|
||
scan time 20;
|
||
import filter { ## un petit filtre des familles
|
||
if dest = RTD_UNREACHABLE then reject; # routes non-joignables
|
||
# c'est surtout utile en IPv6 où il y a des routes bannies dans OpenBSD par défaut
|
||
if net ~ [ 0.0.0.0/0 ] then reject; # passerelle par défaut
|
||
accept;
|
||
};
|
||
export all;
|
||
}
|
||
|
||
protocol device {
|
||
scan time 10;
|
||
}
|
||
|
||
protocol ospf {
|
||
import all;
|
||
export all;
|
||
area 0.0.0.0 {
|
||
interface "vio1" {
|
||
};
|
||
};
|
||
}
|
||
```
|
||
Et évidemment pareil en IPv6, mais avec une route par défaut qui a une tronche un peu différente.
|
||
|
||
Avec tout ce bazar, tout fonctionne très bien à présent, le routage est parfaitement opérationnel. Depuis *artr-1* :
|
||
|
||
```
|
||
$ ping -c3 192.168.1.1
|
||
PING 192.168.1.1 (192.168.1.1): 56 data bytes
|
||
64 bytes from 192.168.1.1: icmp_seq=0 ttl=253 time=1.631 ms
|
||
64 bytes from 192.168.1.1: icmp_seq=1 ttl=253 time=1.782 ms
|
||
64 bytes from 192.168.1.1: icmp_seq=2 ttl=253 time=1.598 ms
|
||
--- 192.168.1.1 ping statistics ---
|
||
3 packets transmitted, 3 packets received, 0.0% packet loss
|
||
round-trip min/avg/max/std-dev = 1.598/1.670/1.782/0.086 ms
|
||
$ ping6 -c3 fddc:a021:7c20:1::1
|
||
PING6(56=40+8+8 bytes) fddc:a021:7c20::1 --fddc:a021:7c20:1::1
|
||
16 bytes from fddc:a021:7c20:1::1, icmp_seq=0 hlim=62 time=1.654 ms
|
||
16 bytes from fddc:a021:7c20:1::1, icmp_seq=1 hlim=62 time=2.075 ms
|
||
16 bytes from fddc:a021:7c20:1::1, icmp_seq=2 hlim=62 time=1.825 ms
|
||
|
||
--- fddc:a021:7c20:1::1 ping6 statistics ---
|
||
3 packets transmitted, 3 packets received, 0.0% packet loss
|
||
round-trip min/avg/max/std-dev = 1.654/1.851/2.075/0.173 ms
|
||
```
|
||
On peut même tester que les routes montent/descendent correctement sur *avpn-1/2* en jouant un peu avec `carpdemote`.
|
||
|
||
# Bon ben du coup, on chiffre ou bien
|
||
|
||
Ça vient jeune puceau, ça vient. Pour faire de la redondance dans l'IPSEC, il va nous falloir trois éléments :
|
||
* `ipsec` activé ;
|
||
* `isakmpd` pour génerer les sessions IPSEC ;
|
||
* `sasyncd` pour synchroniser les sessions en question entre chaque paire de routeurs.
|
||
|
||
Pour les deux premier, c'est extrêmement simple, il suffit d'ajouter ça dans `/etc/rc.conf.local` :
|
||
```
|
||
ipsec=YES
|
||
isakmpd_flags="-K -S"
|
||
```
|
||
Au passage, on peut tout de suite mettre :
|
||
```
|
||
sasyncd_flags=""
|
||
```
|
||
Et configurer `sasyncd` :
|
||
```
|
||
## donne le port d'écoute et l'adresse du copain
|
||
listen on 192.168.0.251
|
||
peer 192.168.0.252
|
||
# l'interface carp à surveiller pour savoir si on est maître ou esclave
|
||
interface carp0
|
||
# une clé partagée
|
||
sharedkey 0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
||
```
|
||
Ça, c'est la configuration pour ''avpn-1''. Il faut bien évidemment faire un symétrique pour ''avpn-2'' et le même genre de chose (mais avec une autre clé) pour *bvpn-1/2*.
|
||
|
||
Et finalement, il va falloir configurer la partie IPSEC en elle-même. Là, ça se passe dans `/etc/ipsec.conf` avec une configuration relativement simple (ici pour les deux routeurs du site A, la configuration est symétrique pour B) :
|
||
```
|
||
## macros de définition des deux sites
|
||
avpn="100.64.0.254"
|
||
bvpn="100.64.0.100"
|
||
## en une seule ligne, le protocole à transporter dans le tunnel et les différents chiffrements pour les phases 1 et 2 d'IPSEC
|
||
ike esp transport proto ipencap from $avpn to $bvpn local $avpn peer $bvpn main auth hmac-sha1 enc aes group modp1536 quick auth hmac-sha1 enc aes group modp1536 psk "pipopipo"
|
||
ike esp transport proto ipv6 from $avpn to $bvpn local $avpn peer $bvpn main auth hmac-sha1 enc aes group modp1536 quick auth hmac-sha1 enc aes group modp1536 psk "pipopipo"
|
||
```
|
||
L'astuce consiste ici à encapsuler dans IPSEC les protocoles IP `ipencap` et `ipv6` qui correspondent respectivement à IPv4 dans IPv4 et IPv6 dans IPv4. L'ensemble de ce qui passe dans le tunnel `gif` sera donc chiffré entre les sites A et B.
|
||
|
||
Une fois que c'est fait, il n'y a plus qu'à redémarrer le tout pour que tout soit pris en compte.
|
||
|
||
## TADAAAAA !!
|
||
|
||
Les flux IPSEC devraient normalement s'établir relativement vite entre les deux routeurs maîtres des deux côtés et les flux et les sessions vont assez vite se répliquer vers les routeurs esclaves des deux côtés :
|
||
```
|
||
$ ipsecctl -sf
|
||
flow esp in proto ipv6 from 100.64.0.100 to 100.64.0.254 peer 100.64.0.100 srcid 100.64.0.254/32 dstid 100.64.0.100/32 type use
|
||
flow esp out proto ipv6 from 100.64.0.254 to 100.64.0.100 peer 100.64.0.100 srcid 100.64.0.254/32 dstid 100.64.0.100/32 type require
|
||
flow esp in proto ipencap from 100.64.0.100 to 100.64.0.254 peer 100.64.0.100 srcid 100.64.0.254/32 dstid 100.64.0.100/32 type use
|
||
flow esp out proto ipencap from 100.64.0.254 to 100.64.0.100 peer 100.64.0.100 srcid 100.64.0.254/32 dstid 100.64.0.100/32 type require
|
||
```
|
||
> Note personnelle : pour une raison que j'ignore, le nombre de sessions actives quand on est en IKE actif/actif est nettemment plus important qu'en IKE actif/passif (avec un routeur qui initie la connexion donc). Si quelqu'un a une explication, je suis preneur.
|
||
|
||
On peut maintenant rebooter à loisir l'un ou l'autre des routeurs de n'importe quel côté et conserver la connexion et le chiffrement sans aucun souci.
|
||
|
||
Pour la prochaine fois, on va faire la version plus sophistiquée, en supposant qu'un des deux côtés à deux opérateurs au lieu d'un.
|