security/tailscale: Allow use of an exit node (#4438)

This commit is contained in:
Ben Smithurst
2025-01-27 16:39:36 +01:00
committed by GitHub
parent 4dce2f33d9
commit 068f22ce54
6 changed files with 52 additions and 0 deletions
@@ -30,6 +30,12 @@
<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>
@@ -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;
}
}
@@ -23,6 +23,7 @@
<default>0</default>
<Required>Y</Required>
</advertiseExitNode>
<useExitNode type=".\ExitNodeField"/>
<acceptSubnetRoutes type="BooleanField">
<default>0</default>
<Required>Y</Required>
@@ -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');
});
@@ -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>');
});
@@ -16,6 +16,9 @@ tailscaled_port="{{ OPNsense.tailscale.settings.listenPort }}"
{% 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 %}