office@safebyte.io București, România ISO 27001:2023 · ISO 9001:2023
Ghid

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_pam se 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 folosind oathtool.

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ște oathtool)

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

SimptomCauză probabilăRemediere
XAuth authentication failed după TOTPOTP greșit / decalaj de timpverificați sincronizarea date și comparați cu telefonul
NO_PROPOSAL_CHOSENcifruri IKE/ESP nepotrivitealiniați ike= și esp= cu setările FortiGate
Tunel activ dar fără traficrutele nu sunt instalatesetați install_routes=yes
Timeout IKE / fragmenteMTU prea marereduceți la 1400, dezactivați offload-urile
Pachetele ESP blocateNAT-T sau firewallverificaț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/0 ca 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.otp trebuie să fie root: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 charondebug la 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 .

← Înapoi la blog