From 04477e3660dfc7bee51bb442c1029e9f78c21437 Mon Sep 17 00:00:00 2001 From: zkhussain Date: Mon, 10 Jan 2022 19:53:11 -0500 Subject: [PATCH 1/4] Add methods to manage proxmox api tokens --- CSLabs.Api/Proxmox/ProxmoxApi.cs | 40 ++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/CSLabs.Api/Proxmox/ProxmoxApi.cs b/CSLabs.Api/Proxmox/ProxmoxApi.cs index 1c55d89..d1f1c26 100644 --- a/CSLabs.Api/Proxmox/ProxmoxApi.cs +++ b/CSLabs.Api/Proxmox/ProxmoxApi.cs @@ -7,6 +7,7 @@ using Corsinvest.ProxmoxVE.Api; using Corsinvest.ProxmoxVE.Api.Extension.Info; using CSLabs.Api.Models.HypervisorModels; +using CSLabs.Api.Models.UserModels; using CSLabs.Api.Proxmox.Responses; using Newtonsoft.Json; @@ -25,7 +26,7 @@ public ProxmoxApi(HypervisorNode hypervisorNode, string password) _password = password; } - private bool loggedIn => DateTime.Now.Subtract(_loggedInAt).TotalMinutes < 15; + private bool loggedIn => DateTime.Now.Subtract(_loggedInAt).TotalMinutes < 60; private async Task LoginIfNotLoggedIn() { @@ -40,11 +41,46 @@ private async Task Login() _loggedInAt = DateTime.Now; } + public async Task ManageApiToken() + { + var userid = $"{HypervisorNode.Hypervisor.UserName}@pam"; + if (string.IsNullOrEmpty(client.ApiToken)) + await GenerateApiToken(userid); + else + await RotateApiToken(userid); + } + + private async Task RotateApiToken(string userid) + { + await PerformRequest(() => client.Access.Users[userid].Token["API_TOKEN"].RemoveToken()); + await GenerateApiToken(userid); + } + + private async Task GenerateApiToken(string userid) + { + var apiTokenResponse = await GetApiToken(userid); + client.ApiToken = $"{userid}!API_TOKEN={apiTokenResponse.FullTokenId}"; + } + + private async Task GetApiToken(string userid) + { + var expireDate = (int) DateTimeOffset.UtcNow.AddDays(30).ToUnixTimeSeconds(); + var apiKey = await PerformRequest(() => + client.Access.Users[userid].Token["API_TOKEN"].GenerateToken(expire: expireDate, privsep: true)); + + return new ApiTokenResponse() + { + FullTokenId = apiKey.Response.data["full-tokenid"], + Info = apiKey.Response.data.info, + Value = apiKey.Response.value, + }; + } + public async Task GetTicket(int vmId) { await LoginIfNotLoggedIn(); var output = await PerformRequest(() => this.client.Nodes[HypervisorNode.Name].Qemu[vmId].Vncproxy.Vncproxy(websocket: true)); - + return new TicketResponse() { Port = int.Parse(output.Response.data.port), From b53e2aa5395fffea31bb232f5c26daa6eccfdc02 Mon Sep 17 00:00:00 2001 From: zkhussain Date: Mon, 10 Jan 2022 19:54:46 -0500 Subject: [PATCH 2/4] Add proxmox api token job and service --- CSLabs.Api/Jobs/ApiTokenJob.cs | 28 +++++++++++++++++++ CSLabs.Api/Jobs/JobRegistry.cs | 2 +- CSLabs.Api/Services/ProxmoxApiTokenService.cs | 26 +++++++++++++++++ .../Services/ProxmoxVmTemplateService.cs | 2 +- CSLabs.Api/Services/ServiceProvider.cs | 1 + 5 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 CSLabs.Api/Jobs/ApiTokenJob.cs create mode 100644 CSLabs.Api/Services/ProxmoxApiTokenService.cs diff --git a/CSLabs.Api/Jobs/ApiTokenJob.cs b/CSLabs.Api/Jobs/ApiTokenJob.cs new file mode 100644 index 0000000..dd84be3 --- /dev/null +++ b/CSLabs.Api/Jobs/ApiTokenJob.cs @@ -0,0 +1,28 @@ +using System; +using System.Threading.Tasks; +using CSLabs.Api.Models; +using CSLabs.Api.Services; +using FluentScheduler; +using Microsoft.Extensions.DependencyInjection; + +namespace CSLabs.Api.Jobs +{ + public class ApiTokenJob : AsyncJob + { + private readonly IServiceProvider _serviceProvider; + + public ApiTokenJob(IServiceProvider provider) + { + _serviceProvider = provider; + } + + protected override async Task ExecuteAsync() + { + using var scope = _serviceProvider.CreateScope(); + var context = scope.ServiceProvider.GetService(); + var service = scope.ServiceProvider.GetService(); + + await service.ManageApiToken(context); + } + } +} \ No newline at end of file diff --git a/CSLabs.Api/Jobs/JobRegistry.cs b/CSLabs.Api/Jobs/JobRegistry.cs index b686db2..f6c0575 100644 --- a/CSLabs.Api/Jobs/JobRegistry.cs +++ b/CSLabs.Api/Jobs/JobRegistry.cs @@ -11,7 +11,7 @@ public JobRegistry(IServiceProvider provider) // Schedule new jobs here Schedule(() => new ExampleJob(provider)).ToRunEvery(1).Minutes(); - + Schedule(() => new ApiTokenJob(provider)).ToRunOnceAt(DateTime.Now).AndEvery(30).Days().At(0, 0); } } } \ No newline at end of file diff --git a/CSLabs.Api/Services/ProxmoxApiTokenService.cs b/CSLabs.Api/Services/ProxmoxApiTokenService.cs new file mode 100644 index 0000000..9f3295c --- /dev/null +++ b/CSLabs.Api/Services/ProxmoxApiTokenService.cs @@ -0,0 +1,26 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using CSLabs.Api.Models; +using CSLabs.Api.Proxmox; +using Microsoft.EntityFrameworkCore; + +namespace CSLabs.Api.Services +{ + public class ProxmoxApiTokenService + { + public ProxmoxManager ProxmoxManager { get;} + + public ProxmoxApiTokenService(ProxmoxManager manager) + { + ProxmoxManager = manager; + } + + public async Task ManageApiToken(DefaultContext context) + { + var hypervisor = await context.Hypervisors.FirstOrDefaultAsync(); + var api = ProxmoxManager.GetProxmoxApi(hypervisor.HypervisorNodes.First()); + await api.ManageApiToken(); + } + } +} \ No newline at end of file diff --git a/CSLabs.Api/Services/ProxmoxVmTemplateService.cs b/CSLabs.Api/Services/ProxmoxVmTemplateService.cs index f4b31bc..0cecb0a 100644 --- a/CSLabs.Api/Services/ProxmoxVmTemplateService.cs +++ b/CSLabs.Api/Services/ProxmoxVmTemplateService.cs @@ -168,7 +168,7 @@ public async Task CreateVmAndImportDisk(string name, SshClient ssh, SftpCli var ovf = ParseOvf(await reader.ReadToEndAsync()); var vmId = await api.CreateVm(name.ToSafeId(), ovf.MemorySizeMb); - var result = ssh.RunCommand($"qm importdisk {vmId} {vmdk.FullName} nasapp -format qcow2"); + var result = ssh.RunCommand($"qm importdisk {vmId} {vmdk.FullName} local-lvm -format qcow2"); if (result.Error != "") { throw new Exception("Failed to import disk! Error: " + result.Error); diff --git a/CSLabs.Api/Services/ServiceProvider.cs b/CSLabs.Api/Services/ServiceProvider.cs index 1934d5a..efd9b2e 100644 --- a/CSLabs.Api/Services/ServiceProvider.cs +++ b/CSLabs.Api/Services/ServiceProvider.cs @@ -32,6 +32,7 @@ public static void ProvideAppServices(this IServiceCollection services) services.AddScoped(); services.ProvideProxmoxApi(); services.AddScoped(); + services.AddScoped(); services.AddTransient(); services.AddTransient(); services.AddSingleton(); From 6e11d0b2112c6bf2b2e54cd3daa906843b211e09 Mon Sep 17 00:00:00 2001 From: zkhussain Date: Mon, 10 Jan 2022 19:55:15 -0500 Subject: [PATCH 3/4] Add token response --- CSLabs.Api/Proxmox/Responses/ApiTokenResponse.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 CSLabs.Api/Proxmox/Responses/ApiTokenResponse.cs diff --git a/CSLabs.Api/Proxmox/Responses/ApiTokenResponse.cs b/CSLabs.Api/Proxmox/Responses/ApiTokenResponse.cs new file mode 100644 index 0000000..54efd8b --- /dev/null +++ b/CSLabs.Api/Proxmox/Responses/ApiTokenResponse.cs @@ -0,0 +1,16 @@ +namespace CSLabs.Api.Proxmox.Responses +{ + public class ApiTokenResponse + { + public string Value { get; set; } + public TokenInfo Info { get; set; } + public string FullTokenId { get; set; } + } + + public class TokenInfo + { + public string Comment { get; set; } + public int Expire { get; set; } + public bool PrivSep { get; set; } + } +} \ No newline at end of file From e0cf8fa4b0e172056b90725dbc65bab61be12317 Mon Sep 17 00:00:00 2001 From: zkhussain Date: Wed, 12 Jan 2022 00:59:31 -0500 Subject: [PATCH 4/4] Update to fix null exception --- CSLabs.Api/Proxmox/ProxmoxApi.cs | 21 +++++++++++++------ CSLabs.Api/Services/ProxmoxApiTokenService.cs | 3 ++- .../Services/ProxmoxVmTemplateService.cs | 2 +- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/CSLabs.Api/Proxmox/ProxmoxApi.cs b/CSLabs.Api/Proxmox/ProxmoxApi.cs index d1f1c26..710d655 100644 --- a/CSLabs.Api/Proxmox/ProxmoxApi.cs +++ b/CSLabs.Api/Proxmox/ProxmoxApi.cs @@ -18,6 +18,7 @@ public class ProxmoxApi private PveClient client; private DateTime _loggedInAt = DateTime.MinValue; private string _password; + public HypervisorNode HypervisorNode { get;} public ProxmoxApi(HypervisorNode hypervisorNode, string password) { @@ -26,7 +27,7 @@ public ProxmoxApi(HypervisorNode hypervisorNode, string password) _password = password; } - private bool loggedIn => DateTime.Now.Subtract(_loggedInAt).TotalMinutes < 60; + private bool loggedIn => DateTime.Now.Subtract(_loggedInAt).TotalMinutes < 15; private async Task LoginIfNotLoggedIn() { @@ -43,6 +44,7 @@ private async Task Login() public async Task ManageApiToken() { + await LoginIfNotLoggedIn(); var userid = $"{HypervisorNode.Hypervisor.UserName}@pam"; if (string.IsNullOrEmpty(client.ApiToken)) await GenerateApiToken(userid); @@ -52,27 +54,34 @@ public async Task ManageApiToken() private async Task RotateApiToken(string userid) { + await LoginIfNotLoggedIn(); await PerformRequest(() => client.Access.Users[userid].Token["API_TOKEN"].RemoveToken()); await GenerateApiToken(userid); } private async Task GenerateApiToken(string userid) { + await LoginIfNotLoggedIn(); var apiTokenResponse = await GetApiToken(userid); - client.ApiToken = $"{userid}!API_TOKEN={apiTokenResponse.FullTokenId}"; + client.ApiToken = $"{userid}!API_TOKEN={apiTokenResponse.Value}"; } private async Task GetApiToken(string userid) { + await LoginIfNotLoggedIn(); var expireDate = (int) DateTimeOffset.UtcNow.AddDays(30).ToUnixTimeSeconds(); var apiKey = await PerformRequest(() => client.Access.Users[userid].Token["API_TOKEN"].GenerateToken(expire: expireDate, privsep: true)); return new ApiTokenResponse() { - FullTokenId = apiKey.Response.data["full-tokenid"], - Info = apiKey.Response.data.info, - Value = apiKey.Response.value, + FullTokenId = ((IDictionary) apiKey.Response.data)["full-tokenid"].ToString(), + Info = new TokenInfo() + { + Expire = int.Parse(apiKey.Response.data.info.expire), + PrivSep = apiKey.Response.data.info.privsep == "1" + }, + Value = apiKey.Response.data.value }; } @@ -80,7 +89,7 @@ public async Task GetTicket(int vmId) { await LoginIfNotLoggedIn(); var output = await PerformRequest(() => this.client.Nodes[HypervisorNode.Name].Qemu[vmId].Vncproxy.Vncproxy(websocket: true)); - + return new TicketResponse() { Port = int.Parse(output.Response.data.port), diff --git a/CSLabs.Api/Services/ProxmoxApiTokenService.cs b/CSLabs.Api/Services/ProxmoxApiTokenService.cs index 9f3295c..d7fe579 100644 --- a/CSLabs.Api/Services/ProxmoxApiTokenService.cs +++ b/CSLabs.Api/Services/ProxmoxApiTokenService.cs @@ -19,7 +19,8 @@ public ProxmoxApiTokenService(ProxmoxManager manager) public async Task ManageApiToken(DefaultContext context) { var hypervisor = await context.Hypervisors.FirstOrDefaultAsync(); - var api = ProxmoxManager.GetProxmoxApi(hypervisor.HypervisorNodes.First()); + var primaryHypervisorNode = await ProxmoxManager.GetPrimaryHypervisorNode(hypervisor); + var api = ProxmoxManager.GetProxmoxApi(primaryHypervisorNode); await api.ManageApiToken(); } } diff --git a/CSLabs.Api/Services/ProxmoxVmTemplateService.cs b/CSLabs.Api/Services/ProxmoxVmTemplateService.cs index 0cecb0a..f4b31bc 100644 --- a/CSLabs.Api/Services/ProxmoxVmTemplateService.cs +++ b/CSLabs.Api/Services/ProxmoxVmTemplateService.cs @@ -168,7 +168,7 @@ public async Task CreateVmAndImportDisk(string name, SshClient ssh, SftpCli var ovf = ParseOvf(await reader.ReadToEndAsync()); var vmId = await api.CreateVm(name.ToSafeId(), ovf.MemorySizeMb); - var result = ssh.RunCommand($"qm importdisk {vmId} {vmdk.FullName} local-lvm -format qcow2"); + var result = ssh.RunCommand($"qm importdisk {vmId} {vmdk.FullName} nasapp -format qcow2"); if (result.Error != "") { throw new Exception("Failed to import disk! Error: " + result.Error);