Come funziona una VPN IPsec IKEv2
IPsec cifra e autentica i pacchetti IP tra due apparati. La negoziazione avviene con IKEv2 (RFC 7296) in due fasi:
- IKE SA: i due capi si autenticano (qui con una chiave condivisa, PSK) e creano un canale protetto per la gestione. Usa UDP 500, oppure UDP 4500 se c’è un NAT di mezzo.
- Child SA: dentro l’IKE SA si negoziano le chiavi per i dati veri. I dati viaggiano in ESP (protocollo IP 50), oppure in UDP 4500 con NAT-T.
Esistono due modi di decidere quale traffico entra nella VPN:
- Basato su policy (crypto map sul Cisco): una access list elenca le coppie di reti da cifrare. È il metodo classico, ancora molto diffuso.
- Basato su rotte (VTI sul Cisco, interfaccia XFRM su Linux): la VPN è un’interfaccia, e ci finisce dentro tutto ciò che instradi verso di essa. Aggiungere una rete significa aggiungere una rotta, non toccare le SA.
In questa guida uso il metodo basato su rotte, più moderno e più semplice da gestire. La crypto map è descritta più avanti come alternativa.
Lo scenario
È lo stesso della guida Tunnel GRE tra un router Cisco e Ubuntu Linux: sede A con router Cisco, sede B con un server Ubuntu che fa da router per la sua LAN. Entrambi hanno un IP pubblico statico. Al posto del tunnel GRE c’è un tunnel IPsec, con gli stessi indirizzi.
| Elemento | Sede A (Cisco) | Sede B (Ubuntu) |
|---|---|---|
| IP pubblico | 198.51.100.1 su Gi0/0 | 203.0.113.10 su ens18 |
| LAN | 192.168.10.0/24, gateway .1 su Gi0/1 | 192.168.20.0/24, gateway .1 su ens19 |
| Interfaccia tunnel | Tunnel0 (VTI) – 10.255.255.1/30 | ipsec0 (XFRM) – 10.255.255.2/30 |
| Identità IKE | 198.51.100.1 | 203.0.113.10 |
Se hai già configurato il tunnel GRE della guida precedente, rimuovilo prima (o usa un’altra /30): le due soluzioni sono alternative.
I parametri da concordare
La causa numero uno delle VPN che non salgono è un parametro diverso tra i due lati. Fissali prima, in una tabella, e copia da lì:
| Parametro | Valore | Cisco IOS XE | strongSwan |
|---|---|---|---|
| Versione IKE | IKEv2 | crypto ikev2 … | version = 2 |
| Cifratura IKE | AES-256-GCM | encryption aes-gcm-256 | aes256gcm16-prfsha256-ecp256 |
| PRF IKE | SHA-256 | prf sha256 | |
| Gruppo DH | 19 (ECP 256 bit) | group 19 | |
| Cifratura ESP | AES-256-GCM, ICV 16 byte | esp-gcm 256 | aes256gcm16-ecp256 |
| PFS sulla Child SA | gruppo 19 | set pfs group19 | |
| Durata IKE SA | 24 ore | predefinita (86400 s) | rekey_time = 24h |
| Durata Child SA | 1 ora | predefinita (3600 s) | rekey_time = 1h |
| Autenticazione | PSK | pre-share | auth = psk |
| DPD | ogni 10 s | dpd 10 3 periodic | dpd_delay = 10s |
Con AES-GCM la cifratura include già l’integrità: per questo nella proposta IKE c’è solo la PRF e non c’è un algoritmo di integrità. Se un apparato non supporta GCM, usa AES-256-CBC con SHA-256: sul Cisco encryption aes-cbc-256 + integrity sha256 e esp-aes 256 esp-sha256-hmac; su strongSwan aes256-sha256-ecp256 per IKE e aes256-sha256-ecp256 per ESP.
Genera una chiave condivisa robusta e casuale:
openssl rand -base64 32
Chi conosce la PSK e un IP da cui presentarsi può tentare di impersonare un capo del tunnel. Usa una chiave lunga e casuale, diversa per ogni tunnel, e non mandarla per e-mail insieme agli indirizzi. Nella guida uso Cambia-Questa-Chiave-Esempio-0000: è un segnaposto, non usarlo mai. Sul Cisco attiva la cifratura delle chiavi nella configurazione con key config-key password-encrypt e password encryption aes; su Ubuntu proteggi il file con chmod 600.
Configurazione del router Cisco
La configurazione IKEv2 su IOS XE si costruisce a blocchi: proposta (algoritmi), policy (quali proposte usare), keyring (chiavi per peer), profilo (identità e autenticazione). Poi il profilo IPsec lega tutto all’interfaccia tunnel.
configure terminal
!
crypto ikev2 proposal PROP-GCM256
encryption aes-gcm-256
prf sha256
group 19
!
crypto ikev2 policy POL-GCM256
match address local 198.51.100.1
proposal PROP-GCM256
!
crypto ikev2 keyring KR-SEDE-B
peer UBUNTU-SEDE-B
address 203.0.113.10
pre-shared-key Cambia-Questa-Chiave-Esempio-0000
!
crypto ikev2 profile IKEV2-SEDE-B
match identity remote address 203.0.113.10 255.255.255.255
identity local address 198.51.100.1
authentication remote pre-share
authentication local pre-share
keyring local KR-SEDE-B
dpd 10 3 periodic
!
crypto ipsec transform-set TS-GCM256 esp-gcm 256
mode tunnel
!
crypto ipsec profile IPSEC-SEDE-B
set transform-set TS-GCM256
set pfs group19
set ikev2-profile IKEV2-SEDE-BPoi l’interfaccia tunnel e la rotta verso la LAN remota:
interface Tunnel0
description VPN IPsec verso sede B (Ubuntu)
ip address 10.255.255.1 255.255.255.252
ip mtu 1400
ip tcp adjust-mss 1360
tunnel source GigabitEthernet0/0
tunnel destination 203.0.113.10
tunnel mode ipsec ipv4
tunnel protection ipsec profile IPSEC-SEDE-B
!
ip route 192.168.20.0 255.255.255.0 10.255.255.2
!
end
write memorytunnel mode ipsec ipv4 trasforma Tunnel0 in una VTI statica: niente GRE, solo ESP. Il Cisco negozia selettori di traffico “qualsiasi verso qualsiasi” (0.0.0.0/0 ↔ 0.0.0.0/0): cosa entra nel tunnel lo decide la tabella di routing. L’MTU a 1400 e l’MSS a 1360 sono valori prudenti che coprono l’overhead di ESP, di un eventuale NAT-T e di una linea PPPoE.
Se sull’interfaccia WAN hai una access list in ingresso, apri IKE, NAT-T ed ESP dal server Ubuntu:
ip access-list extended WAN-IN
permit udp host 203.0.113.10 host 198.51.100.1 eq isakmp
permit udp host 203.0.113.10 host 198.51.100.1 eq non500-isakmp
permit esp host 203.0.113.10 host 198.51.100.1Configurazione di Ubuntu con strongSwan
1. Installare strongSwan con swanctl
Su Ubuntu 24.04 installa il demone moderno charon-systemd e lo strumento swanctl. Non serve il vecchio ipsec.conf (pacchetto strongswan-starter).
sudo apt update
sudo apt install strongswan-swanctl charon-systemd
Il servizio si chiama strongswan.service; all’avvio carica in automatico i file /etc/swanctl/conf.d/*.conf.
2. Abilitare l’instradamento
Come per il tunnel GRE, Ubuntu deve inoltrare i pacchetti tra la LAN e la VPN:
net.ipv4.ip_forward = 1
net.ipv4.conf.all.rp_filter = 2
net.ipv4.conf.default.rp_filter = 2sudo sysctl --system
3. Creare l’interfaccia XFRM
L’equivalente Linux della VTI è un’interfaccia XFRM: i pacchetti instradati verso di essa vengono cifrati dalla SA con lo stesso identificativo (if_id, qui 42). La creo con systemd-networkd, che Netplan usa già come motore, con due file:
[NetDev]
Name=ipsec0
Kind=xfrm
[Xfrm]
InterfaceId=42
Independent=yes[Match]
Name=ipsec0
[Link]
MTUBytes=1400
[Network]
Address=10.255.255.2/30
[Route]
# interfaccia punto-punto: basta indicare la destinazione
Destination=192.168.10.0/24sudo networkctl reload
networkctl status ipsec0
Gli stessi passaggi con ip (si perdono al riavvio):
sudo ip link add ipsec0 type xfrm dev ens18 if_id 42
sudo ip addr add 10.255.255.2/30 dev ipsec0
sudo ip link set ipsec0 mtu 1400 up
sudo ip route add 192.168.10.0/24 dev ipsec04. Configurare la connessione
connections {
sede-a {
version = 2
local_addrs = 203.0.113.10
remote_addrs = 198.51.100.1
proposals = aes256gcm16-prfsha256-ecp256
rekey_time = 24h
dpd_delay = 10s
local {
auth = psk
id = 203.0.113.10
}
remote {
auth = psk
id = 198.51.100.1
}
children {
sede-a-lan {
# selettori "qualsiasi": decide la tabella di routing
local_ts = 0.0.0.0/0
remote_ts = 0.0.0.0/0
if_id_in = 42
if_id_out = 42
esp_proposals = aes256gcm16-ecp256
rekey_time = 1h
start_action = start
dpd_action = restart
}
}
}
}
secrets {
ike-sede-a {
id = 198.51.100.1
secret = "Cambia-Questa-Chiave-Esempio-0000"
}
}I punti da notare:
idlocale e remoto sono gli indirizzi pubblici, gli stessi diidentity localematch identity remotesul Cisco.if_id_ineif_id_outlegano la Child SA all’interfaccia ipsec0. Senza, i selettori 0.0.0.0/0 catturerebbero tutto il traffico del server.esp_proposalscontiene il gruppo DH (ecp256): è il PFS, e deve coincidere conset pfs group19.start_action = startavvia il tunnel al caricamento;dpd_action = restartlo rinegozia se il Cisco smette di rispondere.
Con le interfacce XFRM è meglio che strongSwan non installi rotte proprie (nella tabella 220): le rotte le gestisci tu. Crea un file di impostazioni:
charon {
install_routes = no
}5. Avviare il servizio
sudo chmod 600 /etc/swanctl/conf.d/sede-a.conf
sudo systemctl enable --now strongswan
sudo systemctl restart strongswan # rilegge anche strongswan.d
sudo swanctl --load-all # dopo ogni modifica a swanctl
Firewall, NAT e MSS su Ubuntu
Con nftables (vedi la guida Firewall completo con nftables) servono tre cose: accettare IKE ed ESP dal Cisco, lasciar passare il traffico tra la LAN e ipsec0, e correggere l’MSS.
table inet filtro {
chain input {
# ... regole esistenti ...
ip saddr 198.51.100.1 udp dport { 500, 4500 } accept
ip saddr 198.51.100.1 meta l4proto esp accept
# ping e gestione dal tunnel verso il server stesso
iifname "ipsec0" ip saddr { 10.255.255.0/30, 192.168.10.0/24 } accept
}
chain forward {
# ... regole esistenti ...
tcp flags syn tcp option maxseg size set rt mtu
iifname "ens19" oifname "ipsec0" accept
iifname "ipsec0" oifname "ens19" accept
}
}
table ip nat {
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
# il traffico verso la sede A non va mascherato
ip saddr 192.168.20.0/24 ip daddr 192.168.10.0/24 accept
oifname "ens18" masquerade
}
}Con l’interfaccia XFRM il traffico verso la LAN A esce da ipsec0 e non da ens18, quindi il masquerade non lo toccherebbe comunque. La regola di esclusione diventa indispensabile se passi alla variante con policy (vedi sotto), dove il pacchetto esce da ens18: se viene mascherato prima di essere cifrato, non corrisponde più alla policy e parte in chiaro, o non parte affatto.
Se usi UFW:
sudo ufw allow proto udp from 198.51.100.1 to any port 500,4500
sudo ufw allow proto esp from 198.51.100.1
sudo ufw route allow in on ens19 out on ipsec0
sudo ufw route allow in on ipsec0 out on ens19
L’alternativa: crypto map e policy
Se il router ha già altre VPN su crypto map, o il capo remoto supporta solo VPN basate su policy, puoi usare questo schema. Proposta, policy, keyring e profilo IKEv2 restano identici; cambiano la parte IPsec e l’interfaccia.
ip access-list extended VPN-SEDE-B
permit ip 192.168.10.0 0.0.0.255 192.168.20.0 0.0.0.255
!
crypto map CMAP-WAN 10 ipsec-isakmp
set peer 203.0.113.10
set transform-set TS-GCM256
set pfs group19
set ikev2-profile IKEV2-SEDE-B
match address VPN-SEDE-B
!
interface GigabitEthernet0/0
crypto map CMAP-WAN
!
ip access-list extended NAT-INSIDE
deny ip 192.168.10.0 0.0.0.255 192.168.20.0 0.0.0.255
permit ip 192.168.10.0 0.0.0.255 anyQui non servono Tunnel0 né la rotta via 10.255.255.2: basta che la LAN B sia instradata verso Gi0/0, di solito con la rotta predefinita. Il deny nella access list del NAT è obbligatorio, perché questo traffico esce dall’interfaccia esterna.
Su strongSwan togli if_id_in/if_id_out e l’interfaccia ipsec0, e restringi i selettori alle due LAN, speculari all’access list del Cisco:
children {
sede-a-lan {
local_ts = 192.168.20.0/24
remote_ts = 192.168.10.0/24
esp_proposals = aes256gcm16-ecp256
rekey_time = 1h
start_action = start
dpd_action = restart
}
}In questo caso lascia install_routes al valore predefinito e ricordati la regola di esclusione dal masquerade. Il limite della crypto map è evidente: ogni nuova coppia di reti richiede di modificare l’access list da entrambe le parti, e i selettori devono restare speculari.
Verifiche
Sul router Cisco
show crypto ikev2 sa ! Status READY
show crypto ikev2 sa detailed ! algoritmi, DH 19, durata, DPD
show crypto ipsec sa peer 203.0.113.10 ! #pkts encaps/decaps devono crescere
show crypto session detail
show interfaces Tunnel0 ! up, line protocol is up
ping 10.255.255.2 source Tunnel0
ping 192.168.20.1 source GigabitEthernet0/1
Nella VTI il “line protocol” di Tunnel0 sale solo quando la Child SA è attiva: è già un buon indicatore dello stato della VPN.
Su Ubuntu
sudo swanctl --list-conns # configurazione caricata
sudo swanctl --list-sas # ESTABLISHED e INSTALLED
sudo swanctl --initiate --child sede-a-lan # avvio manuale
sudo ip xfrm state # SA nel kernel
ip -s link show ipsec0 # contatori dell'interfaccia
ping -c 3 -I 192.168.20.1 192.168.10.1
sudo journalctl -u strongswan -f # log in tempo reale
sudo tcpdump -ni ens18 'udp port 500 or udp port 4500 or esp'
In --list-sas la Child SA deve mostrare AES_GCM_16-256/ECP_256: se manca ECP_256, il PFS non è attivo. Per un controllo dell’MTU senza frammentazione, da un client della LAN B:
ping -M do -s 1372 192.168.10.10 # 1372 + 28 = 1400: deve passare
ping -M do -s 1400 192.168.10.10 # deve fallire con "mtu = 1400"
Problemi comuni
| Sintomo | Causa probabile | Cosa fare |
|---|---|---|
NO_PROPOSAL_CHOSEN nei log | Algoritmi IKE o ESP diversi tra i due lati | Confronta la tabella dei parametri; su strongSwan il log mostra le proposte ricevute e quelle configurate |
AUTHENTICATION_FAILED o “no shared key found” | PSK diversa, oppure identità che non corrispondono | Controlla id in swanctl, identity local e match identity remote sul Cisco; ricopia la chiave senza spazi finali |
| “no peer config found” su strongSwan | Il Cisco si presenta con un ID diverso da remote.id | Il log riporta l’ID ricevuto: allinea identity local address sul Cisco |
TS_UNACCEPTABLE | Selettori di traffico non compatibili | Con VTI usa 0.0.0.0/0 su strongSwan; con crypto map access list e local_ts/remote_ts devono essere speculari |
| Il tunnel sale, ma cade dopo circa un’ora | PFS configurato da una sola parte: il primo scambio non usa DH, il rekey sì | Allinea set pfs group19 e il gruppo in esp_proposals |
| Ubuntu dietro un router con NAT | L’IP visto dal Cisco non è quello di ens18 | NAT-T passa su UDP 4500 da solo; inoltra 500 e 4500 verso Ubuntu e usa come ID un nome (id = sede-b.example.org) con match identity remote fqdn sul Cisco |
| Dopo il riavvio di un capo il tunnel resta giù | SA “orfane” senza DPD | Verifica dpd sul Cisco e dpd_delay/dpd_action = restart su strongSwan |
| SA attive, ma le LAN non si vedono | Rotta, inoltro o firewall | ip route get 192.168.10.1 deve indicare ipsec0; controlla la catena forward e i gateway dei client |
| Ping OK, ma siti e condivisioni si bloccano | MTU/MSS | Verifica ip tcp adjust-mss 1360 sul Cisco e la regola MSS su Ubuntu |
In produzione: certificati e passo successivo
La chiave condivisa va bene per due sedi e per un laboratorio. In produzione, e appena i tunnel diventano più di uno, usa i certificati: ogni capo ha la sua chiave privata, la revoca è possibile e non c’è un segreto comune da custodire. Sul Cisco si configura un trustpoint PKI con authentication local rsa-sig (o ecdsa-sig); su strongSwan auth = pubkey, con certificati in /etc/swanctl/x509 e chiavi in /etc/swanctl/private.
Questa VPN trasporta solo unicast IP. Se ti servono OSPF o altri protocolli multicast tra le sedi, la soluzione è un tunnel GRE protetto da IPsec: lo spiego nella guida GRE su IPsec tra Cisco e Ubuntu. Per le basi del tunnel GRE vedi Tunnel GRE tra un router Cisco e Ubuntu Linux.
Prova sempre la configurazione in laboratorio, salva quella attuale prima di modificarla e tieni un accesso di emergenza (console o accesso fuori banda) quando lavori su firewall e VPN da remoto.