VPN IPsec routé avec Linux et strongSwan

Vincent Bernat

La fa­çon la plus cou­rante d’éta­blir un tun­nel IPsec sous Li­nux est d’uti­li­ser un dé­mon IKE, comme ce­lui du pro­jet strongSwan. Voici un exemple mi­ni­mal de confi­gu­ration1 :

conn V2-1
  left        = 2001:db8:1::1
  leftsubnet  = 2001:db8:a1::/64
  right       = 2001:db8:2::1
  rightsubnet = 2001:db8:a2::/64
  authby      = psk
  auto        = route

La même confi­gu­ra­tion peut être uti­li­sée des deux cô­tés. Cha­cun sait dé­ter­mi­ner s’il est « left » ou « right ». Les ter­mi­nai­sons du tun­nel sont 2001:​db8:1::1 et 2001:​db8:2::1. Les ré­seaux pro­té­gés sont 2001:​db8:​a1::/64 et 2001:​db8:​a2::/64. En uti­li­sant cette confi­gu­ra­tion, strongSwan met en place les po­li­tiques de sé­cu­rité sui­vantes dans le noyau :

$ ip xfrm policy
src 2001:db8:a1::/64 dst 2001:db8:a2::/64
        dir out priority 399999 ptype main
        tmpl src 2001:db8:1::1 dst 2001:db8:2::1
                proto esp reqid 4 mode tunnel
src 2001:db8:a2::/64 dst 2001:db8:a1::/64
        dir fwd priority 399999 ptype main
        tmpl src 2001:db8:2::1 dst 2001:db8:1::1
                proto esp reqid 4 mode tunnel
src 2001:db8:a2::/64 dst 2001:db8:a1::/64
        dir in priority 399999 ptype main
        tmpl src 2001:db8:2::1 dst 2001:db8:1::1
                proto esp reqid 4 mode tunnel
[…]

Ce type de tun­nel IPsec se base ex­clu­si­ve­ment sur ces po­li­tiques de sé­cu­rité pour dé­ci­der de l’en­cap­su­la­tion des pa­quets (po­licy-ba­sed VPN). Chaque po­li­tique de sé­cu­rité est consti­tuée des élé­ments suivants :

  • une di­rec­tion (out, in ou fwd2) ;
  • un sé­lec­teur (un en­semble de cri­tères tels que ré­seaux source et des­ti­na­tion, ports, …) ;
  • un mode (transport ou tunnel) ;
  • un pro­to­cole d’en­cap­su­la­tion (esp ou ah) ;
  • les adresses source et des­ti­na­tion du tunnel.

Quand une po­li­tique de sé­cu­rité s’ap­plique, le noyau re­cherche l’as­so­cia­tion de sé­cu­rité cor­res­pon­dante (en uti­li­sant reqid et les adresses du tunnel) :

$ ip xfrm state
src 2001:db8:1::1 dst 2001:db8:2::1
        proto esp spi 0xc1890b6e reqid 4 mode tunnel
        replay-window 0 flag af-unspec
        auth-trunc hmac(sha256) 0x5b68[…]8ba2904 128
        enc cbc(aes) 0x8e0e377ad8fd91e8553648340ff0fa06
        anti-replay context: seq 0x0, oseq 0x0, bitmap 0x00000000
[…]

Si au­cune as­so­cia­tion de sé­cu­rité n’est trou­vée, le pa­quet est mis de côté et le dé­mon IKE est pré­venu pour en né­go­cier une. Lorsqu’un pa­quet en­cap­sulé est reçu, l’en­tête contient le SPI qui per­met d’iden­ti­fier l’as­so­cia­tion de sé­cu­rité à uti­li­ser. Deux as­so­cia­tions de sé­cu­rité sont né­ces­saires pour un tun­nel bi-di­rec­tionnel :

$ tcpdump -pni eth0 -c2 -s0 esp
13:07:30.871150 IP6 2001:db8:1::1 > 2001:db8:2::1: ESP(spi=0xc1890b6e,seq=0x222)
13:07:30.872297 IP6 2001:db8:2::1 > 2001:db8:1::1: ESP(spi=0xcf2426b6,seq=0x204)

Toutes les im­plé­men­ta­tions d’IPsec per­mettent de tra­vailler avec des po­li­tiques de sé­cu­rité. Tou­te­fois, cer­taines confi­gu­ra­tions sont ar­dues à mettre en place de cette fa­çon. Par exemple, consi­dé­rons l’ar­chi­tec­ture sui­vante pour des VPN re­don­dants re­liant trois sites :

VPN redondants entre 3 sites
Trois sites utilisant des tunnels IPsec redondants pour protéger leurs réseaux internes. L’AS 65001 emprunte certaines IP à des réseaux présents sur l’AS 65002.

Une confi­gu­ra­tion pos­sible pour le tun­nel entre V1-1 et V2-1 serait :

conn V1-1-to-V2-1
  left        = 2001:db8:1::1
  leftsubnet  = 2001:db8:a1::/64,2001:db8:a6::cc:1/128,2001:db8:a6::cc:5/128
  right       = 2001:db8:2::1
  rightsubnet = 2001:db8:a2::/64,2001:db8:a6::/64,2001:db8:a8::/64
  authby      = psk
  keyexchange = ikev2
  auto        = route

À chaque fois qu’un ré­seau est ajouté ou re­tiré d’un site, les confi­gu­ra­tions de tous les sites doivent être mises à jour. De plus, le che­vau­che­ment de cer­tains ré­seaux (2001:​db8:​a6::/64 d’un côté et 2001:​db8:​a6::​cc:​1/128 de l’autre) pose quelques pro­blèmes sup­plé­mentaires.

L’al­ter­na­tive est d’uti­li­ser des tun­nels rou­tés : tout pa­quet tra­ver­sant une pseudo-in­ter­face sera en­cap­sulé en uti­li­sant une po­li­tique de sé­cu­rité as­so­ciée à l’in­ter­face. Cela ré­sout prin­ci­pa­le­ment deux problèmes :

  1. Des dé­mons de rou­tage peuvent être uti­li­sés pour dis­tri­buer les routes à pro­té­ger sur chaque site. Cela ré­duit gran­de­ment la quan­tité de confi­gu­ra­tion à gé­rer quand de nom­breux ré­seaux sont présents.
  2. L’en­cap­su­la­tion et la dé­cap­su­la­tion peut s’exé­cu­ter dans une ins­tance de rou­tage ou es­pace de noms dif­fé­rent. Cela per­met de cloi­son­ner ef­fi­ca­ce­ment le rou­tage privé (où les uti­li­sa­teurs du VPN se trouvent) du rou­tage pu­blic (où les ter­mi­nai­sons VPN se trouvent).

VPN routé avec Juniper#

Re­gar­dons d’abord com­ment un tel concept est mis en place sur une pla­te­forme Ju­nos (comme le Ju­ni­per vSRX). Les tun­nels rou­tés sont une fonc­tion­na­lité qui existe de­puis long­temps sur celle-ci (hé­ri­tage des Nets­creen ISG avec ScreenOS).

Nous al­lons confi­gu­rer le tun­nel entre V3-2 et V1-1. Tout d’abord, nous met­tons en place l’in­ter­face tun­nel. Elle est as­so­ciée à l’ins­tance de rou­tage private qui ne contien­dra que les routes in­ternes (avec IPv4, elle ne contien­drait que des routes de type RFC 1918) :

interfaces {
    st0 {
        unit 1 {
            family inet6 {
                address 2001:db8:ff::7/127;
            }
        }
    }
}
routing-instances {
    private {
        instance-type virtual-router;
        interface st0.1;
    }
}

La se­conde étape consiste à confi­gu­rer le VPN :

security {
    /* Configuration de la phase 1 */
    ike {
        proposal IKE-P1 {
            authentication-method pre-shared-keys;
            dh-group group20;
            encryption-algorithm aes-256-gcm;
        }
        policy IKE-V1-1 {
            mode main;
            proposals IKE-P1;
            pre-shared-key ascii-text "d8bdRxaY22oH1j89Z2nATeYyrXfP9ga6xC5mi0RG1uc";
        }
        gateway GW-V1-1 {
            ike-policy IKE-V1-1;
            address 2001:db8:1::1;
            external-interface lo0.1;
            general-ikeid;
            version v2-only;
        }
    }
    /* Configuration de la phase 2 */
    ipsec {
        proposal ESP-P2 {
            protocol esp;
            encryption-algorithm aes-256-gcm;
        }
        policy IPSEC-V1-1 {
            perfect-forward-secrecy keys group20;
            proposals ESP-P2;
        }
        vpn VPN-V1-1 {
            bind-interface st0.1;
            df-bit copy;
            ike {
                gateway GW-V1-1;
                ipsec-policy IPSEC-V1-1;
            }
            establish-tunnels on-traffic;
        }
    }
}

Il s’agit d’un VPN routé car l’in­ter­face st0.1 est as­so­ciée au VPN VPN-V1-1. Une fois le tun­nel fonc­tion­nel, tout pa­quet en­trant dans st0.1 sera en­cap­sulé et en­voyé vers la ter­mi­nai­son 2001:​db8:1::1.

La der­nière étape est de confi­gu­rer BGP dans l’ins­tance private pour échan­ger les routes avec le site distant :

routing-instances {
    private {
        routing-options {
            router-id 1.0.3.2;
            maximum-paths 16;
        }
        protocols {
            bgp {
                preference 140;
                log-updown;
                group v4-VPN {
                    type external;
                    local-as 65003;
                    hold-time 6;
                    neighbor 2001:db8:ff::6 peer-as 65001;
                    multipath;
                    export [ NEXT-HOP-SELF OUR-ROUTES NOTHING ];
                }
            }
        }
    }
}

Le filtre d’ex­port OUR-ROUTES doit sé­lec­tion­ner les routes à pu­blier aux autres sites. Par exemple :

policy-options {
    policy-statement OUR-ROUTES {
        term 10 {
            from {
                protocol ospf3;
                route-type internal;
            }
            then {
                metric 0;
                accept;
            }
        }
    }
}

La confi­gu­ra­tion pour les autres ter­mi­nai­sons est si­mi­laire. La ver­sion com­plète est dis­po­nible sur GitHub. Une fois les ses­sions BGP opé­ra­tion­nelles, les routes des autres sites sont ap­prises lo­ca­le­ment. Par exemple, voici la route pour 2001:​db8:​a1::/64:

> show route 2001:db8:a1::/64 protocol bgp table private.inet6.0 best-path

private.inet6.0: 15 destinations, 19 routes (15 active, 0 holddown, 0 hidden)
+ = Active Route, - = Last Active, * = Both

2001:db8:a1::/64   *[BGP/140] 01:12:32, localpref 100, from 2001:db8:ff::6
                      AS path: 65001 I, validation-state: unverified
                      to 2001:db8:ff::6 via st0.1
                    > to 2001:db8:ff::14 via st0.2

Elle a été ap­prise de V1-1 (via st0.1) et V1-2 (via st0.2). Elle fait par­tie de l’ins­tance de rou­tage private mais les pa­quets en­cap­su­lés sont ma­té­ria­li­sés dans l’ins­tance de rou­tage public. Il n’y a pas be­soin d’échan­ger de routes entre ces deux ins­tances pour que cela soit fonc­tion­nel. L’étan­chéité est donc as­su­rée et le VPN ne peut pas ser­vir de pas­se­relle entre l’in­té­rieur et l’ex­té­rieur. Il au­rait été pos­sible d’uti­li­ser des règles de fil­trage pour ob­te­nir le même ef­fet mais cette sé­pa­ra­tion sim­pli­fie le rou­tage et évite qu’un pro­blème de confi­gu­ra­tion se trans­forme en un dé­sastre de sécurité.

VPN routé avec Linux#

De­puis Li­nux 3.15, une confi­gu­ra­tion si­mi­laire est pos­sible en uti­li­sant les in­ter­faces « VTI » (vir­tual tun­nel in­ter­face)3. Tout d’abord, créons un es­pace de noms « privé » :

# ip netns add private
# ip netns exec private sysctl -qw net.ipv6.conf.all.forwarding=1

Les in­ter­faces consti­tuant le do­maine « privé » doivent être dé­pla­cées dans ce nou­vel es­pace de noms (au­cune IP n’est confi­gu­rée car nous uti­li­sons les adresses lo­cales au lien) :

# ip link set netns private dev eth1
# ip link set netns private dev eth2
# ip netns exec private ip link set up dev eth1
# ip netns exec private ip link set up dev eth2

En­suite, nous créons l’in­ter­face tun­nel vti6 (si­mi­laire à st0.1 dans l’exemple Junos) :

# ip -6 tunnel add vti6 \
>  mode vti6 \
>  local 2001:db8:1::1 \
>  remote 2001:db8:3::2 \
>  key 6
# ip link set netns private dev vti6
# ip netns exec private ip addr add 2001:db8:ff::6/127 dev vti6
# ip netns exec private sysctl -qw net.ipv4.conf.vti6.disable_policy=1
# ip netns exec private ip link set vti6 mtu 1500
# ip netns exec private ip link set vti6 up

L’in­ter­face tun­nel est créée dans l’es­pace de noms ini­tial et dé­pla­cée dans ce­lui « privé ». L’es­pace de noms d’ori­gine est mé­mo­risé par l’in­ter­face et sera uti­lisé pour re­ce­voir et en­voyer les pa­quets en­cap­su­lés. Tout pa­quet pas­sant dans l’in­ter­face aura tem­po­rai­re­ment la marque 6 qui sera uti­li­sée pour trou­ver la po­li­tique IPsec confi­gu­rée par la suite4. Le noyau met en place un MTU as­sez faible pour te­nir compte de tous les pro­to­coles et al­go­rithmes pos­sibles. Nous le ré­ta­blis­sons à 1500 et lais­sons le PMTUD jouer son rôle.

Mise à jour (04.2018)

Le MTU est éga­le­ment trop bas en rai­son d’un bug cor­rigé dans le com­mit c6741fbed6dc (pu­blié avec Li­nux 4.17).

La confi­gu­ra­tion de strongSwan est la suivante5 :

conn V3-2
  left        = 2001:db8:1::1
  leftsubnet  = ::/0
  right       = 2001:db8:3::2
  rightsubnet = ::/0
  authby      = psk
  mark        = 6
  auto        = route
  keyexchange = ikev2
  keyingtries = %forever
  ike         = aes256gcm16-prfsha384-ecp384!
  esp         = aes256gcm16-prfsha384-ecp384!
  mobike      = no

Le dé­mon IKE confi­gure la po­li­tique de sé­cu­rité adé­quate dans le noyau :

$ ip xfrm policy
src ::/0 dst ::/0
        dir out priority 399999 ptype main
        mark 0x6/0xffffffff
        tmpl src 2001:db8:1::1 dst 2001:db8:3::2
                proto esp reqid 1 mode tunnel
src ::/0 dst ::/0
        dir fwd priority 399999 ptype main
        mark 0x6/0xffffffff
        tmpl src 2001:db8:3::2 dst 2001:db8:1::1
                proto esp reqid 1 mode tunnel
src ::/0 dst ::/0
        dir in priority 399999 ptype main
        mark 0x6/0xffffffff
        tmpl src 2001:db8:3::2 dst 2001:db8:1::1
                proto esp reqid 1 mode tunnel
[…]

Cette po­li­tique s’ap­plique quelle que soit la source et la des­ti­na­tion tant que la marque de fi­re­wall est égale à 6, ce qui cor­res­pond à ce qui est confi­guré au ni­veau de l’in­ter­face tunnel.

La der­nière étape est de confi­gu­rer BGP pour l’échange des routes. Nous uti­li­sons BIRD à cet effet :

router id 1.0.1.1;
protocol device {
   scan time 10;
}
protocol kernel {
   persist;
   learn;
   import all;
   export all;
   merge paths yes;
}
protocol bgp IBGP_V3_2 {
   local 2001:db8:ff::6 as 65001;
   neighbor 2001:db8:ff::7 as 65003;
   import all;
   export where ifname ~ "eth*";
   preference 160;
   hold time 6;
}

Une fois que BIRD a dé­marré dans l’es­pace de noms « privé », les routes dis­tantes sont apprises :

$ ip netns exec private ip -6 route show 2001:db8:a3::/64
2001:db8:a3::/64 proto bird metric 1024
        nexthop via 2001:db8:ff::5  dev vti5 weight 1
        nexthop via 2001:db8:ff::7  dev vti6 weight 1

Cette route a été ap­prise de­puis V3-1 (à tra­vers vti5) et V3-2 (à tra­vers vti6). Comme avec Ju­nos, nous n’avons co­pié au­cune route entre les es­paces de noms. Ceux-ci sont iso­lés. Ainsi, le VPN ne peut pas ser­vir de pas­se­relle entre les deux do­maines. Cela nous as­sure qu’un pro­blème de confi­gu­ra­tion (par exemple, dé­mon IKE qui ne dé­marre pas) n’en­traî­nera pas une fuite ac­ci­den­telle des pa­quets internes.

En bo­nus, le tra­fic en clair est ob­ser­vable avec tcpdump sur l’in­ter­face tunnel :

$ ip netns exec private tcpdump -pni vti6 icmp6
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on vti6, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
20:51:15.258708 IP6 2001:db8:a1::1 > 2001:db8:a3::1: ICMP6, echo request, seq 69
20:51:15.260874 IP6 2001:db8:a3::1 > 2001:db8:a1::1: ICMP6, echo reply, seq 69

La confi­gu­ra­tion com­plète est dis­po­nible sur GitHub. La do­cu­men­ta­tion de strongSwan contient aussi une page dé­diée aux VPN rou­tés. Il est pos­sible de rem­pla­cer IPsec par Wire­Guard, une im­plé­men­ta­tion VPN moderne.

Mise à jour (11.2018)

Il est éga­le­ment pos­sible de trans­por­ter IPv4 au-des­sus des tun­nels IPsec IPv6. Le labo a été mis à jour pour dé­mon­trer un tel scénario.

Mise à jour (11.2018)

À par­tir de Li­nux 4.14, il existe un dé­faut im­por­tant im­pac­tant cette confi­gu­ra­tion. Il est né­ces­saire d’ap­pli­quer ces deux mo­di­fi­ca­tions : 9e1437937807 et 0152eee6fc3b (pré­sents dans les ver­sions stables).