security/tailscale dashboard widget, ipv6 advertised routes (#4414)

This commit is contained in:
Sam Sheridan 2024-12-19 15:57:09 +00:00 committed by GitHub
parent e44716bda6
commit d637100cc4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 206 additions and 7 deletions

View file

@ -1,7 +1,7 @@
PLUGIN_NAME= tailscale
PLUGIN_VERSION= 1.0
PLUGIN_VERSION= 1.1
PLUGIN_COMMENT= VPN mesh securely connecting clients using WireGuard
PLUGIN_DEPENDS= tailscale
PLUGIN_MAINTAINER= sam@sheridan.co.uk
PLUGIN_MAINTAINER= sam@sheridan.uk
.include "../../Mk/plugins.mk"

View file

@ -6,6 +6,13 @@ https://tailscale.com/
Plugin Changelog
================
1.1
* add dashboard widget
* change peer status list to DNSname instead of hostname
* show online status for peers in status list
* allow advertising of IPv6 routes
1.0
* initial release

View file

@ -3,7 +3,9 @@
<name>Tailscale</name>
<patterns>
<pattern>ui/tailscale/*</pattern>
<pattern>api/tailscale/*</pattern>
<pattern>api/tailscale/service/*</pattern>
<pattern>api/tailscale/settings/*</pattern>
<pattern>api/tailscale/status/*</pattern>
</patterns>
</page-tailscale-config>
</acl>

View file

@ -26,7 +26,6 @@
<subnet4 type="ArrayField">
<subnet type="NetworkField">
<NetMaskRequired>Y</NetMaskRequired>
<AddressFamily>ipv4</AddressFamily>
<Strict>Y</Strict>
<Required>Y</Required>
</subnet>

View file

@ -8,7 +8,7 @@
return (str + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + breakTag + '$2');
}
function updateNetInfo() {
function updateNetInfo() {
ajaxGet(url = "/api/tailscale/status/net/", sendData={},
callback = function (data, status) {
if (status == "success") {
@ -33,7 +33,13 @@
console.log(tailscaleIp);
}
let row = '<tr><td>' + data.HostName;
let color = 'text-success';
if (data.Online === false) {
color = 'text-danger';
}
let onlineStatus = `<i class="fa fa-circle ${color}"></i> `;
let row = `<tr><td>${onlineStatus}` + data.DNSName;
row += '</td><td>' + tailscaleIp;
row += '</td><td>' + data.LastSeen;
row += '</td><td>' + data.OS;
@ -114,7 +120,7 @@
<table id="peerInfo" class="table table-striped table-condensed table-responsive">
<thead>
<tr>
<th>{{ lang._('Name') }}</th>
<th>{{ lang._('Peer') }}</th>
<th>{{ lang._('Tailscale IPs') }}</th>
<th>{{ lang._('Last Seen') }}</th>
<th>{{ lang._('OS') }}</th>

View file

@ -0,0 +1,25 @@
<metadata>
<Tailscale>
<filename>Tailscale.js</filename>
<endpoints>
<endpoint>/api/tailscale/service/status</endpoint>
<endpoint>/api/tailscale/status/status</endpoint>
</endpoints>
<translations>
<title>Tailscale</title>
<serviceDisabled>Tailscale service is not running</serviceDisabled>
<backendState>Backend State</backendState>
<dnsName>DNS Name</dnsName>
<enabled>Enabled</enabled>
<exitNode>Exit Node</exitNode>
<no>No</no>
<noData>Could not fetch data</noData>
<online>Online</online>
<peers>Peers</peers>
<tailscaleIP>Tailscale IP</tailscaleIP>
<yes>Yes</yes>
<version>Version</version>
</translations>
</Tailscale>
</metadata>

View file

@ -0,0 +1,160 @@
/*
* Copyright (C) 2024 Sheridan Computers
* 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.
*/
export default class Wireguard extends BaseTableWidget {
constructor() {
super();
}
getGridOptions() {
return {
// Automatically triggers vertical scrolling after reaching 650px in height
sizeToContent: 650
};
}
getMarkup() {
let $container = $('<div></div>');
let $tailscaleStatusTable = this.createTable('tailscaleStatusTable', {
headerPosition: 'left'
});
$container.append($tailscaleStatusTable);
return $container;
}
async onWidgetTick() {
// check if Tailscale is enabled
const ServiceStatusData = await this.ajaxCall('/api/tailscale/service/status');
if (!ServiceStatusData || ServiceStatusData.status !== 'running') {
this.displayError(this.translations.serviceDisabled);
return;
}
const tsData = await this.ajaxCall('/api/tailscale/status/status');
if (!tsData) {
this.displayError(this.translations.noData);
return;
}
let statusData = this.parseData(tsData);
if (!this.dataChanged('tailscale-data', statusData)) {
return;
}
this.updateWidgetTable(statusData);
}
parseData(data) {
let result = [];
result['version'] = data.Version;
result['backendState'] = data.BackendState;
result['dnsName'] = data.Self.DNSName;
result['online'] = (data.Self.Online === true) ?
this.translations.yes : this.translations.no;
result['exitNode'] = (data.Self.ExitNode === true) ?
this.translations.yes : this.translations.no;
result['peerCount'] = Object.keys(data.Peer).length;
let ipAddresses = [];
data.TailscaleIPs.forEach(ip => {
ipAddresses.push(ip);
});
result['ipAddresses'] = ipAddresses;
return result;
}
updateWidgetTable(data) {
let rows = [];
let color = "text-success";
if (data['online'] === false) {
color = "text-danger";
}
let row = [
`<div><i class="fa fa-circle ${color}"></i> ${this.translations.online}</div>`,
`<div>${data['online']}</div>`
];
rows.push(row);
// version
row = [
`<div>${this.translations.version}</div>`,
`<div>${data['version']}</div>`
];
rows.push(row);
// backend state
row = [
`<div>${this.translations.backendState}</div>`,
`<div>${data['backendState']}</div>`
];
rows.push(row);
// dns name
row = [
`<div>${this.translations.dnsName}</div>`,
`<div>${data['dnsName']}</div>`
];
rows.push(row);
// ip addreses
row = [
`<div>${this.translations.tailscaleIP}</div>`,
`<div>${data['ipAddresses'].join('<br>')}</div>`
];
rows.push(row);
// exit node
row = [
`<div>${this.translations.exitNode}</div>`,
`<div>${data['exitNode']}</div>`
];
rows.push(row);
// peers
row = [
`<div>${this.translations.peers}</div>`,
`<div>${data['peerCount']}</div>`
];
rows.push(row);
super.updateTable('tailscaleStatusTable', rows);
}
displayError(message) {
$('#tailscaleStatusTable').empty().append(
$(`<div class="error-message">${message}</div>`)
);
}
}