wireguard: merge net/wireguard as of version 2.2 #6827

Omit the dependency on wireguard-kmod as we will be targeting the kernel
module with 24.1.  Some people may run into this but it's safer than
trying to rely on a package that won't be available going from 23.7 to
24.1.
This commit is contained in:
Franco Fichtner 2023-09-29 08:28:48 +02:00
parent a91bc81aaf
commit 871182c4f2
35 changed files with 2086 additions and 1 deletions

View File

@ -6,6 +6,7 @@ Copyright (c) 2005-2008 Bill Marquette <bill.marquette@gmail.com>
Copyright (c) 2003-2005 Bob Zoller <bob@kludgebox.com>
Copyright (c) 2012 Carlos Cesario <carloscesario@gmail.com>
Copyright (c) 2005-2006 Colin Smith <ethethlay@gmail.com>
Copyright (c) 2020 D. Domig
Copyright (c) 2013 Dagorlad
Copyright (c) 2006 Daniel S. Haischt
Copyright (c) 2012 Darren Embry <dse@webonastick.com>
@ -37,8 +38,9 @@ Copyright (c) 2012 Marcello Coutinho
Copyright (c) 2018 Martin Wasley <martin@team-rebellion.net>
Copyright (c) 2022 Maurice Walker <maurice@walker.earth>
Copyright (c) 2010-2015 Michael Bostock
Copyright (c) 2019-2021 Michael Muenz <m.muenz@gmail.com>
Copyright (c) 2018-2021 Michael Muenz <m.muenz@gmail.com>
Copyright (c) 2019 Pascal Mathis <mail@pascalmathis.com>
Copyright (c) 2022 Patrik Kernstock <patrik@kernstock.net>
Copyright (c) 2005-2006 Paul Taylor <paultaylor@winn-dixie.com>
Copyright (c) 2005-2006 Peter Allgeyer <allgeyer@web.de>
Copyright (c) 2004 Peter Curran <peter@closeconsultants.com>

33
plist
View File

@ -43,6 +43,7 @@
/usr/local/etc/inc/plugins.inc.d/unbound.inc
/usr/local/etc/inc/plugins.inc.d/vxlan.inc
/usr/local/etc/inc/plugins.inc.d/webgui.inc
/usr/local/etc/inc/plugins.inc.d/wireguard.inc
/usr/local/etc/inc/rrd.inc
/usr/local/etc/inc/system.inc
/usr/local/etc/inc/util.inc
@ -127,6 +128,7 @@
/usr/local/etc/rc.syshook.d/carp/20-openvpn
/usr/local/etc/rc.syshook.d/carp/20-openvpn-instances
/usr/local/etc/rc.syshook.d/carp/20-ppp
/usr/local/etc/rc.syshook.d/carp/20-wireguard
/usr/local/etc/rc.syshook.d/early/05-upgrade
/usr/local/etc/rc.syshook.d/early/10-configd
/usr/local/etc/rc.syshook.d/early/15-templates
@ -464,6 +466,15 @@
/usr/local/opnsense/mvc/app/controllers/OPNsense/Unbound/forms/dnsbl.xml
/usr/local/opnsense/mvc/app/controllers/OPNsense/Unbound/forms/forwarding.xml
/usr/local/opnsense/mvc/app/controllers/OPNsense/Unbound/forms/general.xml
/usr/local/opnsense/mvc/app/controllers/OPNsense/Wireguard/Api/ClientController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Wireguard/Api/GeneralController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Wireguard/Api/ServerController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Wireguard/Api/ServiceController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Wireguard/DiagnosticsController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Wireguard/GeneralController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Wireguard/forms/dialogEditWireguardClient.xml
/usr/local/opnsense/mvc/app/controllers/OPNsense/Wireguard/forms/dialogEditWireguardServer.xml
/usr/local/opnsense/mvc/app/controllers/OPNsense/Wireguard/forms/general.xml
/usr/local/opnsense/mvc/app/library/Google/API/Drive.php
/usr/local/opnsense/mvc/app/library/OPNsense/Auth/API.php
/usr/local/opnsense/mvc/app/library/OPNsense/Auth/AuthenticationFactory.php
@ -728,6 +739,15 @@
/usr/local/opnsense/mvc/app/models/OPNsense/Unbound/Migrations/M1_0_8.php
/usr/local/opnsense/mvc/app/models/OPNsense/Unbound/Unbound.php
/usr/local/opnsense/mvc/app/models/OPNsense/Unbound/Unbound.xml
/usr/local/opnsense/mvc/app/models/OPNsense/Wireguard/ACL/ACL.xml
/usr/local/opnsense/mvc/app/models/OPNsense/Wireguard/Client.php
/usr/local/opnsense/mvc/app/models/OPNsense/Wireguard/Client.xml
/usr/local/opnsense/mvc/app/models/OPNsense/Wireguard/FieldTypes/ServerField.php
/usr/local/opnsense/mvc/app/models/OPNsense/Wireguard/General.php
/usr/local/opnsense/mvc/app/models/OPNsense/Wireguard/General.xml
/usr/local/opnsense/mvc/app/models/OPNsense/Wireguard/Menu/Menu.xml
/usr/local/opnsense/mvc/app/models/OPNsense/Wireguard/Server.php
/usr/local/opnsense/mvc/app/models/OPNsense/Wireguard/Server.xml
/usr/local/opnsense/mvc/app/views/OPNsense/CaptivePortal/clients.volt
/usr/local/opnsense/mvc/app/views/OPNsense/CaptivePortal/index.volt
/usr/local/opnsense/mvc/app/views/OPNsense/CaptivePortal/vouchers.volt
@ -800,6 +820,8 @@
/usr/local/opnsense/mvc/app/views/OPNsense/Unbound/overrides.volt
/usr/local/opnsense/mvc/app/views/OPNsense/Unbound/overview.volt
/usr/local/opnsense/mvc/app/views/OPNsense/Unbound/stats.volt
/usr/local/opnsense/mvc/app/views/OPNsense/Wireguard/diagnostics.volt
/usr/local/opnsense/mvc/app/views/OPNsense/Wireguard/general.volt
/usr/local/opnsense/mvc/app/views/layout_partials/base_dialog.volt
/usr/local/opnsense/mvc/app/views/layout_partials/base_dialog_processing.volt
/usr/local/opnsense/mvc/app/views/layout_partials/base_form.volt
@ -879,6 +901,10 @@
/usr/local/opnsense/scripts/OPNsense/Monit/carp_status
/usr/local/opnsense/scripts/OPNsense/Monit/gateway_alert
/usr/local/opnsense/scripts/OPNsense/Monit/setup.sh
/usr/local/opnsense/scripts/Wireguard/gen_keypair.py
/usr/local/opnsense/scripts/Wireguard/reresolve-dns.py
/usr/local/opnsense/scripts/Wireguard/wg-service-control.php
/usr/local/opnsense/scripts/Wireguard/wg_show.py
/usr/local/opnsense/scripts/auth/add_user.php
/usr/local/opnsense/scripts/auth/list_group_members.php
/usr/local/opnsense/scripts/dhcp/cleanup_leases4.php
@ -1110,6 +1136,7 @@
/usr/local/opnsense/service/conf/actions.d/actions_template.conf
/usr/local/opnsense/service/conf/actions.d/actions_unbound.conf
/usr/local/opnsense/service/conf/actions.d/actions_webgui.conf
/usr/local/opnsense/service/conf/actions.d/actions_wireguard.conf
/usr/local/opnsense/service/conf/actions.d/actions_zfs.conf
/usr/local/opnsense/service/conf/actions_service.conf
/usr/local/opnsense/service/conf/configd.conf
@ -1222,6 +1249,7 @@
/usr/local/opnsense/service/templates/OPNsense/Syslog/local/squid_access.conf
/usr/local/opnsense/service/templates/OPNsense/Syslog/local/suricata.conf
/usr/local/opnsense/service/templates/OPNsense/Syslog/local/vpn.conf
/usr/local/opnsense/service/templates/OPNsense/Syslog/local/wireguard.conf
/usr/local/opnsense/service/templates/OPNsense/Syslog/local/wireless.conf
/usr/local/opnsense/service/templates/OPNsense/Syslog/newsyslog.conf
/usr/local/opnsense/service/templates/OPNsense/Syslog/rc.conf.d
@ -1246,6 +1274,9 @@
/usr/local/opnsense/service/templates/OPNsense/WebGui/etc.php.ini
/usr/local/opnsense/service/templates/OPNsense/WebGui/lib.php.ini
/usr/local/opnsense/service/templates/OPNsense/WebGui/php.ini
/usr/local/opnsense/service/templates/OPNsense/Wireguard/+TARGETS
/usr/local/opnsense/service/templates/OPNsense/Wireguard/wireguard
/usr/local/opnsense/service/templates/OPNsense/Wireguard/wireguard-server.conf
/usr/local/opnsense/service/tests/__init__.py
/usr/local/opnsense/service/tests/config/config.xml
/usr/local/opnsense/service/tests/core.py
@ -2090,6 +2121,7 @@
/usr/local/www/widgets/include/system_log.inc
/usr/local/www/widgets/include/thermal_sensors.inc
/usr/local/www/widgets/include/traffic_graph.inc
/usr/local/www/widgets/include/wireguard.inc
/usr/local/www/widgets/widgets/carp_status.widget.php
/usr/local/www/widgets/widgets/cpu_usage.widget.php
/usr/local/www/widgets/widgets/gateways.widget.php
@ -2107,6 +2139,7 @@
/usr/local/www/widgets/widgets/system_log.widget.php
/usr/local/www/widgets/widgets/thermal_sensors.widget.php
/usr/local/www/widgets/widgets/traffic_graphs.widget.php
/usr/local/www/widgets/widgets/wireguard.widget.php
/usr/local/www/wizard.php
/usr/local/www/xmlrpc.php
@sample /usr/local/etc/bogons.sample

View File

@ -0,0 +1,138 @@
<?php
/*
* Copyright (C) 2023 Deciso B.V.
* Copyright (C) 2018 Michael Muenz <m.muenz@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
function wireguard_enabled()
{
return (string)(new \OPNsense\Wireguard\General())->enabled == '1';
}
function wireguard_services()
{
$services = [];
if (!wireguard_enabled()) {
return $services;
}
foreach ((new OPNsense\Wireguard\Server())->servers->server->iterateItems() as $key => $node) {
if (!empty((string)$node->enabled)) {
$services[] = [
'description' => "Wireguard " . htmlspecialchars($node->name),
'configd' => [
'start' => ["wireguard start {$key}"],
'restart' => ["wireguard restart {$key}"],
'stop' => ["wireguard stop {$key}"],
],
'nocheck' => true, /* no daemon to check */
'id' => $key,
'name' => "wireguard"
];
}
}
return $services;
}
function wireguard_syslog()
{
return [
'wireguard' => ['facility' => ['wireguard']]
];
}
function wireguard_interfaces()
{
$interfaces = [];
if (!wireguard_enabled()) {
return $interfaces;
}
$interfaces['wireguard'] = [
'descr' => gettext('WireGuard (Group)'),
'if' => 'wireguard',
'virtual' => true,
'enable' => true,
'type' => 'group',
'networks' => [],
];
return $interfaces;
}
function wireguard_xmlrpc_sync()
{
$result = [];
$result['id'] = 'wireguard';
$result['section'] = 'OPNsense.wireguard';
$result['description'] = gettext('WireGuard');
$result['services'] = ['wireguard'];
return [$result];
}
function wireguard_devices()
{
return [['pattern' => '^wg', 'volatile' => true]];
}
function wireguard_configure()
{
return [
'newwanip' => ['wireguard_renew:2'],
'vpn' => ['wireguard_configure_do:2'],
];
}
function wireguard_configure_do($verbose = false, $unused = '')
{
if (!wireguard_enabled()) {
return;
}
service_log('Configuring WireGuard VPN...', $verbose);
configd_run('wireguard configure');
service_log("done.\n", $verbose);
}
function wireguard_renew($verbose = false, $unused = '')
{
if (!wireguard_enabled()) {
return;
}
service_log('Renewing WireGuard VPN...', $verbose);
configd_run('wireguard renew');
service_log("done.\n", $verbose);
}

View File

@ -0,0 +1,3 @@
#!/bin/sh
configctl -dq wireguard configure

View File

@ -0,0 +1,73 @@
<?php
/**
* Copyright (C) 2023 Deciso B.V.
* Copyright (C) 2018 Michael Muenz <m.muenz@gmail.com>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
namespace OPNsense\Wireguard\Api;
use OPNsense\Base\ApiMutableModelControllerBase;
class ClientController extends ApiMutableModelControllerBase
{
protected static $internalModelName = 'client';
protected static $internalModelClass = '\OPNsense\Wireguard\Client';
public function searchClientAction()
{
return $this->searchBase(
'clients.client',
["enabled", "name", "pubkey", "tunneladdress", "serveraddress", "serverport"]
);
}
public function getClientAction($uuid = null)
{
return $this->getBase('client', 'clients.client', $uuid);
}
public function addClientAction()
{
return $this->addBase('client', 'clients.client');
}
public function delClientAction($uuid)
{
return $this->delBase('clients.client', $uuid);
}
public function setClientAction($uuid)
{
return $this->setBase('client', 'clients.client', $uuid);
}
public function toggleClientAction($uuid)
{
return $this->toggleBase('clients.client', $uuid);
}
}

View File

@ -0,0 +1,141 @@
<?php
/**
* Copyright (C) 2018 Michael Muenz <m.muenz@gmail.com>
* Copyright (C) 2022 Patrik Kernstock <patrik@kernstock.net>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
namespace OPNsense\Wireguard\Api;
use OPNsense\Base\ApiMutableModelControllerBase;
use OPNsense\Core\Config;
use OPNsense\Core\Backend;
class GeneralController extends ApiMutableModelControllerBase
{
protected static $internalModelClass = '\OPNsense\Wireguard\General';
protected static $internalModelName = 'general';
/**
* XXX: remove in 24.1 unused
*/
public function getStatusAction()
{
// get wireguard configuration
$config = Config::getInstance()->object();
$config = $config->OPNsense->wireguard;
// craft peers array
$peers = [];
$peers_uuid_pubkey = [];
// enabled, name, pubkey
foreach ($config->client->clients->client as $client) {
$peerUuid = (string)$client->attributes()['uuid'];
$peers_uuid_pubkey[$peerUuid] = (string) $client->pubkey;
$peers[$peerUuid] = [
"name" => (string) $client->name,
"enabled" => (int) $client->enabled,
"publicKey" => (string) $client->pubkey,
];
}
// prepare and initialize the server array
$status = [];
$peer_pubkey_reference = [];
foreach ($config->server->servers->server as $server) {
if ($server->enabled != "1") {
continue;
}
// build basic server array
$interface = "wg" . $server->instance;
$status[$interface] = [
"instance" => (int) $server->instance,
"interface" => (string) $interface,
"enabled" => (int) $server->enabled,
"name" => (string) $server->name,
"peers" => [],
];
// parse and add peers with initial values to array
if (strlen($server->peers) > 0) {
// there is at least one peer defined
$serverPeers = explode(",", (string) $server->peers);
// iteriate over each peer uuid
foreach ($serverPeers as $peerUuid) {
// skipping removed peer that is still referenced in server
if (!isset($peers[$peerUuid])) {
continue;
}
// remember interface and pubkey <> peer-uuid reference for referencing handshake logic below
$peer_pubkey_reference[$interface][$peers_uuid_pubkey[$peerUuid]] = $peerUuid;
// merge peer info and initial values for handshake data
$status[$interface]["peers"][$peerUuid] = array_merge(
$peers[$peerUuid],
[
"lastHandshake" => "0000-00-00 00:00:00+00:00",
]
);
}
}
}
// Get latest handshakes by running CLI command locally
$data = (new Backend())->configdRun("wireguard showhandshake");
// parse and set handshake to status datastructure
$data = trim($data);
if (strlen($data) !== 0) {
$wgHandshakes = explode("\n", $data);
foreach ($wgHandshakes as $handshake) {
$item = explode("\t", trim($handshake));
// set interface name and publickey
$interface = trim($item[0]);
$pubkey = trim($item[1]);
// calculate handshake time based on local timezone
$epoch = $item[2];
if ($epoch > 0) {
$dt = new \DateTime("@$epoch");
$dt->setTimezone(new \DateTimeZone(date_default_timezone_get()));
$latest = $dt->format("Y-m-d H:i:sP");
// set handshake
$peerUuid = $peer_pubkey_reference[$interface][$pubkey];
if (!empty($peerUuid)) {
$status[$interface]["peers"][$peerUuid]["lastHandshake"] = $latest;
}
}
}
}
return [
"items" => $status
];
}
}

View File

@ -0,0 +1,78 @@
<?php
/*
* Copyright (C) 2023 Deciso B.V.
* Copyright (C) 2018 Michael Muenz <m.muenz@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\Wireguard\Api;
use OPNsense\Base\ApiMutableModelControllerBase;
use OPNsense\Core\Backend;
class ServerController extends ApiMutableModelControllerBase
{
protected static $internalModelName = 'server';
protected static $internalModelClass = '\OPNsense\Wireguard\Server';
public function keyPairAction()
{
return json_decode((new Backend())->configdRun('wireguard gen_keypair'), true);
}
public function searchServerAction()
{
$search = $this->searchBase(
'servers.server',
["enabled", "instance", "peers", "name", "networks", "pubkey", "port", "tunneladdress", 'interface']
);
return $search;
}
public function getServerAction($uuid = null)
{
return $this->getBase('server', 'servers.server', $uuid);
}
public function addServerAction($uuid = null)
{
return $this->addBase('server', 'servers.server', $uuid);
}
public function delServerAction($uuid)
{
return $this->delBase('servers.server', $uuid);
}
public function setServerAction($uuid = null)
{
return $this->setBase('server', 'servers.server', $uuid);
}
public function toggleServerAction($uuid)
{
return $this->toggleBase('servers.server', $uuid);
}
}

View File

@ -0,0 +1,131 @@
<?php
/*
* Copyright (C) 2023 Deciso B.V.
* Copyright (C) 2018 Michael Muenz <m.muenz@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\Wireguard\Api;
use OPNsense\Base\ApiMutableServiceControllerBase;
use OPNsense\Core\Backend;
use OPNsense\Wireguard\General;
use OPNsense\Wireguard\Client;
use OPNsense\Wireguard\Server;
/**
* Class ServiceController
* @package OPNsense\Wireguard
*/
class ServiceController extends ApiMutableServiceControllerBase
{
protected static $internalServiceClass = '\OPNsense\Wireguard\General';
protected static $internalServiceTemplate = 'OPNsense/Wireguard';
protected static $internalServiceEnabled = 'enabled';
protected static $internalServiceName = 'wireguard';
/**
* hook group interface registration on reconfigure
* @return bool
*/
protected function invokeInterfaceRegistration()
{
return true;
}
/**
* @return array
*/
public function reconfigureAction()
{
if (!$this->request->isPost()) {
return ['result' => 'failed'];
}
$this->sessionClose();
$backend = new Backend();
$backend->configdRun('template reload ' . escapeshellarg(static::$internalServiceTemplate));
$backend->configdpRun('wireguard configure');
return ['result' => 'ok'];
}
/**
* show wireguard config
* XXX: remove in 24.1
* @return array
*/
public function showconfAction()
{
$response = (new Backend())->configdRun("wireguard showconf");
return array("response" => $response);
}
/**
* show wireguard handshakes
* XXX: remove in 24.1
* @return array
*/
public function showhandshakeAction()
{
$response = (new Backend())->configdRun("wireguard showhandshake");
return array("response" => $response);
}
/**
* wg show all dump output
* @return array
*/
public function showAction()
{
$payload = json_decode((new Backend())->configdRun("wireguard show") ?? '', true);
$records = !empty($payload) && !empty($payload['records']) ? $payload['records'] : [];
$key_descriptions = [];
$ifnames = [];
foreach ((new Client())->clients->client->iterateItems() as $key => $client) {
$key_descriptions[(string)$client->pubkey] = (string)$client->name;
}
foreach ((new Server())->servers->server->iterateItems() as $key => $server) {
$key_descriptions[(string)$server->pubkey] = (string)$server->name;
$ifnames[(string)$server->interface] = (string)$server->name;
}
foreach ($records as &$record) {
if (!empty($record['public-key']) && !empty($key_descriptions[$record['public-key']])) {
$record['name'] = $key_descriptions[$record['public-key']];
} else {
$record['name'] = '';
}
$record['ifname'] = $ifnames[$record['if']];
}
$filter_funct = null;
$types = $this->request->get('type');
if (!empty($types)) {
$filter_funct = function ($record) use ($types) {
return in_array($record['type'], $types);
};
}
return $this->searchRecordsetBase($records, null, null, $filter_funct);
}
}

View File

@ -0,0 +1,44 @@
<?php
/*
* Copyright (C) 2023 Deciso B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\Wireguard;
class DiagnosticsController extends \OPNsense\Base\IndexController
{
protected function templateJSIncludes()
{
$result = parent::templateJSIncludes();
$result[] = '/ui/js/moment-with-locales.min.js';
return $result;
}
public function indexAction()
{
$this->view->pick('OPNsense/Wireguard/diagnostics');
}
}

View File

@ -0,0 +1,40 @@
<?php
/*
Copyright (C) 2018 Michael Muenz <m.muenz@gmail.com>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\Wireguard;
class GeneralController extends \OPNsense\Base\IndexController
{
public function indexAction()
{
$this->view->generalForm = $this->getForm("general");
$this->view->formDialogEditWireguardClient = $this->getForm("dialogEditWireguardClient");
$this->view->formDialogEditWireguardServer = $this->getForm("dialogEditWireguardServer");
$this->view->pick('OPNsense/Wireguard/general');
}
}

View File

@ -0,0 +1,52 @@
<form>
<field>
<id>client.enabled</id>
<label>Enabled</label>
<type>checkbox</type>
<help>This will enable or disable the client config.</help>
</field>
<field>
<id>client.name</id>
<label>Name</label>
<type>text</type>
<help>Set the name for this instance.</help>
</field>
<field>
<id>client.pubkey</id>
<label>Public Key</label>
<type>text</type>
<help>Public key of this instance.</help>
</field>
<field>
<id>client.psk</id>
<label>Shared Secret</label>
<type>text</type>
<help>Shared secret (PSK) for this peer. You can generate a key using "wg genpsk" on a client with WireGuard installed.</help>
</field>
<field>
<id>client.tunneladdress</id>
<label>Allowed IPs</label>
<style>tokenize</style>
<type>select_multiple</type>
<allownew>true</allownew>
<help>List of addresses allowed to pass trough the tunnel adapter. Please use CIDR notation like 10.0.0.1/24.</help>
</field>
<field>
<id>client.serveraddress</id>
<label>Endpoint Address</label>
<type>text</type>
<help>Set public IP address the endpoint listens to.</help>
</field>
<field>
<id>client.serverport</id>
<label>Endpoint Port</label>
<type>text</type>
<help>Set port the endpoint listens to.</help>
</field>
<field>
<id>client.keepalive</id>
<label>Keepalive Interval</label>
<type>text</type>
<help>Set persistent keepalive interval in seconds.</help>
</field>
</form>

View File

@ -0,0 +1,88 @@
<form>
<field>
<id>server.enabled</id>
<label>Enabled</label>
<type>checkbox</type>
<help>This will enable or disable the server config.</help>
</field>
<field>
<id>server.name</id>
<label>Name</label>
<type>text</type>
<help>Set the name for this instance.</help>
</field>
<field>
<id>server.instance</id>
<label>Instance</label>
<type>info</type>
<help>This is the instance number to give the wg interface a unique name (wgX).</help>
</field>
<field>
<id>server.pubkey</id>
<label>Public Key</label>
<type>text</type>
<help>Public key of this instance. You can specify your own one, or a key will be generated after saving.</help>
</field>
<field>
<id>server.privkey</id>
<label>Private Key</label>
<type>text</type>
<help>Private key of this instance. You can specify your own one, or a key will be generated after saving. Please keep this key safe.</help>
</field>
<field>
<id>server.port</id>
<label>Listen Port</label>
<type>text</type>
<help>Optionally set a fixed port for this instance to listen on. The standard port range starts at 51820.</help>
</field>
<field>
<id>server.mtu</id>
<label>MTU</label>
<type>text</type>
<advanced>true</advanced>
<help>Set the interface MTU for this interface. Leaving empty uses the MTU from main interface which is fine for most setups.</help>
</field>
<field>
<id>server.dns</id>
<label>DNS Server</label>
<type>select_multiple</type>
<style>tokenize</style>
<allownew>true</allownew>
<advanced>true</advanced>
<help>Set the interface specific DNS server.</help>
</field>
<field>
<id>server.tunneladdress</id>
<label>Tunnel Address</label>
<style>tokenize</style>
<type>select_multiple</type>
<allownew>true</allownew>
<help>List of addresses to configure on the tunnel adapter. Please use CIDR notation like 10.0.0.1/24.</help>
</field>
<field>
<id>server.carp_depend_on</id>
<label>Depend on (CARP)</label>
<type>dropdown</type>
<help>The CARP VHID to depend on. When this virtual address is not in master state, then the instance will be shutdown.</help>
</field>
<field>
<id>server.peers</id>
<label>Peers</label>
<type>select_multiple</type>
<allownew>true</allownew>
<help>List of peers for this server.</help>
</field>
<field>
<id>server.disableroutes</id>
<label>Disable Routes</label>
<type>checkbox</type>
<help>This will prevent installing routes. Usually you only enable this to do own routing decisions via a local gateway and gateway rules.</help>
</field>
<field>
<id>server.gateway</id>
<label>Gateway</label>
<type>text</type>
<advanced>true</advanced>
<help>Set the gateway IP here when using Disable Routes feature. You also have to add this as a gateway in OPNsense.</help>
</field>
</form>

View File

@ -0,0 +1,8 @@
<form>
<field>
<id>general.enabled</id>
<label>Enable WireGuard</label>
<type>checkbox</type>
<help>This will activate WireGuard and start all enabled instances.</help>
</field>
</form>

View File

@ -0,0 +1,9 @@
<acl>
<page-wireguard-config>
<name>VPN: Wireguard</name>
<patterns>
<pattern>ui/wireguard/*</pattern>
<pattern>api/wireguard/*</pattern>
</patterns>
</page-wireguard-config>
</acl>

View File

@ -0,0 +1,31 @@
<?php
/*
Copyright (C) 2018 Michael Muenz <m.muenz@gmail.com>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\Wireguard;
use OPNsense\Base\BaseModel;
class Client extends BaseModel
{
}

View File

@ -0,0 +1,45 @@
<model>
<mount>//OPNsense/wireguard/client</mount>
<description>Wireguard Client configuration</description>
<version>0.0.7</version>
<items>
<clients>
<client type="ArrayField">
<enabled type="BooleanField">
<Default>1</Default>
<Required>Y</Required>
</enabled>
<name type="TextField">
<Required>Y</Required>
<mask>/^([0-9a-zA-Z._\-]){1,64}$/u</mask>
<ValidationMessage>Should be a string between 1 and 64 characters. Allowed characters are alphanumeric characters, dash and underscores.</ValidationMessage>
</name>
<pubkey type="Base64Field">
<Required>Y</Required>
<ValidationMessage>Should be a base64-encoded 32 byte string.</ValidationMessage>
</pubkey>
<psk type="Base64Field">
<Required>N</Required>
<ValidationMessage>Should be a base64-encoded 32 byte string.</ValidationMessage>
</psk>
<tunneladdress type="NetworkField">
<FieldSeparator>,</FieldSeparator>
<Required>Y</Required>
<asList>Y</asList>
</tunneladdress>
<serveraddress type="HostnameField">
<Required>N</Required>
</serveraddress>
<serverport type="PortField">
<Required>N</Required>
</serverport>
<keepalive type="IntegerField">
<MinimumValue>1</MinimumValue>
<MaximumValue>86400</MaximumValue>
<ValidationMessage>Please specify a value between 1 and 86400.</ValidationMessage>
<Required>N</Required>
</keepalive>
</client>
</clients>
</items>
</model>

View File

@ -0,0 +1,58 @@
<?php
/*
* Copyright (C) 2023 Deciso B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\Wireguard\FieldTypes;
use OPNsense\Base\FieldTypes\ArrayField;
use OPNsense\Base\FieldTypes\TextField;
class ServerField extends ArrayField
{
/**
* push internal reusable properties as virtuals
*/
protected function actionPostLoadingEvent()
{
foreach ($this->internalChildnodes as $node) {
if (!$node->getInternalIsVirtual()) {
$files = [
'cnfFilename' => "/usr/local/etc/wireguard/wg{$node->instance}.conf",
'statFilename' => "/usr/local/etc/wireguard/wg{$node->instance}.stat",
'interface' => "wg{$node->instance}",
];
foreach ($files as $name => $payload) {
$new_item = new TextField();
$new_item->setInternalIsVirtual();
$new_item->setValue($payload);
$node->addChildNode($name, $new_item);
}
}
}
return parent::actionPostLoadingEvent();
}
}

View File

@ -0,0 +1,35 @@
<?php
/*
Copyright (C) 2018 Michael Muenz <m.muenz@gmail.com>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\Wireguard;
use OPNsense\Base\BaseModel;
class General extends BaseModel
{
}

View File

@ -0,0 +1,11 @@
<model>
<mount>//OPNsense/wireguard/general</mount>
<description>WireGuard configuration</description>
<version>0.0.1</version>
<items>
<enabled type="BooleanField">
<Default>0</Default>
<Required>Y</Required>
</enabled>
</items>
</model>

View File

@ -0,0 +1,9 @@
<menu>
<VPN>
<WireGuard cssClass="fa fa-lock fa-fw" order="150">
<Settings order="10" url="/ui/wireguard/general/"/>
<Diagnostics order="20" url="/ui/wireguard/diagnostics/"/>
<LogFile order="70" VisibleName="Log File" url="/ui/diagnostics/log/core/wireguard"/>
</WireGuard>
</VPN>
</menu>

View File

@ -0,0 +1,31 @@
<?php
/*
Copyright (C) 2018 Michael Muenz <m.muenz@gmail.com>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\Wireguard;
use OPNsense\Base\BaseModel;
class Server extends BaseModel
{
}

View File

@ -0,0 +1,82 @@
<model>
<mount>//OPNsense/wireguard/server</mount>
<description>Wireguard Server configuration</description>
<version>0.0.4</version>
<items>
<servers>
<server type=".\ServerField">
<enabled type="BooleanField">
<Default>1</Default>
<Required>Y</Required>
</enabled>
<name type="TextField">
<Required>Y</Required>
<mask>/^([0-9a-zA-Z._\-]){1,64}$/u</mask>
<ValidationMessage>Should be a string between 1 and 64 characters. Allowed characters are alphanumeric characters, dash and underscores.</ValidationMessage>
</name>
<instance type="AutoNumberField">
<Required>Y</Required>
</instance>
<pubkey type="TextField">
<Required>Y</Required>
<ValidationMessage>A public key is required</ValidationMessage>
</pubkey>
<privkey type="TextField">
<Required>Y</Required>
<ValidationMessage>A private key is required</ValidationMessage>
</privkey>
<port type="PortField">
<Required>N</Required>
</port>
<mtu type="IntegerField">
<MinimumValue>1</MinimumValue>
<MaximumValue>9300</MaximumValue>
<Required>N</Required>
</mtu>
<dns type="CSVListField">
<Required>N</Required>
<mask>/^([a-fA-F0-9\.:\[\]]*?,)*([a-fA-F0-9\.:\[\]]*)$/</mask>
<ValidationMessage>Please use valid IPv4 or IPv6 addresses.</ValidationMessage>
</dns>
<tunneladdress type="NetworkField">
<FieldSeparator>,</FieldSeparator>
<Required>N</Required>
<asList>Y</asList>
</tunneladdress>
<disableroutes type="BooleanField">
<Default>0</Default>
<Required>Y</Required>
<Constraints>
<check001>
<ValidationMessage>You have to enable Disable Routes option.</ValidationMessage>
<type>DependConstraint</type>
<addFields>
<field1>gateway</field1>
</addFields>
</check001>
</Constraints>
</disableroutes>
<gateway type="NetworkField">
<Required>N</Required>
</gateway>
<carp_depend_on type="VirtualIPField">
<type>carp</type>
<Required>N</Required>
<key>mvc</key>
</carp_depend_on>
<peers type="ModelRelationField">
<Model>
<template>
<source>OPNsense.Wireguard.Client</source>
<items>clients.client</items>
<display>name</display>
</template>
</Model>
<Multiple>Y</Multiple>
<Required>N</Required>
<ValidationMessage>Choose an Peer.</ValidationMessage>
</peers>
</server>
</servers>
</items>
</model>

View File

@ -0,0 +1,98 @@
{#
# Copyright (c) 2023 Deciso B.V.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#}
<script>
$( document ).ready(function() {
$("#grid-sessions").UIBootgrid({
search:'/api/wireguard/service/show',
options:{
multiSelect: false,
rowSelect: false,
selection: false,
formatters:{
bytes: function(column, row) {
if (row[column.id] && row[column.id] > 0) {
return byteFormat(row[column.id], 2);
}
return row[column.id];
},
epoch: function(column, row) {
if (row[column.id]) {
return moment.unix(row[column.id]).local().format('YYYY-MM-DD HH:mm:ss');
} else {
return '';
}
}
},
requestHandler: function(request){
if ( $('#type_filter').val().length > 0) {
request['type'] = $('#type_filter').val();
}
return request;
},
}
});
$("#type_filter").change(function(){
$('#grid-sessions').bootgrid('reload');
});
$("#type_filter_container").detach().prependTo('#grid-sessions-header > .row > .actionBar > .actions');
});
</script>
<div class="tab-content content-box">
<div class="hidden">
<!-- filter per type container -->
<div id="type_filter_container" class="btn-group">
<select id="type_filter" data-title="{{ lang._('Type') }}" class="selectpicker" multiple="multiple" data-width="200px">
<option value="interface">{{ lang._('Interface') }}</option>
<option value="peer">{{ lang._('Peer') }}</option>
</select>
</div>
</div>
<table id="grid-sessions" class="table table-condensed table-hover table-striped table-responsive">
<thead>
<tr>
<th data-column-id="if" data-type="string" data-width="8em">{{ lang._('Interface') }}</th>
<th data-column-id="type" data-type="string" data-width="8em" data-visible="false">{{ lang._('Type') }}</th>
<th data-column-id="status" data-type="string" data-width="8em" >{{ lang._('Status') }}</th>
<th data-column-id="public-key" data-type="string" data-identifier="true">{{ lang._('Public key') }}</th>
<th data-column-id="name" data-type="string">{{ lang._('Name') }}</th>
<th data-column-id="endpoint" data-type="string">{{ lang._('Port / Endpoint') }}</th>
<th data-column-id="latest-handshake" data-formatter="epoch" data-type="numeric">{{ lang._('Handshake') }}</th>
<th data-column-id="transfer-tx" data-formatter="bytes" data-type="numeric">{{ lang._('Send') }}</th>
<th data-column-id="transfer-rx" data-formatter="bytes" data-type="numeric">{{ lang._('Received') }}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>

View File

@ -0,0 +1,170 @@
{#
# OPNsense (c) 2014-2023 by Deciso B.V.
# OPNsense (c) 2018 Michael Muenz <m.muenz@gmail.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#}
<script>
$( document ).ready(function() {
var data_get_map = {'frm_general_settings':"/api/wireguard/general/get"};
mapDataToFormUI(data_get_map).done(function(data){
formatTokenizersUI();
$('.selectpicker').selectpicker('refresh');
});
$("#grid-clients").UIBootgrid(
{
'search':'/api/wireguard/client/searchClient',
'get':'/api/wireguard/client/getClient/',
'set':'/api/wireguard/client/setClient/',
'add':'/api/wireguard/client/addClient/',
'del':'/api/wireguard/client/delClient/',
'toggle':'/api/wireguard/client/toggleClient/'
}
);
$("#grid-servers").UIBootgrid(
{
'search':'/api/wireguard/server/searchServer',
'get':'/api/wireguard/server/getServer/',
'set':'/api/wireguard/server/setServer/',
'add':'/api/wireguard/server/addServer/',
'del':'/api/wireguard/server/delServer/',
'toggle':'/api/wireguard/server/toggleServer/'
}
);
$("#reconfigureAct").SimpleActionButton({
onPreAction: function() {
const dfObj = new $.Deferred();
saveFormToEndpoint("/api/wireguard/general/set", 'frm_general_settings', function(){
dfObj.resolve();
});
return dfObj;
}
});
/**
* Move keypair generation button inside the server form and hook api event
*/
$("#control_label_server\\.pubkey").append($("#keygen_div").detach().show());
$("#keygen").click(function(){
ajaxGet("/api/wireguard/server/key_pair", {}, function(data, status){
if (data.status && data.status === 'ok') {
$("#server\\.pubkey").val(data.pubkey);
$("#server\\.privkey").val(data.privkey);
}
});
})
});
</script>
<!-- Navigation bar -->
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs">
<li class="active"><a data-toggle="tab" href="#general">{{ lang._('General') }}</a></li>
<li><a data-toggle="tab" href="#servers">{{ lang._('Local') }}</a></li>
<li><a data-toggle="tab" href="#clients">{{ lang._('Endpoints') }}</a></li>
</ul>
<div class="tab-content content-box tab-content">
<div id="general" class="tab-pane fade in active">
<div class="content-box" style="padding-bottom: 1.5em;">
{{ partial("layout_partials/base_form",['fields':generalForm,'id':'frm_general_settings'])}}
</div>
</div>
<div id="clients" class="tab-pane fade in">
<table id="grid-clients" class="table table-responsive" data-editDialog="dialogEditWireguardClient">
<thead>
<tr>
<th data-column-id="enabled" data-type="string" data-formatter="rowtoggle">{{ lang._('Enabled') }}</th>
<th data-column-id="name" data-type="string" data-visible="true">{{ lang._('Name') }}</th>
<th data-column-id="serveraddress" data-type="string" data-visible="true">{{ lang._('Endpoint Address') }}</th>
<th data-column-id="serverport" data-type="string" data-visible="true">{{ lang._('Endpoint Port') }}</th>
<th data-column-id="tunneladdress" data-type="string" data-visible="true">{{ lang._('Allowed IPs') }}</th>
<th data-column-id="uuid" data-type="string" data-identifier="true" data-visible="false">{{ lang._('ID') }}</th>
<th data-column-id="commands" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<td colspan="6"></td>
<td>
<button data-action="add" type="button" class="btn btn-xs btn-default"><span class="fa fa-plus"></span></button>
</td>
</tr>
</tfoot>
</table>
</div>
<div id="servers" class="tab-pane fade in">
<span id="keygen_div" style="display:none" class="pull-right">
<button id="keygen" type="button" class="btn btn-secondary" title="{{ lang._('Generate new keypair.') }}" data-toggle="tooltip">
<i class="fa fa-fw fa-gear"></i>
</button>
</span>
<table id="grid-servers" class="table table-responsive" data-editDialog="dialogEditWireguardServer">
<thead>
<tr>
<th data-column-id="enabled" data-type="string" data-formatter="rowtoggle">{{ lang._('Enabled') }}</th>
<th data-column-id="name" data-type="string" data-visible="true">{{ lang._('Name') }}</th>
<th data-column-id="interface" data-type="string" data-visible="true">{{ lang._('Interface') }}</th>
<th data-column-id="tunneladdress" data-type="string" data-visible="true">{{ lang._('Tunnel Address') }}</th>
<th data-column-id="port" data-type="string" data-visible="true">{{ lang._('Port') }}</th>
<th data-column-id="peers" data-type="string" data-visible="true">{{ lang._('Endpoints') }}</th>
<th data-column-id="uuid" data-type="string" data-identifier="true" data-visible="false">{{ lang._('ID') }}</th>
<th data-column-id="commands" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<td colspan="7"></td>
<td>
<button data-action="add" type="button" class="btn btn-xs btn-default"><span class="fa fa-plus"></span></button>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
<section class="page-content-main">
<div class="content-box">
<div class="col-md-12">
<br/>
<button class="btn btn-primary" id="reconfigureAct"
data-endpoint='/api/wireguard/service/reconfigure'
data-label="{{ lang._('Apply') }}"
data-error-title="{{ lang._('Error reconfiguring Wireguard') }}"
type="button"
></button>
<br/><br/>
</div>
</div>
</section>
{{ partial("layout_partials/base_dialog",['fields':formDialogEditWireguardClient,'id':'dialogEditWireguardClient','label':lang._('Edit Endpoint')])}}
{{ partial("layout_partials/base_dialog",['fields':formDialogEditWireguardServer,'id':'dialogEditWireguardServer','label':lang._('Edit Local Configuration')])}}

View File

@ -0,0 +1,46 @@
#!/usr/local/bin/python3
"""
Copyright (c) 2023 Ad Schellevis <ad@opnsense.org>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
"""
import subprocess
import ujson
def keypair():
sp = subprocess.run(['/usr/bin/wg', 'genkey'], capture_output=True, text=True)
if sp.returncode == 0:
privkey = sp.stdout.strip()
sp = subprocess.run(['/usr/bin/wg', 'pubkey'], input=privkey, capture_output=True, text=True)
if sp.returncode == 0:
return {'privkey': privkey, 'pubkey': sp.stdout.strip()}
return None
response = keypair()
if not response:
print(ujson.dumps({'status': 'failed'}))
else:
response['status'] = 'ok'
print(ujson.dumps(response))

View File

@ -0,0 +1,75 @@
#!/usr/local/bin/python3
"""
Copyright (c) 2023 Ad Schellevis <ad@opnsense.org>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
"""
# Python implementation to re-resolve dns entries, for reference see:
# https://github.com/WireGuard/wireguard-tools/tree/master/contrib/reresolve-dns
import glob
import os
import time
import subprocess
sp = subprocess.run(['/usr/bin/wg', 'show', 'all', 'latest-handshakes'], capture_output=True, text=True)
ts_now = time.time()
handshakes = {}
for line in sp.stdout.split('\n'):
parts = line.split()
if len(parts) == 3 and parts[2].isdigit():
handshakes["%s-%s" % (parts[0], parts[1])] = ts_now - int(parts[2])
for filename in glob.glob('/usr/local/etc/wireguard/*.conf'):
this_peer = {}
ifname = os.path.basename(filename).split('.')[0]
with open(filename, 'r') as fhandle:
for line in fhandle:
if line.startswith('[Peer]'):
this_peer = {'ifname': ifname}
elif line.startswith('PublicKey'):
this_peer['PublicKey'] = line.split('=', 1)[1].strip()
elif line.startswith('Endpoint'):
this_peer['Endpoint'] = line.split('=', 1)[1].strip()
if 'Endpoint' in this_peer and 'PublicKey' in this_peer:
peer_key = "%(ifname)s-%(PublicKey)s" % this_peer
if handshakes.get(peer_key, 999) > 135:
# skip if there has been a handshake recently
subprocess.run(
[
'/usr/bin/wg',
'set',
ifname,
'peer',
this_peer['PublicKey'],
'endpoint',
this_peer['Endpoint']
],
capture_output=True,
text=True
)
this_peer = {}

View File

@ -0,0 +1,271 @@
#!/usr/local/bin/php
<?php
/*
* Copyright (C) 2023 Deciso B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
require_once('script/load_phalcon.php');
require_once('util.inc');
require_once('interfaces.inc');
/**
* collect carp status per vhid
*/
function get_vhid_status()
{
$vhids = [];
$uuids = [];
foreach ((new OPNsense\Interfaces\Vip())->vip->iterateItems() as $id => $item) {
if ($item->mode == 'carp') {
$uuids[(string)$item->vhid] = $id;
}
}
foreach (legacy_interfaces_details() as $ifdata) {
if (!empty($ifdata['carp'])) {
foreach ($ifdata['carp'] as $data) {
if (isset($uuids[$data['vhid']])) {
$vhids[$uuids[$data['vhid']]] = $data['status'];
}
}
}
}
return $vhids;
}
/**
* mimic wg-quick behaviour, but bound to our config
*/
function wg_start($server, $fhandle, $ifcfgflag = 'up')
{
if (!does_interface_exist($server->interface)) {
mwexecf('/sbin/ifconfig wg create name %s', [$server->interface]);
mwexecf('/sbin/ifconfig %s group wireguard', [$server->interface]);
}
mwexecf('/usr/bin/wg setconf %s %s', [$server->interface, $server->cnfFilename]);
foreach (explode(',', (string)$server->tunneladdress) as $alias) {
$proto = strpos($alias, ':') === false ? "inet" : "inet6";
mwexecf('/sbin/ifconfig %s %s %s alias', [$server->interface, $proto, $alias]);
}
if (!empty((string)$server->mtu)) {
mwexecf('/sbin/ifconfig %s mtu %s', [$server->interface, $server->mtu]);
}
mwexecf('/sbin/ifconfig %s %s', [$server->interface, $ifcfgflag]);
if (empty((string)$server->disableroutes)) {
/**
* Add routes for all configured peers, wg-quick seems to parse 'wg show wgX allowed-ips' for this,
* but this should logically congtain the same networks.
*
* XXX: For some reason these routes look a bit off, not very well integrated into OPNsense.
* In the long run it might make sense to have some sort of pluggable model facility
* where these (and maybe other) static routes hook into.
**/
$peers = explode(',', $server->peers);
$routes_to_add = ['inet' => [], 'inet6' => []];
foreach ((new OPNsense\Wireguard\Client())->clients->client->iterateItems() as $key => $client) {
if (empty((string)$client->enabled) || !in_array($key, $peers)) {
continue;
}
foreach (explode(',', (string)$client->tunneladdress) as $tunneladdress) {
$ipproto = strpos($tunneladdress, ":") === false ? "inet" : "inet6";
/* wg-quick seems to prevent /0 being routed and translates this automatically */
if (str_ends_with(trim($tunneladdress), '/0')) {
if ($ipproto == 'inet') {
array_push($routes_to_add[$ipproto], '0.0.0.0/1', '128.0.0.0/1');
} else {
array_push($routes_to_add[$ipproto], '::/1', '8000::/1');
}
} else {
$routes_to_add[$ipproto][] = $tunneladdress;
}
}
}
foreach ($routes_to_add as $ipproto => $routes) {
foreach (array_unique($routes) as $route) {
mwexecf('/sbin/route -q -n add -%s %s -interface %s', [$ipproto, $route, $server->interface]);
}
}
} elseif (!empty((string)$server->gateway)) {
/* Only bind the gateway ip to the tunnel */
$ipprefix = strpos($tunneladdress, ":") === false ? "-4" : "-6";
mwexecf('/sbin/route -q -n add %s %s -iface %s', [$ipprefix, $server->gateway, $server->interface]);
}
// flush checksum to ease change detection
fseek($fhandle, 0);
ftruncate($fhandle, 0);
fwrite($fhandle, @md5_file($server->cnfFilename) . "|" . wg_reconfigure_hash($server));
syslog(LOG_NOTICE, "Wireguard interface {$server->name} ({$server->interface}) started");
}
/**
* stop wireguard tunnel, kill the device, the routes should drop automatically.
*/
function wg_stop($server)
{
if (does_interface_exist($server->interface)) {
legacy_interface_destroy($server->interface);
}
syslog(LOG_NOTICE, "Wireguard interface {$server->name} ({$server->interface}) stopped");
}
/**
* Calculate a hash which determines if we are able to reconfigure without a restart of the tunnel.
* We currently assume if something changed on the interface or peer routes are being pushed, it's safer to
* restart then reload.
*/
function wg_reconfigure_hash($server)
{
if (empty((string)$server->disableroutes)) {
return md5(uniqid('', true)); // random hash, should always reconfigure
}
return md5(
sprintf(
'%s|%s|%s',
$server->tunneladdress,
$server->mtu,
$server->gateway
)
);
}
/**
* The stat hash file answers two questions, [1] has anything changed, which is answered using an md5 hash of the
* configuration file. The second question, if something has changed, is it safe to only reload the configuration.
* This is answered by wg_reconfigure_hash() for the instance in question.
*/
function get_stat_hash($fhandle)
{
fseek($fhandle, 0);
$payload = stream_get_contents($fhandle) ?? '';
$parts = explode('|', $payload);
return [
'file' => $parts[0] ?? '',
'interface' => $parts[1] ?? ''
];
}
$opts = getopt('ah', [], $optind);
$args = array_slice($argv, $optind);
/* setup syslog logging */
openlog("wireguard", LOG_ODELAY, LOG_AUTH);
if (isset($opts['h']) || empty($args) || !in_array($args[0], ['start', 'stop', 'restart', 'configure'])) {
echo "Usage: wg-service-control.php [-a] [-h] [stop|start|restart|configure] [uuid]\n\n";
echo "\t-a all instances\n";
} elseif (isset($opts['a']) || !empty($args[1])) {
$server_id = $args[1] ?? null;
$action = $args[0];
$server_devs = [];
if (!empty((string)(new OPNsense\Wireguard\General())->enabled)) {
$ifdetails = legacy_interfaces_details();
$vhids = get_vhid_status();
foreach ((new OPNsense\Wireguard\Server())->servers->server->iterateItems() as $key => $node) {
if (empty((string)$node->enabled)) {
continue;
}
if ($server_id != null && $key != $server_id) {
continue;
}
/**
* CARP may influence the interface status (up or down).
* In order to fluently switch between roles, one should only have to change the interface flag in this
* case, which means we can still reconfigure an interface in the usual way and just omit sending traffic
* when in BACKUP or INIT mode.
*/
$carp_if_flag = 'up';
if (
!empty($vhids[(string)$node->carp_depend_on]) &&
$vhids[(string)$node->carp_depend_on] != 'MASTER'
) {
$carp_if_flag = 'down';
}
$server_devs[] = (string)$node->interface;
$statHandle = fopen($node->statFilename, "a+");
if (flock($statHandle, LOCK_EX)) {
switch ($action) {
case 'stop':
wg_stop($node);
break;
case 'start':
wg_start($node, $statHandle, $carp_if_flag);
break;
case 'restart':
wg_stop($node);
wg_start($node, $statHandle, $carp_if_flag);
break;
case 'configure':
if (
@md5_file($node->cnfFilename) != get_stat_hash($statHandle)['file'] ||
!isset($ifdetails[(string)$node->interface])
) {
if (get_stat_hash($statHandle)['interface'] != wg_reconfigure_hash($node)) {
// Fluent reloading not supported for this instance, make sure the user is informed
syslog(
LOG_NOTICE,
"Wireguard interface {$node->name} ({$node->interface}) " .
"can not reconfigure without stopping it first."
);
wg_stop($node);
}
wg_start($node, $statHandle, $carp_if_flag);
} else {
// when triggered via a CARP event, check our interface status [UP|DOWN]
$tmp = in_array('up', $ifdetails[(string)$node->interface]['flags']) ? 'up' : 'down';
if ($tmp != $carp_if_flag) {
mwexecf('/sbin/ifconfig %s %s', [$node->interface, $carp_if_flag]);
}
}
break;
}
flock($statHandle, LOCK_UN);
}
fclose($statHandle);
}
}
/**
* When -a is specified, cleaup up old or disabled instances (files and interfaces)
*/
if ($server_id == null) {
foreach (glob('/usr/local/etc/wireguard/wg*') as $filename) {
$this_dev = explode('.', basename($filename))[0];
if (!in_array($this_dev, $server_devs)) {
@unlink($filename);
if (does_interface_exist($this_dev)) {
legacy_interface_destroy($this_dev);
}
}
}
}
mwexecf('/usr/local/etc/rc.routing_configure');
}
closelog();

View File

@ -0,0 +1,72 @@
#!/usr/local/bin/python3
"""
Copyright (c) 2023 Ad Schellevis <ad@opnsense.org>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
"""
import subprocess
import ujson
interfaces = {}
for line in subprocess.run(['/sbin/ifconfig'], capture_output=True, text=True).stdout.split("\n"):
if not line.startswith('\t') and line.find('<') > -1:
ifname = line.split(':')[0]
interfaces[ifname] = 'up' if 'UP' in line.split('<')[1].split('>')[0].split(',') else 'down'
sp = subprocess.run(['/usr/bin/wg', 'show', 'all', 'dump'], capture_output=True, text=True)
result = {'records': []}
if sp.returncode == 0:
for line in sp.stdout.split("\n"):
record = {}
parts = line.split("\t")
# parse fields as explained in 'man wg'
record['if'] = parts[0] if len(parts) else None
if len(parts) == 5:
# intentially skip private key, should not expose it
record['type'] = 'interface'
record['public-key'] = parts[2]
record['listen-port'] = parts[3]
record['fwmark'] = parts[4]
# convenience, copy listen-port to endpoint
record['endpoint'] = parts[3]
record['status'] = interfaces.get(record['if'], 'down')
elif len(parts) == 9:
record['type'] = 'peer'
record['public-key'] = parts[1]
# intentially skip preshared-key, should not expose it
record['endpoint'] = parts[3]
record['allowed-ips'] = parts[4]
record['latest-handshake'] = int(parts[5]) if parts[5].isdigit() else 0
record['transfer-rx'] = int(parts[6]) if parts[6].isdigit() else 0
record['transfer-tx'] = int(parts[7]) if parts[7].isdigit() else 0
record['persistent-keepalive'] = parts[8]
else:
continue
result['records'].append(record)
result['status'] = 'ok'
else:
result['status'] = 'failed'
print(ujson.dumps(result))

View File

@ -0,0 +1,54 @@
[start]
command:/usr/local/opnsense/scripts/Wireguard/wg-service-control.php
parameters: start %s
type:script
message: start wireguard instance %s
[stop]
command:/usr/local/opnsense/scripts/Wireguard/wg-service-control.php
parameters: stop %s
type:script
message: stop wireguard instance %s
[restart]
command:/usr/local/opnsense/scripts/Wireguard/wg-service-control.php
parameters: restart %s
type:script
message: restart wireguard instance %s
[configure]
command:/usr/local/opnsense/scripts/Wireguard/wg-service-control.php
parameters: -a configure
type:script
message: configure wireguard instances
[renew]
command:/usr/local/opnsense/scripts/Wireguard/reresolve-dns.py
parameters:
type:script
message:Renew DNS for WireGuard
description:Renew DNS for WireGuard on stale connections
[gen_keypair]
command:/usr/local/opnsense/scripts/Wireguard/gen_keypair.py
parameters:
type:script_output
message:Generating WireGuard keypair
[show]
command:/usr/local/opnsense/scripts/Wireguard/wg_show.py
parameters:
type:script_output
message:show WireGuard statistics [dump]
[showconf]
command:/usr/bin/wg show all
parameters:
type:script_output
message:Show WireGuard config
[showhandshake]
command:/usr/bin/wg show all latest-handshakes
parameters:
type:script_output
message:Show WireGuard handshakes

View File

@ -0,0 +1,6 @@
###################################################################
# Local syslog-ng configuration filter definition [wireguard].
###################################################################
filter f_local_wireguard {
program("wireguard");
};

View File

@ -0,0 +1,2 @@
wireguard:/etc/rc.conf.d/wireguard
wireguard-server.conf:/usr/local/etc/wireguard/wg[OPNsense.wireguard.server.servers.server.%.instance].conf

View File

@ -0,0 +1,2 @@
# disable the wireguard rc scripts when installed, bootup handled via rc.syshook
wireguard_enable="NO"

View File

@ -0,0 +1,48 @@
{% if helpers.exists('OPNsense.wireguard.general.enabled') and OPNsense.wireguard.general.enabled == '1' %}
{% if helpers.exists('OPNsense.wireguard.server.servers.server') %}
{% for server_list in helpers.toList('OPNsense.wireguard.server.servers.server') %}
{% if TARGET_FILTERS['OPNsense.wireguard.server.servers.server.' ~ loop.index0] or TARGET_FILTERS['OPNsense.wireguard.server.servers.server'] %}
{% if server_list.enabled == '1' %}
####################################################
# Interface settings, not used by `wg` #
# Only used for reference and detection of changes #
# in the configuration #
####################################################
# Address = {{server_list.tunneladdress|default('')}}
# DNS = {{ server_list.dns|default('')}}
# MTU = {{ server_list.mtu|default('') }}
# disableroutes = {{server_list.disableroutes}}
# gateway = {{server_list.gateway}}
[Interface]
PrivateKey = {{ server_list.privkey }}
{% if server_list.port|default('') != '' %}
ListenPort = {{ server_list.port }}
{% endif %}
{% if server_list.peers|default('') != '' %}
{% for peerlist in server_list.peers.split(",") %}
{% set peerlist2_data = helpers.getUUID(peerlist) %}
{% if peerlist2_data != {} and peerlist2_data.enabled == '1' %}
[Peer]
# friendly_name = {{ peerlist2_data.name }}
PublicKey = {{ peerlist2_data.pubkey }}
{% if peerlist2_data.psk|default('') != '' %}
PresharedKey = {{ peerlist2_data.psk }}
{% endif %}
{% if peerlist2_data.serveraddress|default('') != '' %}
Endpoint = {{ peerlist2_data.serveraddress }}{% if peerlist2_data.serverport|default('') != '' %}:{{ peerlist2_data.serverport }}{% else %}:51820{% endif %}
{% endif %}
AllowedIPs = {{ peerlist2_data.tunneladdress }}
{% if peerlist2_data.keepalive|default('') != '' %}
PersistentKeepalive = {{ peerlist2_data.keepalive }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endif %}

View File

@ -0,0 +1,4 @@
<?php
$wireguard_title = gettext('WireGuard');
$wireguard_title_link = 'ui/wireguard/general';

View File

@ -0,0 +1,95 @@
<?php
/*
* Copyright (C) 2020-2023 Deciso B.V.
* Copyright (C) 2020 D. Domig
* Copyright (C) 2022 Patrik Kernstock <patrik@kernstock.net>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
?>
<table class="table table-striped table-condensed" id="wg-table">
<thead>
<tr>
<th><?= gettext("Interface") ?></th>
<th><?= gettext("Endpoint") ?></th>
<th><?= gettext("Public Key") ?></th>
<th><?= gettext("Latest Handshake") ?></th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot style="display: none;">
<tr>
<td colspan="4"><?= gettext("No WireGuard instance defined or enabled.") ?></td>
</tr>
</tfoot>
</table>
<style>
.psk_td {
max-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
text-decoration: underline;
}
</style>
<script>
$(window).on("load", function() {
function wgUpdateStatus()
{
ajaxGet("/api/wireguard/service/show", {}, function(data, status) {
let $target = $("#wg-table > tbody").empty();
if (data.rows !== undefined && data.rows.length > 0) {
$("#wg-table > tfoot").hide();
for (let i=0; data.rows.length > i; ++i) {
let row = data.rows[i];
let $tr = $("<tr/>");
let ifname = row.ifname ? row.if + ' (' + row.ifname + ') ' : row.if;
$tr.append($("<td>").append(ifname));
$tr.append($("<td>").append(row.name));
$tr.append($("<td class='psk_td'>").append(row['public-key']));
let latest_handhake = '';
if (row['latest-handshake']) {
latest_handhake = moment.unix(row['latest-handshake']).local().format('YYYY-MM-DD HH:mm:ss');
}
$tr.append($("<td>").append(latest_handhake));
$target.append($tr);
}
$(".psk_td").each(function(){
$(this).tooltip({title: $(this).text(), container: 'body', trigger: 'hover'});
});
} else{
$("#wg-table > tfoot").show();
}
setTimeout(wgUpdateStatus, 10000);
});
};
wgUpdateStatus();
});
</script>