extend update for wireguard, correct is_int to is_numeric for polling purposes, and clean up the app page (#16182)

* replace is_int with is_numeric

* update the docs for wireguard for the new extend

* relocate the app data bit

* update the data for the wireguard test

* start reworking the wg app page... details part done

* app page mostly done

* finish the wireguard app page

* style fix

* now sorted

* add additional info to the peer page

* style fixes

* a small fix for the links

* style fix

* make the pubkey column in details link to the peer page

* escape the pubkey
This commit is contained in:
Zane C. Bowers-Hadley
2024-07-14 22:58:41 -05:00
committed by GitHub
parent 99f72823bb
commit 8738e978ce
4 changed files with 406 additions and 131 deletions

View File

@@ -3491,35 +3491,52 @@ The Wireguard application polls the Wireguard service and scrapes all client sta
1. Copy the python script, wireguard.py, to the desired host
```
wget https://github.com/librenms/librenms-agent/raw/master/snmp/wireguard.py -O /etc/snmp/wireguard.py
wget https://github.com/librenms/librenms-agent/raw/master/snmp/wireguard.pl -O /etc/snmp/wireguard.pl
```
2. Make the script executable
2. Install the depends.
```
chmod +x /etc/snmp/wireguard.py
# FreeBSD
pkg install p5-JSON p5-File-Slurp p5-MIME-Base64
# Debian
apt-get install libjson-perl libmime-base64-perl libfile-slurp-perl
```
3. Edit your snmpd.conf file and add:
3. Make the script executable
```
extend wireguard /etc/snmp/wireguard.py
chmod +x /etc/snmp/wireguard.pl
```
4. Create a /etc/snmp/wireguard.json file and specify:
1. (optional) "wg_cmd" - String path to the wg binary ["/usr/bin/wg"]
2. "public_key_to_arbitrary_name" - A dictionary to convert between the publickey assigned to the client (specified in the wireguard interface conf file) to an arbitrary, friendly name. The friendly names MUST be unique within each interface. Also note that the interface name and friendly names are used in the RRD filename, so using special characters is highly discouraged.
4. Edit your snmpd.conf file and add:
```
{
"wg_cmd": "/bin/wg",
"public_key_to_arbitrary_name": {
"wg0": {
"z1iSIymFEFi/PS8rR19AFBle7O4tWowMWuFzHO7oRlE=": "client1",
"XqWJRE21Fw1ke47mH1yPg/lyWqCCfjkIXiS6JobuhTI=": "server.domain.com"
}
}
}
extend wireguard /etc/snmp/wireguard.pl
```
5. Restart snmpd.
5. Create the optional config file,
`/usr/local/etc/wireguard_extend.json`.
| key | default | description |
|------------------------------|-------------|-------------------------------------------------------------|
| include_pubkey | 0 | Include the pubkey with the return. |
| use_short_hostname | 1 | If the hostname should be shortened to just the first part. |
| public_key_to_arbitrary_name | {} | A hash of pubkeys to name mappings. |
| pubkey_resolvers | <see below> | Resolvers to use for the pubkeys. |
The default for `pubkey_resolvers` is
`config,endpoint_if_first_allowed_is_subnet_use_hosts,endpoint_if_first_allowed_is_subnet_use_ip,first_allowed_use_hosts,first_allowed_use_ip`.
| resolver | description |
|------------------------------------------------|------------------------------------------------------------------------------------------------------|
| config | Use the mappings from `.public_key_to_arbitrary_name` . |
| endpoint_if_first_allowed_is_subnet_use_hosts | If the first allowed IP is a subnet, see if a matching IP can be found in hosts for the endpoint. |
| endpoint_if_first_allowed_is_subnet_use_getent | If the first allowed IP is a subnet, see if a hit can be found for the endpoint IP via getent hosts. |
| endpoint_if_first_allowed_is_subnet_use_ip | If the first allowed IP is a subnet, use the endpoint IP for the name. |
| first_allowed_use_hosts | See if a match can be found in hosts for the first allowed IP. |
| first_allowed_use_getent | Use getent hosts to see try to fetch a match for the first allowed IP. |
| first_allowed_use_ip | Use the first allowed IP as the name. |
6. Restart snmpd.
## ZFS

View File

@@ -1,41 +1,9 @@
<?php
/**
* Builds a graph array and outputs the graph.
*
* @param string $gtype
* @param string $app_id
* @param null|string $interface
* @param null|string $client
* @param string $gtext
*/
function wireguard_graph_printer($gtype, $app_id, $interface, $client, $gtext)
{
$graph_type = $gtype;
$graph_array['height'] = '100';
$graph_array['width'] = '215';
$graph_array['to'] = time();
$graph_array['id'] = $app_id;
$graph_array['type'] = 'application_' . $gtype;
if (! is_null($interface)) {
$graph_array['interface'] = $interface;
}
if (! is_null($client)) {
$graph_array['client'] = $client;
}
echo '<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">' .
$gtext .
'</h3>
</div>
<div class="panel-body">
<div class="row">';
include 'includes/html/print-graphrow.inc.php';
echo '</div>';
echo '</div>';
echo '</div>';
}
use App\Models\Device;
use App\Models\Ipv4Address;
use App\Models\Ipv6Address;
use App\Models\Port;
$link_array = [
'page' => 'device',
@@ -45,104 +13,392 @@ $link_array = [
];
$interface_client_map = $app->data['mappings'] ?? [];
$graph_map = [
'interface' => [
'clients' => [],
'total' => [],
],
'total' => [],
];
$returned_data = $app->data['data'] ?? [];
// put interfaces in order
ksort($interface_client_map);
print_optionbar_start();
echo generate_link('All Interfaces', $link_array);
$label =
(! isset($vars['wg_page']) && ! isset($vars['interface']))
? '<span class="pagemenu-selected">All Interfaces</span>'
: 'All Interfaces';
echo generate_link($label, $link_array);
if (count($returned_data) > 0) {
echo ' | ';
$label =
$vars['wg_page'] == 'details'
? '<span class="pagemenu-selected">Details</span>'
: 'Details';
echo generate_link($label, $link_array, ['wg_page' => 'details']);
}
echo ' | Interfaces: ';
// generate interface links on the host application page
$i = 0;
foreach ($interface_client_map as $interface => $client_list) {
$label =
$vars['interface'] == $interface
($vars['interface'] == $interface && ! isset($vars['wg_page']))
? '<span class="pagemenu-selected">' . $interface . '</span>'
: $interface;
echo generate_link($label, $link_array, ['interface' => $interface]);
echo '(';
$label =
($vars['interface'] == $interface && $vars['wg_page'] == 'peer_bw')
? '<span class="pagemenu-selected">' . 'BW' . '</span>'
: 'BW';
echo generate_link($label, $link_array, ['interface' => $interface, 'wg_page' => 'peer_bw']);
echo ', ';
$label =
($vars['interface'] == $interface && $vars['wg_page'] == 'peer_last')
? '<span class="pagemenu-selected">' . 'Last' . '</span>'
: 'Last';
echo generate_link($label, $link_array, ['interface' => $interface, 'wg_page' => 'peer_last']);
echo ')';
if ($i < count(array_keys($interface_client_map)) - 1) {
echo ', ';
}
$i++;
}
print_optionbar_end();
// if we have a interface specified and it exists, print a list of peers
if (isset($vars['interface']) && isset($interface_client_map[$vars['interface']])) {
// order the peer information for the interface
asort($interface_client_map[$vars['interface']]);
// generates the global wireguard graph mapping
if (! isset($vars['interface'])) {
$graph_map['total'] = [
'wireguard_traffic' => 'Wireguard Total Traffic',
];
}
$i = 0;
echo '<br>Peers: ';
foreach ($interface_client_map[$vars['interface']] as $peer_key => $peer) {
$label =
$vars['client'] == $peer
? '<span class="pagemenu-selected">' . $peer . '</span>'
: $peer;
echo generate_link($label, $link_array, ['interface' => $interface, 'client' => $peer]);
foreach ($interface_client_map as $interface => $client_list) {
if (
! isset($vars['interface']) ||
(isset($vars['interface']) && $interface == $vars['interface'])
) {
// generates the interface graph mapping
$graph_map['interface']['total'][$interface] = [
'wireguard_traffic' => $interface . ' ' . 'Total Traffic',
];
foreach ($client_list as $client) {
// generates the interface+client graph mapping
$graph_map['interface']['clients'][$interface][$client] = [
'wireguard_traffic' => $interface . ' ' . $client . ' Traffic',
'wireguard_time' => $interface .
' ' .
$client .
' Minutes Since Last Handshake',
];
if ($i < count(array_keys($interface_client_map[$vars['interface']])) - 1) {
echo ', ';
}
$i++;
}
}
// print graphs
foreach ($graph_map as $category => $category_map) {
foreach ($category_map as $subcategory => $subcategory_map) {
if ($category === 'total') {
// print graphs for global wireguard metrics
wireguard_graph_printer(
$subcategory,
$app['app_id'],
null,
null,
$subcategory_map
);
} elseif ($category === 'interface') {
foreach ($subcategory_map as $interface => $interface_map) {
foreach ($interface_map as $client => $client_map) {
if ($subcategory === 'total') {
// print graphs for wireguard interface metrics
wireguard_graph_printer(
$client,
$app['app_id'],
$interface,
null,
$client_map
);
} elseif ($subcategory === 'clients') {
foreach ($client_map as $gtype => $gtext) {
// print graphs for wireguard interface+client metrics
wireguard_graph_printer(
$gtype,
$app['app_id'],
$interface,
$client,
$gtext
);
}
}
// if displaying peer information, display additional useful information
if (isset($vars['interface']) &&
isset($interface_client_map[$vars['interface']]) &&
isset($vars['client']) &&
isset($returned_data[$vars['interface']][$vars['client']]) &&
! isset($vars['wg_page'])
) {
$peer = $returned_data[$vars['interface']][$vars['client']];
echo "\n<hr>Hostname: ";
if (isset($peer['hostname'])) {
$peer_dev = Device::firstWhere(['hostname' => $peer['hostname']]);
if (isset($peer_dev)) {
echo generate_device_link(['device_id' => $peer_dev->device_id], $name) . "<br>\n";
} else {
echo htmlspecialchars($peer['hostname']) . "<br>\n";
}
} else {
echo "*unknown*<br>\n";
}
echo 'PubKey: ';
if (is_null($peer['pubkey'])) {
echo "*hidden*<br>\n";
} else {
echo htmlspecialchars($peer['pubkey']) . "<br>\n";
}
echo 'Interface: ';
$port = Port::with('device')->firstWhere(['ifName' => $vars['interface'], 'device_id' => $device['device_id']]);
if (isset($port)) {
echo generate_port_link([
'label' => $port->label,
'port_id' => $port->port_id,
'ifName' => $port->ifName,
'device_id' => $port->device_id,
]) . "<br>\n";
} else {
echo htmlspecialchars($vars['interface']) . "<br>\n";
}
echo 'Endpoint Host: ';
if (preg_match('/^[\:A-Fa-f0-9]+$/', $peer['endpoint_host'])) {
$ip_info = Ipv6Address::firstWhere(['ipv6_address' => $peer['endpoint_host']]);
} elseif (preg_match('/^[\.0-9]+$/', $peer['endpoint_host'])) {
$ip_info = Ipv4Address::firstWhere(['ipv4_address' => $peer['endpoint_host']]);
}
if (isset($ip_info)) {
$port = Port::with('device')->firstWhere(['port_id' => $ip_info->port_id]);
echo $peer['endpoint_host'] . '(' . generate_device_link(['device_id' => $port->device_id]) . ', ' .
generate_port_link([
'label' => $port->label,
'port_id' => $port->port_id,
'ifName' => $port->ifName,
'device_id' => $port->device_id,
]) . ")<br>\n";
} else {
echo htmlspecialchars($peer['endpoint_host']) . "<br>\n";
}
echo 'Endpoint Port: ' . htmlspecialchars($peer['endpoint_port']) . "<br>\n";
echo 'Minutes Since Last Handshake: ' . htmlspecialchars($peer['minutes_since_last_handshake']) . "<br>\n";
echo 'Allowed IPs: ';
$allowed_ips = '';
if (isset($peer['allowed_ips']) && ! is_null($peer['allowed_ips']) && is_array($peer['allowed_ips'])) {
foreach ($peer['allowed_ips'] as $allowed_ips_key => $allowed_ip) {
$ip_found = false;
if (preg_match('/^[\:A-Fa-f0-9]+$/', $allowed_ip)) {
$ip_info = Ipv6Address::firstWhere(['ipv6_address' => $allowed_ip]);
if (isset($ip_info)) {
$ip_found = true;
}
} elseif (preg_match('/^[\.0-9]+$/', $allowed_ip)) {
$ip_info = Ipv4Address::firstWhere(['ipv4_address' => $allowed_ip]);
if (isset($ip_info)) {
$ip_found = true;
}
}
if ($ip_found) {
$port = Port::with('device')->firstWhere(['port_id' => $ip_info->port_id]);
$ip_info_string = generate_device_link(['device_id' => $port->device_id], $allowed_ip) . '(' .
generate_port_link([
'label' => $port->label,
'port_id' => $port->port_id,
'ifName' => $port->ifName,
'device_id' => $port->device_id,
]) . ')';
if ($allowed_ips == '') {
$allowed_ips = $ip_info_string;
} else {
$allowed_ips = $allowed_ips . ', ' . $ip_info_string;
}
} else {
if ($allowed_ips == '') {
$allowed_ips = htmlspecialchars($allowed_ip);
} else {
$allowed_ips = $allowed_ips . ', ' . htmlspecialchars($allowed_ip);
}
}
}
}
echo $allowed_ips . "<br>\n";
}
print_optionbar_end();
if (isset($vars['wg_page']) and $vars['wg_page'] == 'details') {
$table_info = [
'headers' => [
'Name',
'Interface',
'PubKey',
'Recv',
'Sent',
'Endpoint IP',
'Port',
'Last Handshake',
'Allowed IPs',
],
'rows' => [],
];
ksort($returned_data);
foreach ($returned_data as $returned_data_key => $interface) {
ksort($interface);
$port = Port::with('device')->firstWhere(['ifName' => $returned_data_key, 'device_id' => $device['device_id']]);
if (isset($port)) {
$interface_info_raw = true;
$interface_info = generate_port_link([
'label' => $port->label,
'port_id' => $port->port_id,
'ifName' => $port->ifName,
'device_id' => $port->device_id,
]);
} else {
$interface_info_raw = false;
}
foreach ($interface as $interface_key => $peer) {
$name = $interface_key;
$name_raw = false;
// see if the hostname resolves to a hostname of a device
if (isset($peer['hostname']) && ! is_null($peer['hostname'])) {
$peer_dev = Device::firstWhere(['hostname' => $peer['hostname']]);
if (isset($peer_dev)) {
$name_raw = true;
$name = generate_device_link(['device_id' => $peer_dev->device_id], $name);
}
}
// if this is null, it means the extend did not return it as that options is set to 0
if (is_null($peer['pubkey'])) {
$peer['pubkey'] = '*hidden*';
}
// ensure we have something set for endpoint host
if (! isset($peer['endpoint_host']) || is_null($peer['endpoint_host'])) {
$peer['endpoint_host'] = '';
} else { // if we have data, see if we can resolve that to a machine for generating dev/if links
$endpoint_raw = false;
if (preg_match('/^[\:A-Fa-f0-9]+$/', $peer['endpoint_host'])) {
$ip_info = Ipv6Address::firstWhere(['ipv6_address' => $peer['endpoint_host']]);
} elseif (preg_match('/^[\.0-9]+$/', $peer['endpoint_host'])) {
$ip_info = Ipv4Address::firstWhere(['ipv4_address' => $peer['endpoint_host']]);
}
if (isset($ip_info)) {
$endpoint_raw = true;
$port = Port::with('device')->firstWhere(['port_id' => $ip_info->port_id]);
$peer['endpoint_host'] = $peer['endpoint_host'] . '(' . generate_device_link(['device_id' => $port->device_id]) . ', ' .
generate_port_link([
'label' => $port->label,
'port_id' => $port->port_id,
'ifName' => $port->ifName,
'device_id' => $port->device_id,
]) . ')';
}
}
// ensure we have something set for the endpoint port
if (! isset($peer['endpoint_port']) || is_null($peer['endpoint_port'])) {
$peer['endpoint_port'] = '';
}
// build string of allowed IPs
$allowed_ips = '';
if (isset($peer['allowed_ips']) && ! is_null($peer['allowed_ips']) && is_array($peer['allowed_ips'])) {
foreach ($peer['allowed_ips'] as $allowed_ips_key => $allowed_ip) {
$ip_found = false;
if (preg_match('/^[\:A-Fa-f0-9]+$/', $allowed_ip)) {
$ip_info = Ipv6Address::firstWhere(['ipv6_address' => $allowed_ip]);
if (isset($ip_info)) {
$ip_found = true;
}
} elseif (preg_match('/^[\.0-9]+$/', $allowed_ip)) {
$ip_info = Ipv4Address::firstWhere(['ipv4_address' => $allowed_ip]);
if (isset($ip_info)) {
$ip_found = true;
}
}
if ($ip_found) {
$port = Port::with('device')->firstWhere(['port_id' => $ip_info->port_id]);
$ip_info_string = generate_device_link(['device_id' => $port->device_id], $allowed_ip) . '(' .
generate_port_link([
'label' => $port->label,
'port_id' => $port->port_id,
'ifName' => $port->ifName,
'device_id' => $port->device_id,
]) . ')';
if ($allowed_ips == '') {
$allowed_ips = $ip_info_string;
} else {
$allowed_ips = $allowed_ips . ', ' . $ip_info_string;
}
} else {
if ($allowed_ips == '') {
$allowed_ips = htmlspecialchars($allowed_ip);
} else {
$allowed_ips = $allowed_ips . ', ' . htmlspecialchars($allowed_ip);
}
}
}
}
$peer['pubkey'] = generate_link(htmlspecialchars($peer['pubkey']), $link_array, ['interface' => $returned_data_key, 'client' => $interface_key]);
$row = [
['data' => $name, 'raw' => $name_raw],
['data' => $interface_info, 'raw' => $interface_info_raw],
['data' => $peer['pubkey'], 'raw' => true],
['data' => $peer['bytes_rcvd']],
['data' => $peer['bytes_sent']],
['data' => $peer['endpoint_host'], 'raw' => $endpoint_raw],
['data' => $peer['endpoint_port']],
['data' => sprintf('%01.2f', $peer['minutes_since_last_handshake'])],
['data' => $allowed_ips, 'raw' => true],
];
$table_info['rows'][] = $row;
}
}
echo view('widgets/sortable_table', $table_info);
} elseif (! isset($vars['interface'])) {
$graphs = [
'total' => [
'type' => 'wireguard_traffic',
'description' => 'Total Wireguard Traffic',
],
];
} elseif (isset($vars['interface']) && ! isset($vars['client']) && $vars['wg_page'] == 'peer_bw') {
$graphs = [
'interface_total' => [
'type' => 'wireguard_traffic',
'description' => 'Total Wireguard Traffic, ' . $vars['interface'],
'interface' => $vars['interface'],
],
];
foreach ($interface_client_map[$vars['interface']] as $peer_key => $peer) {
$graphs['peer_bw_' . $peer] = [
'type' => 'wireguard_traffic',
'description' => 'Peer Traffic, ' . $vars['interface'] . ' - ' . $peer,
'interface' => $vars['interface'],
'client' => $peer,
];
}
} elseif (isset($vars['interface']) && ! isset($vars['client']) && $vars['wg_page'] == 'peer_last') {
$graphs = [];
foreach ($interface_client_map[$vars['interface']] as $peer_key => $peer) {
$graphs['peer_last_' . $peer] = [
'type' => 'wireguard_time',
'description' => 'Peer Minutes Since Last Handshake , ' . $vars['interface'] . ' - ' . $peer,
'interface' => $vars['interface'],
'client' => $peer,
];
}
} elseif (isset($vars['interface']) && ! isset($vars['client'])) {
$graphs = [
'interface_total' => [
'type' => 'wireguard_traffic',
'description' => 'Total Wireguard Traffic, ' . $vars['interface'],
'interface' => $vars['interface'],
],
];
} elseif (isset($vars['interface']) && isset($vars['client'])) {
$graphs = [
'client_bw' => [
'type' => 'wireguard_traffic',
'description' => 'Peer Traffic, ' . $vars['interface'] . ' - ' . $vars['client'],
'interface' => $vars['interface'],
'client' => $vars['client'],
],
'client_handshake' => [
'type' => 'wireguard_time',
'description' => 'Peer Minutes Since Last Handshake , ' . $vars['interface'] . ' - ' . $vars['client'],
'interface' => $vars['interface'],
'client' => $vars['client'],
],
];
}
foreach ($graphs as $key => $graph_info) {
$graph_type = $graph_info['type'];
$graph_array['height'] = '100';
$graph_array['width'] = '215';
$graph_array['to'] = time();
$graph_array['id'] = $app['app_id'];
$graph_array['type'] = 'application_' . $graph_info['type'];
if (! is_null($graph_info['interface'])) {
$graph_array['interface'] = $graph_info['interface'];
}
if (! is_null($graph_info['client'])) {
$graph_array['client'] = $graph_info['client'];
}
echo '<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">' . $graph_info['description'] . '</h3>
</div>
<div class="panel-body">
<div class="row">';
include 'includes/html/print-graphrow.inc.php';
echo '</div>';
echo '</div>';
echo '</div>';
}

View File

@@ -84,24 +84,24 @@ foreach ($interface_client_map as $interface => $client_list) {
}
array_push($mappings[$finterface], $fclient);
$bytes_rcvd = is_int($client_data['bytes_rcvd'])
$bytes_rcvd = is_numeric($client_data['bytes_rcvd'])
? $client_data['bytes_rcvd']
: null;
$bytes_sent = is_int($client_data['bytes_sent'])
$bytes_sent = is_numeric($client_data['bytes_sent'])
? $client_data['bytes_sent']
: null;
$minutes_since_last_handshake = is_int(
$minutes_since_last_handshake = is_numeric(
$client_data['minutes_since_last_handshake']
)
? $client_data['minutes_since_last_handshake']
: null;
if (is_int($bytes_rcvd)) {
if (is_numeric($bytes_rcvd)) {
$bytes_rcvd_total_intf += $bytes_rcvd;
$bytes_rcvd_total += $bytes_rcvd;
}
if (is_int($bytes_sent)) {
if (is_numeric($bytes_sent)) {
$bytes_sent_total_intf += $bytes_sent;
$bytes_sent_total += $bytes_sent;
}
@@ -170,11 +170,13 @@ $mappings_updated = false;
// get old mappings
$old_mappings = $app->data['mappings'] ?? [];
// update here even if there are no added or reel in any changes for table info display
$app->data = ['mappings' => $mappings, 'data' => $interface_client_map];
// check for interface changes
$added_interfaces = array_diff_key($mappings, $old_mappings);
$removed_interfaces = array_diff_key($old_mappings, $mappings);
if (count($added_interfaces) > 0 || count($removed_interfaces) > 0) {
$app->data = ['mappings' => $mappings];
$mappings_updated = true;
$log_message = 'Wireguard Interfaces Change:';
$log_message .=

View File

@@ -44,7 +44,7 @@
"app_state_prev": "UNKNOWN",
"app_status": "",
"app_instance": "",
"data": "{\"mappings\":{\"wg0\":[\"client1.domain.com\",\"client2\",\"my_phone\",\"it_admin.domain.org\",\"computer\"]}}",
"data": "{\"mappings\":{\"wg0\":[\"client1.domain.com\",\"client2\",\"my_phone\",\"it_admin.domain.org\",\"computer\"]},\"data\":{\"wg0\":{\"client1.domain.com\":{\"minutes_since_last_handshake\":null,\"bytes_rcvd\":0,\"bytes_sent\":0},\"client2\":{\"minutes_since_last_handshake\":null,\"bytes_rcvd\":0,\"bytes_sent\":0},\"my_phone\":{\"minutes_since_last_handshake\":null,\"bytes_rcvd\":0,\"bytes_sent\":0},\"it_admin.domain.org\":{\"minutes_since_last_handshake\":null,\"bytes_rcvd\":0,\"bytes_sent\":0},\"computer\":{\"minutes_since_last_handshake\":2,\"bytes_rcvd\":14891148,\"bytes_sent\":105501824}}}}",
"deleted_at": null
}
],