diff --git a/src/opnsense/mvc/app/controllers/OPNsense/OpenVPN/forms/dialogInstance.xml b/src/opnsense/mvc/app/controllers/OPNsense/OpenVPN/forms/dialogInstance.xml
index 1d2603ee2..187421757 100644
--- a/src/opnsense/mvc/app/controllers/OPNsense/OpenVPN/forms/dialogInstance.xml
+++ b/src/opnsense/mvc/app/controllers/OPNsense/OpenVPN/forms/dialogInstance.xml
@@ -311,6 +311,17 @@ Set to 0 to disable, remember to change your client as well.
When set to 0, the token will never expire, any other value specifies the lifetime in seconds.
+
+ instance.provision_exclusive
+
+ checkbox
+
+ true
+
+ Require, as a condition for authentication, that a tunnel address will be provisioned either from a local defined client-specific override or offered by an authenticator (such as RADIUS) .
+ This is similar to OpenVPN's 'ccd-exclusive' option, but stricter as we expect the client to receive a tunnel address for the protocol used.
+
+
header
diff --git a/src/opnsense/mvc/app/models/OPNsense/OpenVPN/OpenVPN.php b/src/opnsense/mvc/app/models/OPNsense/OpenVPN/OpenVPN.php
index 0189ca669..729263391 100644
--- a/src/opnsense/mvc/app/models/OPNsense/OpenVPN/OpenVPN.php
+++ b/src/opnsense/mvc/app/models/OPNsense/OpenVPN/OpenVPN.php
@@ -170,9 +170,10 @@ class OpenVPN extends BaseModel
* Retrieve overwrite content in legacy format
* @param string $server_id vpnid
* @param string $common_name certificate common name (or username when specified)
- * @return array legacy overwrite data
+ * @param array $overlay overwrite CSO properties
+ * @return array legacy overwrite data, empty when failed
*/
- public function getOverwrite($server_id, $common_name)
+ public function getOverwrite($server_id, $common_name, $overlay = [])
{
$result = [];
foreach ($this->Overwrites->Overwrite->iterateItems() as $cso) {
@@ -234,6 +235,29 @@ class OpenVPN extends BaseModel
}
}
+ if (empty($result)) {
+ $result['common_name'] = $common_name;
+ }
+
+ // overlay is fed by authentication backends and takes precedence
+ $result = array_merge($result, $overlay);
+
+ // check if provisioning by authentication backend is mandatory
+ foreach ($this->Instances->Instance->iterateItems() as $node_uuid => $node) {
+ if (
+ !empty((string)$node->enabled) &&
+ $server_id == $node_uuid &&
+ (string)$node->role == 'server' &&
+ !empty((string)$node->provision_exclusive)
+ ) {
+ if (!empty((string)$node->server) && empty($result['tunnel_network'])) {
+ return [];
+ } elseif (!empty((string)$node->server_ipv6) && empty($result['tunnel_networkv6'])) {
+ return [];
+ }
+ }
+ }
+
return $result;
}
diff --git a/src/opnsense/mvc/app/models/OPNsense/OpenVPN/OpenVPN.xml b/src/opnsense/mvc/app/models/OPNsense/OpenVPN/OpenVPN.xml
index fee809efe..931cdfc23 100644
--- a/src/opnsense/mvc/app/models/OPNsense/OpenVPN/OpenVPN.xml
+++ b/src/opnsense/mvc/app/models/OPNsense/OpenVPN/OpenVPN.xml
@@ -1,6 +1,6 @@
//OPNsense/OpenVPN
- 1.0.0
+ 1.0.1
OpenVPN
@@ -314,6 +314,10 @@
0
+
+ 0
+ Y
+
Y
diff --git a/src/opnsense/scripts/openvpn/client_connect.php b/src/opnsense/scripts/openvpn/client_connect.php
index f6dcd1bd6..2c806c7c0 100755
--- a/src/opnsense/scripts/openvpn/client_connect.php
+++ b/src/opnsense/scripts/openvpn/client_connect.php
@@ -40,7 +40,8 @@ $server = (new OPNsense\OpenVPN\OpenVPN())->getInstanceById($vpnid, 'server');
if ($server) {
$cso = (new OPNsense\OpenVPN\OpenVPN())->getOverwrite($vpnid, $common_name);
if (empty($cso)) {
- $cso = array("common_name" => $common_name);
+ syslog(LOG_NOTICE, "authentication failed for user '{$common_name}'. No tunnel network provisioned, but required.");
+ exit(1);
}
if (!empty($config_file)) {
$cso_filename = openvpn_csc_conf_write($cso, $server, $config_file);
diff --git a/src/opnsense/scripts/openvpn/user_pass_verify.php b/src/opnsense/scripts/openvpn/user_pass_verify.php
index 24d6e6c94..d3e4756d0 100755
--- a/src/opnsense/scripts/openvpn/user_pass_verify.php
+++ b/src/opnsense/scripts/openvpn/user_pass_verify.php
@@ -125,12 +125,10 @@ function do_auth($common_name, $serverid, $method, $auth_file)
LOG_NOTICE,
"Locate overwrite for '{$common_name}' using server '{$serverid}' (vpnid: {$a_server['vpnid']})"
);
- $cso = (new OPNsense\OpenVPN\OpenVPN())->getOverwrite($serverid, $common_name);
+ $cso = (new OPNsense\OpenVPN\OpenVPN())->getOverwrite($serverid, $common_name, parse_auth_properties($authenticator->getLastAuthProperties()));
if (empty($cso)) {
- $cso = array("common_name" => $common_name);
+ return "authentication failed for user '{$username}'. No tunnel network provisioned, but required.";
}
-
- $cso = array_merge($cso, parse_auth_properties($authenticator->getLastAuthProperties()));
$cso_filename = openvpn_csc_conf_write($cso, $a_server);
if (!empty($cso_filename)) {
$tmp = empty($a_server['cso_login_matching']) ? "CSO [CN]" : "CSO [USER]";