From 9ad2ce50e3cff65e1277123d8e189fb2f5ff1251 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Sat, 16 Mar 2024 14:03:06 -0500 Subject: [PATCH 1/5] Start ZeroTier One integration Starting the scaffolding of controlling the ZeroTier One Windows service to connect to a network. --- LANCommander.SDK/Enums/VPNStatus.cs | 12 ++++ LANCommander.SDK/Enums/VPNType.cs | 11 ++++ LANCommander.SDK/VPN/Clients/IVPNClient.cs | 13 ++++ .../VPN/Clients/ZeroTierClient.cs | 46 ++++++++++++++ .../VPN/Configurations/VPNConfiguration.cs | 13 ++++ .../Configurations/ZeroTierConfiguration.cs | 12 ++++ LANCommander.SDK/VPN/ConnectionStatus.cs | 12 ++++ .../Exceptions/ClientConnectionException.cs | 11 ++++ .../ClientInitializationException.cs | 11 ++++ .../ZeroTier/ServiceNotInstalledException.cs | 11 ++++ .../Models/ZeroTier/LeaveNetworkRequest.cs | 11 ++++ .../Models/ZeroTier/LeaveNetworkResponse.cs | 11 ++++ .../VPN/Models/ZeroTier/NodeStatusResponse.cs | 13 ++++ .../ZeroTier/UpdateNetworkSettingsRequest.cs | 14 +++++ .../ZeroTier/UpdateNetworkSettingsResponse.cs | 10 +++ LANCommander.SDK/VPNService.cs | 63 +++++++++++++++++++ LANCommander/Controllers/Api/VPNController.cs | 26 ++++++++ 17 files changed, 300 insertions(+) create mode 100644 LANCommander.SDK/Enums/VPNStatus.cs create mode 100644 LANCommander.SDK/Enums/VPNType.cs create mode 100644 LANCommander.SDK/VPN/Clients/IVPNClient.cs create mode 100644 LANCommander.SDK/VPN/Clients/ZeroTierClient.cs create mode 100644 LANCommander.SDK/VPN/Configurations/VPNConfiguration.cs create mode 100644 LANCommander.SDK/VPN/Configurations/ZeroTierConfiguration.cs create mode 100644 LANCommander.SDK/VPN/ConnectionStatus.cs create mode 100644 LANCommander.SDK/VPN/Exceptions/ClientConnectionException.cs create mode 100644 LANCommander.SDK/VPN/Exceptions/ClientInitializationException.cs create mode 100644 LANCommander.SDK/VPN/Exceptions/ZeroTier/ServiceNotInstalledException.cs create mode 100644 LANCommander.SDK/VPN/Models/ZeroTier/LeaveNetworkRequest.cs create mode 100644 LANCommander.SDK/VPN/Models/ZeroTier/LeaveNetworkResponse.cs create mode 100644 LANCommander.SDK/VPN/Models/ZeroTier/NodeStatusResponse.cs create mode 100644 LANCommander.SDK/VPN/Models/ZeroTier/UpdateNetworkSettingsRequest.cs create mode 100644 LANCommander.SDK/VPN/Models/ZeroTier/UpdateNetworkSettingsResponse.cs create mode 100644 LANCommander.SDK/VPNService.cs create mode 100644 LANCommander/Controllers/Api/VPNController.cs diff --git a/LANCommander.SDK/Enums/VPNStatus.cs b/LANCommander.SDK/Enums/VPNStatus.cs new file mode 100644 index 00000000..d6ea99f4 --- /dev/null +++ b/LANCommander.SDK/Enums/VPNStatus.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LANCommander.SDK.Enums +{ + public enum VPNStatus + { + Disconnected, + Connected, + } +} diff --git a/LANCommander.SDK/Enums/VPNType.cs b/LANCommander.SDK/Enums/VPNType.cs new file mode 100644 index 00000000..0b665e77 --- /dev/null +++ b/LANCommander.SDK/Enums/VPNType.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LANCommander.SDK.Enums +{ + public enum VPNType + { + ZeroTier + } +} diff --git a/LANCommander.SDK/VPN/Clients/IVPNClient.cs b/LANCommander.SDK/VPN/Clients/IVPNClient.cs new file mode 100644 index 00000000..54ccbf20 --- /dev/null +++ b/LANCommander.SDK/VPN/Clients/IVPNClient.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LANCommander.SDK.VPN.Clients +{ + public interface IVPNClient + { + bool Connect(); + bool Disconnect(); + VPNConnectionStatus GetStatus(); + } +} diff --git a/LANCommander.SDK/VPN/Clients/ZeroTierClient.cs b/LANCommander.SDK/VPN/Clients/ZeroTierClient.cs new file mode 100644 index 00000000..1ff3bc92 --- /dev/null +++ b/LANCommander.SDK/VPN/Clients/ZeroTierClient.cs @@ -0,0 +1,46 @@ +using LANCommander.SDK.VPN.Configurations; +using LANCommander.SDK.VPN.Exceptions; +using RestSharp; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace LANCommander.SDK.VPN.Clients +{ + public class ZeroTierClient : IVPNClient + { + private ZeroTierConfiguration Configuration; + private RestClient ApiClient; + private string Token; + private const string BaseUrl = "http://localhost:9993"; + + public ZeroTierClient(ZeroTierConfiguration configuration) { + Configuration = configuration; + + ApiClient = new RestClient(BaseUrl); + + var tokenPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "ZeroTier", "One", "authtoken.secret"); + + if (File.Exists(tokenPath)) + Token = File.ReadAllText(tokenPath); + else + throw new ClientInitializationException("Could not read the token"); + } + + public bool Connect() + { + throw new NotImplementedException(); + } + + public bool Disconnect() + { + throw new NotImplementedException(); + } + + public VPNConnectionStatus GetStatus() + { + throw new NotImplementedException(); + } + } +} diff --git a/LANCommander.SDK/VPN/Configurations/VPNConfiguration.cs b/LANCommander.SDK/VPN/Configurations/VPNConfiguration.cs new file mode 100644 index 00000000..1310f3d1 --- /dev/null +++ b/LANCommander.SDK/VPN/Configurations/VPNConfiguration.cs @@ -0,0 +1,13 @@ +using LANCommander.SDK.Enums; +using System; +using System.Collections.Generic; +using System.Text; + +namespace LANCommander.SDK.VPN.Configurations +{ + public class VPNConfiguration + { + public VPNType Type { get; set; } + public object Data { get; set; } + } +} diff --git a/LANCommander.SDK/VPN/Configurations/ZeroTierConfiguration.cs b/LANCommander.SDK/VPN/Configurations/ZeroTierConfiguration.cs new file mode 100644 index 00000000..35fd9a6b --- /dev/null +++ b/LANCommander.SDK/VPN/Configurations/ZeroTierConfiguration.cs @@ -0,0 +1,12 @@ +using LANCommander.SDK.Enums; +using System; +using System.Collections.Generic; +using System.Text; + +namespace LANCommander.SDK.VPN.Configurations +{ + public class ZeroTierConfiguration + { + public string NetworkId { get; set; } + } +} diff --git a/LANCommander.SDK/VPN/ConnectionStatus.cs b/LANCommander.SDK/VPN/ConnectionStatus.cs new file mode 100644 index 00000000..3e062465 --- /dev/null +++ b/LANCommander.SDK/VPN/ConnectionStatus.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LANCommander.SDK.VPN +{ + public class VPNConnectionStatus + { + public bool Connected { get; set; } + public string Status { get; set; } + } +} diff --git a/LANCommander.SDK/VPN/Exceptions/ClientConnectionException.cs b/LANCommander.SDK/VPN/Exceptions/ClientConnectionException.cs new file mode 100644 index 00000000..2eda92d7 --- /dev/null +++ b/LANCommander.SDK/VPN/Exceptions/ClientConnectionException.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LANCommander.SDK.VPN.Exceptions +{ + public class ClientConnectionException : Exception + { + public ClientConnectionException(string message) : base(message) { } + } +} diff --git a/LANCommander.SDK/VPN/Exceptions/ClientInitializationException.cs b/LANCommander.SDK/VPN/Exceptions/ClientInitializationException.cs new file mode 100644 index 00000000..acdd2ddc --- /dev/null +++ b/LANCommander.SDK/VPN/Exceptions/ClientInitializationException.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LANCommander.SDK.VPN.Exceptions +{ + public class ClientInitializationException : Exception + { + public ClientInitializationException(string message) : base(message) { } + } +} diff --git a/LANCommander.SDK/VPN/Exceptions/ZeroTier/ServiceNotInstalledException.cs b/LANCommander.SDK/VPN/Exceptions/ZeroTier/ServiceNotInstalledException.cs new file mode 100644 index 00000000..0b03b1ef --- /dev/null +++ b/LANCommander.SDK/VPN/Exceptions/ZeroTier/ServiceNotInstalledException.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LANCommander.SDK.VPN.Exceptions.ZeroTier +{ + public class ServiceNotInstalledException : Exception + { + public ServiceNotInstalledException(string message) : base(message) { } + } +} diff --git a/LANCommander.SDK/VPN/Models/ZeroTier/LeaveNetworkRequest.cs b/LANCommander.SDK/VPN/Models/ZeroTier/LeaveNetworkRequest.cs new file mode 100644 index 00000000..a7c78405 --- /dev/null +++ b/LANCommander.SDK/VPN/Models/ZeroTier/LeaveNetworkRequest.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LANCommander.SDK.VPN.Models.ZeroTier +{ + public class LeaveNetworkRequest + { + public string NetworkId { get; set; } + } +} diff --git a/LANCommander.SDK/VPN/Models/ZeroTier/LeaveNetworkResponse.cs b/LANCommander.SDK/VPN/Models/ZeroTier/LeaveNetworkResponse.cs new file mode 100644 index 00000000..5ebd213b --- /dev/null +++ b/LANCommander.SDK/VPN/Models/ZeroTier/LeaveNetworkResponse.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LANCommander.SDK.VPN.Models.ZeroTier +{ + public class LeaveNetworkResponse + { + public bool Result { get; set; } + } +} diff --git a/LANCommander.SDK/VPN/Models/ZeroTier/NodeStatusResponse.cs b/LANCommander.SDK/VPN/Models/ZeroTier/NodeStatusResponse.cs new file mode 100644 index 00000000..0c452785 --- /dev/null +++ b/LANCommander.SDK/VPN/Models/ZeroTier/NodeStatusResponse.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LANCommander.SDK.VPN.Models.ZeroTier +{ + public class NodeStatusResponse + { + public string Address { get; set; } + public bool Online { get; set; } + public bool TcpFallbackActive { get; set; } + } +} diff --git a/LANCommander.SDK/VPN/Models/ZeroTier/UpdateNetworkSettingsRequest.cs b/LANCommander.SDK/VPN/Models/ZeroTier/UpdateNetworkSettingsRequest.cs new file mode 100644 index 00000000..040642e2 --- /dev/null +++ b/LANCommander.SDK/VPN/Models/ZeroTier/UpdateNetworkSettingsRequest.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LANCommander.SDK.VPN.Models.ZeroTier +{ + public class UpdateNetworkSettingsRequest + { + public bool AllowDNS { get; set; } + public bool AllowDefault { get; set; } + public bool AllowManaged { get; set; } + public bool AllowGlobal { get; set; } + } +} diff --git a/LANCommander.SDK/VPN/Models/ZeroTier/UpdateNetworkSettingsResponse.cs b/LANCommander.SDK/VPN/Models/ZeroTier/UpdateNetworkSettingsResponse.cs new file mode 100644 index 00000000..14fb2891 --- /dev/null +++ b/LANCommander.SDK/VPN/Models/ZeroTier/UpdateNetworkSettingsResponse.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LANCommander.SDK.VPN.Models.ZeroTier +{ + internal class UpdateNetworkSettingsResponse + { + } +} diff --git a/LANCommander.SDK/VPNService.cs b/LANCommander.SDK/VPNService.cs new file mode 100644 index 00000000..7e20eef4 --- /dev/null +++ b/LANCommander.SDK/VPNService.cs @@ -0,0 +1,63 @@ +using LANCommander.SDK.VPN; +using LANCommander.SDK.VPN.Clients; +using LANCommander.SDK.VPN.Configurations; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace LANCommander.SDK +{ + public class VPNService + { + private readonly ILogger Logger; + private Client Client { get; set; } + + public delegate void OnConnectedHandler(object sender); + public event OnConnectedHandler OnConnected; + + public delegate void OnDisconnectedHandler(object sender); + public event OnDisconnectedHandler OnDisconnected; + + private IVPNClient VPNClient; + + public VPNService(Client client) + { + Client = client; + } + + public VPNService(Client client, ILogger logger) + { + Client = client; + Logger = logger; + } + + public void Init() + { + var configuration = Client.GetRequest("/api/VPN/Configuration"); + + switch (configuration.Type) + { + case Enums.VPNType.ZeroTier: + VPNClient = new ZeroTierClient(configuration.Data as ZeroTierConfiguration); + break; + } + } + + public bool Connect() + { + return VPNClient.Connect(); + } + + public bool Disconnect() + { + return VPNClient.Disconnect(); + } + + public VPNConnectionStatus GetStatus() + { + return VPNClient.GetStatus(); + } + } +} diff --git a/LANCommander/Controllers/Api/VPNController.cs b/LANCommander/Controllers/Api/VPNController.cs new file mode 100644 index 00000000..3a32cd34 --- /dev/null +++ b/LANCommander/Controllers/Api/VPNController.cs @@ -0,0 +1,26 @@ +using LANCommander.SDK.VPN.Configurations; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace LANCommander.Controllers.Api +{ + [Authorize(AuthenticationSchemes = "Bearer")] + [Route("api/[controller]")] + [ApiController] + public class VPNController : ControllerBase + { + public VPNController() { } + + public async Task GetConfiguration() + { + return new VPNConfiguration() + { + Type = SDK.Enums.VPNType.ZeroTier, + Data = new ZeroTierConfiguration + { + NetworkId = "" + } + }; + } + } +} From 5e018358269e913601239bda3aec00d7c0afb2cc Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Wed, 20 Mar 2024 19:19:59 -0500 Subject: [PATCH 2/5] Added configuration page in settings --- LANCommander/Models/Settings.cs | 12 ++- .../Pages/Settings/VPN/Forms/ZeroTier.razor | 27 ++++++ .../UI/Pages/Settings/VPN/Index.razor | 95 +++++++++++++++++++ 3 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 LANCommander/UI/Pages/Settings/VPN/Forms/ZeroTier.razor create mode 100644 LANCommander/UI/Pages/Settings/VPN/Index.razor diff --git a/LANCommander/Models/Settings.cs b/LANCommander/Models/Settings.cs index 2253d419..d6d5b0b5 100644 --- a/LANCommander/Models/Settings.cs +++ b/LANCommander/Models/Settings.cs @@ -1,4 +1,5 @@ -using NLog.Targets; +using LANCommander.SDK.Enums; +using NLog.Targets; namespace LANCommander.Models { @@ -23,6 +24,7 @@ namespace LANCommander.Models public LANCommanderArchiveSettings Archives { get; set; } = new LANCommanderArchiveSettings(); public LANCommanderMediaSettings Media { get; set; } = new LANCommanderMediaSettings(); public LANCommanderIPXRelaySettings IPXRelay { get; set; } = new LANCommanderIPXRelaySettings(); + public LANCommanderVPNSettings VPN { get; set; } = new LANCommanderVPNSettings(); public LANCommanderWikiSettings Wiki { get; set; } = new LANCommanderWikiSettings(); public LANCommanderUpdateSettings Update { get; set; } = new LANCommanderUpdateSettings(); public LANCommanderLogSettings Logs { get; set; } = new LANCommanderLogSettings(); @@ -90,6 +92,14 @@ namespace LANCommander.Models public bool Logging { get; set; } = false; } + public class LANCommanderVPNSettings + { + public bool Enabled { get; set; } = false; + public IEnumerable AllowedRoles { get; set; } + public VPNType Type { get; set; } = VPNType.ZeroTier; + public object Configuration { get; set; } + } + public class LANCommanderWikiSettings { public bool Enabled { get; set; } = false; diff --git a/LANCommander/UI/Pages/Settings/VPN/Forms/ZeroTier.razor b/LANCommander/UI/Pages/Settings/VPN/Forms/ZeroTier.razor new file mode 100644 index 00000000..9b77cd50 --- /dev/null +++ b/LANCommander/UI/Pages/Settings/VPN/Forms/ZeroTier.razor @@ -0,0 +1,27 @@ +@using LANCommander.SDK.VPN.Configurations + + + + + +@code { + [Parameter] public string Value { get; set; } = ""; + [Parameter] public EventCallback ValueChanged { get; set; } + + [Parameter] public ZeroTierConfiguration Configuration { get; set; } = new ZeroTierConfiguration(); + [Parameter] public EventCallback ConfigurationChanged { get; set; } + + protected override async Task OnInitializedAsync() + { + if (Configuration == null) + { + Configuration = new ZeroTierConfiguration(); + await ConfigurationChanged.InvokeAsync(Configuration); + } + } + + async Task Changed() + { + await ConfigurationChanged.InvokeAsync(Configuration); + } +} diff --git a/LANCommander/UI/Pages/Settings/VPN/Index.razor b/LANCommander/UI/Pages/Settings/VPN/Index.razor new file mode 100644 index 00000000..875d6b5c --- /dev/null +++ b/LANCommander/UI/Pages/Settings/VPN/Index.razor @@ -0,0 +1,95 @@ +@page "/Settings/VPN" +@using LANCommander.SDK.VPN.Configurations +@using Microsoft.EntityFrameworkCore +@using LANCommander.Models; +@using LANCommander.Extensions +@using LANCommander.SDK.Enums +@layout SettingsLayout +@inject SettingService SettingService +@inject IMessageService MessageService +@inject RoleManager RoleManager +@attribute [Authorize(Roles = "Administrator")] + + + +
+
+ + + + + + + + + + + + + @switch (context.VPN.Type) + { + case VPNType.ZeroTier: + + break; + } + + + + + +
+ +@code { + LANCommanderSettings Settings; + + ICollection Roles = new List(); + IEnumerable AllowedRolesTransferItems { get; set; } = new List(); + List AllowedRolesTargetKeys { get; set; } = new List(); + ICollection SelectedAllowedRoles = new List(); + + protected override async Task OnInitializedAsync() + { + Settings = SettingService.GetSettings(); + Roles = await RoleManager.Roles.ToListAsync(); + + SelectedAllowedRoles = Roles.Where(r => Settings.VPN.AllowedRoles.Contains(r.Id)).ToList(); + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + AllowedRolesTransferItems = Roles.Select(r => new TransferItem() + { + Key = r.Id.ToString(), + Title = r.Name + }); + + if (SelectedAllowedRoles != null) + AllowedRolesTargetKeys = SelectedAllowedRoles.Select(i => i.Id.ToString()).ToList(); + } + } + + async Task OnChangeAllowedRoles(TransferChangeArgs e) + { + SelectedAllowedRoles = Roles.Where(r => e.TargetKeys.Contains(r.Id.ToString())).ToList(); + + Settings.VPN.AllowedRoles = SelectedAllowedRoles.Select(r => r.Id); + } + + private void Save() + { + try + { + SettingService.SaveSettings(Settings); + MessageService.Success("Settings saved!"); + } + catch + { + MessageService.Error("An unknown error occurred."); + } + } +} From 716fc98f8a136b3731846d7b4275c735fe8db62d Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Wed, 20 Mar 2024 19:20:40 -0500 Subject: [PATCH 3/5] Change initial integer value of VPN type --- LANCommander.SDK/Enums/VPNType.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LANCommander.SDK/Enums/VPNType.cs b/LANCommander.SDK/Enums/VPNType.cs index 0b665e77..7dbad984 100644 --- a/LANCommander.SDK/Enums/VPNType.cs +++ b/LANCommander.SDK/Enums/VPNType.cs @@ -6,6 +6,6 @@ namespace LANCommander.SDK.Enums { public enum VPNType { - ZeroTier + ZeroTier = 1 } } From 656a3c2daeacae31d0ffd4b208814be55c8355a3 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Wed, 20 Mar 2024 19:32:28 -0500 Subject: [PATCH 4/5] Add functionality to talk to local ZT1 service --- .../VPN/Clients/ZeroTierClient.cs | 140 +++++++++++++++++- .../VPN/Models/ZeroTier/ApproveNodeRequest.cs | 11 ++ .../ZeroTier/UpdateNetworkSettingsResponse.cs | 3 +- LANCommander.SDK/VPNService.cs | 2 +- 4 files changed, 148 insertions(+), 8 deletions(-) create mode 100644 LANCommander.SDK/VPN/Models/ZeroTier/ApproveNodeRequest.cs diff --git a/LANCommander.SDK/VPN/Clients/ZeroTierClient.cs b/LANCommander.SDK/VPN/Clients/ZeroTierClient.cs index 1ff3bc92..cb140576 100644 --- a/LANCommander.SDK/VPN/Clients/ZeroTierClient.cs +++ b/LANCommander.SDK/VPN/Clients/ZeroTierClient.cs @@ -1,24 +1,28 @@ using LANCommander.SDK.VPN.Configurations; using LANCommander.SDK.VPN.Exceptions; +using LANCommander.SDK.VPN.Models.ZeroTier; using RestSharp; using System; using System.Collections.Generic; using System.IO; using System.Text; +using System.Threading.Tasks; namespace LANCommander.SDK.VPN.Clients { public class ZeroTierClient : IVPNClient { private ZeroTierConfiguration Configuration; - private RestClient ApiClient; + private RestClient ZeroTierServiceClient; + private Client Client; private string Token; private const string BaseUrl = "http://localhost:9993"; - public ZeroTierClient(ZeroTierConfiguration configuration) { + public ZeroTierClient(ZeroTierConfiguration configuration, Client client) { Configuration = configuration; + Client = client; - ApiClient = new RestClient(BaseUrl); + ZeroTierServiceClient = new RestClient(BaseUrl); var tokenPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "ZeroTier", "One", "authtoken.secret"); @@ -30,17 +34,141 @@ namespace LANCommander.SDK.VPN.Clients public bool Connect() { - throw new NotImplementedException(); + try + { + var task = PostRequest($"/network/{Configuration.NetworkId}", new UpdateNetworkSettingsRequest + { + AllowDefault = false, + AllowDNS = true, + AllowManaged = true, + AllowGlobal = false + }); + + task.Wait(); + + var response = task.Result; + + Client.PostRequest("/api/ZeroTier/ApproveNode", new ApproveNodeRequest + { + NodeId = response.Id + }); + } + catch (Exception ex) + { + return false; + } + + return true; } public bool Disconnect() { - throw new NotImplementedException(); + try + { + var status = GetNodeStatus(); + + var task = DeleteRequest($"/network/{Configuration.NetworkId}"); + + task.Wait(); + + var response = task.Result; + + if (response.Result) + { + Client.PostRequest("/api/ZeroTier/RemoveNode", new ApproveNodeRequest + { + NodeId = status.Address + }); + } + + return response.Result; + } + catch (Exception ex) + { + return false; + } } public VPNConnectionStatus GetStatus() { - throw new NotImplementedException(); + try + { + var response = GetNodeStatus(); + + return new VPNConnectionStatus + { + Connected = response.Online, + Status = response.Online ? "Connected" : "Not Connected" + }; + } + catch (Exception ex) + { + return null; + } + } + + private NodeStatusResponse GetNodeStatus() + { + var task = GetRequest("/status"); + + task.Wait(); + + var response = task.Result; + + return response; + } + + private async Task PostRequest(string route, object body) + { + if (Token == null) + return default; + + var request = new RestRequest(route) + .AddJsonBody(body) + .AddHeader("X-ZT1-AUTH", Token); + + var response = await ZeroTierServiceClient.PostAsync(request); + + return response; + } + + private async Task PostRequest(string route) + { + if (Token == null) + return default; + + var request = new RestRequest(route) + .AddHeader("X-ZT1-AUTH", Token); + + var response = await ZeroTierServiceClient.PostAsync(request); + + return response; + } + + private async Task GetRequest(string route) + { + if (Token == null) + return default; + + var request = new RestRequest(route) + .AddHeader("X-ZT1-AUTH", Token); + + var response = await ZeroTierServiceClient.GetAsync(request); + + return response; + } + + private async Task DeleteRequest(string route) + { + if (Token == null) + return default; + + var request = new RestRequest(route) + .AddHeader("X-ZT1-AUTH", Token); + + var response = await ZeroTierServiceClient.DeleteAsync(request); + + return response; } } } diff --git a/LANCommander.SDK/VPN/Models/ZeroTier/ApproveNodeRequest.cs b/LANCommander.SDK/VPN/Models/ZeroTier/ApproveNodeRequest.cs new file mode 100644 index 00000000..934b1308 --- /dev/null +++ b/LANCommander.SDK/VPN/Models/ZeroTier/ApproveNodeRequest.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LANCommander.SDK.VPN.Models.ZeroTier +{ + public class ApproveNodeRequest + { + public string NodeId { get; set; } + } +} diff --git a/LANCommander.SDK/VPN/Models/ZeroTier/UpdateNetworkSettingsResponse.cs b/LANCommander.SDK/VPN/Models/ZeroTier/UpdateNetworkSettingsResponse.cs index 14fb2891..ff16c301 100644 --- a/LANCommander.SDK/VPN/Models/ZeroTier/UpdateNetworkSettingsResponse.cs +++ b/LANCommander.SDK/VPN/Models/ZeroTier/UpdateNetworkSettingsResponse.cs @@ -4,7 +4,8 @@ using System.Text; namespace LANCommander.SDK.VPN.Models.ZeroTier { - internal class UpdateNetworkSettingsResponse + public class UpdateNetworkSettingsResponse { + public string Id { get; set; } } } diff --git a/LANCommander.SDK/VPNService.cs b/LANCommander.SDK/VPNService.cs index 7e20eef4..6695f9b8 100644 --- a/LANCommander.SDK/VPNService.cs +++ b/LANCommander.SDK/VPNService.cs @@ -40,7 +40,7 @@ namespace LANCommander.SDK switch (configuration.Type) { case Enums.VPNType.ZeroTier: - VPNClient = new ZeroTierClient(configuration.Data as ZeroTierConfiguration); + VPNClient = new ZeroTierClient(configuration.Data as ZeroTierConfiguration, Client); break; } } From d1ed6f17c94d37ab6a750808b0d6230dadbb243c Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Sat, 23 Mar 2024 11:45:45 -0500 Subject: [PATCH 5/5] Added VPN controller routes, add ZT settings --- .../VPN/Models/ZeroTier/RemoveNodeRequest.cs | 11 +++ LANCommander/Controllers/Api/VPNController.cs | 26 ++++-- .../Controllers/Api/ZeroTierController.cs | 74 +++++++++++++++ LANCommander/Models/Settings.cs | 6 ++ LANCommander/Models/ZeroTier/MemberConfig.cs | 8 ++ .../Models/ZeroTier/ModifyMemberRequest.cs | 10 ++ .../Services/VPNServices/ZeroTierService.cs | 92 +++++++++++++++++++ .../Pages/Settings/VPN/Forms/ZeroTier.razor | 9 +- .../UI/Pages/Settings/VPN/Index.razor | 2 +- 9 files changed, 226 insertions(+), 12 deletions(-) create mode 100644 LANCommander.SDK/VPN/Models/ZeroTier/RemoveNodeRequest.cs create mode 100644 LANCommander/Controllers/Api/ZeroTierController.cs create mode 100644 LANCommander/Models/ZeroTier/MemberConfig.cs create mode 100644 LANCommander/Models/ZeroTier/ModifyMemberRequest.cs create mode 100644 LANCommander/Services/VPNServices/ZeroTierService.cs diff --git a/LANCommander.SDK/VPN/Models/ZeroTier/RemoveNodeRequest.cs b/LANCommander.SDK/VPN/Models/ZeroTier/RemoveNodeRequest.cs new file mode 100644 index 00000000..90ae2a5a --- /dev/null +++ b/LANCommander.SDK/VPN/Models/ZeroTier/RemoveNodeRequest.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LANCommander.SDK.VPN.Models.ZeroTier +{ + public class RemoveNodeRequest + { + public string NodeId { get; set; } + } +} diff --git a/LANCommander/Controllers/Api/VPNController.cs b/LANCommander/Controllers/Api/VPNController.cs index 3a32cd34..108fde81 100644 --- a/LANCommander/Controllers/Api/VPNController.cs +++ b/LANCommander/Controllers/Api/VPNController.cs @@ -1,4 +1,6 @@ -using LANCommander.SDK.VPN.Configurations; +using LANCommander.Models; +using LANCommander.SDK.VPN.Configurations; +using LANCommander.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -11,16 +13,26 @@ namespace LANCommander.Controllers.Api { public VPNController() { } + [HttpGet] public async Task GetConfiguration() { - return new VPNConfiguration() + var settings = SettingService.GetSettings(); + var configuration = new VPNConfiguration() { - Type = SDK.Enums.VPNType.ZeroTier, - Data = new ZeroTierConfiguration - { - NetworkId = "" - } + Type = SDK.Enums.VPNType.ZeroTier }; + + switch (settings.VPN.Type) + { + case SDK.Enums.VPNType.ZeroTier: + configuration.Data = new ZeroTierConfiguration + { + NetworkId = (settings.VPN.Configuration as LANCommanderZeroTierSettings).NetworkId + }; + break; + } + + return configuration; } } } diff --git a/LANCommander/Controllers/Api/ZeroTierController.cs b/LANCommander/Controllers/Api/ZeroTierController.cs new file mode 100644 index 00000000..246c9443 --- /dev/null +++ b/LANCommander/Controllers/Api/ZeroTierController.cs @@ -0,0 +1,74 @@ +using LANCommander.Data.Models; +using LANCommander.Models; +using LANCommander.SDK.VPN.Models.ZeroTier; +using LANCommander.Services; +using LANCommander.Services.VPNServices; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; + +namespace LANCommander.Controllers.Api +{ + [Authorize(AuthenticationSchemes = "Bearer")] + [Route("api/[controller]")] + [ApiController] + public class ZeroTierController : ControllerBase + { + private readonly ZeroTierService ZeroTierService; + private readonly UserManager UserManager; + private readonly RoleManager RoleManager; + + public ZeroTierController(ZeroTierService zeroTierService, UserManager userManager, RoleManager roleManager) + { + ZeroTierService = zeroTierService; + UserManager = userManager; + RoleManager = roleManager; + } + + [HttpPost] + public async Task ApproveNode(ApproveNodeRequest request) + { + var settings = SettingService.GetSettings(); + + var user = await UserManager.FindByNameAsync(User.Identity.Name); + var roleNames = await UserManager.GetRolesAsync(user); + + bool allowed = false; + + foreach (var roleName in roleNames) + { + var role = await RoleManager.FindByNameAsync(roleName); + + if (role != null && settings.VPN.AllowedRoles.Contains(role.Id)) + { + allowed = true; + break; + } + } + + if (!allowed) + return Forbid(); + + var config = settings.VPN.Configuration as LANCommanderZeroTierSettings; + + await ZeroTierService.ApproveNode(config.NetworkId, user.UserName); + + return Ok(); + } + + [HttpPost] + public async Task RemoveNode(RemoveNodeRequest request) + { + var settings = SettingService.GetSettings(); + + var user = await UserManager.FindByNameAsync(User.Identity.Name); + + // Do we add any check here that the user is authorized to do VPN stuff? + // We should at least do a check that they own that + + await ZeroTierService.RemoveNode(request.NodeId); + + return Ok(); + } + } +} diff --git a/LANCommander/Models/Settings.cs b/LANCommander/Models/Settings.cs index d6d5b0b5..f7d423fa 100644 --- a/LANCommander/Models/Settings.cs +++ b/LANCommander/Models/Settings.cs @@ -100,6 +100,12 @@ namespace LANCommander.Models public object Configuration { get; set; } } + public class LANCommanderZeroTierSettings + { + public string NetworkId { get; set; } + public string ApiKey { get; set; } + } + public class LANCommanderWikiSettings { public bool Enabled { get; set; } = false; diff --git a/LANCommander/Models/ZeroTier/MemberConfig.cs b/LANCommander/Models/ZeroTier/MemberConfig.cs new file mode 100644 index 00000000..29ddd316 --- /dev/null +++ b/LANCommander/Models/ZeroTier/MemberConfig.cs @@ -0,0 +1,8 @@ +namespace LANCommander.Models.ZeroTier +{ + public class MemberConfig + { + public bool ActiveBridge { get; set; } + public bool Authorized { get; set; } + } +} diff --git a/LANCommander/Models/ZeroTier/ModifyMemberRequest.cs b/LANCommander/Models/ZeroTier/ModifyMemberRequest.cs new file mode 100644 index 00000000..96d12a6d --- /dev/null +++ b/LANCommander/Models/ZeroTier/ModifyMemberRequest.cs @@ -0,0 +1,10 @@ +namespace LANCommander.Models.ZeroTier +{ + public class ModifyMemberRequest + { + public bool Hidden { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public MemberConfig Config { get; set; } + } +} diff --git a/LANCommander/Services/VPNServices/ZeroTierService.cs b/LANCommander/Services/VPNServices/ZeroTierService.cs new file mode 100644 index 00000000..9b85a0cd --- /dev/null +++ b/LANCommander/Services/VPNServices/ZeroTierService.cs @@ -0,0 +1,92 @@ +using LANCommander.Models; +using LANCommander.Models.ZeroTier; +using RestSharp; + +namespace LANCommander.Services.VPNServices +{ + public class ZeroTierService + { + private const string BaseUrl = "https://api.zerotier.com/api/v1"; + private RestClient Client { get; set; } + private LANCommanderZeroTierSettings Configuration { get; set; } + + public ZeroTierService() { + Client = new RestClient(BaseUrl); + + var settings = SettingService.GetSettings(); + + Configuration = settings.VPN.Configuration as LANCommanderZeroTierSettings; + } + + public async Task ApproveNode(string nodeId, string username) + { + await PostRequestAsync($"/network/{Configuration.NetworkId}/member/{nodeId}", new ModifyMemberRequest + { + Hidden = false, + Name = username, + Config = new MemberConfig + { + Authorized = true + } + }); + } + + public async Task RemoveNode(string nodeId) + { + await DeleteRequest($"/network/{Configuration.NetworkId}/member/{nodeId}"); + } + + private async Task PostRequestAsync(string route, object body) + { + if (String.IsNullOrWhiteSpace(Configuration.ApiKey)) + return default; + + var request = new RestRequest(route) + .AddJsonBody(body) + .AddHeader("Authorization", $"token {Configuration.ApiKey}"); + + var response = await Client.PostAsync(request); + + return response; + } + + private async Task PostRequestAsync(string route) + { + if (String.IsNullOrWhiteSpace(Configuration.ApiKey)) + return default; + + var request = new RestRequest(route) + .AddHeader("Authorization", $"token {Configuration.ApiKey}"); + + var response = await Client.PostAsync(request); + + return response; + } + + private async Task GetRequest(string route) + { + if (String.IsNullOrWhiteSpace(Configuration.ApiKey)) + return default; + + var request = new RestRequest(route) + .AddHeader("Authorization", $"token {Configuration.ApiKey}"); + + var response = await Client.GetAsync(request); + + return response; + } + + private async Task DeleteRequest(string route) + { + if (String.IsNullOrWhiteSpace(Configuration.ApiKey)) + return default; + + var request = new RestRequest(route) + .AddHeader("Authorization", $"token {Configuration.ApiKey}"); + + var response = await Client.DeleteAsync(request); + + return response; + } + } +} diff --git a/LANCommander/UI/Pages/Settings/VPN/Forms/ZeroTier.razor b/LANCommander/UI/Pages/Settings/VPN/Forms/ZeroTier.razor index 9b77cd50..2fd2ac19 100644 --- a/LANCommander/UI/Pages/Settings/VPN/Forms/ZeroTier.razor +++ b/LANCommander/UI/Pages/Settings/VPN/Forms/ZeroTier.razor @@ -1,4 +1,5 @@ -@using LANCommander.SDK.VPN.Configurations +@using LANCommander.Models +@using LANCommander.SDK.VPN.Configurations @@ -8,14 +9,14 @@ [Parameter] public string Value { get; set; } = ""; [Parameter] public EventCallback ValueChanged { get; set; } - [Parameter] public ZeroTierConfiguration Configuration { get; set; } = new ZeroTierConfiguration(); - [Parameter] public EventCallback ConfigurationChanged { get; set; } + [Parameter] public LANCommanderZeroTierSettings Configuration { get; set; } = new LANCommanderZeroTierSettings(); + [Parameter] public EventCallback ConfigurationChanged { get; set; } protected override async Task OnInitializedAsync() { if (Configuration == null) { - Configuration = new ZeroTierConfiguration(); + Configuration = new LANCommanderZeroTierSettings(); await ConfigurationChanged.InvokeAsync(Configuration); } } diff --git a/LANCommander/UI/Pages/Settings/VPN/Index.razor b/LANCommander/UI/Pages/Settings/VPN/Index.razor index 875d6b5c..6f536952 100644 --- a/LANCommander/UI/Pages/Settings/VPN/Index.razor +++ b/LANCommander/UI/Pages/Settings/VPN/Index.razor @@ -32,7 +32,7 @@ @switch (context.VPN.Type) { case VPNType.ZeroTier: - + break; }