Skip to content

Commit

Permalink
VPN: IPsec: Mobile Clients - move charon attributes to "Advanced sett…
Browse files Browse the repository at this point in the history
…ings" for #8349 (#8380)

Rename previous "advanced settings" to "mobile & advanced settings" to guide people into the right direction, strongswan.conf contains both sets of data.
Keep legacy page for settings that are only relevant for the old components.

Since our pam authenticator hooks into the configuration, refactor to use the model as well.

Cleanup code in the model that was only used in the legacy glue.
  • Loading branch information
AdSchellevis authored Feb 28, 2025
1 parent 387c381 commit a893cdc
Show file tree
Hide file tree
Showing 10 changed files with 418 additions and 482 deletions.
1 change: 1 addition & 0 deletions plist
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,7 @@
/usr/local/opnsense/mvc/app/models/OPNsense/IPsec/Migrations/M1_0_1.php
/usr/local/opnsense/mvc/app/models/OPNsense/IPsec/Migrations/M1_0_2.php
/usr/local/opnsense/mvc/app/models/OPNsense/IPsec/Migrations/M1_0_3.php
/usr/local/opnsense/mvc/app/models/OPNsense/IPsec/Migrations/M1_0_4.php
/usr/local/opnsense/mvc/app/models/OPNsense/IPsec/Swanctl.php
/usr/local/opnsense/mvc/app/models/OPNsense/IPsec/Swanctl.xml
/usr/local/opnsense/mvc/app/models/OPNsense/Interfaces/ACL/ACL.xml
Expand Down
136 changes: 14 additions & 122 deletions src/etc/inc/plugins.inc.d/ipsec.inc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php

/*
* Copyright (C) 2016-2023 Deciso B.V.
* Copyright (C) 2016-2025 Deciso B.V.
* Copyright (C) 2019 Pascal Mathis <[email protected]>
* Copyright (C) 2008 Shrew Soft Inc. <[email protected]>
* Copyright (C) 2008 Ermal Luçi
Expand Down Expand Up @@ -925,6 +925,8 @@ function ipsec_write_strongswan_conf()

$strongswanTree = (new \OPNsense\IPsec\IPsec())->strongswanTree();

/* legacy overwrites for strongswan.conf */

foreach ($a_phase1 as $ph1ent) {
if (isset($ph1ent['disabled'])) {
continue;
Expand All @@ -936,18 +938,10 @@ function ipsec_write_strongswan_conf()
}
}

$strongswanTree['charon']['install_routes'] = 'no';
if (isset($a_client['enable']) && isset($a_client['net_list'])) {
$strongswanTree['charon']['cisco_unity'] = 'yes';
}

$strongswanTree['charon']['plugins'] = [];

$radius_auth_servers = null;
$disable_xauth = false;
if (isset($a_client['enable'])) {
if (isset($a_client['enable']) && empty($strongswanTree['charon']['plugins']['attr']['subnet'])) {
/* legacy subnet collection, can only be used when not offered manually */
$net_list = [];
if (isset($a_client['net_list'])) {
if ($strongswanTree['charon']['cisco_unity'] == 'yes') {
foreach ($a_phase1 as $ph1ent) {
if (isset($ph1ent['disabled']) || !isset($ph1ent['mobile'])) {
continue;
Expand All @@ -960,123 +954,21 @@ function ipsec_write_strongswan_conf()
}
}

$strongswanTree['charon']['plugins']['attr'] = [];
if (!isset($strongswanTree['charon']['plugins']['attr'])) {
$strongswanTree['charon']['plugins']['attr'] = [];
}
if (!empty($net_list)) {
$net_list_str = implode(",", $net_list);
$strongswanTree['charon']['plugins']['attr']['subnet'] = $net_list_str;
$strongswanTree['charon']['plugins']['attr']['split-include'] = $net_list_str;
}
$cfgservers = [];
foreach (array('dns_server1', 'dns_server2', 'dns_server3', 'dns_server4') as $dns_server) {
if (!empty($a_client[$dns_server])) {
$cfgservers[] = $a_client[$dns_server];
}
}
if (!empty($cfgservers)) {
$strongswanTree['charon']['plugins']['attr']['dns'] = implode(",", $cfgservers);
}
$cfgservers = [];
if (!empty($a_client['wins_server1'])) {
$cfgservers[] = $a_client['wins_server1'];
}
if (!empty($a_client['wins_server2'])) {
$cfgservers[] = $a_client['wins_server2'];
}
if (!empty($cfgservers)) {
$strongswanTree['charon']['plugins']['attr']['nbns'] = implode(",", $cfgservers);
}

if (!empty($a_client['dns_domain'])) {
$strongswanTree['charon']['plugins']['attr']['# Search domain and default domain'] = '';
$strongswanTree['charon']['plugins']['attr']['28674'] = $a_client['dns_domain'];
}

/*
* 28675 --> UNITY_SPLITDNS_NAME
* 25 --> INTERNAL_DNS_DOMAIN
*/
foreach (array("28675", "25") as $attr) {
if (!empty($a_client['dns_split'])) {
$strongswanTree['charon']['plugins']['attr'][$attr] = $a_client['dns_split'];
} elseif (!empty($a_client['dns_domain'])) {
$strongswanTree['charon']['plugins']['attr'][$attr] = $a_client['dns_domain'];
}
}

if (!empty($a_client['dns_split'])) {
$strongswanTree['charon']['plugins']['attr']['28675'] = $a_client['dns_split'];
}

if (!empty($a_client['login_banner'])) {
/* defang login banner, it may be multiple lines and we should not let it escape */
$strongswanTree['charon']['plugins']['attr']['28672'] = '"' . str_replace(['\\', '"'], '', $a_client['login_banner']) . '"';
}

if (isset($a_client['save_passwd'])) {
$strongswanTree['charon']['plugins']['attr']['28673'] = 1;
}

if (!empty($a_client['pfs_group'])) {
$strongswanTree['charon']['plugins']['attr']['28679'] = $a_client['pfs_group'];
}

foreach ($a_phase1 as $ph1ent) {
if (!isset($ph1ent['disabled']) && isset($ph1ent['mobile'])) {
if ($ph1ent['authentication_method'] == "eap-radius") {
$radius_auth_servers = $ph1ent['authservers'];
break; // there can only be one mobile phase1, exit loop
}
}
}
}
if (empty($radius_auth_servers) && !empty($a_client['radius_source'])) {
$radius_auth_servers = $a_client['radius_source'];
}
$mdl = new \OPNsense\IPsec\Swanctl();
if ((isset($a_client['enable']) || $mdl->isEnabled()) && !empty($radius_auth_servers)) {
$disable_xauth = true; // disable Xauth when radius is used.
$strongswanTree['charon']['plugins']['eap-radius'] = [];
$strongswanTree['charon']['plugins']['eap-radius']['servers'] = [];
$radius_server_num = 1;
$radius_accounting_enabled = false;

foreach (auth_get_authserver_list() as $auth_server) {
if (in_array($auth_server['name'], explode(',', $radius_auth_servers))) {
$server = [
'address' => $auth_server['host'],
'secret' => '"' . $auth_server['radius_secret'] . '"',
'auth_port' => $auth_server['radius_auth_port'],
];

if (!empty($auth_server['radius_acct_port'])) {
$server['acct_port'] = $auth_server['radius_acct_port'];
}
$strongswanTree['charon']['plugins']['eap-radius']['servers']['server' . $radius_server_num] = $server;

if (!empty($auth_server['radius_acct_port'])) {
$radius_accounting_enabled = true;
}
$radius_server_num += 1;
}
}
if ($radius_accounting_enabled) {
$strongswanTree['charon']['plugins']['eap-radius']['accounting'] = 'yes';
}
if ($mdl->radiusUsesGroups()) {
$strongswanTree['charon']['plugins']['eap-radius']['class_group'] = 'yes';
}
}
if ((isset($a_client['enable']) && !$disable_xauth) || (new \OPNsense\IPsec\Swanctl())->isEnabled()) {
$strongswanTree['charon']['plugins']['xauth-pam'] = [
'pam_service' => 'ipsec',
'session' => 'no',
'trim_email' => 'yes'
];
}

$strongswan = generate_strongswan_conf($strongswanTree);
$strongswan .= "\ninclude strongswan.opnsense.d/*.conf\n";
@file_put_contents("/usr/local/etc/strongswan.conf", $strongswan);
/* flush to disk */
@file_put_contents(
"/usr/local/etc/strongswan.conf",
sprintf("%s\ninclude strongswan.opnsense.d/*.conf\n", generate_strongswan_conf($strongswanTree))
);
}

/**
Expand Down
132 changes: 132 additions & 0 deletions src/opnsense/mvc/app/controllers/OPNsense/IPsec/forms/settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,59 @@
When sending all traffic to the remote location, you probably want to add your lan network(s) here.
</help>
</field>
<field>
<id>ipsec.general.user_source</id>
<label>Authentication (Xauth)</label>
<type>select_multiple</type>
<style>selectpicker</style>
<help>Select authentication methods to use, leave empty if no challenge response authentication is needed.</help>
</field>
<field>
<id>ipsec.general.local_group</id>
<label>Enforce local group (Xauth)</label>
<type>dropdown</type>
<style>selectpicker</style>
<help>Restrict access to users in the selected local group. Please be aware that other authentication backends will refuse to authenticate when using this option.</help>
</field>
</tab>
<tab id="ipsec-eap-radius" description="eap-radius">
<field>
<id>ipsec.charon.plugins.eap-radius.servers</id>
<label>Servers</label>
<type>select_multiple</type>
<help>RADIUS servers to configure</help>
</field>
<field>
<id>ipsec.charon.plugins.eap-radius.accounting</id>
<label>Accounting</label>
<type>checkbox</type>
<help>Enable RADIUS accounting</help>
</field>
<field>
<id>ipsec.charon.plugins.eap-radius.class_group</id>
<label>Group selection (class_group)</label>
<type>checkbox</type>
</field>
</tab>
<tab id="ipsec-xauth-pam" description="xauth-pam">
<field>
<id>ipsec.charon.plugins.xauth-pam.pam_service</id>
<label>Pam_service</label>
<type>text</type>
<help>PAM service to use for authentication.</help>
</field>
<field>
<id>ipsec.charon.plugins.xauth-pam.session</id>
<label>Session</label>
<type>checkbox</type>
<help>Open/close a PAM session for each active IKE_SA</help>
</field>
<field>
<id>ipsec.charon.plugins.xauth-pam.trim_email</id>
<label>Trim_email</label>
<type>checkbox</type>
<help>If an email address is received as an XAuth username, trim it to just the username part</help>
</field>
</tab>
<tab id="ipsec-charon" description="Charon">
<field>
Expand Down Expand Up @@ -84,6 +137,18 @@
<type>checkbox</type>
<help>Initiate IKEv2 reauthentication with a make-before-break instead of a break-before-make scheme. Make-before-break uses overlapping IKE and CHILD SA during reauthentication by first recreating all new SAs before deleting the old ones. This behavior can be beneficial to avoid connectivity gaps during reauthentication, but requires support for overlapping SAs by the peer.</help>
</field>
<field>
<id>ipsec.charon.install_routes</id>
<label>Install routes</label>
<type>checkbox</type>
<help>Install routes into a separate routing table for established IPsec tunnels. If disabled a more efficient lookup for source and next-hop addresses is used.</help>
</field>
<field>
<id>ipsec.charon.cisco_unity</id>
<label>Cisco Unity</label>
<type>checkbox</type>
<help>Send Cisco Unity vendor ID payload (IKEv1 only).</help>
</field>
<field>
<type>header</type>
<label>Retransmission</label>
Expand Down Expand Up @@ -231,5 +296,72 @@
<type>dropdown</type>
</field>
</tab>
<tab id="ipsec-attr" description="Attr">
<field>
<id>ipsec.charon.plugins.attr.subnet</id>
<label>Subnet</label>
<type>select_multiple</type>
<style>tokenize</style>
<allownew>true</allownew>
<help>The protected sub-networks that this edge-device protects (in CIDR notation). Usually ignored in deference to local_ts, though macOS clients will use this for routes</help>
</field>
<field>
<id>ipsec.charon.plugins.attr.dns</id>
<label>DNS</label>
<type>select_multiple</type>
<style>tokenize</style>
<allownew>true</allownew>
<help>DNS server</help>
</field>
<field>
<id>ipsec.charon.plugins.attr.nbns</id>
<label>NBNS</label>
<type>select_multiple</type>
<style>tokenize</style>
<allownew>true</allownew>
<help>WINS server</help>
</field>
<field>
<type>header</type>
<label>Cisco Unity</label>
</field>
<field>
<id>ipsec.charon.plugins.attr.split-include</id>
<label>Split-include</label>
<type>select_multiple</type>
<style>tokenize</style>
<allownew>true</allownew>
<help>Comma-separated list of subnets to tunnel. The unity plugin provides a connection specific approach to assign this attribute.</help>
</field>
<field>
<id>ipsec.charon.plugins.attr.x_28674</id>
<label>Default search</label>
<type>text</type>
<help>Default search domain used when resolving host names via the assigned DNS servers</help>
</field>
<field>
<id>ipsec.charon.plugins.attr.x_28675</id>
<label>Split DNS name</label>
<type>text</type>
<help>If split tunneling is used clients might not install the assigned DNS servers globally. This space-separated list of domain names allows clients, such as macOS, to selectively query the assigned DNS servers. Seems Mac OS X uses only the first item in the list</help>
</field>
<field>
<id>ipsec.charon.plugins.attr.x_28672</id>
<label>Login banner</label>
<type>textbox</type>
<help>Message displayed on certain clients after login</help>
</field>
<field>
<id>ipsec.charon.plugins.attr.x_28673</id>
<label>Save password</label>
<type>checkbox</type>
<help>Allow client to save Xauth password in local storage</help>
</field>
<field>
<id>ipsec.charon.plugins.attr.x_28679</id>
<label>PFS group</label>
<type>dropdown</type>
</field>
</tab>
<activetab>ipsec-general</activetab>
</form>
16 changes: 8 additions & 8 deletions src/opnsense/mvc/app/library/OPNsense/Auth/Services/IPsec.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ public static function aliases()
*/
public function supportedAuthenticators()
{
$result = array();
$configObj = Config::getInstance()->object();
if (!empty((string)$configObj->ipsec->client->user_source)) {
$result = explode(',', (string)$configObj->ipsec->client->user_source);
$result = [];
$mdl = new \OPNsense\IPsec\IPsec();
if (!empty((string)$mdl->general->user_source)) {
$result = explode(',', (string)$mdl->general->user_source);
} else {
$result[] = 'Local Database';
}
Expand Down Expand Up @@ -87,11 +87,11 @@ public function getUserName()
*/
public function checkConstraints()
{
$configObj = Config::getInstance()->object();
if (!empty((string)$configObj->ipsec->client->local_group)) {
$mdl = new \OPNsense\IPsec\IPsec();
if (!empty((string)$mdl->general->local_group)) {
// Enforce group constraint when set
$local_group = (string)$configObj->ipsec->client->local_group;
return (new ACL())->inGroup($this->getUserName(), $local_group);
$local_group = (string)$mdl->general->local_group;
return (new ACL())->inGroup($this->getUserName(), $local_group, false);
} else {
// no constraints
return true;
Expand Down
Loading

0 comments on commit a893cdc

Please sign in to comment.