Conectarea strongSwan la FortiGate cu PSK + XAuth + TOTP (2FA)
Un deep-dive din laboratorul Safebyte Consulting
Articol de: Teodor Lupan
La Safebyte Consulting integrăm frecvent sisteme Linux în infrastructuri VPN enterprise, ca parte din misiunile noastre de securitate — audituri sau pentesturi. Într-un proiect recent de conectare a Kali Linux (strongSwan) la gateway-uri FortiGate care impun PSK + XAuth + TOTP (2FA), am implementat un patch minimal pentru strongSwan care gestionează fluxul XAuth în doi pași al FortiGate (utilizator/parolă, apoi TOTP).
Pe parcursul stabilizării autentificării și a problemelor de MTU/offload, am descoperit un comportament secundar — și important din punct de vedere operațional: configurațiile Windows FortiClient trimit de obicei o rută implicită prin VPN, dar cu strongSwan am putut configura rutarea pe partea de client pentru a obține comportament split-tunnel. Aceasta a relevat o suprafață practică de atac și un risc operațional pentru organizațiile care se bazează pe impunerea strictă a full-tunnel de la gateway.
Acest articol documentează întreaga poveste tehnică: diagnostic, rațiunea de proiectare, patch-ul pe xauth_generic.c, pașii de compilare și testare, automatizarea OTP, descoperirea legată de rutare și analiza de risc, precum și măsurile de remediere. Anexele conțin patch-ul exact, scripturile helper și configurațiile exemplu.
Context și definirea problemei
Majoritatea VPN-urilor FortiGate folosite în medii enterprise se bazează pe IKEv1 cu XAuth pentru autentificarea utilizatorilor. Când este activat 2FA, secvența de autentificare devine:
- FortiGate solicită utilizatorul și parola
- După verificarea acestora, trimite un al doilea mesaj XAuth care cere „TOTP:”
- Clientul trebuie să răspundă la acest challenge cu parola curentă de unică folosință (6 cifre)
Din păcate, strongSwan-ul standard așteaptă fie:
- un singur schimb utilizator/parolă (fără prompt secundar), fie
- interacțiune cu utilizatorul pe stdin (nepotrivit pentru operare headless)
Ca urmare, încercările de conectare a unei mașini virtuale Linux prin strongSwan eșuează după prima fază:
<22> charon: 12[IKE] XAuth request received: XAUTH_USER_NAME, XAUTH_USER_PASSWORD
<22> charon: 12[IKE] XAuth authentication started
<22> charon: 12[IKE] XAuth prompt: TOTP:
<22> charon: 12[IKE] no credentials found for TOTP prompt
<22> charon: 12[IKE] XAuth authentication failed
Cum funcționează XAuth-ul în doi pași al FortiGate și de ce eșuează strongSwan
[IKE] XAuth request: XAUTH_USER_NAME, XAUTH_USER_PASSWORD
[IKE] XAuth request: XAUTH_USER_PASSWORD (prompt: "TOTP:")
Aspecte importante:
- Serverul folosește același tip de atribut (
XAUTH_USER_PASSWORD) pentru a doua fază, dar schimbă prompt-ul pentru a indica TOTP. - Al doilea prompt nu este o cerință de concatenare — așteaptă OTP-ul ca răspuns separat.
Plugin-ul standard xauth_generic nu are logica necesară pentru a detecta și a răspunde la acest al doilea prompt în mod non-interactiv — de obicei răspunde o singură dată cu parola stocată și apoi eșuează.
Diagnosticul inițial: verificarea rețelei și a mediului
Înainte de a depana autentificarea, am verificat calea de rețea între mașina virtuală de test și gateway-ul FortiGate.
Offload-uri VM și MTU
Pachetele IKE sau ESP fragmentate pot cauza comportamente erratice. Am dezactivat toate offload-urile și am fixat MTU-ul:
ethtool -K eth0 tso off gso off gro off
ip link set dev eth0 mtu 1400
Cum interacționează XAuth cu TOTP pe FortiGate
Dialogul XAuth al FortiGate arată astfel (capturat din debug-ul charon):
<IKE> XAuth request received:
XAUTH_USER_NAME: 'Username:'
XAUTH_USER_PASSWORD: 'Password:'
<IKE> XAuth request received:
XAUTH_USER_PASSWORD: 'TOTP:'
Al doilea mesaj este diferența crucială: FortiGate reutilizează același tip de atribut (XAUTH_USER_PASSWORD) dar schimbă string-ul de prompt în „TOTP:”.
Dacă clientul răspunde pur și simplu cu cifrele OTP, autentificarea reușește.
Plugin-ul standard xauth_generic nu are logică pentru a detecta acest lucru — încearcă să reutilizeze parola stocată, ceea ce duce la eșec.
Alternative luate în considerare (și de ce au eșuat)
Înainte de a scrie patch-ul, am testat toate metodele „oficiale” sau „creative” de a face strongSwan să gestioneze fluxul XAuth + TOTP în doi pași al FortiGate fără a modifica codul sursă. Toate au eșuat în modul headless din motive structurale.
1. Prompt interactiv (operatorul introduce OTP-ul)
La prima vedere pare simplu: când FortiGate cere „TOTP:”, strongSwan ar putea pur și simplu să solicite utilizatorului.
Realitatea este însă că atunci când se folosește modelul normal ipsec starter / daemon charon, nu există un TTY pe care procesul să poată întreba.
ipsec up <conn> trimite doar comenzi către daemon prin socket-ul de control; daemon-ul nu poate afișa un prompt pe stdin.
Am încercat mai multe combinații, inclusiv lansarea ipsec up direct dintr-un shell și așteptarea unui prompt OTP — niciunul nu a apărut.
Acest comportament este așteptat: singurele componente strongSwan capabile să solicite intervenția unui operator sunt:
- NetworkManager-strongSwan, care afișează dialoguri GUI (nepotrivit pentru servere), și
- charon-cmd, folosit în principal pentru sesiuni IKEv2/EAP de tip one-shot, nu pentru IKEv1/XAuth cu FortiGate.
Cu alte cuvinte, abordarea interactivă funcționează doar pentru utilizatorii de desktop, nu pentru deployment-uri automatizate sau headless.
2. Wrapper-e externe (expect, oathtool sau pre-scrierea /etc/ipsec.otp)
O altă idee a fost să rulăm strongSwan sub expect, să interceptăm prompt-urile „Password:” sau „TOTP:” și să le alimentăm automat.
Aceasta funcționează pentru programele care afișează efectiv pe stdout și citesc de pe stdin — dar charon nu face asta.
Starter-ul strongSwan comunică cu charon prin socket-uri de control (stroke/VICI), nu prin terminal, deci nu există niciun prompt de interceptat.
Chiar dacă un script expect ar încerca să citească de la proces, nu ar vedea nimic; schimbul are loc intern în daemon.
Am testat și trucul de a scrie OTP-ul într-un fișier înainte de a doua fază (de ex. /etc/ipsec.otp), sperând că plugin-ul l-ar putea citi.
Plugin-ul standard xauth_generic ignoră orice fișier extern — reutilizează pur și simplu string-ul static al parolei din configurație, ceea ce duce la eșecul autentificării.
3. Hook-uri PAM sau VICI
Alte opțiuni teoretice, precum xauth_pam sau folosirea API-ului VICI pentru a injecta atribute în timpul schimbului, nu se aplică nici ele:
xauth_pamse folosește când strongSwan acționează ca server XAuth, nu ca client.- API-ul VICI nu are un eveniment sau callback pentru un al doilea atribut
XAUTH_USER_PASSWORDîn IKEv1; nu poate trimite un OTP dinamic.
4. Singura metodă fiabilă
Deoarece niciuna dintre variantele de mai sus nu putea furniza o credențială de fază secundară în mod non-interactiv, singura soluție curată și fiabilă a fost extinderea plugin-ului existent.
Prin adăugarea logicii în xauth_generic pentru a detecta "TOTP:" și a prelua codul dintr-un fișier sau un script helper, am păstrat întregul schimb în interiorul mașinii de stări normale a charon — fără I/O extern, fără condiții de cursă și cu suport complet headless.
Proiectarea și fluxul patch-ului
Am extins process_request() din
src/libcharon/plugins/xauth_generic/xauth_generic.c.
Detecție:
Dacă string-ul de prompt conține "TOTP", plugin-ul execută un helper pentru a obține OTP-ul și setează această valoare ca răspuns pentru atributul curent.
Sursa OTP:
/etc/ipsec.otp— fișier static (poate fi scris de automatizare când OTP-ul sosește prin SMS), sau/usr/local/sbin/ipsec-totp.sh— generator dinamic folosindoathtool.
Securitate:
- Fișiere citibile doar de root (0600)
- OTP-ul este stocat în memorie pe scurt, golit după utilizare
- Fără concatenare cu parola (challenge separat)
Rezultat: Autentificare XAuth în doi pași, transparentă, pe Linux.
Abordarea patch-ului: rațiune și flux
Am inserat logica de detecție în
src/libcharon/plugins/xauth_generic/xauth_generic.c → process_request().
Flux:
- La primirea
XAUTH_USER_PASSWORD, se verifică string-ul de prompt. - Dacă conține
"TOTP", se preia OTP-ul (fișier sau script). - Se returnează OTP-ul ca valoare a atributului.
- Buffer-ele se golesc după utilizare.
Două surse suportate:
/etc/ipsec.otp(static, de ex. bazat pe SMS)/usr/local/sbin/ipsec-totp.sh(dinamic, foloseșteoathtool)
Securitate: fișierele sunt root:root 0600; OTP-ul există doar temporar în memorie; debug-ul nu afișează niciodată secrete.
Compilarea și testarea strongSwan-ului cu patch
1 Obținerea surselor și a dependențelor
apt install build-essential libgmp-dev libssl-dev libsystemd-dev \
flex bison git autoconf libtool pkg-config oathtool
git clone https://github.com/strongswan/strongswan.git
cd strongswan
git checkout 5.9.13
git apply /path/to/0001-xauth-totp.patch
2 Configurare și compilare
./autogen.sh
./configure --prefix=/usr --sysconfdir=/etc \
--enable-unity --enable-xauth-generic \
--enable-openssl --enable-systemd
make -j$(nproc)
make install
3 Instalare configurații
Folosiți exemplele din Anexa B–C, apoi reîncărcați ipsec:
ipsec restart
4 Rularea unui test
ipsec up fortigate-base
[IKE] XAuth request: Username/Password
[IKE] credentials accepted
[IKE] XAuth request: TOTP:
[xauth-totp] sent OTP in response to TOTP challenge
[IKE] XAuth authentication of 'user1' successful
[CFG] CHILD_SA fortigate-internal{1} established
Detalii de configurare IPsec
/etc/ipsec.conf (simplificat):
config setup
charondebug="ike 2, knl 2, cfg 2"
uniqueids=yes
conn %default
keyexchange=ikev1
authby=xauthpsk
xauth=client
xauth_identity=user1
left=%defaultroute
leftsourceip=%config
right=<fortigate_public_ip>
rightid=@FGT
ike=aes256-sha256-modp1024!
esp=aes256-sha256!
ikelifetime=8h
keylife=1h
aggressive=yes
modecfgpull=yes
dpdaction=restart
dpddelay=30
dpdtimeout=120
conn fortigate-internal
also=%default
rightsubnet=10.0.0.0/8
auto=add
Setări de rulare strongSwan
/etc/strongswan.conf:
charon {
install_routes = yes
load_modular = yes
plugins {
xauth-generic {
enable = yes
}
}
filelog {
/var/log/charon.log {
time_format = %b %e %T
ike_name = yes
append = yes
default = 2
flush_line = yes
}
}
}
Automatizarea OTP
/usr/local/sbin/ipsec-totp.sh:
#!/usr/bin/env bash
# Safebyte Consulting - TOTP helper for strongSwan
set -euo pipefail
CONN="fortigate-base"
OTP_FILE="/etc/ipsec.otp"
SECRET_FILE="/root/.totp_base32"
IPSEC="/usr/local/sbin/ipsec"
# 1) verificări rapide
if ! command -v oathtool >/dev/null 2>&1; then
echo "Error: oathtool is not installed. Install it with: sudo apt-get install -y oathtool" >&2
exit 1
fi
if [[ ! -r "$SECRET_FILE" ]]; then
echo "Error: missing $SECRET_FILE (key TOTP base32). Create it with 600 permissions." >&2
exit 2
fi
# 2) OTP curent (fără newline)
SECRET=$(tr -d '\r\n ' < "$SECRET_FILE")
OTP=$(oathtool --totp -b "$SECRET")
printf "%s" "$OTP" | sudo tee "$OTP_FILE" >/dev/null
sudo chmod 600 "$OTP_FILE"
# 3) inițiază conexiunea
$IPSEC up "$CONN" || { echo "ipsec up failed"; exit 3; }
# 4) mică așteptare + verificare
sleep 3
if $IPSEC statusall | grep -qE "$CONN\{[0-9]+\}.*ESTABLISHED|$CONN.*CHILD_SA.*established"; then
echo "✅ $CONN is up. Cleaning OTP."
sudo shred -u "$OTP_FILE" || sudo rm -f "$OTP_FILE"
exit 0
else
echo "⚠️ $CONN is not up yet. Leaving OTP for rapid retry (expiring in ~30s)." >&2
exit 4
fi
Permisiuni:
chmod 700 /usr/local/sbin/ipsec-totp.sh
chmod 600 /etc/ipsec.secret.totp
Dacă FortiGate trimite OTP-ul prin SMS, scrieți-l în /etc/ipsec.otp înainte de a rula ipsec up.
Lista de verificare pentru depanare
| Simptom | Cauză probabilă | Remediere |
|---|---|---|
XAuth authentication failed după TOTP | OTP greșit / decalaj de timp | verificați sincronizarea date și comparați cu telefonul |
NO_PROPOSAL_CHOSEN | cifruri IKE/ESP nepotrivite | aliniați ike= și esp= cu setările FortiGate |
| Tunel activ dar fără trafic | rutele nu sunt instalate | setați install_routes=yes |
| Timeout IKE / fragmente | MTU prea mare | reduceți la 1400, dezactivați offload-urile |
| Pachetele ESP blocate | NAT-T sau firewall | verificați UDP 4500 deschis, folosiți tcpdump -n -i eth0 esp or port 4500 |
Exemplu statusall după conectare reușită:
Status of IKE charon daemon (strongSwan 5.9.13):
IKEv1 Connections:
fortigate-internal[1]: ESTABLISHED, IKEv1
IKE SPIs: 07cda3fa8d...
XAuth authentication of 'user1' successful
CHILD_SA fortigate-internal{1} INSTALLED
10.0.0.0/8 === 192.168.10.0/24
Descoperire: ruta implicită Windows vs. split-tunnel Linux
Pe parcursul validării rutării am comparat clienții:
- Windows FortiClient: FortiGate trimite și impune
0.0.0.0/0ca rută implicită → tot traficul trece prin VPN (full-tunnel). - Linux strongSwan: implicit instalează doar rutele din CHILD_SA sau cele listate explicit în
rightsubnet=. Rutele locale rămân → split-tunnel.
Impact de securitate
Dacă politica presupune full-tunnel, un utilizator sau un atacator ar putea:
- exfiltra date direct către internet,
- ocoli sistemele DLP/IDS situate în spatele FortiGate,
- amesteca traficul corporativ cu cel extern.
Note operaționale și precauții de securitate
- Separarea OTP: parola și OTP-ul sunt schimburi distincte; evitați reutilizarea aceleiași căi de cod.
- Stocarea secretelor:
/etc/ipsec.secret.totpși/etc/ipsec.otptrebuie să fieroot:root 0600. - Gestionarea memoriei: buffer-ele sunt golite după utilizare (vezi patch-ul).
- Politica de build: mențineți patch-ul într-o ramură semnată și versionată pentru reproductibilitate.
- Logare: păstrați
charondebugla niveluri scăzute în producție pentru a evita expunerea prompt-urilor.
Concluzii și recomandări
Patch-ul permite strongSwan să interopereze complet cu fluxul XAuth + TOTP în doi pași al FortiGate — non-interactiv, securizat și compatibil cu automatizarea.
La fel de important, analiza noastră de rutare a revelat că clienții strongSwan pot funcționa neintenționat în modul split-tunnel chiar și atunci când FortiGate așteaptă impunerea full-tunnel — o breșă de politică subtilă dar serioasă.
Concluzii cheie:
- Mențineți ramura strongSwan cu patch sub controlul versiunilor.
- Protejați secretele OTP; folosiți token-uri hardware când este fezabil.
- Impuneți sau verificați rutarea full-tunnel conform politicii.
- Monitorizați traficul de ieșire al endpoint-urilor pentru ocoliri split-tunnel.
- Mențineți ceasurile sincronizate; TOTP depinde de timp.
Anexa A — xauth_generic.c — varianta TOTP Safebyte
/*
* Copyright (C) 2011 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*ee
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
* xauth_generic.c — Safebyte TOTP variant (verbatim)
*/
#include "xauth_generic.h"
#include <daemon.h>
#include <library.h>
#include <stdio.h>
#include <sys/stat.h>
/* citește OTP din /etc/ipsec.otp, fără newline */
static int read_otp_file(const char *path, char *buf, size_t buflen)
{
FILE *f = fopen(path, "r");
if (!f) return -1;
size_t r = fread(buf, 1, buflen - 1, f);
fclose(f);
while (r > 0 && (buf[r-1] == '\n' || buf[r-1] == '\r')) r--;
buf[r] = '\0';
return (int)r;
}
typedef struct private_xauth_generic_t private_xauth_generic_t;
/**
* Private data of an xauth_generic_t object.
*/
struct private_xauth_generic_t {
/**
* Public interface.
*/
xauth_generic_t public;
/**
* ID of the server
*/
identification_t *server;
/**
* ID of the peer
*/
identification_t *peer;
};
METHOD(xauth_method_t, initiate_peer, status_t,
private_xauth_generic_t *this, cp_payload_t **out)
{
/* peer never initiates */
return FAILED;
}
METHOD(xauth_method_t, process_peer, status_t,
private_xauth_generic_t *this, cp_payload_t *in, cp_payload_t **out)
{
configuration_attribute_t *attr;
enumerator_t *enumerator;
shared_key_t *shared;
cp_payload_t *cp;
chunk_t msg;
bool totp_challenge = FALSE;
char otpbuf[128] = {0};
enumerator = in->create_attribute_enumerator(in);
while (enumerator->enumerate(enumerator, &attr))
{
if (attr->get_type(attr) == XAUTH_MESSAGE)
{
chunk_printable(attr->get_chunk(attr), &msg, '?');
DBG1(DBG_CFG, "XAuth message: %.*s", (int)msg.len, msg.ptr);
/* Detectează challenge TOTP: */
if (msg.len >= 4)
{
/* compară prefixul "TOTP" case-insensitive */
size_t n = msg.len > 4 ? 4 : msg.len;
char head[5] = {0};
memcpy(head, msg.ptr, n);
for (size_t i = 0; i < n; i++)
{
if (head[i] >= 'a' && head[i] <= 'z') head[i] -= 32;
}
if (memcmp(head, "TOTP", 4) == 0)
{
if (read_otp_file("/etc/ipsec.otp", otpbuf, sizeof(otpbuf)) > 0)
{
totp_challenge = TRUE;
DBG1(DBG_IKE, "xauth-totp: OTP loaded from /etc/ipsec.otp");
}
else
{
DBG1(DBG_IKE, "xauth-totp: failed to read /etc/ipsec.otp");
}
}
}
free(msg.ptr);
}
}
enumerator->destroy(enumerator);
cp = cp_payload_create_type(PLV1_CONFIGURATION, CFG_REPLY);
enumerator = in->create_attribute_enumerator(in);
while (enumerator->enumerate(enumerator, &attr))
{
shared_key_type_t type = SHARED_EAP;
switch (attr->get_type(attr))
{
case XAUTH_USER_NAME:
cp->add_attribute(cp, configuration_attribute_create_chunk(
PLV1_CONFIGURATION_ATTRIBUTE, XAUTH_USER_NAME,
this->peer->get_encoding(this->peer)));
break;
case XAUTH_NEXT_PIN:
type = SHARED_PIN;
/* FALL */
case XAUTH_USER_PASSWORD:
if (totp_challenge && otpbuf[0] != '\0')
{
chunk_t otp = chunk_create(otpbuf, strlen(otpbuf));
cp->add_attribute(cp, configuration_attribute_create_chunk(
PLV1_CONFIGURATION_ATTRIBUTE, XAUTH_USER_PASSWORD, otp));
DBG1(DBG_IKE, "xauth-totp: injected OTP into XAUTH_USER_PASSWORD");
}
else
{
shared = lib->credmgr->get_shared(lib->credmgr, type,
this->peer, this->server);
if (!shared)
{
DBG1(DBG_IKE, "no XAuth %s found for '%Y' - '%Y'",
type == SHARED_EAP ? "password" : "PIN",
this->peer, this->server);
enumerator->destroy(enumerator);
cp->destroy(cp);
return FAILED;
}
cp->add_attribute(cp, configuration_attribute_create_chunk(
PLV1_CONFIGURATION_ATTRIBUTE, attr->get_type(attr),
shared->get_key(shared)));
shared->destroy(shared);
}
break;
default:
break;
}
}
enumerator->destroy(enumerator);
*out = cp;
return NEED_MORE;
}
METHOD(xauth_method_t, initiate_server, status_t,
private_xauth_generic_t *this, cp_payload_t **out)
{
cp_payload_t *cp;
cp = cp_payload_create_type(PLV1_CONFIGURATION, CFG_REQUEST);
cp->add_attribute(cp, configuration_attribute_create_chunk(
PLV1_CONFIGURATION_ATTRIBUTE, XAUTH_USER_NAME, chunk_empty));
cp->add_attribute(cp, configuration_attribute_create_chunk(
PLV1_CONFIGURATION_ATTRIBUTE, XAUTH_USER_PASSWORD, chunk_empty));
*out = cp;
return NEED_MORE;
}
METHOD(xauth_method_t, process_server, status_t,
private_xauth_generic_t *this, cp_payload_t *in, cp_payload_t **out)
{
configuration_attribute_t *attr;
enumerator_t *enumerator;
shared_key_t *shared;
identification_t *id;
chunk_t user = chunk_empty, pass = chunk_empty;
status_t status = FAILED;
int tried = 0;
enumerator = in->create_attribute_enumerator(in);
while (enumerator->enumerate(enumerator, &attr))
{
switch (attr->get_type(attr))
{
case XAUTH_USER_NAME:
user = attr->get_chunk(attr);
break;
case XAUTH_USER_PASSWORD:
pass = attr->get_chunk(attr);
break;
default:
break;
}
}
enumerator->destroy(enumerator);
if (!user.ptr || !pass.ptr)
{
DBG1(DBG_IKE, "peer did not respond to our XAuth request");
return FAILED;
}
if (user.len)
{
id = identification_create_from_data(user);
if (!id)
{
DBG1(DBG_IKE, "failed to parse provided XAuth username");
return FAILED;
}
this->peer->destroy(this->peer);
this->peer = id;
}
if (pass.len && pass.ptr[pass.len - 1] == 0)
{ /* fix null-terminated passwords (Android etc.) */
pass.len -= 1;
}
enumerator = lib->credmgr->create_shared_enumerator(lib->credmgr,
SHARED_EAP, this->server, this->peer);
while (enumerator->enumerate(enumerator, &shared, NULL, NULL))
{
if (chunk_equals_const(shared->get_key(shared), pass))
{
status = SUCCESS;
break;
}
tried++;
}
enumerator->destroy(enumerator);
if (status != SUCCESS)
{
if (!tried)
{
DBG1(DBG_IKE, "no XAuth secret found for '%Y' - '%Y'",
this->server, this->peer);
}
else
{
DBG1(DBG_IKE, "none of %d found XAuth secrets for '%Y' - '%Y' "
"matched", tried, this->server, this->peer);
}
}
return status;
}
METHOD(xauth_method_t, get_identity, identification_t*,
private_xauth_generic_t *this)
{
return this->peer;
}
METHOD(xauth_method_t, destroy, void,
private_xauth_generic_t *this)
{
this->server->destroy(this->server);
this->peer->destroy(this->peer);
free(this);
}
/*
* Described in header.
*/
xauth_generic_t *xauth_generic_create_peer(identification_t *server,
identification_t *peer,
char *profile)
{
private_xauth_generic_t *this;
INIT(this,
.public = {
.xauth_method = {
.initiate = _initiate_peer,
.process = _process_peer,
.get_identity = _get_identity,
.destroy = _destroy,
},
},
.server = server->clone(server),
.peer = peer->clone(peer),
);
return &this->public;
}
/*
* Described in header.
*/
xauth_generic_t *xauth_generic_create_server(identification_t *server,
identification_t *peer,
char *profile)
{
private_xauth_generic_t *this;
INIT(this,
.public = {
.xauth_method = {
.initiate = _initiate_server,
.process = _process_server,
.get_identity = _get_identity,
.destroy = _destroy,
},
},
.server = server->clone(server),
.peer = peer->clone(peer),
);
return &this->public;
}
Anexa B (addendum) — Subrețele multiple prin conexiuni child
Când nu doriți o rută implicită completă ci vizați o configurație split-tunnel, definiți conexiuni child suplimentare care reutilizează un IKE SA de bază:
conn fortigate-base
keyexchange=ikev1
authby=xauthpsk
xauth=client
xauth_identity=user1
left=%defaultroute
leftsourceip=%config
right=<REDACTED_FGT_IP>
rightid=@FGT
ike=aes256-sha256-modp1024!
esp=aes256-sha256!
aggressive=yes
modecfgpull=yes
dpdaction=restart
auto=add
# Internal networks
conn fortigate-10-0-0-0_8
also=fortigate-base
rightsubnet=10.0.0.0/8
auto=add
conn fortigate-172-16-0-0_12
also=fortigate-base
rightsubnet=172.16.0.0/12
auto=add
conn fortigate-192-168-100-0_24
also=fortigate-base
rightsubnet=192.168.100.0/24
auto=add
Sfat: păstrați charon { install_routes = yes } în strongswan.conf pentru ca rutele din kernel să fie instalate automat pentru fiecare CHILD_SA.
Compilarea celui mai recent strongswan 5.9.x pe Debian bullseye funcționează din start – dar pe GNU gcc 15 (kali rolling la data acestui articol) e o altă provocare – dacă cineva vrea patch-urile complete, mă poate contacta la teodor.lupan[at]safebyte.io .