VPN: WireGuard: Diagnostics - remove diagnostics tabs from "VPN: WireGuard: Settings" and replace it for a searchable grid containing the same information at "VPN: WireGuard: Diagnostics". Keep text mode endpoints for backwards compatibility in case someone queries them (to be removed in 24.1).

This commit is contained in:
Ad Schellevis
2023-08-27 18:57:48 +02:00
parent 15a3559d58
commit 32a83e6487
8 changed files with 236 additions and 27 deletions
+2
View File
@@ -25,6 +25,8 @@ Changelog
* Reimplement https://github.com/WireGuard/wireguard-tools/tree/master/contrib/reresolve-dns using Python in reresolve-dns.py
* Enforce wireguard-tools rc script to be disabled when still installed, this should prevent bootup issues
* Move 'interface' calculated field to model for easy reusability
* Move diagnostics to VPN: WireGuard: Diagnostics
* Change keypair generation to a separate API call and form button to ease copy/paste when adding new servers.
* Change plugin maintainer
1.13
@@ -32,6 +32,8 @@ 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
@@ -72,6 +74,7 @@ class ServiceController extends ApiMutableServiceControllerBase
/**
* show wireguard config
* XXX: remove in 24.1
* @return array
*/
public function showconfAction()
@@ -82,6 +85,7 @@ class ServiceController extends ApiMutableServiceControllerBase
/**
* show wireguard handshakes
* XXX: remove in 24.1
* @return array
*/
public function showhandshakeAction()
@@ -89,4 +93,36 @@ class ServiceController extends ApiMutableServiceControllerBase
$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 = [];
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;
}
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'] = '';
}
}
$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);
}
}
@@ -0,0 +1,37 @@
<?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
{
public function indexAction()
{
$this->view->pick('OPNsense/Wireguard/diagnostics');
}
}
@@ -1,7 +1,8 @@
<menu>
<VPN>
<WireGuard cssClass="fa fa-lock fa-fw" order="150">
<Settings order="10" url="/ui/wireguard/general/index"/>
<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>
@@ -0,0 +1,89 @@
{#
# 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() {
let grid_phase1 = $("#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];
}
},
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="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-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>
@@ -78,24 +78,6 @@
}
});
})
// Put API call into a function, needed for auto-refresh
function update_showconf() {
ajaxCall(url="/api/wireguard/service/showconf", sendData={}, callback=function(data,status) {
$("#listshowconf").text(data['response']);
setTimeout(update_showconf, 5000);
});
}
function update_showhandshake() {
ajaxCall(url="/api/wireguard/service/showhandshake", sendData={}, callback=function(data,status) {
$("#listshowhandshake").text(data['response']);
setTimeout(update_showhandshake, 5000);
});
}
// Call update funcs once when page loaded
update_showconf();
update_showhandshake();
});
</script>
<!-- Navigation bar -->
@@ -103,8 +85,6 @@
<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>
<li><a data-toggle="tab" href="#showconf">{{ lang._('Status') }}</a></li>
<li><a data-toggle="tab" href="#showhandshake">{{ lang._('Handshakes') }}</a></li>
</ul>
<div class="tab-content content-box tab-content">
@@ -169,12 +149,6 @@
</tfoot>
</table>
</div>
<div id="showconf" class="tab-pane fade in">
<pre id="listshowconf"></pre>
</div>
<div id="showhandshake" class="tab-pane fade in">
<pre id="listshowhandshake"></pre>
</div>
</div>
<section class="page-content-main">
+64
View File
@@ -0,0 +1,64 @@
#!/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
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]
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))
@@ -35,6 +35,12 @@ 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: