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:
ms2k 2026-08-02 23:17:32 +09:00
commit 464388a250
3 changed files with 160 additions and 0 deletions

View 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'));
}

View 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);
}

View 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