Integrate reconciliation with OPNsense runtime
Register the managed interface, periodic reconciler, configd action, outbound NAT, UDP port exemption, and availability-dependent fail-open or fail-closed rules.
This commit is contained in:
parent
6c4cdebdb1
commit
464388a250
3 changed files with 160 additions and 0 deletions
135
src/etc/inc/plugins.inc.d/nordvpn.inc
Normal file
135
src/etc/inc/plugins.inc.d/nordvpn.inc
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
// SPDX-FileCopyrightText: 2026 ms2k
|
||||||
|
|
||||||
|
use OPNsense\Firewall\Plugin;
|
||||||
|
use OPNsense\NordVPN\ManagedConfiguration;
|
||||||
|
use OPNsense\NordVPN\Settings;
|
||||||
|
use OPNsense\NordVPN\StatusStore;
|
||||||
|
|
||||||
|
function nordvpn_cron()
|
||||||
|
{
|
||||||
|
return [[
|
||||||
|
'autocron' => [
|
||||||
|
'/usr/local/bin/flock -n -E 0 -o /var/run/nordvpn.lock ' .
|
||||||
|
'/usr/local/opnsense/scripts/OPNsense/NordVPN/reconcile.php',
|
||||||
|
'*',
|
||||||
|
],
|
||||||
|
]];
|
||||||
|
}
|
||||||
|
|
||||||
|
function nordvpn_interfaces()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$configuration = new ManagedConfiguration();
|
||||||
|
$interface = $configuration->interfaceName();
|
||||||
|
if ($interface === null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return [ManagedConfiguration::INTERFACE => [
|
||||||
|
'descr' => 'NordVPN',
|
||||||
|
'enable' => true,
|
||||||
|
'if' => $interface,
|
||||||
|
'ipaddr' => 'none',
|
||||||
|
'ipaddrv6' => 'none',
|
||||||
|
'networks' => ['10.5.0.0/16'],
|
||||||
|
'type' => 'none',
|
||||||
|
'virtual' => true,
|
||||||
|
]];
|
||||||
|
} catch (Throwable $exception) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function nordvpn_firewall(Plugin $firewall)
|
||||||
|
{
|
||||||
|
$settings = new Settings();
|
||||||
|
if (empty((string)$settings->managed->instance_uuid)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$interfaces = nordvpn_values((string)$settings->general->interfaces);
|
||||||
|
$source = (string)$settings->general->source ?: 'any';
|
||||||
|
$destination = (string)$settings->general->destination ?: 'any';
|
||||||
|
$directGateway = (string)$settings->general->direct_gateway;
|
||||||
|
$status = (new StatusStore())->read();
|
||||||
|
$available = !empty($status['available']);
|
||||||
|
|
||||||
|
if ((string)$settings->general->exempt_wireguard === '1') {
|
||||||
|
foreach ($interfaces as $interface) {
|
||||||
|
$rule = nordvpn_filter_rule($interface, 'any', 'any', false, false);
|
||||||
|
$rule['protocol'] = 'udp';
|
||||||
|
$rule['to_port'] = ManagedConfiguration::PORT;
|
||||||
|
$rule['descr'] = 'NordVPN WireGuard direct egress';
|
||||||
|
if ($directGateway !== '') {
|
||||||
|
$rule['gateway'] = $directGateway;
|
||||||
|
}
|
||||||
|
$firewall->registerFilterRule(190000, $rule);
|
||||||
|
}
|
||||||
|
$rule = nordvpn_filter_rule('', 'any', 'any', false, false);
|
||||||
|
$rule['direction'] = 'out';
|
||||||
|
$rule['floating'] = true;
|
||||||
|
$rule['protocol'] = 'udp';
|
||||||
|
$rule['to_port'] = ManagedConfiguration::PORT;
|
||||||
|
$rule['descr'] = 'NordVPN appliance WireGuard direct egress';
|
||||||
|
if ($directGateway !== '') {
|
||||||
|
$rule['gateway'] = $directGateway;
|
||||||
|
}
|
||||||
|
$firewall->registerFilterRule(190000, $rule);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($interfaces as $interface) {
|
||||||
|
$rule = nordvpn_filter_rule(
|
||||||
|
$interface,
|
||||||
|
$source,
|
||||||
|
$destination,
|
||||||
|
(string)$settings->general->source_not === '1',
|
||||||
|
(string)$settings->general->destination_not === '1'
|
||||||
|
);
|
||||||
|
$rule['descr'] = 'NordVPN egress enrollment';
|
||||||
|
if ($available) {
|
||||||
|
$rule['gateway'] = ManagedConfiguration::GATEWAY;
|
||||||
|
} elseif ((string)$settings->general->failure_policy === 'fail_open') {
|
||||||
|
if ($directGateway !== '') {
|
||||||
|
$rule['gateway'] = $directGateway;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$rule['type'] = 'block';
|
||||||
|
unset($rule['statetype']);
|
||||||
|
}
|
||||||
|
$firewall->registerFilterRule(190100, $rule);
|
||||||
|
}
|
||||||
|
|
||||||
|
$firewall->registerSNatRule(40, [
|
||||||
|
'interface' => ManagedConfiguration::INTERFACE,
|
||||||
|
'ipprotocol' => 'inet',
|
||||||
|
'from' => $source,
|
||||||
|
'from_not' => (string)$settings->general->source_not === '1',
|
||||||
|
'to' => $destination,
|
||||||
|
'to_not' => (string)$settings->general->destination_not === '1',
|
||||||
|
'descr' => 'NordVPN managed outbound NAT',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function nordvpn_filter_rule(string $interface, string $source, string $destination, bool $sourceNot, bool $destinationNot)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'type' => 'pass',
|
||||||
|
'direction' => 'in',
|
||||||
|
'quick' => true,
|
||||||
|
'interface' => $interface,
|
||||||
|
'ipprotocol' => 'inet',
|
||||||
|
'from' => $source,
|
||||||
|
'from_not' => $sourceNot,
|
||||||
|
'to' => $destination,
|
||||||
|
'to_not' => $destinationNot,
|
||||||
|
'statetype' => 'keep',
|
||||||
|
'#ref' => 'ui/nordvpn',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function nordvpn_values(string $csv)
|
||||||
|
{
|
||||||
|
return array_values(array_filter(array_map('trim', explode(',', $csv)), 'strlen'));
|
||||||
|
}
|
||||||
18
src/opnsense/scripts/OPNsense/NordVPN/reconcile.php
Executable file
18
src/opnsense/scripts/OPNsense/NordVPN/reconcile.php
Executable file
|
|
@ -0,0 +1,18 @@
|
||||||
|
#!/usr/local/bin/php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
// SPDX-FileCopyrightText: 2026 ms2k
|
||||||
|
|
||||||
|
require_once('script/load_phalcon.php');
|
||||||
|
|
||||||
|
use OPNsense\NordVPN\Reconciler;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$status = (new Reconciler())->run();
|
||||||
|
echo json_encode($status, JSON_UNESCAPED_SLASHES) . "\n";
|
||||||
|
exit(0);
|
||||||
|
} catch (Throwable $exception) {
|
||||||
|
syslog(LOG_ERR, 'NordVPN reconciliation failed: ' . $exception->getMessage());
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
7
src/opnsense/service/conf/actions.d/actions_nordvpn.conf
Normal file
7
src/opnsense/service/conf/actions.d/actions_nordvpn.conf
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
[reconcile]
|
||||||
|
command:/usr/local/bin/flock
|
||||||
|
parameters:-n -E 0 -o /var/run/nordvpn.lock /usr/local/opnsense/scripts/OPNsense/NordVPN/reconcile.php
|
||||||
|
type:script_output
|
||||||
|
message:Reconcile NordVPN managed tunnel
|
||||||
|
description:Reconcile NordVPN managed tunnel
|
||||||
|
allowed_groups:wheel,wwwonly
|
||||||
Loading…
Reference in a new issue