From cadc03aebd3dd2a701428089dc3acf532e0de4b6 Mon Sep 17 00:00:00 2001 From: Sam Sheridan Date: Fri, 6 Dec 2024 15:48:34 +0000 Subject: [PATCH] add tailscale plugin (#4386) --- security/tailscale/Makefile | 8 ++ security/tailscale/pkg-descr | 11 ++ .../src/etc/inc/plugins.inc.d/tailscale.inc | 65 +++++++++ .../Api/AuthenticationController.php | 39 ++++++ .../Tailscale/Api/ServiceController.php | 45 ++++++ .../Tailscale/Api/SettingsController.php | 77 ++++++++++ .../Tailscale/Api/StatusController.php | 65 +++++++++ .../Tailscale/AuthenticationController.php | 38 +++++ .../OPNsense/Tailscale/SettingsController.php | 39 ++++++ .../OPNsense/Tailscale/StatusController.php | 37 +++++ .../Tailscale/forms/authentication.xml | 14 ++ .../Tailscale/forms/dialogSubnet4.xml | 14 ++ .../OPNsense/Tailscale/forms/settings.xml | 32 +++++ .../app/models/OPNsense/Tailscale/ACL/ACL.xml | 9 ++ .../OPNsense/Tailscale/Authentication.php | 35 +++++ .../OPNsense/Tailscale/Authentication.xml | 14 ++ .../models/OPNsense/Tailscale/Menu/Menu.xml | 9 ++ .../models/OPNsense/Tailscale/Settings.php | 35 +++++ .../models/OPNsense/Tailscale/Settings.xml | 37 +++++ .../app/models/OPNsense/Tailscale/Status.php | 35 +++++ .../app/models/OPNsense/Tailscale/Status.xml | 6 + .../OPNsense/Tailscale/authentication.volt | 39 ++++++ .../views/OPNsense/Tailscale/settings.volt | 75 ++++++++++ .../app/views/OPNsense/Tailscale/status.volt | 132 ++++++++++++++++++ .../conf/actions.d/actions_tailscale.conf | 47 +++++++ .../templates/OPNsense/Tailscale/+TARGETS | 1 + .../templates/OPNsense/Tailscale/rc.conf.d | 49 +++++++ 27 files changed, 1007 insertions(+) create mode 100644 security/tailscale/Makefile create mode 100644 security/tailscale/pkg-descr create mode 100644 security/tailscale/src/etc/inc/plugins.inc.d/tailscale.inc create mode 100644 security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/Api/AuthenticationController.php create mode 100644 security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/Api/ServiceController.php create mode 100644 security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/Api/SettingsController.php create mode 100644 security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/Api/StatusController.php create mode 100644 security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/AuthenticationController.php create mode 100644 security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/SettingsController.php create mode 100644 security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/StatusController.php create mode 100644 security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/forms/authentication.xml create mode 100644 security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/forms/dialogSubnet4.xml create mode 100644 security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/forms/settings.xml create mode 100644 security/tailscale/src/opnsense/mvc/app/models/OPNsense/Tailscale/ACL/ACL.xml create mode 100644 security/tailscale/src/opnsense/mvc/app/models/OPNsense/Tailscale/Authentication.php create mode 100644 security/tailscale/src/opnsense/mvc/app/models/OPNsense/Tailscale/Authentication.xml create mode 100644 security/tailscale/src/opnsense/mvc/app/models/OPNsense/Tailscale/Menu/Menu.xml create mode 100644 security/tailscale/src/opnsense/mvc/app/models/OPNsense/Tailscale/Settings.php create mode 100644 security/tailscale/src/opnsense/mvc/app/models/OPNsense/Tailscale/Settings.xml create mode 100644 security/tailscale/src/opnsense/mvc/app/models/OPNsense/Tailscale/Status.php create mode 100644 security/tailscale/src/opnsense/mvc/app/models/OPNsense/Tailscale/Status.xml create mode 100644 security/tailscale/src/opnsense/mvc/app/views/OPNsense/Tailscale/authentication.volt create mode 100644 security/tailscale/src/opnsense/mvc/app/views/OPNsense/Tailscale/settings.volt create mode 100644 security/tailscale/src/opnsense/mvc/app/views/OPNsense/Tailscale/status.volt create mode 100644 security/tailscale/src/opnsense/service/conf/actions.d/actions_tailscale.conf create mode 100644 security/tailscale/src/opnsense/service/templates/OPNsense/Tailscale/+TARGETS create mode 100644 security/tailscale/src/opnsense/service/templates/OPNsense/Tailscale/rc.conf.d diff --git a/security/tailscale/Makefile b/security/tailscale/Makefile new file mode 100644 index 000000000..b995e68c9 --- /dev/null +++ b/security/tailscale/Makefile @@ -0,0 +1,8 @@ +PLUGIN_NAME= tailscale +PLUGIN_VERSION= 1.0.d +PLUGIN_DEVEL= yes +PLUGIN_COMMENT= Tailscale makes creating software-defined networks easy +PLUGIN_DEPENDS= tailscale +PLUGIN_MAINTAINER= sam@sheridan.co.uk + +.include "../../Mk/plugins.mk" diff --git a/security/tailscale/pkg-descr b/security/tailscale/pkg-descr new file mode 100644 index 000000000..9befef3b6 --- /dev/null +++ b/security/tailscale/pkg-descr @@ -0,0 +1,11 @@ +Tailscale is a mesh VPN alternative, based on WireGuard, that connects your +computers, databases, and services together securely without any proxies. + +https://tailscale.com/ + +Plugin Changelog +================ + +1.0 + +* initial development release diff --git a/security/tailscale/src/etc/inc/plugins.inc.d/tailscale.inc b/security/tailscale/src/etc/inc/plugins.inc.d/tailscale.inc new file mode 100644 index 000000000..dc1bf6b0b --- /dev/null +++ b/security/tailscale/src/etc/inc/plugins.inc.d/tailscale.inc @@ -0,0 +1,65 @@ +enabled); +} + +function tailscale_services() +{ + $services = []; + + if (!tailscale_enabled()) { + return $services; + } + + $services[] = [ + 'description' => gettext('Tailscale'), + 'pidfile' => '/var/run/tailscaled.pid', + 'configd' => [ + 'restart' => ['tailscale restart'], + 'start' => ['tailscale start'], + 'stop' => ['tailscale stop'], + ], + 'name' => 'tailscale', + ]; + + return $services; +} + +function tailscale_devices() +{ + return [[ + 'pattern' => '^tailscale0$', + 'configurable' => false, + 'spoofmac' => false, + 'volatile' => true, + ]]; +} + diff --git a/security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/Api/AuthenticationController.php b/security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/Api/AuthenticationController.php new file mode 100644 index 000000000..0161b75b6 --- /dev/null +++ b/security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/Api/AuthenticationController.php @@ -0,0 +1,39 @@ +searchBase("subnets.subnet4", null, "subnet"); + } + public function setSubnetAction($uuid) + { + return $this->setBase("subnet4", "subnets.subnet4", $uuid); + } + public function addSubnetAction() + { + return $this->addBase("subnet4", "subnets.subnet4"); + } + + public function getSubnetAction($uuid = null) + { + return $this->getBase("subnet4", "subnets.subnet4", $uuid); + } + + public function delSubnetAction($uuid) + { + return $this->delBase("subnets.subnet4", $uuid); + } + + public function reloadAction() + { + $mdl = $this->getModel(); + $enabled = $mdl->enabled->__toString() === '1'; + $response = $this->toggleTailScaleService($enabled); + return ['result ' => $response]; + } + + private function toggleTailscaleService($enabled) + { + $backend = new Backend(); + $backend->configdRun('template reload OPNsense/Tailscale'); + $action = $enabled ? 'start' : 'stop'; + return trim($backend->configdRun('tailscale ' . $action)); + } +} diff --git a/security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/Api/StatusController.php b/security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/Api/StatusController.php new file mode 100644 index 000000000..f3688ac9c --- /dev/null +++ b/security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/Api/StatusController.php @@ -0,0 +1,65 @@ +configdRun('tailscale tailscale-status')), true); + if ($response !== null) { + return $response; + } + return ['error' => 'Unable to determine Tailscale status, is the service running?']; + } + + public function ipAction() + { + $response = trim((new Backend())->configdRun('tailscale tailscale-ip')); + return ['result' => $response]; + } + + public function netAction() + { + $response = trim((new Backend())->configdRun('tailscale tailscale-netcheck')); + return ['result' => $response]; + } + + private function isJson($string) { + return is_string($string) + && (is_object(json_decode($string)) + || is_array(json_decode($string))); + } +} diff --git a/security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/AuthenticationController.php b/security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/AuthenticationController.php new file mode 100644 index 000000000..98b9397b2 --- /dev/null +++ b/security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/AuthenticationController.php @@ -0,0 +1,38 @@ +view->pick('OPNsense/Tailscale/authentication'); + $this->view->authenticationForm = $this->getForm('authentication'); + } +} diff --git a/security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/SettingsController.php b/security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/SettingsController.php new file mode 100644 index 000000000..0170af957 --- /dev/null +++ b/security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/SettingsController.php @@ -0,0 +1,39 @@ +view->pick('OPNsense/Tailscale/settings'); + $this->view->settingsForm = $this->getForm('settings'); + $this->view->formDialogSubnet = $this->getForm("dialogSubnet4"); + } +} diff --git a/security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/StatusController.php b/security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/StatusController.php new file mode 100644 index 000000000..707b41dd3 --- /dev/null +++ b/security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/StatusController.php @@ -0,0 +1,37 @@ +view->pick('OPNsense/Tailscale/status'); + } +} diff --git a/security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/forms/authentication.xml b/security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/forms/authentication.xml new file mode 100644 index 000000000..b2bcae8b3 --- /dev/null +++ b/security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/forms/authentication.xml @@ -0,0 +1,14 @@ +
+ + authentication.loginServer + + text + Base URL of login (control) server. + + + authentication.preAuthKey + + text + Use a non-reusable auth key and disable expiration + +
diff --git a/security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/forms/dialogSubnet4.xml b/security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/forms/dialogSubnet4.xml new file mode 100644 index 000000000..ce35d9f3d --- /dev/null +++ b/security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/forms/dialogSubnet4.xml @@ -0,0 +1,14 @@ +
+ + subnet4.subnet + + text + Subnet to use, should be large enough to hold the specified pools and reservations + + + subnet4.description + + text + You may enter a description here for your reference (not parsed). + +
diff --git a/security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/forms/settings.xml b/security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/forms/settings.xml new file mode 100644 index 000000000..6b35707ae --- /dev/null +++ b/security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/forms/settings.xml @@ -0,0 +1,32 @@ +
+ + settings.enabled + + checkbox + This will activate the Tailscale service. + + + settings.listenPort + + text + UDP port to listen on for Wireguard and peer-to-peer traffic. + + + settings.acceptDNS + + checkbox + Accept DNS configuration from the control server. + + + settings.advertiseExitNode + + checkbox + Offer to be an exit node for outbound internet traffic from the Tailscale network. + + + settings.acceptSubnetRoutes + + checkbox + Accept subnet routes that other nodes advertise. + +
diff --git a/security/tailscale/src/opnsense/mvc/app/models/OPNsense/Tailscale/ACL/ACL.xml b/security/tailscale/src/opnsense/mvc/app/models/OPNsense/Tailscale/ACL/ACL.xml new file mode 100644 index 000000000..d0f6a9138 --- /dev/null +++ b/security/tailscale/src/opnsense/mvc/app/models/OPNsense/Tailscale/ACL/ACL.xml @@ -0,0 +1,9 @@ + + + Tailscale + + ui/tailscale/* + api/tailscale/* + + + diff --git a/security/tailscale/src/opnsense/mvc/app/models/OPNsense/Tailscale/Authentication.php b/security/tailscale/src/opnsense/mvc/app/models/OPNsense/Tailscale/Authentication.php new file mode 100644 index 000000000..eb9ee4d7f --- /dev/null +++ b/security/tailscale/src/opnsense/mvc/app/models/OPNsense/Tailscale/Authentication.php @@ -0,0 +1,35 @@ + + //OPNsense/tailscale/authentication + Tailscale authentication settings + + + https://controlplane.tailscale.com + Y + Please enter a valid URL + + + Y + + + diff --git a/security/tailscale/src/opnsense/mvc/app/models/OPNsense/Tailscale/Menu/Menu.xml b/security/tailscale/src/opnsense/mvc/app/models/OPNsense/Tailscale/Menu/Menu.xml new file mode 100644 index 000000000..b28d1794f --- /dev/null +++ b/security/tailscale/src/opnsense/mvc/app/models/OPNsense/Tailscale/Menu/Menu.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/security/tailscale/src/opnsense/mvc/app/models/OPNsense/Tailscale/Settings.php b/security/tailscale/src/opnsense/mvc/app/models/OPNsense/Tailscale/Settings.php new file mode 100644 index 000000000..791f600e2 --- /dev/null +++ b/security/tailscale/src/opnsense/mvc/app/models/OPNsense/Tailscale/Settings.php @@ -0,0 +1,35 @@ + + //OPNsense/tailscale/settings + Tailscale general settings + + + 0 + Y + + + 41641 + Y + + + 1 + Y + + + 0 + Y + + + 0 + Y + + + + + Y + ipv4 + Y + Y + + + + + + diff --git a/security/tailscale/src/opnsense/mvc/app/models/OPNsense/Tailscale/Status.php b/security/tailscale/src/opnsense/mvc/app/models/OPNsense/Tailscale/Status.php new file mode 100644 index 000000000..b70a04b95 --- /dev/null +++ b/security/tailscale/src/opnsense/mvc/app/models/OPNsense/Tailscale/Status.php @@ -0,0 +1,35 @@ + + //OPNsense/tailscale/status + Tailscale status settings + + + diff --git a/security/tailscale/src/opnsense/mvc/app/views/OPNsense/Tailscale/authentication.volt b/security/tailscale/src/opnsense/mvc/app/views/OPNsense/Tailscale/authentication.volt new file mode 100644 index 000000000..52cb55b6a --- /dev/null +++ b/security/tailscale/src/opnsense/mvc/app/views/OPNsense/Tailscale/authentication.volt @@ -0,0 +1,39 @@ + +
+ {{ partial("layout_partials/base_form",['fields':authenticationForm,'id':'frmAuthentication']) }} +
+
+
+
+
+ + +

+
+
+
diff --git a/security/tailscale/src/opnsense/mvc/app/views/OPNsense/Tailscale/settings.volt b/security/tailscale/src/opnsense/mvc/app/views/OPNsense/Tailscale/settings.volt new file mode 100644 index 000000000..aee1bd69f --- /dev/null +++ b/security/tailscale/src/opnsense/mvc/app/views/OPNsense/Tailscale/settings.volt @@ -0,0 +1,75 @@ + + +
+
+ {{ partial("layout_partials/base_form",['fields':settingsForm,'id':'frmSettings'])}} +
+
+ + + + + + + + + + + + + + + + + +
{{ lang._('ID') }}{{ lang._('Subnet') }}{{ lang._('Description') }}{{ lang._('Commands') }}
+ +
+
+
+
+
+
+
+ +

+
+
+
+ +{{ partial("layout_partials/base_dialog",['fields':formDialogSubnet,'id':'DialogSubnet','label':lang._('Edit Subnet')])}} diff --git a/security/tailscale/src/opnsense/mvc/app/views/OPNsense/Tailscale/status.volt b/security/tailscale/src/opnsense/mvc/app/views/OPNsense/Tailscale/status.volt new file mode 100644 index 000000000..b911f1393 --- /dev/null +++ b/security/tailscale/src/opnsense/mvc/app/views/OPNsense/Tailscale/status.volt @@ -0,0 +1,132 @@ + + + + + +
+
+
+ + + + + + + + + +
{{ lang._('Name') }}{{ lang._('Value') }}
+
+
+ +
+ + + + + + + + + + + +
{{ lang._('Name') }}{{ lang._('Tailscale IPs') }}{{ lang._('Last Seen') }}{{ lang._('OS') }}
+
+ +
+
+
diff --git a/security/tailscale/src/opnsense/service/conf/actions.d/actions_tailscale.conf b/security/tailscale/src/opnsense/service/conf/actions.d/actions_tailscale.conf new file mode 100644 index 000000000..e5d6e3b3d --- /dev/null +++ b/security/tailscale/src/opnsense/service/conf/actions.d/actions_tailscale.conf @@ -0,0 +1,47 @@ +[start] +command:/usr/local/etc/rc.d/tailscaled start +type: script +message: starting tailscale service + +[stop] +command:/usr/local/etc/rc.d/tailscaled stop; exit 0 +type: script +message: stopping tailscale service + +[status] +command:/usr/local/etc/rc.d/tailscaled status; exit 0 +type: script_output +message: tailscaled status + +[restart] +command:/usr/local/etc/rc.d/tailscaled restart +type: script +message: restarting tailscale services + +[reload] +command:/usr/local/etc/rc.d/tailscaled reload +type: script +message: reload tailscale configuration + +[tailscale-status] +command:/usr/local/bin/tailscale status --json; exit 0 +type:script_output +message: request tailscale status + +[tailscale-ip] +command:/usr/local/bin/tailscale ip; exit 0 +type:script_output +message: request tailscale ip + +[tailscale-version] +command:/usr/local/bin/tailscale version; exit 0 +type:script_output +message: request tailscale version + +[tailscale-netcheck] +command:/usr/local/bin/tailscale netcheck; exit 0 +type:script_output +message: request tailscale netcheck + + + diff --git a/security/tailscale/src/opnsense/service/templates/OPNsense/Tailscale/+TARGETS b/security/tailscale/src/opnsense/service/templates/OPNsense/Tailscale/+TARGETS new file mode 100644 index 000000000..e9380f1c2 --- /dev/null +++ b/security/tailscale/src/opnsense/service/templates/OPNsense/Tailscale/+TARGETS @@ -0,0 +1 @@ +rc.conf.d:/etc/rc.conf.d/tailscaled diff --git a/security/tailscale/src/opnsense/service/templates/OPNsense/Tailscale/rc.conf.d b/security/tailscale/src/opnsense/service/templates/OPNsense/Tailscale/rc.conf.d new file mode 100644 index 000000000..6713fa87e --- /dev/null +++ b/security/tailscale/src/opnsense/service/templates/OPNsense/Tailscale/rc.conf.d @@ -0,0 +1,49 @@ +# DO NOT EDIT +# THIS FILE IS AUTOMATICALLY GENERATED - ANY CHANGES WILL BE OVERWRITTEN +# +{% if not helpers.empty('OPNsense.tailscale.settings.enabled') %} +tailscaled_enable="YES" +# Uncommenting the below breaks being able to access subnets +# see - https://github.com/tailscale/tailscale/issues/5573#issuecomment-1584695981 +# tailscaled_env="TS_DEBUG_NETSTACK_SUBNETS=0" +{% if helpers.exists('OPNsense.tailscale.settings.listenPort') %} +tailscaled_port="{{ OPNsense.tailscale.settings.listenPort }}" +{% endif %} +{% set up_args = [] %} +{% 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.acceptSubnetRoutes') and OPNsense.tailscale.settings.acceptSubnetRoutes|default("0") == "1" %} +{% do up_args.append("--accept-routes") %} +{% else %} +{% do up_args.append("--accept-routes=false") %} +{% endif %} +{% if helpers.exists('OPNsense.tailscale.settings.acceptDNS') and OPNsense.tailscale.settings.acceptDNS|default("0") == "1" %} +{% do up_args.append("--accept-dns") %} +{% else %} +{% do up_args.append("--accept-dns=false") %} +{% endif %} +{% if helpers.exists('OPNsense.tailscale.authentication.loginServer') %} +{% do up_args.append("--login-server=" + OPNsense.tailscale.authentication.loginServer) %} +{% endif %} +{% if helpers.exists('OPNsense.tailscale.authentication.preAuthKey') %} +{% do up_args.append("--auth-key=" + OPNsense.tailscale.authentication.preAuthKey) %} +{% endif %} +{# loop through subnets to build list #} +{% if helpers.exists('OPNsense.tailscale.settings.subnets.subnet4') %} +{% set subnets = [] %} +{% for subnet_list in helpers.toList('OPNsense.tailscale.settings.subnets.subnet4') %} +{% do subnets.append(subnet_list.subnet) %} +{% endfor %} +{% set subnetString = subnets|join(',') %} +{% do up_args.append("--advertise-routes=" + subnetString) %} +{% else %} +{% do up_args.append("--advertise-routes=") %} +{% endif %} +tailscaled_up_args="{{ up_args|join(' ') }}" +{% else %} +tailscaled_enable=NO +{% endif %} +