Site-to-Site-VPN zwischen UniFi Security Gateway und Fritz!Box

TL;DR

Die Konfigurationen weiter unten entsprechend dem eigenen Setup anpassen und Listing 1 in der Fritz!Box als neues VPN aus einer Konfigurationsdatei importieren. Listing 2 als config.gateway.json im Verzeichnis der entsprechenden Site auf dem UniFi Controller ablegen und eine Provisionierung erzwingen. Danach sollte die Verbindung binnen weniger Minuten bestehen. Falls nicht, gibt es eine Troubleshooting Sektion am Ende dieses Artikels.

Bei der Suche nach einer Möglichkeit, ein UniFi USG mit einer Fritz!Box über ein VPN zu verbinden stellte sich leider heraus, dass ein Site-to-Site VPN mit einer Fritz!Box als Gegenstelle doch etwas manuelle Konfiguration erfordert. Da ich keine zufriedenstellende Konfiguration ohne mehr als notwendiges Frickeln im Internet fand, möchte ich für alle Interessierten meine Konfiguration der USG und Fritz!Box relativ ausführlich beschreiben.

Hinweis: Alle in Großbuchstaben mit < und > gekennzeichneten Zeichenketten sind durch die entsprechend eurem Setup passenden Werte zu ersetzen. REMOTE bezieht sich immer auf die jeweilige Gegenseite aus Sicht der USG und Fritz!Box.

Wir fangen mit der Konfiguration der Fritz!Box an. Leider ist diese nicht einfach über das Webinterface möglich. Stattdessen muss die folgende Konfiguration unter Internet → Freigaben → VPN → VPN-Verbindung hinzufügen mit der Option Eine VPN-Konfiguration aus einer vorhandenen VPN-Einstellungsdatei importieren eingelesen werden. Ich habe die wichtigsten Settings kommentiert und werde später noch auf den von mir gewählten IKEv1 Phase 1 Modus eingehen.

vpncfg {
    connections {
        enabled = yes;
        editable = no;
        conn_type = conntype_lan; // Site2Site
        name = "<CONNECTION_NAME>";
        always_renew = yes; // Von Fritz!Box initiierte Verbindung aufrechterhalten
        reject_not_encrypted = no; // Internetzugang neben VPN erlauben
        dont_filter_netbios = no; // NetBIOS filtern (bei Bedarf auf 'yes' setzen)
        localip = 0.0.0.0;
        local_virtualip = 0.0.0.0;
        remoteip = 0.0.0.0;
        remote_virtualip = 0.0.0.0;
        remotehostname = "<REMOTE_HOSTNAME>";
        keepalive_ip = 0.0.0.0; // Optional: Ständig erreichbare IP auf der Gegenseite, um Verbindungsabbruch frühzeitig zu erkennen

        localid {
            fqdn = "<LOCAL_HOSTNAME>";
        }
        remoteid {
            fqdn = "<REMOTE_HOSTNAME>";
        }

        mode = phase1_mode_idp; // Aggressive mode: 'phase1_mode_aggressive'
        phase1ss = "all/all/all"; // https://avm.de/fileadmin/user_upload/DE/Service/VPN/ike_1.pdf
        keytype = connkeytype_pre_shared;
        key = "<PRE_SHARED_KEY>";
        cert_do_server_auth = no;
        use_nat_t = yes; // NAT traversal
        use_xauth = no; // Keine zusätzliche XAUTH Authentifizierung
        use_cfgmode = no; // Keine Konfigurationsdetails an die Gegenseite pushen (Cisco MODECFG)

        phase2localid {
            ipnet {
                ipaddr = <LOCAL_PRIVATE_SUBNET>;
                mask = 255.255.255.0;
            }
        }
        phase2remoteid {
            ipnet {
                ipaddr = <REMOTE_PRIVATE_SUBNET>;
                mask = 255.255.255.0;
            }
        }

        phase2ss = "esp-all-all/ah-none/comp-all/pfs"; // https://avm.de/fileadmin/user_upload/DE/Service/VPN/ike_2.pdf
                                                       // Verwende keinen 'Authentication Header'. AH ist inkompatibel mit NAT und ESP reicht.
        accesslist = "permit ip any <REMOTE_PRIVATE_SUBNET> 255.255.255.0",
                     "permit ip any <REMOTE_PRIVATE_SUBNET_2> 255.255.255.0"; // Optional, falls weitere Netzwerke erreicht werden sollen. Auf Rückrouten achten!
    }

    ike_forward_rules = "udp 0.0.0.0:500 0.0.0.0:500",
                        "udp 0.0.0.0:4500 0.0.0.0:4500";
}

Damit ist die Fritz!Box einsatzbereit und wir widmen uns der USG. Auch die USG können wir leider nicht über das Webinterface des Controllers konfigurieren. Stattdessen müssen wir auch hier eine Konfigurationsdatei anlegen, die als config.gateway.json im entsprechenden Verzeichnis der gewünschten Site abgelegt werden muss.
Unter Debian könnte der Pfad zum Beispiel so aussehen: /var/lib/unifi/sites/default/config.gateway.json. Nähere Informationen zur config.gateway.json bietet Ubiquiti in ihrem Support Center.

Bei der nachfolgenden Konfiguration ist darauf zu achten, dass das JSON valide ist, ansonsten begibt sich die USG in einen Provisioning Loop, aus welchem sie mit einer gültigen config.gateway.json und einem anschließenden Neustart jedoch wieder herauskommen sollte.
Die syntaktische Gültigkeit der Konfiguration kann z.B. auf jsonlint.com überprüft werden. Die Settings ähneln der Fritz!Box Konfiguration und sollten mit den obigen Kommentaren verständlich sein. Falls etwas unklar bleibt, beantworte ich offene Fragen gerne in den Kommentaren.

{
    "vpn": {
        "ipsec": {
            "esp-group": {
                "ESP-FritzBox": {
                    "compression": "disable",
                    "lifetime": "3600",
                    "mode": "tunnel",
                    "pfs": "enable",
                    "proposal": {
                        "1": {
                            "encryption": "aes256",
                            "hash": "sha1"
                        }
                    }
                }
            },
            "ike-group": {
                "IKE-FritzBox": {
                    "lifetime": "3600",
                    "key-exchange": "ikev1",
                    "proposal": {
                        "1": {
                            "dh-group": "2",
                            "encryption": "aes256",
                            "hash": "sha1"
                        }
                    }
                }
            },
            "auto-firewall-nat-exclude": "enable",
            "auto-update": "60",
            "site-to-site": {
                "peer": {
                    "<REMOTE_HOSTNAME>": {
                        "authentication": {
                            "mode": "pre-shared-secret",
                            "pre-shared-secret": "<PRE_SHARED_KEY>",
                            "id": "<LOCAL_HOSTNAME>",
                            "remote-id": "<REMOTE_HOSTNAME>"
                        },
                        "connection-type": "initiate",
                        "ike-group": "IKE-FritzBox",
                        "local-address": "any",
                        "tunnel": {
                            "1": {
                                "esp-group": "ESP-FritzBox",
                                "local": {
                                    "prefix": "<LOCAL_PRIVATE_SUBNET>/24"
                                },
                                "remote": {
                                    "prefix": "<REMOTE_PRIVATE_SUBNET>/24"
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

IKEv1 Phase 1 Modi

Für Phase 1 im Internet Key Exchange Protokoll (IKE) sind zwei verschiedene Modi definiert, die bei der Authentisierung einen wichtigen Unterschied besitzen.
Während der gehashte Pre-shared key zur Authentisierung im Main Mode erst nach Aushandlung der Verschlüsselung verschlüsselt übertragen wird, geschieht dies zur Beschleunigung des Vorgangs im Aggressive Mode schon bevor die Verschlüsselung die Verbindung absichert. Somit wird der gehashte PSK im Aggressive Mode unverschlüsselt übertragen, was Angreifern eine Brute-Force Attacke ermöglicht.

Wer dennoch den Agressive Mode verwenden muss, kommt um einen manuellen Eingriff auf der USG nicht herum. Ubiquiti verwendet die unter Linux häufig eingesetzte Umsetzung des IKE Protokolls strongSwan. Die strongSwan Entwickler haben sich nach langer Diskussion darauf geeinigt, den Aggressive Mode von IKEv1, trotz der Schwachstelle, zu erlauben. Vorher muss allerdings die Option charon.i_dont_care_about_security_and_use_aggressive_mode_psk in der strongSwan Konfiguration aktiviert werden.
Es gibt standardmäßig keine Möglichkeit diese Option über die config.gateway.json zu aktivieren. Somit muss die Änderung nach jedem Firmware Update erneut angewandt werden. Alternativ kann man dies auch automatisiert über ein im Pfad /config/scripts/pre-config.d/ zu platzierndes Script umsetzen.

Ich hoffe ich konnte die IKEv1 Phase 1 Thematik etwas aufklären. Fragen beantworte ich gerne in den Kommentaren.

Troubleshooting

Sollte die Verbindung wider Erwarten auch nach einigen Minuten nicht bestehen, sollte man sich das Systemprotokoll der Fritz!Box anschauen und einen manuellen Start der Verbindung auf der USG erzwingen.

Das Systemprotokoll findet sich unter System → Ereignisse. Erscheint ein VPN-Fehler, bietet AVM nach einem Klick auf das Ereignis eine einigermaßen ausführliche Beschreibung.

Bessere Informationen erhält man meist, wenn man sich per SSH auf der USG einloggt und sich mit dem Kommando ipsec statusall alle eingerichteten VPN Verbindungen, unabhängig ihres derzeitigen Status, anzeigen lässt. Danach sucht man sich den Namen der problematischen Verbindung aus der Liste und versucht mit dem Kommando ipsec up <NAME> die Verbindung manuell zu initiieren. Schlägt dies fehl, sollten detaillierte Fehlerinformationen erscheinen.