Merge remote-tracking branch 'origin/master' into vnstat-multi-interface

This commit is contained in:
Ben Smithurst 2025-01-27 19:05:53 +00:00
commit 027ec7932c
9 changed files with 106 additions and 5 deletions

View file

@ -1,5 +1,5 @@
PLUGIN_NAME= tailscale
PLUGIN_VERSION= 1.1
PLUGIN_VERSION= 1.2
PLUGIN_COMMENT= VPN mesh securely connecting clients using WireGuard
PLUGIN_DEPENDS= tailscale
PLUGIN_MAINTAINER= sam@sheridan.uk

View file

@ -6,6 +6,14 @@ https://tailscale.com/
Plugin Changelog
================
1.2
* add option to allow Tailscale to manage SSH connections
* add option to disable SNAT routing (experimental)
* add login timeout (10s default) for when login server is unavailable causing OPNsense to hang on boot (contributed by Ben Smithurst)
* add exit node option (contributed by Ben Smithurst)
* fix dashboard widget always showing exit node as no
1.1
* add dashboard widget

View file

@ -5,10 +5,17 @@
<type>checkbox</type>
<help>This will activate the Tailscale service.</help>
</field>
<field>
<id>settings.loginTimeout</id>
<label>Login timeout</label>
<type>text</type>
<advanced>true</advanced>
<help>Maximum time to wait for successful login, in seconds. Set to 0 to wait indefinitely, however this may prevent OPNsense booting completely if the Tailscale control plane is unavailable. Default is 10 seconds.</help>
</field>
<field>
<id>settings.listenPort</id>
<label>Listen Port</label>
<type>text</type>
<type>text</type>
<help>UDP port to listen on for WireGuard and peer-to-peer traffic.</help>
</field>
<field>
@ -23,10 +30,30 @@
<type>checkbox</type>
<help>Offer to be an exit node for outbound internet traffic from the Tailscale network.</help>
</field>
<field>
<id>settings.useExitNode</id>
<label>Use Exit Node</label>
<type>dropdown</type>
<help>Route traffic to the specified exit node. Note that this only affects traffic routed into your Tailscale interface, which you will have to configure separately using firewall rules and hybrid outbound NAT rules.</help>
</field>
<field>
<id>settings.acceptSubnetRoutes</id>
<label>Accept Subnet Routes</label>
<type>checkbox</type>
<help>Accept subnet routes that other nodes advertise.</help>
</field>
<field>
<id>settings.enableSSH</id>
<label>Enable SSH</label>
<advanced>true</advanced>
<type>checkbox</type>
<help>Allow Tailscale to manage SSH connections in your tailnet.</help>
</field>
<field>
<id>settings.disableSNAT</id>
<label>Disable SNAT</label>
<advanced>true</advanced>
<type>checkbox</type>
<help>Disable source NAT to disable subnet routing (experimental).</help>
</field>
</form>

View file

@ -0,0 +1,31 @@
<?php
namespace OPNsense\Tailscale\FieldTypes;
use OPNsense\Base\FieldTypes\BaseListField;
use OPNsense\Core\Backend;
class ExitNodeField extends BaseListField
{
private static array $internalCacheOptionList = [];
protected $internalIsContainer = false;
protected function actionPostLoadingEvent()
{
if (empty(self::$internalCacheOptionList)) {
$response = json_decode(trim((new Backend())->configdRun('tailscale tailscale-status')), true);
$exitNodes = ['' => gettext('None')];
if (is_array($response) && array_key_exists('Peer', $response) && is_array($response['Peer'])) {
foreach ($response['Peer'] as $peer) {
if ($peer['ExitNodeOption']) {
$exitNodes[$peer['TailscaleIPs'][0]] = $peer['HostName'];
}
}
}
self::$internalCacheOptionList = $exitNodes;
}
$this->internalOptionList = self::$internalCacheOptionList;
}
}

View file

@ -1,11 +1,16 @@
<model>
<mount>//OPNsense/tailscale/settings</mount>
<description>Tailscale general settings</description>
<version>1.0.0</version>
<items>
<enabled type="BooleanField">
<default>0</default>
<Required>Y</Required>
</enabled>
<loginTimeout type="IntegerField">
<default>10</default>
<Required>Y</Required>
</loginTimeout>
<listenPort type="PortField">
<default>41641</default>
<Required>Y</Required>
@ -18,10 +23,19 @@
<default>0</default>
<Required>Y</Required>
</advertiseExitNode>
<useExitNode type=".\ExitNodeField"/>
<acceptSubnetRoutes type="BooleanField">
<default>0</default>
<Required>Y</Required>
</acceptSubnetRoutes>
<enableSSH type="BooleanField">
<default>0</default>
<Required>Y</Required>
</enableSSH>
<disableSNAT type="BooleanField">
<default>0</default>
<Required>Y</Required>
</disableSNAT>
<subnets>
<subnet4 type="ArrayField">
<subnet type="NetworkField">

View file

@ -1,6 +1,7 @@
<script type="text/javascript">
$( document ).ready(function() {
mapDataToFormUI({'frmSettings':"/api/tailscale/settings/get"}).done(function(data) {
$('.selectpicker').selectpicker('refresh');
updateServiceControlUI('tailscale');
});

View file

@ -76,6 +76,16 @@
return true;
}
if (key == 'ExitNodeStatus') {
var newValue = value.TailscaleIPs[0];
if (value.Online) {
newValue += ' (online)';
} else {
newValue += ' (offline)';
}
value = newValue;
}
$('#statusList > tbody').append('<tr><td>' + key + '</td>' +
'<td>' + value + '</td></tr>');
});

View file

@ -3,18 +3,23 @@
#
{% if not helpers.empty('OPNsense.tailscale.settings.enabled') %}
tailscaled_enable="YES"
# Uncommenting the below breaks being able to access subnets
{% if helpers.exists('OPNsense.tailscale.settings.disableSNAT') and OPNsense.tailscale.settings.disableSNAT|default("0") == "1" %}
# see - https://github.com/tailscale/tailscale/issues/5573#issuecomment-1584695981
# tailscaled_env="TS_DEBUG_NETSTACK_SUBNETS=0"
tailscaled_env="TS_DEBUG_NETSTACK_SUBNETS=0"
{% endif %}
{% if helpers.exists('OPNsense.tailscale.settings.listenPort') %}
tailscaled_port="{{ OPNsense.tailscale.settings.listenPort }}"
{% endif %}
{% set up_args = [] %}
{% do up_args.append("--timeout=" + OPNsense.tailscale.settings.loginTimeout + "s") %}
{% if helpers.exists('OPNsense.tailscale.settings.advertiseExitNode') and OPNsense.tailscale.settings.advertiseExitNode|default("0") == "1" %}
{% do up_args.append("--advertise-exit-node") %}
{% else %}
{% do up_args.append("--advertise-exit-node=false") %}
{% endif %}
{% if helpers.exists('OPNsense.tailscale.settings.useExitNode') %}
{% do up_args.append("--exit-node=" + OPNsense.tailscale.settings.useExitNode) %}
{% endif %}
{% if helpers.exists('OPNsense.tailscale.settings.acceptSubnetRoutes') and OPNsense.tailscale.settings.acceptSubnetRoutes|default("0") == "1" %}
{% do up_args.append("--accept-routes") %}
{% else %}
@ -25,6 +30,11 @@ tailscaled_port="{{ OPNsense.tailscale.settings.listenPort }}"
{% else %}
{% do up_args.append("--accept-dns=false") %}
{% endif %}
{% if helpers.exists('OPNsense.tailscale.settings.enableSSH') and OPNsense.tailscale.settings.enableSSH|default("0") == "1" %}
{% do up_args.append("--ssh=true") %}
{% else %}
{% do up_args.append("--ssh=false") %}
{% endif %}
{% if helpers.exists('OPNsense.tailscale.authentication.loginServer') %}
{% do up_args.append("--login-server=" + OPNsense.tailscale.authentication.loginServer) %}
{% endif %}

View file

@ -78,7 +78,7 @@ export default class Tailscale extends BaseTableWidget {
result['online'] = (data.Self.Online === true) ?
this.translations.yes : this.translations.no;
result['exitNode'] = (data.Self.ExitNode === true) ?
result['exitNode'] = (data.Self.ExitNodeOption === true) ?
this.translations.yes : this.translations.no;
result['peerCount'] = Object.keys(data.Peer).length;