From 67e10fbfc191234a66f881329001dbf126890767 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Nov 2025 23:53:47 +0000 Subject: [PATCH 01/23] Initial plan From bb326f4839572a1a8488db44712a7ee61f7b421f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Nov 2025 23:57:32 +0000 Subject: [PATCH 02/23] Initial plan for certificate update API endpoint Co-authored-by: robgrame <12012136+robgrame@users.noreply.github.com> --- ....GeneratedMSBuildEditorConfig.editorconfig | 6 +- .../SecureBootDashboard.Api.GlobalUsings.g.cs | 32 ++++----- ....GeneratedMSBuildEditorConfig.editorconfig | 68 +++++++++--------- .../SecureBootDashboard.Web.GlobalUsings.g.cs | 32 ++++----- ...cher.Client.csproj.AssemblyReference.cache | Bin 57350 -> 57350 bytes ....GeneratedMSBuildEditorConfig.editorconfig | 2 +- 6 files changed, 68 insertions(+), 72 deletions(-) diff --git a/SecureBootDashboard.Api/obj/Debug/net8.0/SecureBootDashboard.Api.GeneratedMSBuildEditorConfig.editorconfig b/SecureBootDashboard.Api/obj/Debug/net8.0/SecureBootDashboard.Api.GeneratedMSBuildEditorConfig.editorconfig index b4de29c..5ca1945 100644 --- a/SecureBootDashboard.Api/obj/Debug/net8.0/SecureBootDashboard.Api.GeneratedMSBuildEditorConfig.editorconfig +++ b/SecureBootDashboard.Api/obj/Debug/net8.0/SecureBootDashboard.Api.GeneratedMSBuildEditorConfig.editorconfig @@ -15,17 +15,15 @@ build_property.EnforceExtendedAnalyzerRules = build_property.EnforceExtendedAnalyzerRules = build_property._SupportedPlatformList = Linux,macOS,Windows build_property._SupportedPlatformList = Linux,macOS,Windows -build_property.TargetFrameworkIdentifier = .NETCoreApp -build_property.TargetFrameworkVersion = v8.0 build_property.RootNamespace = SecureBootDashboard.Api build_property.RootNamespace = SecureBootDashboard.Api -build_property.ProjectDir = C:\Users\nefario\source\repos\robgrame\Nimbus.BootCertWatcher\SecureBootDashboard.Api\ +build_property.ProjectDir = /home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Api/ build_property.EnableComHosting = build_property.EnableGeneratedComInterfaceComImportInterop = build_property.RazorLangVersion = 8.0 build_property.SupportLocalizedComponentNames = build_property.GenerateRazorMetadataSourceChecksumAttributes = -build_property.MSBuildProjectDirectory = C:\Users\nefario\source\repos\robgrame\Nimbus.BootCertWatcher\SecureBootDashboard.Api +build_property.MSBuildProjectDirectory = /home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Api build_property._RazorSourceGeneratorDebug = build_property.EffectiveAnalysisLevelStyle = 8.0 build_property.EnableCodeStyleSeverity = diff --git a/SecureBootDashboard.Api/obj/Debug/net8.0/SecureBootDashboard.Api.GlobalUsings.g.cs b/SecureBootDashboard.Api/obj/Debug/net8.0/SecureBootDashboard.Api.GlobalUsings.g.cs index 5e6145d..025530a 100644 --- a/SecureBootDashboard.Api/obj/Debug/net8.0/SecureBootDashboard.Api.GlobalUsings.g.cs +++ b/SecureBootDashboard.Api/obj/Debug/net8.0/SecureBootDashboard.Api.GlobalUsings.g.cs @@ -1,17 +1,17 @@ // -global using Microsoft.AspNetCore.Builder; -global using Microsoft.AspNetCore.Hosting; -global using Microsoft.AspNetCore.Http; -global using Microsoft.AspNetCore.Routing; -global using Microsoft.Extensions.Configuration; -global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.Hosting; -global using Microsoft.Extensions.Logging; -global using System; -global using System.Collections.Generic; -global using System.IO; -global using System.Linq; -global using System.Net.Http; -global using System.Net.Http.Json; -global using System.Threading; -global using System.Threading.Tasks; +global using global::Microsoft.AspNetCore.Builder; +global using global::Microsoft.AspNetCore.Hosting; +global using global::Microsoft.AspNetCore.Http; +global using global::Microsoft.AspNetCore.Routing; +global using global::Microsoft.Extensions.Configuration; +global using global::Microsoft.Extensions.DependencyInjection; +global using global::Microsoft.Extensions.Hosting; +global using global::Microsoft.Extensions.Logging; +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Net.Http; +global using global::System.Net.Http.Json; +global using global::System.Threading; +global using global::System.Threading.Tasks; diff --git a/SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.GeneratedMSBuildEditorConfig.editorconfig b/SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.GeneratedMSBuildEditorConfig.editorconfig index 918f2ba..cc25631 100644 --- a/SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.GeneratedMSBuildEditorConfig.editorconfig +++ b/SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.GeneratedMSBuildEditorConfig.editorconfig @@ -1,7 +1,5 @@ is_global = true build_property.TargetFramework = net8.0 -build_property.TargetFrameworkIdentifier = .NETCoreApp -build_property.TargetFrameworkVersion = v8.0 build_property.TargetPlatformMinVersion = build_property.UsingMicrosoftNETSdkWeb = true build_property.ProjectTypeGuids = @@ -11,73 +9,73 @@ build_property.EnforceExtendedAnalyzerRules = build_property._SupportedPlatformList = Linux,macOS,Windows build_property.RootNamespace = SecureBootDashboard.Web build_property.RootNamespace = SecureBootDashboard.Web -build_property.ProjectDir = C:\Users\nefario\source\repos\robgrame\Nimbus.BootCertWatcher\SecureBootdashboard.web\ +build_property.ProjectDir = /home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/ build_property.EnableComHosting = build_property.EnableGeneratedComInterfaceComImportInterop = build_property.RazorLangVersion = 8.0 build_property.SupportLocalizedComponentNames = build_property.GenerateRazorMetadataSourceChecksumAttributes = -build_property.MSBuildProjectDirectory = C:\Users\nefario\source\repos\robgrame\Nimbus.BootCertWatcher\SecureBootdashboard.web +build_property.MSBuildProjectDirectory = /home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web build_property._RazorSourceGeneratorDebug = build_property.EffectiveAnalysisLevelStyle = 8.0 build_property.EnableCodeStyleSeverity = -[C:/Users/nefario/source/repos/robgrame/Nimbus.BootCertWatcher/SecureBootdashboard.web/Pages/About.cshtml] -build_metadata.AdditionalFiles.TargetPath = UGFnZXNcQWJvdXQuY3NodG1s +[/home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/Pages/About.cshtml] +build_metadata.AdditionalFiles.TargetPath = UGFnZXMvQWJvdXQuY3NodG1s build_metadata.AdditionalFiles.CssScope = -[C:/Users/nefario/source/repos/robgrame/Nimbus.BootCertWatcher/SecureBootdashboard.web/Pages/Account/Login.cshtml] -build_metadata.AdditionalFiles.TargetPath = UGFnZXNcQWNjb3VudFxMb2dpbi5jc2h0bWw= +[/home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/Pages/Account/Login.cshtml] +build_metadata.AdditionalFiles.TargetPath = UGFnZXMvQWNjb3VudC9Mb2dpbi5jc2h0bWw= build_metadata.AdditionalFiles.CssScope = -[C:/Users/nefario/source/repos/robgrame/Nimbus.BootCertWatcher/SecureBootdashboard.web/Pages/Account/Logout.cshtml] -build_metadata.AdditionalFiles.TargetPath = UGFnZXNcQWNjb3VudFxMb2dvdXQuY3NodG1s +[/home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/Pages/Account/Logout.cshtml] +build_metadata.AdditionalFiles.TargetPath = UGFnZXMvQWNjb3VudC9Mb2dvdXQuY3NodG1s build_metadata.AdditionalFiles.CssScope = -[C:/Users/nefario/source/repos/robgrame/Nimbus.BootCertWatcher/SecureBootdashboard.web/Pages/Devices/Details.cshtml] -build_metadata.AdditionalFiles.TargetPath = UGFnZXNcRGV2aWNlc1xEZXRhaWxzLmNzaHRtbA== +[/home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/Pages/Devices/Details.cshtml] +build_metadata.AdditionalFiles.TargetPath = UGFnZXMvRGV2aWNlcy9EZXRhaWxzLmNzaHRtbA== build_metadata.AdditionalFiles.CssScope = -[C:/Users/nefario/source/repos/robgrame/Nimbus.BootCertWatcher/SecureBootdashboard.web/Pages/Devices/List.cshtml] -build_metadata.AdditionalFiles.TargetPath = UGFnZXNcRGV2aWNlc1xMaXN0LmNzaHRtbA== +[/home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/Pages/Devices/List.cshtml] +build_metadata.AdditionalFiles.TargetPath = UGFnZXMvRGV2aWNlcy9MaXN0LmNzaHRtbA== build_metadata.AdditionalFiles.CssScope = -[C:/Users/nefario/source/repos/robgrame/Nimbus.BootCertWatcher/SecureBootdashboard.web/Pages/Devices/Reports.cshtml] -build_metadata.AdditionalFiles.TargetPath = UGFnZXNcRGV2aWNlc1xSZXBvcnRzLmNzaHRtbA== +[/home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/Pages/Devices/Reports.cshtml] +build_metadata.AdditionalFiles.TargetPath = UGFnZXMvRGV2aWNlcy9SZXBvcnRzLmNzaHRtbA== build_metadata.AdditionalFiles.CssScope = -[C:/Users/nefario/source/repos/robgrame/Nimbus.BootCertWatcher/SecureBootdashboard.web/Pages/Error.cshtml] -build_metadata.AdditionalFiles.TargetPath = UGFnZXNcRXJyb3IuY3NodG1s +[/home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/Pages/Error.cshtml] +build_metadata.AdditionalFiles.TargetPath = UGFnZXMvRXJyb3IuY3NodG1s build_metadata.AdditionalFiles.CssScope = -[C:/Users/nefario/source/repos/robgrame/Nimbus.BootCertWatcher/SecureBootdashboard.web/Pages/Index.cshtml] -build_metadata.AdditionalFiles.TargetPath = UGFnZXNcSW5kZXguY3NodG1s +[/home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/Pages/Index.cshtml] +build_metadata.AdditionalFiles.TargetPath = UGFnZXMvSW5kZXguY3NodG1s build_metadata.AdditionalFiles.CssScope = -[C:/Users/nefario/source/repos/robgrame/Nimbus.BootCertWatcher/SecureBootdashboard.web/Pages/Privacy.cshtml] -build_metadata.AdditionalFiles.TargetPath = UGFnZXNcUHJpdmFjeS5jc2h0bWw= +[/home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/Pages/Privacy.cshtml] +build_metadata.AdditionalFiles.TargetPath = UGFnZXMvUHJpdmFjeS5jc2h0bWw= build_metadata.AdditionalFiles.CssScope = -[C:/Users/nefario/source/repos/robgrame/Nimbus.BootCertWatcher/SecureBootdashboard.web/Pages/Reports/Details.cshtml] -build_metadata.AdditionalFiles.TargetPath = UGFnZXNcUmVwb3J0c1xEZXRhaWxzLmNzaHRtbA== +[/home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/Pages/Reports/Details.cshtml] +build_metadata.AdditionalFiles.TargetPath = UGFnZXMvUmVwb3J0cy9EZXRhaWxzLmNzaHRtbA== build_metadata.AdditionalFiles.CssScope = -[C:/Users/nefario/source/repos/robgrame/Nimbus.BootCertWatcher/SecureBootdashboard.web/Pages/Shared/_ValidationScriptsPartial.cshtml] -build_metadata.AdditionalFiles.TargetPath = UGFnZXNcU2hhcmVkXF9WYWxpZGF0aW9uU2NyaXB0c1BhcnRpYWwuY3NodG1s +[/home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/Pages/Shared/_ValidationScriptsPartial.cshtml] +build_metadata.AdditionalFiles.TargetPath = UGFnZXMvU2hhcmVkL19WYWxpZGF0aW9uU2NyaXB0c1BhcnRpYWwuY3NodG1s build_metadata.AdditionalFiles.CssScope = -[C:/Users/nefario/source/repos/robgrame/Nimbus.BootCertWatcher/SecureBootdashboard.web/Pages/Welcome.cshtml] -build_metadata.AdditionalFiles.TargetPath = UGFnZXNcV2VsY29tZS5jc2h0bWw= +[/home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/Pages/Welcome.cshtml] +build_metadata.AdditionalFiles.TargetPath = UGFnZXMvV2VsY29tZS5jc2h0bWw= build_metadata.AdditionalFiles.CssScope = -[C:/Users/nefario/source/repos/robgrame/Nimbus.BootCertWatcher/SecureBootdashboard.web/Pages/_ViewImports.cshtml] -build_metadata.AdditionalFiles.TargetPath = UGFnZXNcX1ZpZXdJbXBvcnRzLmNzaHRtbA== +[/home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/Pages/_ViewImports.cshtml] +build_metadata.AdditionalFiles.TargetPath = UGFnZXMvX1ZpZXdJbXBvcnRzLmNzaHRtbA== build_metadata.AdditionalFiles.CssScope = -[C:/Users/nefario/source/repos/robgrame/Nimbus.BootCertWatcher/SecureBootdashboard.web/Pages/_ViewStart.cshtml] -build_metadata.AdditionalFiles.TargetPath = UGFnZXNcX1ZpZXdTdGFydC5jc2h0bWw= +[/home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/Pages/_ViewStart.cshtml] +build_metadata.AdditionalFiles.TargetPath = UGFnZXMvX1ZpZXdTdGFydC5jc2h0bWw= build_metadata.AdditionalFiles.CssScope = -[C:/Users/nefario/source/repos/robgrame/Nimbus.BootCertWatcher/SecureBootdashboard.web/Pages/Shared/_Layout.cshtml] -build_metadata.AdditionalFiles.TargetPath = UGFnZXNcU2hhcmVkXF9MYXlvdXQuY3NodG1s -build_metadata.AdditionalFiles.CssScope = b-g7ac228pnz +[/home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/Pages/Shared/_Layout.cshtml] +build_metadata.AdditionalFiles.TargetPath = UGFnZXMvU2hhcmVkL19MYXlvdXQuY3NodG1s +build_metadata.AdditionalFiles.CssScope = b-fz1gbt3di4 diff --git a/SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.GlobalUsings.g.cs b/SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.GlobalUsings.g.cs index 5e6145d..025530a 100644 --- a/SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.GlobalUsings.g.cs +++ b/SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.GlobalUsings.g.cs @@ -1,17 +1,17 @@ // -global using Microsoft.AspNetCore.Builder; -global using Microsoft.AspNetCore.Hosting; -global using Microsoft.AspNetCore.Http; -global using Microsoft.AspNetCore.Routing; -global using Microsoft.Extensions.Configuration; -global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.Hosting; -global using Microsoft.Extensions.Logging; -global using System; -global using System.Collections.Generic; -global using System.IO; -global using System.Linq; -global using System.Net.Http; -global using System.Net.Http.Json; -global using System.Threading; -global using System.Threading.Tasks; +global using global::Microsoft.AspNetCore.Builder; +global using global::Microsoft.AspNetCore.Hosting; +global using global::Microsoft.AspNetCore.Http; +global using global::Microsoft.AspNetCore.Routing; +global using global::Microsoft.Extensions.Configuration; +global using global::Microsoft.Extensions.DependencyInjection; +global using global::Microsoft.Extensions.Hosting; +global using global::Microsoft.Extensions.Logging; +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Net.Http; +global using global::System.Net.Http.Json; +global using global::System.Threading; +global using global::System.Threading.Tasks; diff --git a/SecureBootWatcher.Client/obj/Debug/net48/SecureBootWatcher.Client.csproj.AssemblyReference.cache b/SecureBootWatcher.Client/obj/Debug/net48/SecureBootWatcher.Client.csproj.AssemblyReference.cache index 8ae0dc833efcf4d028f38401a3bad48310400406..affca4103c36e865cc12348929b4c3af49e825cf 100644 GIT binary patch delta 45 zcmV+|0Mh@4zypTB1CX2ykwj58Ssva90RaIRk?$Chzy$#VlTiy8k=`|vVvi@YIswey DZ_^Ne delta 47 zcmV+~0MP%2zypTB1CX2yD32DYjvd|z0RaIRk?$Chzy$#TlTiy8k=``{F_W Date: Sat, 8 Nov 2025 00:03:23 +0000 Subject: [PATCH 03/23] Add certificate update API endpoint with tests Co-authored-by: robgrame <12012136+robgrame@users.noreply.github.com> --- .../CertificateUpdateControllerTests.cs | 152 +++++++++ .../SecureBootDashboard.Api.Tests.csproj | 1 + .../CertificateUpdateServiceOptions.cs | 75 +++++ .../CertificateUpdateController.cs | 134 ++++++++ SecureBootDashboard.Api/Program.cs | 20 ++ .../Services/CertificateUpdateService.cs | 297 ++++++++++++++++++ .../Services/ICertificateUpdateService.cs | 53 ++++ .../appsettings.Development.json | 6 + SecureBootDashboard.Api/appsettings.json | 11 + .../Models/CertificateUpdateCommand.cs | 50 +++ .../CertificateUpdateCommandEnvelope.cs | 19 ++ 11 files changed, 818 insertions(+) create mode 100644 SecureBootDashboard.Api.Tests/Controllers/CertificateUpdateControllerTests.cs create mode 100644 SecureBootDashboard.Api/Configuration/CertificateUpdateServiceOptions.cs create mode 100644 SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs create mode 100644 SecureBootDashboard.Api/Services/CertificateUpdateService.cs create mode 100644 SecureBootDashboard.Api/Services/ICertificateUpdateService.cs create mode 100644 SecureBootWatcher.Shared/Models/CertificateUpdateCommand.cs create mode 100644 SecureBootWatcher.Shared/Transport/CertificateUpdateCommandEnvelope.cs diff --git a/SecureBootDashboard.Api.Tests/Controllers/CertificateUpdateControllerTests.cs b/SecureBootDashboard.Api.Tests/Controllers/CertificateUpdateControllerTests.cs new file mode 100644 index 0000000..dae5cbe --- /dev/null +++ b/SecureBootDashboard.Api.Tests/Controllers/CertificateUpdateControllerTests.cs @@ -0,0 +1,152 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Moq; +using SecureBootDashboard.Api.Controllers; +using SecureBootDashboard.Api.Services; +using SecureBootWatcher.Shared.Models; +using Xunit; + +namespace SecureBootDashboard.Api.Tests.Controllers +{ + public class CertificateUpdateControllerTests + { + private readonly Mock _mockUpdateService; + private readonly Mock> _mockLogger; + private readonly CertificateUpdateController _controller; + + public CertificateUpdateControllerTests() + { + _mockUpdateService = new Mock(); + _mockLogger = new Mock>(); + _controller = new CertificateUpdateController(_mockUpdateService.Object, _mockLogger.Object); + } + + [Fact] + public async Task TriggerUpdateAsync_ValidRequest_ReturnsOkResult() + { + // Arrange + var request = new CertificateUpdateController.CertificateUpdateRequest + { + FleetId = "fleet-01", + IssuedBy = "admin@example.com", + Notes = "Test update" + }; + + var expectedResult = new CertificateUpdateResult( + Guid.NewGuid(), + 10, + "Update command sent successfully to 10 device(s)"); + + _mockUpdateService + .Setup(s => s.SendUpdateCommandAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(expectedResult); + + // Act + var result = await _controller.TriggerUpdateAsync(request, CancellationToken.None); + + // Assert + var okResult = Assert.IsType(result.Result); + var returnValue = Assert.IsType(okResult.Value); + Assert.Equal(expectedResult.TargetDeviceCount, returnValue.TargetDeviceCount); + } + + [Fact] + public async Task TriggerUpdateAsync_ServiceThrowsException_Returns500() + { + // Arrange + var request = new CertificateUpdateController.CertificateUpdateRequest + { + FleetId = "fleet-01", + IssuedBy = "admin@example.com" + }; + + _mockUpdateService + .Setup(s => s.SendUpdateCommandAsync(It.IsAny(), It.IsAny())) + .ThrowsAsync(new Exception("Test exception")); + + // Act + var result = await _controller.TriggerUpdateAsync(request, CancellationToken.None); + + // Assert + var statusCodeResult = Assert.IsType(result.Result); + Assert.Equal(500, statusCodeResult.StatusCode); + } + + [Fact] + public async Task GetCommandStatusAsync_ValidCommandId_ReturnsOkResult() + { + // Arrange + var commandId = Guid.NewGuid(); + var expectedStatus = new CertificateUpdateCommandStatus( + commandId, + "fleet-01", + 10, + 5, + DateTimeOffset.UtcNow, + null, + "InProgress"); + + _mockUpdateService + .Setup(s => s.GetCommandStatusAsync(commandId, It.IsAny())) + .ReturnsAsync(expectedStatus); + + // Act + var result = await _controller.GetCommandStatusAsync(commandId, CancellationToken.None); + + // Assert + var okResult = Assert.IsType(result.Result); + var returnValue = Assert.IsType(okResult.Value); + Assert.Equal(commandId, returnValue.CommandId); + } + + [Fact] + public async Task GetCommandStatusAsync_CommandNotFound_ReturnsNotFound() + { + // Arrange + var commandId = Guid.NewGuid(); + + _mockUpdateService + .Setup(s => s.GetCommandStatusAsync(commandId, It.IsAny())) + .ReturnsAsync((CertificateUpdateCommandStatus?)null); + + // Act + var result = await _controller.GetCommandStatusAsync(commandId, CancellationToken.None); + + // Assert + Assert.IsType(result.Result); + } + + [Fact] + public async Task TriggerUpdateAsync_WithTargetDevices_SendsCorrectCommand() + { + // Arrange + var targetDevices = new[] { "DEVICE-01", "DEVICE-02" }; + var request = new CertificateUpdateController.CertificateUpdateRequest + { + FleetId = "fleet-01", + TargetDevices = targetDevices, + IssuedBy = "admin@example.com", + UpdateFlags = 0x5944 + }; + + CertificateUpdateCommand? capturedCommand = null; + _mockUpdateService + .Setup(s => s.SendUpdateCommandAsync(It.IsAny(), It.IsAny())) + .Callback((cmd, ct) => capturedCommand = cmd) + .ReturnsAsync(new CertificateUpdateResult(Guid.NewGuid(), 2, "Success")); + + // Act + await _controller.TriggerUpdateAsync(request, CancellationToken.None); + + // Assert + Assert.NotNull(capturedCommand); + Assert.Equal("fleet-01", capturedCommand.FleetId); + Assert.Equal(targetDevices, capturedCommand.TargetDevices); + Assert.Equal((uint)0x5944, capturedCommand.UpdateFlags); + Assert.Equal("admin@example.com", capturedCommand.IssuedBy); + } + } +} diff --git a/SecureBootDashboard.Api.Tests/SecureBootDashboard.Api.Tests.csproj b/SecureBootDashboard.Api.Tests/SecureBootDashboard.Api.Tests.csproj index 00391d6..e8635f4 100644 --- a/SecureBootDashboard.Api.Tests/SecureBootDashboard.Api.Tests.csproj +++ b/SecureBootDashboard.Api.Tests/SecureBootDashboard.Api.Tests.csproj @@ -12,6 +12,7 @@ + diff --git a/SecureBootDashboard.Api/Configuration/CertificateUpdateServiceOptions.cs b/SecureBootDashboard.Api/Configuration/CertificateUpdateServiceOptions.cs new file mode 100644 index 0000000..5b9cf66 --- /dev/null +++ b/SecureBootDashboard.Api/Configuration/CertificateUpdateServiceOptions.cs @@ -0,0 +1,75 @@ +using System; + +namespace SecureBootDashboard.Api.Configuration +{ + /// + /// Configuration options for the Certificate Update Service. + /// + public sealed class CertificateUpdateServiceOptions + { + /// + /// Whether the certificate update service is enabled. + /// + public bool Enabled { get; set; } = false; + + /// + /// Storage account queue service URI (e.g., https://mystorageaccount.queue.core.windows.net). + /// + public Uri? QueueServiceUri { get; set; } + + /// + /// Queue name for certificate update commands. + /// + public string CommandQueueName { get; set; } = "secureboot-update-commands"; + + /// + /// Authentication method: "ManagedIdentity", "AppRegistration", "Certificate", "DefaultAzureCredential", or "ConnectionString". + /// + public string AuthenticationMethod { get; set; } = "DefaultAzureCredential"; + + /// + /// Connection string (only used if AuthenticationMethod is "ConnectionString"). + /// + public string? ConnectionString { get; set; } + + /// + /// Application (Client) ID from Azure App Registration. + /// + public string? ClientId { get; set; } + + /// + /// Directory (Tenant) ID. + /// + public string? TenantId { get; set; } + + /// + /// Client Secret from App Registration. + /// + public string? ClientSecret { get; set; } + + /// + /// Path to certificate file (.pfx or .pem). + /// + public string? CertificatePath { get; set; } + + /// + /// Password for the certificate file. + /// + public string? CertificatePassword { get; set; } + + /// + /// Certificate thumbprint for certificate store. + /// + public string? CertificateThumbprint { get; set; } + + /// + /// Certificate store location. + /// + public string CertificateStoreLocation { get; set; } = "CurrentUser"; + + /// + /// Certificate store name. + /// + public string CertificateStoreName { get; set; } = "My"; + } +} diff --git a/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs b/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs new file mode 100644 index 0000000..d6a53d3 --- /dev/null +++ b/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs @@ -0,0 +1,134 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using SecureBootDashboard.Api.Services; +using SecureBootWatcher.Shared.Models; + +namespace SecureBootDashboard.Api.Controllers +{ + /// + /// Controller for managing certificate update commands. + /// + [ApiController] + [Route("api/[controller]")] + public sealed class CertificateUpdateController : ControllerBase + { + private readonly ICertificateUpdateService _updateService; + private readonly ILogger _logger; + + public CertificateUpdateController( + ICertificateUpdateService updateService, + ILogger logger) + { + _updateService = updateService; + _logger = logger; + } + + /// + /// Trigger certificate update for a fleet or group of devices. + /// + /// The update command request. + /// Cancellation token. + /// Result of the update command. + [HttpPost("trigger")] + public async Task> TriggerUpdateAsync( + [FromBody] CertificateUpdateRequest request, + CancellationToken cancellationToken) + { + try + { + _logger.LogInformation( + "Received certificate update request for fleet {FleetId}", + request.FleetId ?? "ALL"); + + var command = new CertificateUpdateCommand + { + FleetId = request.FleetId, + TargetDevices = request.TargetDevices ?? Array.Empty(), + UpdateFlags = request.UpdateFlags, + IssuedBy = request.IssuedBy, + Notes = request.Notes, + ExpiresAtUtc = request.ExpiresAtUtc + }; + + var result = await _updateService.SendUpdateCommandAsync(command, cancellationToken); + + return Ok(result); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to trigger certificate update"); + return StatusCode(500, new { Error = "Failed to trigger certificate update" }); + } + } + + /// + /// Get the status of a certificate update command. + /// + /// The command ID. + /// Cancellation token. + /// Command status. + [HttpGet("status/{commandId:guid}")] + public async Task> GetCommandStatusAsync( + Guid commandId, + CancellationToken cancellationToken) + { + try + { + var status = await _updateService.GetCommandStatusAsync(commandId, cancellationToken); + + if (status == null) + { + return NotFound(new { Error = "Command not found" }); + } + + return Ok(status); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to get command status for {CommandId}", commandId); + return StatusCode(500, new { Error = "Failed to get command status" }); + } + } + + /// + /// Request model for triggering certificate updates. + /// + public sealed record CertificateUpdateRequest + { + /// + /// The fleet ID to target. If null, targets all devices. + /// + public string? FleetId { get; init; } + + /// + /// Specific device machine names to target. If empty, targets all devices in fleet. + /// + public string[]? TargetDevices { get; init; } + + /// + /// The update flags to apply. If null, uses Windows defaults. + /// + public uint? UpdateFlags { get; init; } + + /// + /// When this command expires (optional). + /// + public DateTimeOffset? ExpiresAtUtc { get; init; } + + /// + /// User or system that issued this command. + /// + [Required] + public string? IssuedBy { get; init; } + + /// + /// Additional notes or reason for this update command. + /// + public string? Notes { get; init; } + } + } +} diff --git a/SecureBootDashboard.Api/Program.cs b/SecureBootDashboard.Api/Program.cs index b6636cc..e4db8b6 100644 --- a/SecureBootDashboard.Api/Program.cs +++ b/SecureBootDashboard.Api/Program.cs @@ -138,6 +138,26 @@ Log.Information("Configuring Export Service..."); builder.Services.AddScoped(); + // Configure Certificate Update Service + Log.Information("Configuring Certificate Update Service..."); + var updateConfig = builder.Configuration.GetSection("CertificateUpdateService"); + var updateEnabled = updateConfig.GetValue("Enabled"); + Log.Information("Certificate Update Service Enabled: {Enabled}", updateEnabled); + + if (updateEnabled) + { + var updateQueueUri = updateConfig.GetValue("QueueServiceUri"); + var updateQueueName = updateConfig.GetValue("CommandQueueName"); + var updateAuthMethod = updateConfig.GetValue("AuthenticationMethod"); + + Log.Information(" Command Queue URI: {QueueUri}", updateQueueUri); + Log.Information(" Command Queue Name: {QueueName}", updateQueueName); + Log.Information(" Auth Method: {AuthMethod}", updateAuthMethod); + } + + builder.Services.Configure(updateConfig); + builder.Services.AddScoped(); + // Configure Azure Queue Processor Log.Information("Configuring Queue Processor..."); var queueConfig = builder.Configuration.GetSection("QueueProcessor"); diff --git a/SecureBootDashboard.Api/Services/CertificateUpdateService.cs b/SecureBootDashboard.Api/Services/CertificateUpdateService.cs new file mode 100644 index 0000000..fe55f03 --- /dev/null +++ b/SecureBootDashboard.Api/Services/CertificateUpdateService.cs @@ -0,0 +1,297 @@ +using System; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Azure.Core; +using Azure.Identity; +using Azure.Storage.Queues; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using SecureBootDashboard.Api.Configuration; +using SecureBootDashboard.Api.Data; +using SecureBootWatcher.Shared.Models; +using SecureBootWatcher.Shared.Transport; + +namespace SecureBootDashboard.Api.Services +{ + /// + /// Service for managing certificate update commands via Azure Queue Storage. + /// + public sealed class CertificateUpdateService : ICertificateUpdateService + { + private readonly SecureBootDbContext _dbContext; + private readonly ILogger _logger; + private readonly IOptionsMonitor _optionsMonitor; + private QueueClient? _commandQueueClient; + + public CertificateUpdateService( + SecureBootDbContext dbContext, + ILogger logger, + IOptionsMonitor optionsMonitor) + { + _dbContext = dbContext; + _logger = logger; + _optionsMonitor = optionsMonitor; + } + + public async Task SendUpdateCommandAsync( + CertificateUpdateCommand command, + CancellationToken cancellationToken = default) + { + var options = _optionsMonitor.CurrentValue; + + if (!options.Enabled) + { + _logger.LogWarning("Certificate update service is disabled"); + return new CertificateUpdateResult( + command.CommandId, + 0, + "Certificate update service is disabled"); + } + + // Determine target devices + var targetDevices = await GetTargetDevicesAsync(command, cancellationToken); + var targetCount = targetDevices.Count; + + _logger.LogInformation( + "Certificate update command {CommandId} targeting {Count} devices in fleet {FleetId}", + command.CommandId, + targetCount, + command.FleetId ?? "ALL"); + + if (targetCount == 0) + { + return new CertificateUpdateResult( + command.CommandId, + 0, + "No devices found matching the criteria"); + } + + // Initialize queue client if needed + if (_commandQueueClient == null) + { + _commandQueueClient = CreateQueueClient(options); + if (_commandQueueClient == null) + { + throw new InvalidOperationException("Failed to create queue client"); + } + } + + // Send command to queue + var envelope = new CertificateUpdateCommandEnvelope + { + Command = command, + EnqueuedAtUtc = DateTimeOffset.UtcNow + }; + + var messageJson = JsonSerializer.Serialize(envelope); + + try + { + await _commandQueueClient.SendMessageAsync(messageJson, cancellationToken); + + _logger.LogInformation( + "Certificate update command {CommandId} sent to queue for {Count} devices", + command.CommandId, + targetCount); + + return new CertificateUpdateResult( + command.CommandId, + targetCount, + $"Update command sent successfully to {targetCount} device(s)"); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to send certificate update command {CommandId}", command.CommandId); + throw; + } + } + + public Task GetCommandStatusAsync( + Guid commandId, + CancellationToken cancellationToken = default) + { + // Note: This is a basic implementation. In a production system, you would: + // 1. Store command metadata in a database table + // 2. Track device acknowledgments and completion + // 3. Monitor queue message processing + + // For now, return a placeholder status + _logger.LogInformation("Getting status for command {CommandId}", commandId); + + // This would be enhanced to query a commands table in the database + var status = new CertificateUpdateCommandStatus( + commandId, + null, + 0, + 0, + DateTimeOffset.UtcNow, + null, + "Pending"); + + return Task.FromResult(status); + } + + private async Task> GetTargetDevicesAsync( + CertificateUpdateCommand command, + CancellationToken cancellationToken) + { + IQueryable query = _dbContext.Devices; + + // Filter by fleet if specified + if (!string.IsNullOrWhiteSpace(command.FleetId)) + { + query = query.Where(d => d.FleetId == command.FleetId); + } + + // Filter by specific devices if specified + if (command.TargetDevices != null && command.TargetDevices.Length > 0) + { + query = query.Where(d => command.TargetDevices.Contains(d.MachineName)); + } + + var devices = await query + .Select(d => d.MachineName) + .ToListAsync(cancellationToken); + + return devices; + } + + private QueueClient? CreateQueueClient(CertificateUpdateServiceOptions options) + { + try + { + // Connection String method + if (options.AuthenticationMethod.Equals("ConnectionString", StringComparison.OrdinalIgnoreCase) && + !string.IsNullOrWhiteSpace(options.ConnectionString)) + { + _logger.LogWarning("Using Connection String authentication. NOT recommended for production."); + return new QueueClient(options.ConnectionString, options.CommandQueueName); + } + + // Validate QueueServiceUri for Entra ID auth + if (options.QueueServiceUri == null) + { + _logger.LogError("QueueServiceUri is required for Entra ID authentication."); + return null; + } + + var queueUri = new Uri(options.QueueServiceUri, options.CommandQueueName); + TokenCredential credential; + + // App Registration + Client Secret + if (options.AuthenticationMethod.Equals("AppRegistration", StringComparison.OrdinalIgnoreCase)) + { + if (string.IsNullOrWhiteSpace(options.TenantId) || + string.IsNullOrWhiteSpace(options.ClientId) || + string.IsNullOrWhiteSpace(options.ClientSecret)) + { + _logger.LogError("TenantId, ClientId, and ClientSecret are required for AppRegistration authentication."); + return null; + } + + _logger.LogInformation("Using App Registration authentication for command queue"); + credential = new ClientSecretCredential(options.TenantId, options.ClientId, options.ClientSecret); + return new QueueClient(queueUri, credential); + } + + // Certificate-based authentication + if (options.AuthenticationMethod.Equals("Certificate", StringComparison.OrdinalIgnoreCase)) + { + if (string.IsNullOrWhiteSpace(options.TenantId) || string.IsNullOrWhiteSpace(options.ClientId)) + { + _logger.LogError("TenantId and ClientId are required for Certificate authentication."); + return null; + } + + X509Certificate2? certificate = null; + + // From file + if (!string.IsNullOrWhiteSpace(options.CertificatePath)) + { + var password = options.CertificatePassword; + certificate = string.IsNullOrWhiteSpace(password) + ? new X509Certificate2(options.CertificatePath) + : new X509Certificate2(options.CertificatePath, password); + _logger.LogInformation("Loaded certificate from file for command queue"); + } + // From Windows Certificate Store + else if (!string.IsNullOrWhiteSpace(options.CertificateThumbprint)) + { + var storeLocation = options.CertificateStoreLocation.Equals("LocalMachine", StringComparison.OrdinalIgnoreCase) + ? StoreLocation.LocalMachine + : StoreLocation.CurrentUser; + + var storeName = Enum.TryParse(options.CertificateStoreName, true, out var parsed) + ? parsed + : StoreName.My; + + using var store = new X509Store(storeName, storeLocation); + store.Open(OpenFlags.ReadOnly); + + var certificates = store.Certificates.Find( + X509FindType.FindByThumbprint, + options.CertificateThumbprint.Replace(" ", "").Replace(":", ""), + validOnly: false); + + if (certificates.Count == 0) + { + _logger.LogError("Certificate not found in store"); + return null; + } + + certificate = certificates[0]; + _logger.LogInformation("Loaded certificate from store for command queue"); + } + + if (certificate == null) + { + _logger.LogError("Certificate could not be loaded."); + return null; + } + + credential = new ClientCertificateCredential(options.TenantId, options.ClientId, certificate); + return new QueueClient(queueUri, credential); + } + + // Managed Identity + if (options.AuthenticationMethod.Equals("ManagedIdentity", StringComparison.OrdinalIgnoreCase)) + { + credential = string.IsNullOrWhiteSpace(options.ClientId) + ? new ManagedIdentityCredential() + : new ManagedIdentityCredential(options.ClientId); + + _logger.LogInformation("Using Managed Identity for command queue"); + return new QueueClient(queueUri, credential); + } + + // DefaultAzureCredential + if (options.AuthenticationMethod.Equals("DefaultAzureCredential", StringComparison.OrdinalIgnoreCase) || + string.IsNullOrWhiteSpace(options.AuthenticationMethod)) + { + var credentialOptions = new DefaultAzureCredentialOptions(); + + if (!string.IsNullOrWhiteSpace(options.ClientId)) + { + credentialOptions.ManagedIdentityClientId = options.ClientId; + } + + credential = new DefaultAzureCredential(credentialOptions); + _logger.LogInformation("Using DefaultAzureCredential for command queue"); + return new QueueClient(queueUri, credential); + } + + _logger.LogError("Invalid AuthenticationMethod: {Method}", options.AuthenticationMethod); + return null; + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to create command queue client"); + return null; + } + } + } +} diff --git a/SecureBootDashboard.Api/Services/ICertificateUpdateService.cs b/SecureBootDashboard.Api/Services/ICertificateUpdateService.cs new file mode 100644 index 0000000..f836560 --- /dev/null +++ b/SecureBootDashboard.Api/Services/ICertificateUpdateService.cs @@ -0,0 +1,53 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using SecureBootWatcher.Shared.Models; + +namespace SecureBootDashboard.Api.Services +{ + /// + /// Service for managing certificate update commands. + /// + public interface ICertificateUpdateService + { + /// + /// Sends a certificate update command for a fleet or group of devices. + /// + /// The update command to send. + /// Cancellation token. + /// The command ID and number of target devices. + Task SendUpdateCommandAsync( + CertificateUpdateCommand command, + CancellationToken cancellationToken = default); + + /// + /// Gets the status of a certificate update command. + /// + /// The command ID. + /// Cancellation token. + /// The command status. + Task GetCommandStatusAsync( + Guid commandId, + CancellationToken cancellationToken = default); + } + + /// + /// Result of sending a certificate update command. + /// + public sealed record CertificateUpdateResult( + Guid CommandId, + int TargetDeviceCount, + string Message); + + /// + /// Status of a certificate update command. + /// + public sealed record CertificateUpdateCommandStatus( + Guid CommandId, + string? FleetId, + int TargetDeviceCount, + int ProcessedDeviceCount, + DateTimeOffset IssuedAtUtc, + DateTimeOffset? CompletedAtUtc, + string Status); +} diff --git a/SecureBootDashboard.Api/appsettings.Development.json b/SecureBootDashboard.Api/appsettings.Development.json index 0c208ae..07a7c18 100644 --- a/SecureBootDashboard.Api/appsettings.Development.json +++ b/SecureBootDashboard.Api/appsettings.Development.json @@ -4,5 +4,11 @@ "Default": "Information", "Microsoft.AspNetCore": "Warning" } + }, + "CertificateUpdateService": { + "Enabled": false, + "QueueServiceUri": "https://localhost:10001/devstoreaccount1", + "CommandQueueName": "secureboot-update-commands", + "AuthenticationMethod": "DefaultAzureCredential" } } diff --git a/SecureBootDashboard.Api/appsettings.json b/SecureBootDashboard.Api/appsettings.json index 5c63528..9ccd043 100644 --- a/SecureBootDashboard.Api/appsettings.json +++ b/SecureBootDashboard.Api/appsettings.json @@ -64,5 +64,16 @@ "EmptyQueuePollInterval": "00:00:10", "VisibilityTimeout": "00:05:00", "MaxDequeueCount": 5 + }, + "CertificateUpdateService": { + "Enabled": false, + "QueueServiceUri": "https://secbootcert.queue.core.windows.net", + "CommandQueueName": "secureboot-update-commands", + "AuthenticationMethod": "Certificate", + "TenantId": "d6dbad84-5922-4700-a049-c7068c37c884", + "ClientId": "c8034569-4990-4823-9f1d-b46223789c35", + "CertificateThumbprint": "61FC110D5BABD61419B106862B304C2FFF57A262", + "CertificateStoreLocation": "LocalMachine", + "CertificateStoreName": "My" } } diff --git a/SecureBootWatcher.Shared/Models/CertificateUpdateCommand.cs b/SecureBootWatcher.Shared/Models/CertificateUpdateCommand.cs new file mode 100644 index 0000000..09fa653 --- /dev/null +++ b/SecureBootWatcher.Shared/Models/CertificateUpdateCommand.cs @@ -0,0 +1,50 @@ +using System; + +namespace SecureBootWatcher.Shared.Models +{ + /// + /// Represents a command to trigger certificate updates on client devices. + /// + public sealed class CertificateUpdateCommand + { + /// + /// Unique identifier for this command. + /// + public Guid CommandId { get; set; } = Guid.NewGuid(); + + /// + /// The fleet ID this command targets. If null, targets all devices. + /// + public string? FleetId { get; set; } + + /// + /// Specific device machine names to target. If empty, targets all devices in fleet. + /// + public string[] TargetDevices { get; set; } = Array.Empty(); + + /// + /// The update flags to apply. If null, uses Windows defaults. + /// + public uint? UpdateFlags { get; set; } + + /// + /// When this command was issued. + /// + public DateTimeOffset IssuedAtUtc { get; set; } = DateTimeOffset.UtcNow; + + /// + /// When this command expires (optional). + /// + public DateTimeOffset? ExpiresAtUtc { get; set; } + + /// + /// User or system that issued this command. + /// + public string? IssuedBy { get; set; } + + /// + /// Additional notes or reason for this update command. + /// + public string? Notes { get; set; } + } +} diff --git a/SecureBootWatcher.Shared/Transport/CertificateUpdateCommandEnvelope.cs b/SecureBootWatcher.Shared/Transport/CertificateUpdateCommandEnvelope.cs new file mode 100644 index 0000000..8d9fde7 --- /dev/null +++ b/SecureBootWatcher.Shared/Transport/CertificateUpdateCommandEnvelope.cs @@ -0,0 +1,19 @@ +using System; +using SecureBootWatcher.Shared.Models; + +namespace SecureBootWatcher.Shared.Transport +{ + /// + /// Represents the payload for certificate update commands in Azure Queue Storage. + /// + public sealed class CertificateUpdateCommandEnvelope + { + public string Version { get; set; } = "1.0"; + + public string MessageType { get; set; } = "CertificateUpdateCommand"; + + public CertificateUpdateCommand Command { get; set; } = new CertificateUpdateCommand(); + + public DateTimeOffset EnqueuedAtUtc { get; set; } = DateTimeOffset.UtcNow; + } +} From a5b931a41b04b05b0af59b326e26545f86364281 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Nov 2025 00:05:19 +0000 Subject: [PATCH 04/23] Add comprehensive documentation for certificate update API Co-authored-by: robgrame <12012136+robgrame@users.noreply.github.com> --- .../SecureBootDashboard.Api.AssemblyInfo.cs | 1 - .../SecureBootWatcher.Shared.AssemblyInfo.cs | 1 - docs/CERTIFICATE_UPDATE_API.md | 457 ++++++++++++++++++ 3 files changed, 457 insertions(+), 2 deletions(-) create mode 100644 docs/CERTIFICATE_UPDATE_API.md diff --git a/SecureBootDashboard.Api/obj/Debug/net8.0/SecureBootDashboard.Api.AssemblyInfo.cs b/SecureBootDashboard.Api/obj/Debug/net8.0/SecureBootDashboard.Api.AssemblyInfo.cs index 2a163df..f9ae54c 100644 --- a/SecureBootDashboard.Api/obj/Debug/net8.0/SecureBootDashboard.Api.AssemblyInfo.cs +++ b/SecureBootDashboard.Api/obj/Debug/net8.0/SecureBootDashboard.Api.AssemblyInfo.cs @@ -1,7 +1,6 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. diff --git a/SecureBootWatcher.Shared/obj/Debug/netstandard2.0/SecureBootWatcher.Shared.AssemblyInfo.cs b/SecureBootWatcher.Shared/obj/Debug/netstandard2.0/SecureBootWatcher.Shared.AssemblyInfo.cs index 448768b..cb91eaa 100644 --- a/SecureBootWatcher.Shared/obj/Debug/netstandard2.0/SecureBootWatcher.Shared.AssemblyInfo.cs +++ b/SecureBootWatcher.Shared/obj/Debug/netstandard2.0/SecureBootWatcher.Shared.AssemblyInfo.cs @@ -1,7 +1,6 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. diff --git a/docs/CERTIFICATE_UPDATE_API.md b/docs/CERTIFICATE_UPDATE_API.md new file mode 100644 index 0000000..f9e67fe --- /dev/null +++ b/docs/CERTIFICATE_UPDATE_API.md @@ -0,0 +1,457 @@ +# Certificate Update API + +## Overview + +The Certificate Update API allows administrators to remotely trigger Secure Boot certificate updates for groups of devices. This feature enables centralized management of certificate deployment across Windows fleets. + +## Architecture + +The certificate update feature uses a command queue pattern: + +``` +┌─────────────────────────────────────────────┐ +│ Dashboard API (ASP.NET Core 8) │ +│ ┌───────────────────────────────────────┐ │ +│ │ POST /api/CertificateUpdate/trigger │ │ +│ │ • Validates request │ │ +│ │ • Queries target devices │ │ +│ │ • Sends command to Azure Queue │ │ +│ └───────────────────────────────────────┘ │ +└─────────────────────────────────────────────┘ + │ + ▼ + ┌───────────────────────────────┐ + │ Azure Queue Storage │ + │ secureboot-update-commands │ + └───────────────────────────────┘ + │ + ▼ (clients poll) +┌─────────────────────────────────────────────┐ +│ Windows Devices (.NET Framework 4.8) │ +│ ┌───────────────────────────────────────┐ │ +│ │ SecureBootWatcher.Client │ │ +│ │ • Polls command queue │ │ +│ │ • Processes update commands │ │ +│ │ • Applies certificate updates │ │ +│ │ • Reports status │ │ +│ └───────────────────────────────────────┘ │ +└─────────────────────────────────────────────┘ +``` + +## API Endpoints + +### Trigger Certificate Update + +Sends a certificate update command for a fleet or specific devices. + +**Endpoint**: `POST /api/CertificateUpdate/trigger` + +**Request Body**: +```json +{ + "fleetId": "fleet-01", + "targetDevices": ["DEVICE-01", "DEVICE-02"], + "updateFlags": 23876, + "issuedBy": "admin@example.com", + "notes": "Deploying UEFI CA 2023 certificates", + "expiresAtUtc": "2025-12-31T23:59:59Z" +} +``` + +**Request Parameters**: + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `fleetId` | string | No | The fleet ID to target. If null, targets all devices. | +| `targetDevices` | string[] | No | Specific device machine names. If empty, targets all devices in fleet. | +| `updateFlags` | uint | No | The update flags to apply. If null, uses Windows defaults. See [Update Flags](#update-flags) below. | +| `issuedBy` | string | Yes | User or system that issued this command. | +| `notes` | string | No | Additional notes or reason for this update. | +| `expiresAtUtc` | DateTimeOffset | No | When this command expires (optional). | + +**Response** (200 OK): +```json +{ + "commandId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "targetDeviceCount": 10, + "message": "Update command sent successfully to 10 device(s)" +} +``` + +**Response** (500 Internal Server Error): +```json +{ + "error": "Failed to trigger certificate update" +} +``` + +### Get Command Status + +Retrieves the status of a certificate update command. + +**Endpoint**: `GET /api/CertificateUpdate/status/{commandId}` + +**Response** (200 OK): +```json +{ + "commandId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "fleetId": "fleet-01", + "targetDeviceCount": 10, + "processedDeviceCount": 5, + "issuedAtUtc": "2025-11-07T23:00:00Z", + "completedAtUtc": null, + "status": "InProgress" +} +``` + +**Response** (404 Not Found): +```json +{ + "error": "Command not found" +} +``` + +## Update Flags + +The `updateFlags` parameter controls which certificates are deployed. Common values: + +| Value (Hex) | Value (Decimal) | Description | +|-------------|-----------------|-------------| +| `0x5944` | 23876 | Initial state - All updates pending | +| `0x5904` | 22788 | Windows UEFI CA 2023 applied | +| `0x5104` | 20740 | Microsoft UEFI CA 2023 applied | +| `0x4104` | 16644 | Microsoft Option ROM CA 2023 applied | +| `0x4100` | 16640 | Microsoft KEK 2023 applied | +| `0x4000` | 16384 | Deployment complete | +| `null` | - | Use Windows defaults | + +Individual flags: + +| Flag | Value (Hex) | Value (Decimal) | Description | +|------|-------------|-----------------|-------------| +| `WindowsUefiCA2023` | `0x0040` | 64 | Apply Windows UEFI CA 2023 certificate | +| `MicrosoftUefiCA2023` | `0x0800` | 2048 | Apply Microsoft UEFI CA 2023 | +| `MicrosoftOptionRomCA2023` | `0x1000` | 4096 | Apply Microsoft Option ROM CA 2023 | +| `MicrosoftKEK2023` | `0x0004` | 4 | Apply Microsoft KEK 2023 | +| `WindowsBootManager2023` | `0x0100` | 256 | Apply Windows Boot Manager 2023 | +| `ConditionalMicrosoftCAs` | `0x4000` | 16384 | Apply MS CAs only if MS UEFI CA 2011 exists | + +## Configuration + +### Enable the Service + +In `appsettings.json`: + +```json +{ + "CertificateUpdateService": { + "Enabled": true, + "QueueServiceUri": "https://yourstorageaccount.queue.core.windows.net", + "CommandQueueName": "secureboot-update-commands", + "AuthenticationMethod": "DefaultAzureCredential" + } +} +``` + +### Configuration Options + +| Setting | Description | Default | +|---------|-------------|---------| +| `Enabled` | Enable/disable the certificate update service | `false` | +| `QueueServiceUri` | Azure Storage Queue service URI | - | +| `CommandQueueName` | Queue name for update commands | `secureboot-update-commands` | +| `AuthenticationMethod` | Authentication method (see below) | `DefaultAzureCredential` | + +### Authentication Methods + +The service supports multiple authentication methods: + +#### 1. DefaultAzureCredential (Recommended for Development) +```json +{ + "AuthenticationMethod": "DefaultAzureCredential" +} +``` +Tries multiple credential sources automatically (environment variables, managed identity, Visual Studio, Azure CLI, etc.). + +#### 2. Managed Identity (Recommended for Production) +```json +{ + "AuthenticationMethod": "ManagedIdentity", + "ClientId": "optional-client-id-for-user-assigned-identity" +} +``` + +#### 3. App Registration (Client Secret) +```json +{ + "AuthenticationMethod": "AppRegistration", + "TenantId": "your-tenant-id", + "ClientId": "your-client-id", + "ClientSecret": "your-client-secret" +} +``` + +#### 4. Certificate-based Authentication +```json +{ + "AuthenticationMethod": "Certificate", + "TenantId": "your-tenant-id", + "ClientId": "your-client-id", + "CertificateThumbprint": "YOUR-CERT-THUMBPRINT", + "CertificateStoreLocation": "LocalMachine", + "CertificateStoreName": "My" +} +``` + +Or with certificate file: +```json +{ + "AuthenticationMethod": "Certificate", + "TenantId": "your-tenant-id", + "ClientId": "your-client-id", + "CertificatePath": "C:\\path\\to\\certificate.pfx", + "CertificatePassword": "certificate-password" +} +``` + +## Azure Setup + +### 1. Create Storage Account Queue + +```bash +# Create queue using Azure CLI +az storage queue create \ + --name secureboot-update-commands \ + --account-name yourstorageaccount +``` + +### 2. Assign Permissions + +For Managed Identity or App Registration: + +```bash +# Get the principal ID (managed identity or service principal) +PRINCIPAL_ID="your-principal-id" + +# Assign Storage Queue Data Contributor role +az role assignment create \ + --role "Storage Queue Data Contributor" \ + --assignee $PRINCIPAL_ID \ + --scope "/subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.Storage/storageAccounts/{storage-account}/queueServices/default/queues/secureboot-update-commands" +``` + +## Usage Examples + +### Example 1: Update All Devices in a Fleet + +```bash +curl -X POST https://your-api.azurewebsites.net/api/CertificateUpdate/trigger \ + -H "Content-Type: application/json" \ + -d '{ + "fleetId": "fleet-production", + "issuedBy": "admin@contoso.com", + "notes": "Deploying UEFI CA 2023 to production fleet" + }' +``` + +### Example 2: Update Specific Devices + +```bash +curl -X POST https://your-api.azurewebsites.net/api/CertificateUpdate/trigger \ + -H "Content-Type: application/json" \ + -d '{ + "targetDevices": ["WORKSTATION-01", "WORKSTATION-02", "WORKSTATION-03"], + "issuedBy": "admin@contoso.com", + "notes": "Test deployment to pilot group" + }' +``` + +### Example 3: Update with Custom Flags + +```bash +curl -X POST https://your-api.azurewebsites.net/api/CertificateUpdate/trigger \ + -H "Content-Type: application/json" \ + -d '{ + "fleetId": "fleet-test", + "updateFlags": 64, + "issuedBy": "admin@contoso.com", + "notes": "Deploy only Windows UEFI CA 2023 (0x0040)" + }' +``` + +### Example 4: Check Command Status + +```bash +curl https://your-api.azurewebsites.net/api/CertificateUpdate/status/3fa85f64-5717-4562-b3fc-2c963f66afa6 +``` + +## Client Implementation (Future) + +The client-side implementation to poll and process update commands is not yet implemented. The client will need to: + +1. Poll the `secureboot-update-commands` queue periodically +2. Receive and deserialize `CertificateUpdateCommandEnvelope` messages +3. Check if the command targets this device (by fleet ID or machine name) +4. Apply the certificate updates using Windows Update or registry manipulation +5. Send a status report back to the API +6. Delete the message from the queue + +**Client polling logic** (pseudocode): +```csharp +while (true) +{ + var messages = await queueClient.ReceiveMessagesAsync(); + + foreach (var message in messages) + { + var envelope = JsonSerializer.Deserialize(message.MessageText); + var command = envelope.Command; + + // Check if this device is targeted + if (ShouldProcessCommand(command)) + { + // Apply the update + await ApplyCertificateUpdateAsync(command); + + // Report status + await ReportUpdateStatusAsync(command.CommandId); + } + + // Delete message + await queueClient.DeleteMessageAsync(message.MessageId, message.PopReceipt); + } + + await Task.Delay(TimeSpan.FromMinutes(5)); +} +``` + +## Security Considerations + +- **Authentication**: Add `[Authorize]` attribute to controller endpoints in production +- **Authorization**: Implement role-based access control (RBAC) to restrict who can trigger updates +- **Audit Logging**: All update commands are logged with issuer information +- **Command Expiration**: Use `expiresAtUtc` to prevent old commands from being processed +- **Queue Access**: Use managed identity or certificates instead of connection strings +- **TLS/HTTPS**: Ensure all API calls use HTTPS +- **Input Validation**: The API validates all input parameters + +## Monitoring + +### Application Insights Queries + +**Track update commands sent**: +```kusto +traces +| where message contains "Certificate update command" +| where message contains "sent to queue" +| project timestamp, commandId = extract("CommandId: ([a-f0-9-]+)", 1, message), deviceCount = extract("for ([0-9]+) devices", 1, message) +``` + +**Track update failures**: +```kusto +exceptions +| where outerMessage contains "CertificateUpdate" +| project timestamp, problemId, outerMessage, innermostMessage +``` + +### Metrics to Monitor + +- Number of update commands issued per day +- Success rate of command queue sends +- Average devices targeted per command +- Command processing latency +- Queue message age + +## Troubleshooting + +### Service is Disabled + +**Symptom**: API returns "Certificate update service is disabled" + +**Solution**: Set `CertificateUpdateService:Enabled` to `true` in configuration + +### Queue Client Creation Failed + +**Symptom**: Error "Failed to create queue client" + +**Solution**: +- Verify `QueueServiceUri` is correct +- Check authentication settings (TenantId, ClientId, etc.) +- Ensure queue exists in storage account +- Verify permissions on the queue + +### No Devices Found + +**Symptom**: Response shows `targetDeviceCount: 0` + +**Solution**: +- Verify fleet ID is correct +- Check device names are exact matches +- Ensure devices have reported to the API at least once + +### Authentication Failed + +**Symptom**: Azure authentication errors in logs + +**Solution**: +- For Managed Identity: Ensure identity is assigned to App Service +- For App Registration: Verify client secret hasn't expired +- For Certificate: Check certificate is installed and accessible +- Verify role assignments on storage queue + +## API Reference + +### Models + +#### CertificateUpdateCommand + +```csharp +public sealed class CertificateUpdateCommand +{ + public Guid CommandId { get; set; } + public string? FleetId { get; set; } + public string[] TargetDevices { get; set; } + public uint? UpdateFlags { get; set; } + public DateTimeOffset IssuedAtUtc { get; set; } + public DateTimeOffset? ExpiresAtUtc { get; set; } + public string? IssuedBy { get; set; } + public string? Notes { get; set; } +} +``` + +#### CertificateUpdateCommandEnvelope + +```csharp +public sealed class CertificateUpdateCommandEnvelope +{ + public string Version { get; set; } = "1.0"; + public string MessageType { get; set; } = "CertificateUpdateCommand"; + public CertificateUpdateCommand Command { get; set; } + public DateTimeOffset EnqueuedAtUtc { get; set; } +} +``` + +## Future Enhancements + +1. **Command Tracking**: Store commands in database with full audit trail +2. **Device Acknowledgment**: Track which devices have processed each command +3. **Retry Logic**: Automatic retry for failed updates +4. **Scheduling**: Schedule updates for specific date/time +5. **Staged Rollout**: Deploy to devices in batches over time +6. **Approval Workflow**: Multi-stage approval before sending commands +7. **PowerShell Integration**: PowerShell cmdlets for triggering updates +8. **Web UI**: Dashboard interface for managing update campaigns + +## Related Documentation + +- [Secure Boot Update Flags](../SecureBootWatcher.Shared/Models/SecureBootUpdateFlags.cs) +- [Azure Queue Entra ID Authentication](AZURE_QUEUE_ENTRA_ID_AUTH.md) +- [Queue Processor Service](QUEUE_PROCESSOR_MONITORING.md) +- [API Architecture](WEB_IMPLEMENTATION_SUMMARY.md) + +## Support + +For issues or questions: +- GitHub Issues: [Report bugs or request features](https://github.com/robgrame/Nimbus.BootCertWatcher/issues) +- Documentation: See [docs/](.) directory From 5a368dc6923b3ce8b24c048332f55967930ac8de Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Nov 2025 00:10:17 +0000 Subject: [PATCH 05/23] Fix log forging security vulnerabilities Co-authored-by: robgrame <12012136+robgrame@users.noreply.github.com> --- .../Controllers/CertificateUpdateController.cs | 4 +++- SecureBootDashboard.Api/Services/CertificateUpdateService.cs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs b/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs index d6a53d3..ac0cb42 100644 --- a/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs +++ b/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs @@ -40,9 +40,11 @@ public async Task> TriggerUpdateAsync( { try { + // Sanitize fleet ID for logging to prevent log forging + var sanitizedFleetId = request.FleetId?.Replace("\r", "").Replace("\n", "") ?? "ALL"; _logger.LogInformation( "Received certificate update request for fleet {FleetId}", - request.FleetId ?? "ALL"); + sanitizedFleetId); var command = new CertificateUpdateCommand { diff --git a/SecureBootDashboard.Api/Services/CertificateUpdateService.cs b/SecureBootDashboard.Api/Services/CertificateUpdateService.cs index fe55f03..10b0bec 100644 --- a/SecureBootDashboard.Api/Services/CertificateUpdateService.cs +++ b/SecureBootDashboard.Api/Services/CertificateUpdateService.cs @@ -56,11 +56,13 @@ public async Task SendUpdateCommandAsync( var targetDevices = await GetTargetDevicesAsync(command, cancellationToken); var targetCount = targetDevices.Count; + // Sanitize fleet ID for logging to prevent log forging + var sanitizedFleetId = command.FleetId?.Replace("\r", "").Replace("\n", "") ?? "ALL"; _logger.LogInformation( "Certificate update command {CommandId} targeting {Count} devices in fleet {FleetId}", command.CommandId, targetCount, - command.FleetId ?? "ALL"); + sanitizedFleetId); if (targetCount == 0) { From ecc70477526cc7d033c20ee0c5d6ebc4134dd62d Mon Sep 17 00:00:00 2001 From: Roberto Gramellini Date: Sat, 8 Nov 2025 08:24:33 +0100 Subject: [PATCH 06/23] Update CertificateUpdateController.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Controllers/CertificateUpdateController.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs b/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs index ac0cb42..dbcfff1 100644 --- a/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs +++ b/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs @@ -46,13 +46,17 @@ public async Task> TriggerUpdateAsync( "Received certificate update request for fleet {FleetId}", sanitizedFleetId); + // Sanitize IssuedBy and Notes to prevent log forging + var sanitizedIssuedBy = request.IssuedBy?.Replace("\r", "").Replace("\n", ""); + var sanitizedNotes = request.Notes?.Replace("\r", "").Replace("\n", ""); + var command = new CertificateUpdateCommand { FleetId = request.FleetId, TargetDevices = request.TargetDevices ?? Array.Empty(), UpdateFlags = request.UpdateFlags, - IssuedBy = request.IssuedBy, - Notes = request.Notes, + IssuedBy = sanitizedIssuedBy, + Notes = sanitizedNotes, ExpiresAtUtc = request.ExpiresAtUtc }; From c816fad09d8e58c94989f0d523ffff6718337957 Mon Sep 17 00:00:00 2001 From: Roberto Gramellini Date: Sat, 8 Nov 2025 08:24:50 +0100 Subject: [PATCH 07/23] Update CERTIFICATE_UPDATE_API.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/CERTIFICATE_UPDATE_API.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/CERTIFICATE_UPDATE_API.md b/docs/CERTIFICATE_UPDATE_API.md index f9e67fe..5f9988c 100644 --- a/docs/CERTIFICATE_UPDATE_API.md +++ b/docs/CERTIFICATE_UPDATE_API.md @@ -316,10 +316,10 @@ while (true) // Report status await ReportUpdateStatusAsync(command.CommandId); + + // Delete message only if processed by this device + await queueClient.DeleteMessageAsync(message.MessageId, message.PopReceipt); } - - // Delete message - await queueClient.DeleteMessageAsync(message.MessageId, message.PopReceipt); } await Task.Delay(TimeSpan.FromMinutes(5)); From 9e9d315f2174121c8d8c0933e2c223b028493b3b Mon Sep 17 00:00:00 2001 From: Roberto Gramellini Date: Sat, 8 Nov 2025 08:25:01 +0100 Subject: [PATCH 08/23] Update CertificateUpdateController.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../CertificateUpdateController.cs | 40 ++++++++----------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs b/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs index dbcfff1..8ac2189 100644 --- a/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs +++ b/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs @@ -42,33 +42,25 @@ public async Task> TriggerUpdateAsync( { // Sanitize fleet ID for logging to prevent log forging var sanitizedFleetId = request.FleetId?.Replace("\r", "").Replace("\n", "") ?? "ALL"; - _logger.LogInformation( - "Received certificate update request for fleet {FleetId}", - sanitizedFleetId); + // Sanitize fleet ID for logging to prevent log forging + var sanitizedFleetId = request.FleetId?.Replace("\r", "").Replace("\n", "") ?? "ALL"; + _logger.LogInformation( + "Received certificate update request for fleet {FleetId}", + sanitizedFleetId); - // Sanitize IssuedBy and Notes to prevent log forging - var sanitizedIssuedBy = request.IssuedBy?.Replace("\r", "").Replace("\n", ""); - var sanitizedNotes = request.Notes?.Replace("\r", "").Replace("\n", ""); - - var command = new CertificateUpdateCommand - { - FleetId = request.FleetId, - TargetDevices = request.TargetDevices ?? Array.Empty(), - UpdateFlags = request.UpdateFlags, - IssuedBy = sanitizedIssuedBy, - Notes = sanitizedNotes, - ExpiresAtUtc = request.ExpiresAtUtc - }; + var command = new CertificateUpdateCommand + { + FleetId = request.FleetId, + TargetDevices = request.TargetDevices ?? Array.Empty(), + UpdateFlags = request.UpdateFlags, + IssuedBy = request.IssuedBy, + Notes = request.Notes, + ExpiresAtUtc = request.ExpiresAtUtc + }; - var result = await _updateService.SendUpdateCommandAsync(command, cancellationToken); + var result = await _updateService.SendUpdateCommandAsync(command, cancellationToken); - return Ok(result); - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to trigger certificate update"); - return StatusCode(500, new { Error = "Failed to trigger certificate update" }); - } + return Ok(result); } /// From a1fa9d80a4f93d85d23ef0ae6bbcac800c2af638 Mon Sep 17 00:00:00 2001 From: Roberto Gramellini Date: Sat, 8 Nov 2025 08:33:37 +0100 Subject: [PATCH 09/23] Update CertificateUpdateController.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Controllers/CertificateUpdateController.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs b/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs index 8ac2189..497d463 100644 --- a/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs +++ b/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs @@ -61,6 +61,12 @@ public async Task> TriggerUpdateAsync( var result = await _updateService.SendUpdateCommandAsync(command, cancellationToken); return Ok(result); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to trigger certificate update for fleet {FleetId}", request.FleetId); + return StatusCode(500, new { Error = "Failed to trigger certificate update" }); + } } /// From 8d2962316ecfd7ceb211f23ac0bf01f452a8f285 Mon Sep 17 00:00:00 2001 From: Roberto Gramellini Date: Sat, 8 Nov 2025 08:33:50 +0100 Subject: [PATCH 10/23] Update CertificateUpdateService.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- SecureBootDashboard.Api/Services/CertificateUpdateService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SecureBootDashboard.Api/Services/CertificateUpdateService.cs b/SecureBootDashboard.Api/Services/CertificateUpdateService.cs index 10b0bec..b322670 100644 --- a/SecureBootDashboard.Api/Services/CertificateUpdateService.cs +++ b/SecureBootDashboard.Api/Services/CertificateUpdateService.cs @@ -245,7 +245,7 @@ public async Task SendUpdateCommandAsync( return null; } - certificate = certificates[0]; + certificate = new X509Certificate2(certificates[0].RawData); _logger.LogInformation("Loaded certificate from store for command queue"); } From 31430aee6ef315b517af6301e7fb4e8f50361b0f Mon Sep 17 00:00:00 2001 From: Roberto Gramellini Date: Sat, 8 Nov 2025 08:33:57 +0100 Subject: [PATCH 11/23] Update Program.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- SecureBootDashboard.Api/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SecureBootDashboard.Api/Program.cs b/SecureBootDashboard.Api/Program.cs index e4db8b6..4e1943f 100644 --- a/SecureBootDashboard.Api/Program.cs +++ b/SecureBootDashboard.Api/Program.cs @@ -152,7 +152,7 @@ Log.Information(" Command Queue URI: {QueueUri}", updateQueueUri); Log.Information(" Command Queue Name: {QueueName}", updateQueueName); - Log.Information(" Auth Method: {AuthMethod}", updateAuthMethod); + Log.Information(" Auth Method is configured."); } builder.Services.Configure(updateConfig); From 804ee9e9370bc112c9934c62d257684479849d29 Mon Sep 17 00:00:00 2001 From: Roberto Gramellini Date: Sat, 8 Nov 2025 08:34:05 +0100 Subject: [PATCH 12/23] Update CertificateUpdateController.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Controllers/CertificateUpdateController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs b/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs index 497d463..403a621 100644 --- a/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs +++ b/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs @@ -41,7 +41,6 @@ public async Task> TriggerUpdateAsync( try { // Sanitize fleet ID for logging to prevent log forging - var sanitizedFleetId = request.FleetId?.Replace("\r", "").Replace("\n", "") ?? "ALL"; // Sanitize fleet ID for logging to prevent log forging var sanitizedFleetId = request.FleetId?.Replace("\r", "").Replace("\n", "") ?? "ALL"; _logger.LogInformation( From 21020e2b9ae9d29383459af3149e2870bbc537bb Mon Sep 17 00:00:00 2001 From: Roberto Gramellini Date: Sat, 8 Nov 2025 08:54:14 +0100 Subject: [PATCH 13/23] Update CertificateUpdateController.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Controllers/CertificateUpdateController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs b/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs index 403a621..c573ab1 100644 --- a/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs +++ b/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs @@ -41,7 +41,6 @@ public async Task> TriggerUpdateAsync( try { // Sanitize fleet ID for logging to prevent log forging - // Sanitize fleet ID for logging to prevent log forging var sanitizedFleetId = request.FleetId?.Replace("\r", "").Replace("\n", "") ?? "ALL"; _logger.LogInformation( "Received certificate update request for fleet {FleetId}", From aac2168e366220d67ece7a719e9cebcce7de367e Mon Sep 17 00:00:00 2001 From: Roberto Gramellini Date: Sat, 8 Nov 2025 08:54:24 +0100 Subject: [PATCH 14/23] Update Program.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- SecureBootDashboard.Api/Program.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/SecureBootDashboard.Api/Program.cs b/SecureBootDashboard.Api/Program.cs index 4e1943f..5a8307e 100644 --- a/SecureBootDashboard.Api/Program.cs +++ b/SecureBootDashboard.Api/Program.cs @@ -148,7 +148,6 @@ { var updateQueueUri = updateConfig.GetValue("QueueServiceUri"); var updateQueueName = updateConfig.GetValue("CommandQueueName"); - var updateAuthMethod = updateConfig.GetValue("AuthenticationMethod"); Log.Information(" Command Queue URI: {QueueUri}", updateQueueUri); Log.Information(" Command Queue Name: {QueueName}", updateQueueName); From f0fd1e925a78ad5b221195620db55df52be4ba47 Mon Sep 17 00:00:00 2001 From: Roberto Gramellini Date: Sat, 8 Nov 2025 08:54:37 +0100 Subject: [PATCH 15/23] Update CertificateUpdateController.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Controllers/CertificateUpdateController.cs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs b/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs index c573ab1..1f4a750 100644 --- a/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs +++ b/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs @@ -78,22 +78,14 @@ public async Task> GetCommandStatus Guid commandId, CancellationToken cancellationToken) { - try - { - var status = await _updateService.GetCommandStatusAsync(commandId, cancellationToken); - - if (status == null) - { - return NotFound(new { Error = "Command not found" }); - } + var status = await _updateService.GetCommandStatusAsync(commandId, cancellationToken); - return Ok(status); - } - catch (Exception ex) + if (status == null) { - _logger.LogError(ex, "Failed to get command status for {CommandId}", commandId); - return StatusCode(500, new { Error = "Failed to get command status" }); + return NotFound(new { Error = "Command not found" }); } + + return Ok(status); } /// From 386f0d7144cb8b622b592b266e8d23be0a0936f2 Mon Sep 17 00:00:00 2001 From: Roberto Gramellini Date: Sat, 8 Nov 2025 10:53:21 +0100 Subject: [PATCH 16/23] Update CertificateUpdateService.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- SecureBootDashboard.Api/Services/CertificateUpdateService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SecureBootDashboard.Api/Services/CertificateUpdateService.cs b/SecureBootDashboard.Api/Services/CertificateUpdateService.cs index b322670..10b0bec 100644 --- a/SecureBootDashboard.Api/Services/CertificateUpdateService.cs +++ b/SecureBootDashboard.Api/Services/CertificateUpdateService.cs @@ -245,7 +245,7 @@ public async Task SendUpdateCommandAsync( return null; } - certificate = new X509Certificate2(certificates[0].RawData); + certificate = certificates[0]; _logger.LogInformation("Loaded certificate from store for command queue"); } From 1cefd18fa04d7c50bdacaf23c793f5cd9bf76d7f Mon Sep 17 00:00:00 2001 From: Roberto Gramellini Date: Sat, 8 Nov 2025 11:23:17 +0100 Subject: [PATCH 17/23] Update CertificateUpdateService.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Services/CertificateUpdateService.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/SecureBootDashboard.Api/Services/CertificateUpdateService.cs b/SecureBootDashboard.Api/Services/CertificateUpdateService.cs index 10b0bec..571287e 100644 --- a/SecureBootDashboard.Api/Services/CertificateUpdateService.cs +++ b/SecureBootDashboard.Api/Services/CertificateUpdateService.cs @@ -234,19 +234,20 @@ public async Task SendUpdateCommandAsync( using var store = new X509Store(storeName, storeLocation); store.Open(OpenFlags.ReadOnly); - var certificates = store.Certificates.Find( + using (var certificates = store.Certificates.Find( X509FindType.FindByThumbprint, options.CertificateThumbprint.Replace(" ", "").Replace(":", ""), - validOnly: false); - - if (certificates.Count == 0) + validOnly: false)) { - _logger.LogError("Certificate not found in store"); - return null; + if (certificates.Count == 0) + { + _logger.LogError("Certificate not found in store"); + return null; + } + + certificate = certificates[0]; + _logger.LogInformation("Loaded certificate from store for command queue"); } - - certificate = certificates[0]; - _logger.LogInformation("Loaded certificate from store for command queue"); } if (certificate == null) From e6f1461299d4757d6a08d66d2b656f4ac8b1866d Mon Sep 17 00:00:00 2001 From: Roberto Gramellini Date: Sat, 8 Nov 2025 11:23:26 +0100 Subject: [PATCH 18/23] Update Program.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- SecureBootDashboard.Api/Program.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SecureBootDashboard.Api/Program.cs b/SecureBootDashboard.Api/Program.cs index 5a8307e..739acc6 100644 --- a/SecureBootDashboard.Api/Program.cs +++ b/SecureBootDashboard.Api/Program.cs @@ -152,10 +152,10 @@ Log.Information(" Command Queue URI: {QueueUri}", updateQueueUri); Log.Information(" Command Queue Name: {QueueName}", updateQueueName); Log.Information(" Auth Method is configured."); + + builder.Services.Configure(updateConfig); + builder.Services.AddScoped(); } - - builder.Services.Configure(updateConfig); - builder.Services.AddScoped(); // Configure Azure Queue Processor Log.Information("Configuring Queue Processor..."); From 7a38509d1969e4ae425335dbe9c3da39a535a881 Mon Sep 17 00:00:00 2001 From: Roberto Gramellini Date: Sat, 8 Nov 2025 12:29:09 +0100 Subject: [PATCH 19/23] Update SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Controllers/CertificateUpdateController.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs b/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs index 1f4a750..dd24669 100644 --- a/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs +++ b/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs @@ -117,6 +117,8 @@ public sealed record CertificateUpdateRequest /// User or system that issued this command. /// [Required] + [MaxLength(100)] + [RegularExpression(@"^[\w\-.]+$", ErrorMessage = "IssuedBy must be alphanumeric and may include dash, underscore, or dot.")] public string? IssuedBy { get; init; } /// From 81df76af38b7e82d2506749fc367dfd3a57e2c79 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Nov 2025 11:33:50 +0000 Subject: [PATCH 20/23] Address code review feedback: improve exception handling, add validation, remove build artifacts Co-authored-by: robgrame <12012136+robgrame@users.noreply.github.com> --- ...CoreApp,Version=v8.0.AssemblyAttributes.cs | 4 - ...ureBootDashboard.Api.Tests.AssemblyInfo.cs | 20 ----- ...shboard.Api.Tests.AssemblyInfoInputs.cache | 1 - ....GeneratedMSBuildEditorConfig.editorconfig | 15 ---- ...eBootDashboard.Api.Tests.GlobalUsings.g.cs | 9 -- .../CertificateUpdateController.cs | 1 + .../Services/CertificateUpdateService.cs | 36 +++++--- SecureBootDashboard.Api/appsettings.json | 3 + ...CoreApp,Version=v8.0.AssemblyAttributes.cs | 4 - .../SecureBootDashboard.Api.AssemblyInfo.cs | 19 ---- ...BootDashboard.Api.AssemblyInfoInputs.cache | 1 - ....GeneratedMSBuildEditorConfig.editorconfig | 29 ------- .../SecureBootDashboard.Api.GlobalUsings.g.cs | 17 ---- ...CoreApp,Version=v8.0.AssemblyAttributes.cs | 4 - .../SecureBootDashboard.Web.AssemblyInfo.cs | 20 ----- ...BootDashboard.Web.AssemblyInfoInputs.cache | 1 - ....GeneratedMSBuildEditorConfig.editorconfig | 81 ------------------ .../SecureBootDashboard.Web.GlobalUsings.g.cs | 17 ---- ...eBootDashboard.Web.RazorAssemblyInfo.cache | 1 - ...cureBootDashboard.Web.RazorAssemblyInfo.cs | 18 ---- ...amework,Version=v4.8.AssemblyAttributes.cs | 4 - .../SecureBootWatcher.Client.AssemblyInfo.cs | 20 ----- ...ootWatcher.Client.AssemblyInfoInputs.cache | 1 - ....GeneratedMSBuildEditorConfig.editorconfig | 8 -- ...cher.Client.csproj.AssemblyReference.cache | Bin 57350 -> 0 bytes ...her.Client.exe.withSupportedRuntime.config | 6 -- ...tandard,Version=v2.0.AssemblyAttributes.cs | 4 - .../SecureBootWatcher.Shared.AssemblyInfo.cs | 19 ---- ...ootWatcher.Shared.AssemblyInfoInputs.cache | 1 - ....GeneratedMSBuildEditorConfig.editorconfig | 8 -- 30 files changed, 29 insertions(+), 343 deletions(-) delete mode 100644 SecureBootDashboard.Api.Tests/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs delete mode 100644 SecureBootDashboard.Api.Tests/obj/Debug/net8.0/SecureBootDashboard.Api.Tests.AssemblyInfo.cs delete mode 100644 SecureBootDashboard.Api.Tests/obj/Debug/net8.0/SecureBootDashboard.Api.Tests.AssemblyInfoInputs.cache delete mode 100644 SecureBootDashboard.Api.Tests/obj/Debug/net8.0/SecureBootDashboard.Api.Tests.GeneratedMSBuildEditorConfig.editorconfig delete mode 100644 SecureBootDashboard.Api.Tests/obj/Debug/net8.0/SecureBootDashboard.Api.Tests.GlobalUsings.g.cs delete mode 100644 SecureBootDashboard.Api/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs delete mode 100644 SecureBootDashboard.Api/obj/Debug/net8.0/SecureBootDashboard.Api.AssemblyInfo.cs delete mode 100644 SecureBootDashboard.Api/obj/Debug/net8.0/SecureBootDashboard.Api.AssemblyInfoInputs.cache delete mode 100644 SecureBootDashboard.Api/obj/Debug/net8.0/SecureBootDashboard.Api.GeneratedMSBuildEditorConfig.editorconfig delete mode 100644 SecureBootDashboard.Api/obj/Debug/net8.0/SecureBootDashboard.Api.GlobalUsings.g.cs delete mode 100644 SecureBootDashboard.Web/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs delete mode 100644 SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.AssemblyInfo.cs delete mode 100644 SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.AssemblyInfoInputs.cache delete mode 100644 SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.GeneratedMSBuildEditorConfig.editorconfig delete mode 100644 SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.GlobalUsings.g.cs delete mode 100644 SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.RazorAssemblyInfo.cache delete mode 100644 SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.RazorAssemblyInfo.cs delete mode 100644 SecureBootWatcher.Client/obj/Debug/net48/.NETFramework,Version=v4.8.AssemblyAttributes.cs delete mode 100644 SecureBootWatcher.Client/obj/Debug/net48/SecureBootWatcher.Client.AssemblyInfo.cs delete mode 100644 SecureBootWatcher.Client/obj/Debug/net48/SecureBootWatcher.Client.AssemblyInfoInputs.cache delete mode 100644 SecureBootWatcher.Client/obj/Debug/net48/SecureBootWatcher.Client.GeneratedMSBuildEditorConfig.editorconfig delete mode 100644 SecureBootWatcher.Client/obj/Debug/net48/SecureBootWatcher.Client.csproj.AssemblyReference.cache delete mode 100644 SecureBootWatcher.Client/obj/Debug/net48/SecureBootWatcher.Client.exe.withSupportedRuntime.config delete mode 100644 SecureBootWatcher.Shared/obj/Debug/netstandard2.0/.NETStandard,Version=v2.0.AssemblyAttributes.cs delete mode 100644 SecureBootWatcher.Shared/obj/Debug/netstandard2.0/SecureBootWatcher.Shared.AssemblyInfo.cs delete mode 100644 SecureBootWatcher.Shared/obj/Debug/netstandard2.0/SecureBootWatcher.Shared.AssemblyInfoInputs.cache delete mode 100644 SecureBootWatcher.Shared/obj/Debug/netstandard2.0/SecureBootWatcher.Shared.GeneratedMSBuildEditorConfig.editorconfig diff --git a/SecureBootDashboard.Api.Tests/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs b/SecureBootDashboard.Api.Tests/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs deleted file mode 100644 index 2217181..0000000 --- a/SecureBootDashboard.Api.Tests/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs +++ /dev/null @@ -1,4 +0,0 @@ -// -using System; -using System.Reflection; -[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")] diff --git a/SecureBootDashboard.Api.Tests/obj/Debug/net8.0/SecureBootDashboard.Api.Tests.AssemblyInfo.cs b/SecureBootDashboard.Api.Tests/obj/Debug/net8.0/SecureBootDashboard.Api.Tests.AssemblyInfo.cs deleted file mode 100644 index 72c4929..0000000 --- a/SecureBootDashboard.Api.Tests/obj/Debug/net8.0/SecureBootDashboard.Api.Tests.AssemblyInfo.cs +++ /dev/null @@ -1,20 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -using System; -using System.Reflection; - -[assembly: System.Reflection.AssemblyCompanyAttribute("SecureBootDashboard.Api.Tests")] -[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] -[assembly: System.Reflection.AssemblyProductAttribute("SecureBootDashboard.Api.Tests")] -[assembly: System.Reflection.AssemblyTitleAttribute("SecureBootDashboard.Api.Tests")] - -// Generated by the MSBuild WriteCodeFragment class. - diff --git a/SecureBootDashboard.Api.Tests/obj/Debug/net8.0/SecureBootDashboard.Api.Tests.AssemblyInfoInputs.cache b/SecureBootDashboard.Api.Tests/obj/Debug/net8.0/SecureBootDashboard.Api.Tests.AssemblyInfoInputs.cache deleted file mode 100644 index 9398e74..0000000 --- a/SecureBootDashboard.Api.Tests/obj/Debug/net8.0/SecureBootDashboard.Api.Tests.AssemblyInfoInputs.cache +++ /dev/null @@ -1 +0,0 @@ -f097ba72437f2013fdf8cd265429800cca4aa0861a04f9e9d95b8653ee2886e6 diff --git a/SecureBootDashboard.Api.Tests/obj/Debug/net8.0/SecureBootDashboard.Api.Tests.GeneratedMSBuildEditorConfig.editorconfig b/SecureBootDashboard.Api.Tests/obj/Debug/net8.0/SecureBootDashboard.Api.Tests.GeneratedMSBuildEditorConfig.editorconfig deleted file mode 100644 index 2ca8280..0000000 --- a/SecureBootDashboard.Api.Tests/obj/Debug/net8.0/SecureBootDashboard.Api.Tests.GeneratedMSBuildEditorConfig.editorconfig +++ /dev/null @@ -1,15 +0,0 @@ -is_global = true -build_property.TargetFramework = net8.0 -build_property.TargetPlatformMinVersion = -build_property.UsingMicrosoftNETSdkWeb = -build_property.ProjectTypeGuids = -build_property.InvariantGlobalization = -build_property.PlatformNeutralAssembly = -build_property.EnforceExtendedAnalyzerRules = -build_property._SupportedPlatformList = Linux,macOS,Windows -build_property.RootNamespace = SecureBootDashboard.Api.Tests -build_property.ProjectDir = /home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Api.Tests/ -build_property.EnableComHosting = -build_property.EnableGeneratedComInterfaceComImportInterop = -build_property.EffectiveAnalysisLevelStyle = 8.0 -build_property.EnableCodeStyleSeverity = diff --git a/SecureBootDashboard.Api.Tests/obj/Debug/net8.0/SecureBootDashboard.Api.Tests.GlobalUsings.g.cs b/SecureBootDashboard.Api.Tests/obj/Debug/net8.0/SecureBootDashboard.Api.Tests.GlobalUsings.g.cs deleted file mode 100644 index 2cd3d38..0000000 --- a/SecureBootDashboard.Api.Tests/obj/Debug/net8.0/SecureBootDashboard.Api.Tests.GlobalUsings.g.cs +++ /dev/null @@ -1,9 +0,0 @@ -// -global using global::System; -global using global::System.Collections.Generic; -global using global::System.IO; -global using global::System.Linq; -global using global::System.Net.Http; -global using global::System.Threading; -global using global::System.Threading.Tasks; -global using global::Xunit; diff --git a/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs b/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs index dd24669..216c92b 100644 --- a/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs +++ b/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs @@ -101,6 +101,7 @@ public sealed record CertificateUpdateRequest /// /// Specific device machine names to target. If empty, targets all devices in fleet. /// + [MaxLength(1000, ErrorMessage = "Cannot target more than 1000 devices at once")] public string[]? TargetDevices { get; init; } /// diff --git a/SecureBootDashboard.Api/Services/CertificateUpdateService.cs b/SecureBootDashboard.Api/Services/CertificateUpdateService.cs index 571287e..982453b 100644 --- a/SecureBootDashboard.Api/Services/CertificateUpdateService.cs +++ b/SecureBootDashboard.Api/Services/CertificateUpdateService.cs @@ -234,20 +234,19 @@ public async Task SendUpdateCommandAsync( using var store = new X509Store(storeName, storeLocation); store.Open(OpenFlags.ReadOnly); - using (var certificates = store.Certificates.Find( + var certificates = store.Certificates.Find( X509FindType.FindByThumbprint, options.CertificateThumbprint.Replace(" ", "").Replace(":", ""), - validOnly: false)) + validOnly: false); + + if (certificates.Count == 0) { - if (certificates.Count == 0) - { - _logger.LogError("Certificate not found in store"); - return null; - } - - certificate = certificates[0]; - _logger.LogInformation("Loaded certificate from store for command queue"); + _logger.LogError("Certificate not found in store"); + return null; } + + certificate = certificates[0]; + _logger.LogInformation("Loaded certificate from store for command queue"); } if (certificate == null) @@ -290,9 +289,24 @@ public async Task SendUpdateCommandAsync( _logger.LogError("Invalid AuthenticationMethod: {Method}", options.AuthenticationMethod); return null; } + catch (Azure.Identity.AuthenticationFailedException ex) + { + _logger.LogError(ex, "Authentication failed while creating command queue client"); + return null; + } + catch (Azure.RequestFailedException ex) + { + _logger.LogError(ex, "Azure request failed while creating command queue client"); + return null; + } + catch (UriFormatException ex) + { + _logger.LogError(ex, "Invalid queue URI format"); + return null; + } catch (Exception ex) { - _logger.LogError(ex, "Failed to create command queue client"); + _logger.LogError(ex, "Unexpected error while creating command queue client"); return null; } } diff --git a/SecureBootDashboard.Api/appsettings.json b/SecureBootDashboard.Api/appsettings.json index 9ccd043..b0f7e58 100644 --- a/SecureBootDashboard.Api/appsettings.json +++ b/SecureBootDashboard.Api/appsettings.json @@ -70,6 +70,9 @@ "QueueServiceUri": "https://secbootcert.queue.core.windows.net", "CommandQueueName": "secureboot-update-commands", "AuthenticationMethod": "Certificate", + // NOTE: These values are development/test configuration only. + // In production deployments, use Azure Key Vault or environment variables instead. + // These IDs are not sensitive secrets but should be overridden per environment. "TenantId": "d6dbad84-5922-4700-a049-c7068c37c884", "ClientId": "c8034569-4990-4823-9f1d-b46223789c35", "CertificateThumbprint": "61FC110D5BABD61419B106862B304C2FFF57A262", diff --git a/SecureBootDashboard.Api/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs b/SecureBootDashboard.Api/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs deleted file mode 100644 index 2217181..0000000 --- a/SecureBootDashboard.Api/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs +++ /dev/null @@ -1,4 +0,0 @@ -// -using System; -using System.Reflection; -[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")] diff --git a/SecureBootDashboard.Api/obj/Debug/net8.0/SecureBootDashboard.Api.AssemblyInfo.cs b/SecureBootDashboard.Api/obj/Debug/net8.0/SecureBootDashboard.Api.AssemblyInfo.cs deleted file mode 100644 index f9ae54c..0000000 --- a/SecureBootDashboard.Api/obj/Debug/net8.0/SecureBootDashboard.Api.AssemblyInfo.cs +++ /dev/null @@ -1,19 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -using System; -using System.Reflection; - -[assembly: System.Reflection.AssemblyCompanyAttribute("SecureBootDashboard.Api")] -[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] -[assembly: System.Reflection.AssemblyProductAttribute("SecureBootDashboard.Api")] -[assembly: System.Reflection.AssemblyTitleAttribute("SecureBootDashboard.Api")] - -// Generated by the MSBuild WriteCodeFragment class. - diff --git a/SecureBootDashboard.Api/obj/Debug/net8.0/SecureBootDashboard.Api.AssemblyInfoInputs.cache b/SecureBootDashboard.Api/obj/Debug/net8.0/SecureBootDashboard.Api.AssemblyInfoInputs.cache deleted file mode 100644 index 38dfa60..0000000 --- a/SecureBootDashboard.Api/obj/Debug/net8.0/SecureBootDashboard.Api.AssemblyInfoInputs.cache +++ /dev/null @@ -1 +0,0 @@ -330f15cde29ff0865162a8b698633027975c3326c7d37814fede7aa21de6f746 diff --git a/SecureBootDashboard.Api/obj/Debug/net8.0/SecureBootDashboard.Api.GeneratedMSBuildEditorConfig.editorconfig b/SecureBootDashboard.Api/obj/Debug/net8.0/SecureBootDashboard.Api.GeneratedMSBuildEditorConfig.editorconfig deleted file mode 100644 index 5ca1945..0000000 --- a/SecureBootDashboard.Api/obj/Debug/net8.0/SecureBootDashboard.Api.GeneratedMSBuildEditorConfig.editorconfig +++ /dev/null @@ -1,29 +0,0 @@ -is_global = true -build_property.TargetFramework = net8.0 -build_property.TargetFramework = net8.0 -build_property.TargetPlatformMinVersion = -build_property.TargetPlatformMinVersion = -build_property.UsingMicrosoftNETSdkWeb = true -build_property.UsingMicrosoftNETSdkWeb = true -build_property.ProjectTypeGuids = -build_property.ProjectTypeGuids = -build_property.InvariantGlobalization = -build_property.InvariantGlobalization = -build_property.PlatformNeutralAssembly = -build_property.PlatformNeutralAssembly = -build_property.EnforceExtendedAnalyzerRules = -build_property.EnforceExtendedAnalyzerRules = -build_property._SupportedPlatformList = Linux,macOS,Windows -build_property._SupportedPlatformList = Linux,macOS,Windows -build_property.RootNamespace = SecureBootDashboard.Api -build_property.RootNamespace = SecureBootDashboard.Api -build_property.ProjectDir = /home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Api/ -build_property.EnableComHosting = -build_property.EnableGeneratedComInterfaceComImportInterop = -build_property.RazorLangVersion = 8.0 -build_property.SupportLocalizedComponentNames = -build_property.GenerateRazorMetadataSourceChecksumAttributes = -build_property.MSBuildProjectDirectory = /home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Api -build_property._RazorSourceGeneratorDebug = -build_property.EffectiveAnalysisLevelStyle = 8.0 -build_property.EnableCodeStyleSeverity = diff --git a/SecureBootDashboard.Api/obj/Debug/net8.0/SecureBootDashboard.Api.GlobalUsings.g.cs b/SecureBootDashboard.Api/obj/Debug/net8.0/SecureBootDashboard.Api.GlobalUsings.g.cs deleted file mode 100644 index 025530a..0000000 --- a/SecureBootDashboard.Api/obj/Debug/net8.0/SecureBootDashboard.Api.GlobalUsings.g.cs +++ /dev/null @@ -1,17 +0,0 @@ -// -global using global::Microsoft.AspNetCore.Builder; -global using global::Microsoft.AspNetCore.Hosting; -global using global::Microsoft.AspNetCore.Http; -global using global::Microsoft.AspNetCore.Routing; -global using global::Microsoft.Extensions.Configuration; -global using global::Microsoft.Extensions.DependencyInjection; -global using global::Microsoft.Extensions.Hosting; -global using global::Microsoft.Extensions.Logging; -global using global::System; -global using global::System.Collections.Generic; -global using global::System.IO; -global using global::System.Linq; -global using global::System.Net.Http; -global using global::System.Net.Http.Json; -global using global::System.Threading; -global using global::System.Threading.Tasks; diff --git a/SecureBootDashboard.Web/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs b/SecureBootDashboard.Web/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs deleted file mode 100644 index 2217181..0000000 --- a/SecureBootDashboard.Web/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs +++ /dev/null @@ -1,4 +0,0 @@ -// -using System; -using System.Reflection; -[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")] diff --git a/SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.AssemblyInfo.cs b/SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.AssemblyInfo.cs deleted file mode 100644 index 0788dc0..0000000 --- a/SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.AssemblyInfo.cs +++ /dev/null @@ -1,20 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -using System; -using System.Reflection; - -[assembly: System.Reflection.AssemblyCompanyAttribute("SecureBootDashboard.Web")] -[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] -[assembly: System.Reflection.AssemblyProductAttribute("SecureBootDashboard.Web")] -[assembly: System.Reflection.AssemblyTitleAttribute("SecureBootDashboard.Web")] - -// Generated by the MSBuild WriteCodeFragment class. - diff --git a/SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.AssemblyInfoInputs.cache b/SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.AssemblyInfoInputs.cache deleted file mode 100644 index 650cf4b..0000000 --- a/SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.AssemblyInfoInputs.cache +++ /dev/null @@ -1 +0,0 @@ -dd3abfe8967763194617910f3f645ffa942bed9a0c5cd7be6ca938e74a2b92bc diff --git a/SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.GeneratedMSBuildEditorConfig.editorconfig b/SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.GeneratedMSBuildEditorConfig.editorconfig deleted file mode 100644 index cc25631..0000000 --- a/SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.GeneratedMSBuildEditorConfig.editorconfig +++ /dev/null @@ -1,81 +0,0 @@ -is_global = true -build_property.TargetFramework = net8.0 -build_property.TargetPlatformMinVersion = -build_property.UsingMicrosoftNETSdkWeb = true -build_property.ProjectTypeGuids = -build_property.InvariantGlobalization = -build_property.PlatformNeutralAssembly = -build_property.EnforceExtendedAnalyzerRules = -build_property._SupportedPlatformList = Linux,macOS,Windows -build_property.RootNamespace = SecureBootDashboard.Web -build_property.RootNamespace = SecureBootDashboard.Web -build_property.ProjectDir = /home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/ -build_property.EnableComHosting = -build_property.EnableGeneratedComInterfaceComImportInterop = -build_property.RazorLangVersion = 8.0 -build_property.SupportLocalizedComponentNames = -build_property.GenerateRazorMetadataSourceChecksumAttributes = -build_property.MSBuildProjectDirectory = /home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web -build_property._RazorSourceGeneratorDebug = -build_property.EffectiveAnalysisLevelStyle = 8.0 -build_property.EnableCodeStyleSeverity = - -[/home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/Pages/About.cshtml] -build_metadata.AdditionalFiles.TargetPath = UGFnZXMvQWJvdXQuY3NodG1s -build_metadata.AdditionalFiles.CssScope = - -[/home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/Pages/Account/Login.cshtml] -build_metadata.AdditionalFiles.TargetPath = UGFnZXMvQWNjb3VudC9Mb2dpbi5jc2h0bWw= -build_metadata.AdditionalFiles.CssScope = - -[/home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/Pages/Account/Logout.cshtml] -build_metadata.AdditionalFiles.TargetPath = UGFnZXMvQWNjb3VudC9Mb2dvdXQuY3NodG1s -build_metadata.AdditionalFiles.CssScope = - -[/home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/Pages/Devices/Details.cshtml] -build_metadata.AdditionalFiles.TargetPath = UGFnZXMvRGV2aWNlcy9EZXRhaWxzLmNzaHRtbA== -build_metadata.AdditionalFiles.CssScope = - -[/home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/Pages/Devices/List.cshtml] -build_metadata.AdditionalFiles.TargetPath = UGFnZXMvRGV2aWNlcy9MaXN0LmNzaHRtbA== -build_metadata.AdditionalFiles.CssScope = - -[/home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/Pages/Devices/Reports.cshtml] -build_metadata.AdditionalFiles.TargetPath = UGFnZXMvRGV2aWNlcy9SZXBvcnRzLmNzaHRtbA== -build_metadata.AdditionalFiles.CssScope = - -[/home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/Pages/Error.cshtml] -build_metadata.AdditionalFiles.TargetPath = UGFnZXMvRXJyb3IuY3NodG1s -build_metadata.AdditionalFiles.CssScope = - -[/home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/Pages/Index.cshtml] -build_metadata.AdditionalFiles.TargetPath = UGFnZXMvSW5kZXguY3NodG1s -build_metadata.AdditionalFiles.CssScope = - -[/home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/Pages/Privacy.cshtml] -build_metadata.AdditionalFiles.TargetPath = UGFnZXMvUHJpdmFjeS5jc2h0bWw= -build_metadata.AdditionalFiles.CssScope = - -[/home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/Pages/Reports/Details.cshtml] -build_metadata.AdditionalFiles.TargetPath = UGFnZXMvUmVwb3J0cy9EZXRhaWxzLmNzaHRtbA== -build_metadata.AdditionalFiles.CssScope = - -[/home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/Pages/Shared/_ValidationScriptsPartial.cshtml] -build_metadata.AdditionalFiles.TargetPath = UGFnZXMvU2hhcmVkL19WYWxpZGF0aW9uU2NyaXB0c1BhcnRpYWwuY3NodG1s -build_metadata.AdditionalFiles.CssScope = - -[/home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/Pages/Welcome.cshtml] -build_metadata.AdditionalFiles.TargetPath = UGFnZXMvV2VsY29tZS5jc2h0bWw= -build_metadata.AdditionalFiles.CssScope = - -[/home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/Pages/_ViewImports.cshtml] -build_metadata.AdditionalFiles.TargetPath = UGFnZXMvX1ZpZXdJbXBvcnRzLmNzaHRtbA== -build_metadata.AdditionalFiles.CssScope = - -[/home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/Pages/_ViewStart.cshtml] -build_metadata.AdditionalFiles.TargetPath = UGFnZXMvX1ZpZXdTdGFydC5jc2h0bWw= -build_metadata.AdditionalFiles.CssScope = - -[/home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web/Pages/Shared/_Layout.cshtml] -build_metadata.AdditionalFiles.TargetPath = UGFnZXMvU2hhcmVkL19MYXlvdXQuY3NodG1s -build_metadata.AdditionalFiles.CssScope = b-fz1gbt3di4 diff --git a/SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.GlobalUsings.g.cs b/SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.GlobalUsings.g.cs deleted file mode 100644 index 025530a..0000000 --- a/SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.GlobalUsings.g.cs +++ /dev/null @@ -1,17 +0,0 @@ -// -global using global::Microsoft.AspNetCore.Builder; -global using global::Microsoft.AspNetCore.Hosting; -global using global::Microsoft.AspNetCore.Http; -global using global::Microsoft.AspNetCore.Routing; -global using global::Microsoft.Extensions.Configuration; -global using global::Microsoft.Extensions.DependencyInjection; -global using global::Microsoft.Extensions.Hosting; -global using global::Microsoft.Extensions.Logging; -global using global::System; -global using global::System.Collections.Generic; -global using global::System.IO; -global using global::System.Linq; -global using global::System.Net.Http; -global using global::System.Net.Http.Json; -global using global::System.Threading; -global using global::System.Threading.Tasks; diff --git a/SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.RazorAssemblyInfo.cache b/SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.RazorAssemblyInfo.cache deleted file mode 100644 index ecb9c97..0000000 --- a/SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.RazorAssemblyInfo.cache +++ /dev/null @@ -1 +0,0 @@ -d5ac7ab69059af111e9d7125adeb7b174ca570725d4b64a544cca7bd11ac7ca0 diff --git a/SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.RazorAssemblyInfo.cs b/SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.RazorAssemblyInfo.cs deleted file mode 100644 index cd3853c..0000000 --- a/SecureBootDashboard.Web/obj/Debug/net8.0/SecureBootDashboard.Web.RazorAssemblyInfo.cs +++ /dev/null @@ -1,18 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -using System; -using System.Reflection; - -[assembly: Microsoft.AspNetCore.Mvc.ApplicationParts.ProvideApplicationPartFactoryAttribute("Microsoft.AspNetCore.Mvc.ApplicationParts.ConsolidatedAssemblyApplicationPartFact" + - "ory, Microsoft.AspNetCore.Mvc.Razor")] - -// Generated by the MSBuild WriteCodeFragment class. - diff --git a/SecureBootWatcher.Client/obj/Debug/net48/.NETFramework,Version=v4.8.AssemblyAttributes.cs b/SecureBootWatcher.Client/obj/Debug/net48/.NETFramework,Version=v4.8.AssemblyAttributes.cs deleted file mode 100644 index 15efebf..0000000 --- a/SecureBootWatcher.Client/obj/Debug/net48/.NETFramework,Version=v4.8.AssemblyAttributes.cs +++ /dev/null @@ -1,4 +0,0 @@ -// -using System; -using System.Reflection; -[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] diff --git a/SecureBootWatcher.Client/obj/Debug/net48/SecureBootWatcher.Client.AssemblyInfo.cs b/SecureBootWatcher.Client/obj/Debug/net48/SecureBootWatcher.Client.AssemblyInfo.cs deleted file mode 100644 index f6d4673..0000000 --- a/SecureBootWatcher.Client/obj/Debug/net48/SecureBootWatcher.Client.AssemblyInfo.cs +++ /dev/null @@ -1,20 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -using System; -using System.Reflection; - -[assembly: System.Reflection.AssemblyCompanyAttribute("SecureBootWatcher.Client")] -[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] -[assembly: System.Reflection.AssemblyProductAttribute("SecureBootWatcher.Client")] -[assembly: System.Reflection.AssemblyTitleAttribute("SecureBootWatcher.Client")] - -// Generated by the MSBuild WriteCodeFragment class. - diff --git a/SecureBootWatcher.Client/obj/Debug/net48/SecureBootWatcher.Client.AssemblyInfoInputs.cache b/SecureBootWatcher.Client/obj/Debug/net48/SecureBootWatcher.Client.AssemblyInfoInputs.cache deleted file mode 100644 index 6428687..0000000 --- a/SecureBootWatcher.Client/obj/Debug/net48/SecureBootWatcher.Client.AssemblyInfoInputs.cache +++ /dev/null @@ -1 +0,0 @@ -ab0516815ddaa26328e0bcec9213f5fc50934fa26df4275c23d1eaf7be9df5ea diff --git a/SecureBootWatcher.Client/obj/Debug/net48/SecureBootWatcher.Client.GeneratedMSBuildEditorConfig.editorconfig b/SecureBootWatcher.Client/obj/Debug/net48/SecureBootWatcher.Client.GeneratedMSBuildEditorConfig.editorconfig deleted file mode 100644 index 3e369c5..0000000 --- a/SecureBootWatcher.Client/obj/Debug/net48/SecureBootWatcher.Client.GeneratedMSBuildEditorConfig.editorconfig +++ /dev/null @@ -1,8 +0,0 @@ -is_global = true -build_property.RootNamespace = SecureBootWatcher.Client -build_property.ProjectDir = /home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootWatcher.Client/ -build_property.EnableComHosting = -build_property.EnableGeneratedComInterfaceComImportInterop = -build_property.CsWinRTUseWindowsUIXamlProjections = false -build_property.EffectiveAnalysisLevelStyle = -build_property.EnableCodeStyleSeverity = diff --git a/SecureBootWatcher.Client/obj/Debug/net48/SecureBootWatcher.Client.csproj.AssemblyReference.cache b/SecureBootWatcher.Client/obj/Debug/net48/SecureBootWatcher.Client.csproj.AssemblyReference.cache deleted file mode 100644 index affca4103c36e865cc12348929b4c3af49e825cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57350 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 - - - - - \ No newline at end of file diff --git a/SecureBootWatcher.Shared/obj/Debug/netstandard2.0/.NETStandard,Version=v2.0.AssemblyAttributes.cs b/SecureBootWatcher.Shared/obj/Debug/netstandard2.0/.NETStandard,Version=v2.0.AssemblyAttributes.cs deleted file mode 100644 index 8bf3a42..0000000 --- a/SecureBootWatcher.Shared/obj/Debug/netstandard2.0/.NETStandard,Version=v2.0.AssemblyAttributes.cs +++ /dev/null @@ -1,4 +0,0 @@ -// -using System; -using System.Reflection; -[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")] diff --git a/SecureBootWatcher.Shared/obj/Debug/netstandard2.0/SecureBootWatcher.Shared.AssemblyInfo.cs b/SecureBootWatcher.Shared/obj/Debug/netstandard2.0/SecureBootWatcher.Shared.AssemblyInfo.cs deleted file mode 100644 index cb91eaa..0000000 --- a/SecureBootWatcher.Shared/obj/Debug/netstandard2.0/SecureBootWatcher.Shared.AssemblyInfo.cs +++ /dev/null @@ -1,19 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -using System; -using System.Reflection; - -[assembly: System.Reflection.AssemblyCompanyAttribute("SecureBootWatcher.Shared")] -[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] -[assembly: System.Reflection.AssemblyProductAttribute("SecureBootWatcher.Shared")] -[assembly: System.Reflection.AssemblyTitleAttribute("SecureBootWatcher.Shared")] - -// Generated by the MSBuild WriteCodeFragment class. - diff --git a/SecureBootWatcher.Shared/obj/Debug/netstandard2.0/SecureBootWatcher.Shared.AssemblyInfoInputs.cache b/SecureBootWatcher.Shared/obj/Debug/netstandard2.0/SecureBootWatcher.Shared.AssemblyInfoInputs.cache deleted file mode 100644 index a75bb89..0000000 --- a/SecureBootWatcher.Shared/obj/Debug/netstandard2.0/SecureBootWatcher.Shared.AssemblyInfoInputs.cache +++ /dev/null @@ -1 +0,0 @@ -99a26cd5f17c6e39f218759d91f2d65edc0865fb9bd1f1981e7c2af01f6f0834 diff --git a/SecureBootWatcher.Shared/obj/Debug/netstandard2.0/SecureBootWatcher.Shared.GeneratedMSBuildEditorConfig.editorconfig b/SecureBootWatcher.Shared/obj/Debug/netstandard2.0/SecureBootWatcher.Shared.GeneratedMSBuildEditorConfig.editorconfig deleted file mode 100644 index 7296e63..0000000 --- a/SecureBootWatcher.Shared/obj/Debug/netstandard2.0/SecureBootWatcher.Shared.GeneratedMSBuildEditorConfig.editorconfig +++ /dev/null @@ -1,8 +0,0 @@ -is_global = true -build_property.RootNamespace = SecureBootWatcher.Shared -build_property.ProjectDir = /home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootWatcher.Shared/ -build_property.EnableComHosting = -build_property.EnableGeneratedComInterfaceComImportInterop = -build_property.CsWinRTUseWindowsUIXamlProjections = false -build_property.EffectiveAnalysisLevelStyle = -build_property.EnableCodeStyleSeverity = From c04dcde0353dc3bec7fff9d5b634a3ea74523ff0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Nov 2025 11:40:04 +0000 Subject: [PATCH 21/23] Complete removal of all build artifact files from version control Co-authored-by: robgrame <12012136+robgrame@users.noreply.github.com> --- ...CoreApp,Version=v8.0.AssemblyAttributes.cs | 4 ---- ...ureBootDashboard.Web.Tests.AssemblyInfo.cs | 20 ------------------ ...shboard.Web.Tests.AssemblyInfoInputs.cache | 1 - ....GeneratedMSBuildEditorConfig.editorconfig | 15 ------------- ...eBootDashboard.Web.Tests.GlobalUsings.g.cs | 9 -------- ...amework,Version=v4.8.AssemblyAttributes.cs | 4 ---- ...reBootWatcher.Client.Tests.AssemblyInfo.cs | 20 ------------------ ...cher.Client.Tests.AssemblyInfoInputs.cache | 1 - ....GeneratedMSBuildEditorConfig.editorconfig | 9 -------- ...BootWatcher.Client.Tests.GlobalUsings.g.cs | 8 ------- ...lient.Tests.csproj.AssemblyReference.cache | Bin 107557 -> 0 bytes ...CoreApp,Version=v8.0.AssemblyAttributes.cs | 4 ---- ...reBootWatcher.Shared.Tests.AssemblyInfo.cs | 20 ------------------ ...cher.Shared.Tests.AssemblyInfoInputs.cache | 1 - ....GeneratedMSBuildEditorConfig.editorconfig | 15 ------------- ...BootWatcher.Shared.Tests.GlobalUsings.g.cs | 9 -------- 16 files changed, 140 deletions(-) delete mode 100644 SecureBootDashboard.Web.Tests/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs delete mode 100644 SecureBootDashboard.Web.Tests/obj/Debug/net8.0/SecureBootDashboard.Web.Tests.AssemblyInfo.cs delete mode 100644 SecureBootDashboard.Web.Tests/obj/Debug/net8.0/SecureBootDashboard.Web.Tests.AssemblyInfoInputs.cache delete mode 100644 SecureBootDashboard.Web.Tests/obj/Debug/net8.0/SecureBootDashboard.Web.Tests.GeneratedMSBuildEditorConfig.editorconfig delete mode 100644 SecureBootDashboard.Web.Tests/obj/Debug/net8.0/SecureBootDashboard.Web.Tests.GlobalUsings.g.cs delete mode 100644 SecureBootWatcher.Client.Tests/obj/Debug/net48/.NETFramework,Version=v4.8.AssemblyAttributes.cs delete mode 100644 SecureBootWatcher.Client.Tests/obj/Debug/net48/SecureBootWatcher.Client.Tests.AssemblyInfo.cs delete mode 100644 SecureBootWatcher.Client.Tests/obj/Debug/net48/SecureBootWatcher.Client.Tests.AssemblyInfoInputs.cache delete mode 100644 SecureBootWatcher.Client.Tests/obj/Debug/net48/SecureBootWatcher.Client.Tests.GeneratedMSBuildEditorConfig.editorconfig delete mode 100644 SecureBootWatcher.Client.Tests/obj/Debug/net48/SecureBootWatcher.Client.Tests.GlobalUsings.g.cs delete mode 100644 SecureBootWatcher.Client.Tests/obj/Debug/net48/SecureBootWatcher.Client.Tests.csproj.AssemblyReference.cache delete mode 100644 SecureBootWatcher.Shared.Tests/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs delete mode 100644 SecureBootWatcher.Shared.Tests/obj/Debug/net8.0/SecureBootWatcher.Shared.Tests.AssemblyInfo.cs delete mode 100644 SecureBootWatcher.Shared.Tests/obj/Debug/net8.0/SecureBootWatcher.Shared.Tests.AssemblyInfoInputs.cache delete mode 100644 SecureBootWatcher.Shared.Tests/obj/Debug/net8.0/SecureBootWatcher.Shared.Tests.GeneratedMSBuildEditorConfig.editorconfig delete mode 100644 SecureBootWatcher.Shared.Tests/obj/Debug/net8.0/SecureBootWatcher.Shared.Tests.GlobalUsings.g.cs diff --git a/SecureBootDashboard.Web.Tests/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs b/SecureBootDashboard.Web.Tests/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs deleted file mode 100644 index 2217181..0000000 --- a/SecureBootDashboard.Web.Tests/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs +++ /dev/null @@ -1,4 +0,0 @@ -// -using System; -using System.Reflection; -[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")] diff --git a/SecureBootDashboard.Web.Tests/obj/Debug/net8.0/SecureBootDashboard.Web.Tests.AssemblyInfo.cs b/SecureBootDashboard.Web.Tests/obj/Debug/net8.0/SecureBootDashboard.Web.Tests.AssemblyInfo.cs deleted file mode 100644 index c448a48..0000000 --- a/SecureBootDashboard.Web.Tests/obj/Debug/net8.0/SecureBootDashboard.Web.Tests.AssemblyInfo.cs +++ /dev/null @@ -1,20 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -using System; -using System.Reflection; - -[assembly: System.Reflection.AssemblyCompanyAttribute("SecureBootDashboard.Web.Tests")] -[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] -[assembly: System.Reflection.AssemblyProductAttribute("SecureBootDashboard.Web.Tests")] -[assembly: System.Reflection.AssemblyTitleAttribute("SecureBootDashboard.Web.Tests")] - -// Generated by the MSBuild WriteCodeFragment class. - diff --git a/SecureBootDashboard.Web.Tests/obj/Debug/net8.0/SecureBootDashboard.Web.Tests.AssemblyInfoInputs.cache b/SecureBootDashboard.Web.Tests/obj/Debug/net8.0/SecureBootDashboard.Web.Tests.AssemblyInfoInputs.cache deleted file mode 100644 index 8cac64a..0000000 --- a/SecureBootDashboard.Web.Tests/obj/Debug/net8.0/SecureBootDashboard.Web.Tests.AssemblyInfoInputs.cache +++ /dev/null @@ -1 +0,0 @@ -a3c8c3fe34666d672d32eb769544a3bd9af8560e521948600931faaeed007746 diff --git a/SecureBootDashboard.Web.Tests/obj/Debug/net8.0/SecureBootDashboard.Web.Tests.GeneratedMSBuildEditorConfig.editorconfig b/SecureBootDashboard.Web.Tests/obj/Debug/net8.0/SecureBootDashboard.Web.Tests.GeneratedMSBuildEditorConfig.editorconfig deleted file mode 100644 index 8466a2d..0000000 --- a/SecureBootDashboard.Web.Tests/obj/Debug/net8.0/SecureBootDashboard.Web.Tests.GeneratedMSBuildEditorConfig.editorconfig +++ /dev/null @@ -1,15 +0,0 @@ -is_global = true -build_property.TargetFramework = net8.0 -build_property.TargetPlatformMinVersion = -build_property.UsingMicrosoftNETSdkWeb = -build_property.ProjectTypeGuids = -build_property.InvariantGlobalization = -build_property.PlatformNeutralAssembly = -build_property.EnforceExtendedAnalyzerRules = -build_property._SupportedPlatformList = Linux,macOS,Windows -build_property.RootNamespace = SecureBootDashboard.Web.Tests -build_property.ProjectDir = /home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootDashboard.Web.Tests/ -build_property.EnableComHosting = -build_property.EnableGeneratedComInterfaceComImportInterop = -build_property.EffectiveAnalysisLevelStyle = 8.0 -build_property.EnableCodeStyleSeverity = diff --git a/SecureBootDashboard.Web.Tests/obj/Debug/net8.0/SecureBootDashboard.Web.Tests.GlobalUsings.g.cs b/SecureBootDashboard.Web.Tests/obj/Debug/net8.0/SecureBootDashboard.Web.Tests.GlobalUsings.g.cs deleted file mode 100644 index 2cd3d38..0000000 --- a/SecureBootDashboard.Web.Tests/obj/Debug/net8.0/SecureBootDashboard.Web.Tests.GlobalUsings.g.cs +++ /dev/null @@ -1,9 +0,0 @@ -// -global using global::System; -global using global::System.Collections.Generic; -global using global::System.IO; -global using global::System.Linq; -global using global::System.Net.Http; -global using global::System.Threading; -global using global::System.Threading.Tasks; -global using global::Xunit; diff --git a/SecureBootWatcher.Client.Tests/obj/Debug/net48/.NETFramework,Version=v4.8.AssemblyAttributes.cs b/SecureBootWatcher.Client.Tests/obj/Debug/net48/.NETFramework,Version=v4.8.AssemblyAttributes.cs deleted file mode 100644 index 15efebf..0000000 --- a/SecureBootWatcher.Client.Tests/obj/Debug/net48/.NETFramework,Version=v4.8.AssemblyAttributes.cs +++ /dev/null @@ -1,4 +0,0 @@ -// -using System; -using System.Reflection; -[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] diff --git a/SecureBootWatcher.Client.Tests/obj/Debug/net48/SecureBootWatcher.Client.Tests.AssemblyInfo.cs b/SecureBootWatcher.Client.Tests/obj/Debug/net48/SecureBootWatcher.Client.Tests.AssemblyInfo.cs deleted file mode 100644 index d6d66d0..0000000 --- a/SecureBootWatcher.Client.Tests/obj/Debug/net48/SecureBootWatcher.Client.Tests.AssemblyInfo.cs +++ /dev/null @@ -1,20 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -using System; -using System.Reflection; - -[assembly: System.Reflection.AssemblyCompanyAttribute("SecureBootWatcher.Client.Tests")] -[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] -[assembly: System.Reflection.AssemblyProductAttribute("SecureBootWatcher.Client.Tests")] -[assembly: System.Reflection.AssemblyTitleAttribute("SecureBootWatcher.Client.Tests")] - -// Generated by the MSBuild WriteCodeFragment class. - diff --git a/SecureBootWatcher.Client.Tests/obj/Debug/net48/SecureBootWatcher.Client.Tests.AssemblyInfoInputs.cache b/SecureBootWatcher.Client.Tests/obj/Debug/net48/SecureBootWatcher.Client.Tests.AssemblyInfoInputs.cache deleted file mode 100644 index 079b6da..0000000 --- a/SecureBootWatcher.Client.Tests/obj/Debug/net48/SecureBootWatcher.Client.Tests.AssemblyInfoInputs.cache +++ /dev/null @@ -1 +0,0 @@ -aedb671d9b6bf348ccbdc1e1007a49ab21b4241adadafad5084ec890a8954172 diff --git a/SecureBootWatcher.Client.Tests/obj/Debug/net48/SecureBootWatcher.Client.Tests.GeneratedMSBuildEditorConfig.editorconfig b/SecureBootWatcher.Client.Tests/obj/Debug/net48/SecureBootWatcher.Client.Tests.GeneratedMSBuildEditorConfig.editorconfig deleted file mode 100644 index 4d2c7b3..0000000 --- a/SecureBootWatcher.Client.Tests/obj/Debug/net48/SecureBootWatcher.Client.Tests.GeneratedMSBuildEditorConfig.editorconfig +++ /dev/null @@ -1,9 +0,0 @@ -is_global = true -build_property.IsMSTestTestAdapterReferenced = true -build_property.RootNamespace = SecureBootWatcher.Client.Tests -build_property.ProjectDir = C:\Users\nefario\source\repos\robgrame\Nimbus.BootCertWatcher\SecureBootWatcher.Client.Tests\ -build_property.EnableComHosting = -build_property.EnableGeneratedComInterfaceComImportInterop = -build_property.CsWinRTUseWindowsUIXamlProjections = false -build_property.EffectiveAnalysisLevelStyle = -build_property.EnableCodeStyleSeverity = diff --git a/SecureBootWatcher.Client.Tests/obj/Debug/net48/SecureBootWatcher.Client.Tests.GlobalUsings.g.cs b/SecureBootWatcher.Client.Tests/obj/Debug/net48/SecureBootWatcher.Client.Tests.GlobalUsings.g.cs deleted file mode 100644 index 9ab263f..0000000 --- a/SecureBootWatcher.Client.Tests/obj/Debug/net48/SecureBootWatcher.Client.Tests.GlobalUsings.g.cs +++ /dev/null @@ -1,8 +0,0 @@ -// -global using global::Microsoft.VisualStudio.TestTools.UnitTesting; -global using global::System; -global using global::System.Collections.Generic; -global using global::System.IO; -global using global::System.Linq; -global using global::System.Threading; -global using global::System.Threading.Tasks; diff --git a/SecureBootWatcher.Client.Tests/obj/Debug/net48/SecureBootWatcher.Client.Tests.csproj.AssemblyReference.cache b/SecureBootWatcher.Client.Tests/obj/Debug/net48/SecureBootWatcher.Client.Tests.csproj.AssemblyReference.cache deleted file mode 100644 index fcda2a73aaa5c525c65d001f3d85fab22682881a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 107557 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 -using System; -using System.Reflection; -[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")] diff --git a/SecureBootWatcher.Shared.Tests/obj/Debug/net8.0/SecureBootWatcher.Shared.Tests.AssemblyInfo.cs b/SecureBootWatcher.Shared.Tests/obj/Debug/net8.0/SecureBootWatcher.Shared.Tests.AssemblyInfo.cs deleted file mode 100644 index 378cfc4..0000000 --- a/SecureBootWatcher.Shared.Tests/obj/Debug/net8.0/SecureBootWatcher.Shared.Tests.AssemblyInfo.cs +++ /dev/null @@ -1,20 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -using System; -using System.Reflection; - -[assembly: System.Reflection.AssemblyCompanyAttribute("SecureBootWatcher.Shared.Tests")] -[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] -[assembly: System.Reflection.AssemblyProductAttribute("SecureBootWatcher.Shared.Tests")] -[assembly: System.Reflection.AssemblyTitleAttribute("SecureBootWatcher.Shared.Tests")] - -// Generated by the MSBuild WriteCodeFragment class. - diff --git a/SecureBootWatcher.Shared.Tests/obj/Debug/net8.0/SecureBootWatcher.Shared.Tests.AssemblyInfoInputs.cache b/SecureBootWatcher.Shared.Tests/obj/Debug/net8.0/SecureBootWatcher.Shared.Tests.AssemblyInfoInputs.cache deleted file mode 100644 index 4034ab0..0000000 --- a/SecureBootWatcher.Shared.Tests/obj/Debug/net8.0/SecureBootWatcher.Shared.Tests.AssemblyInfoInputs.cache +++ /dev/null @@ -1 +0,0 @@ -869a4e2e3f7898d62d8fd35daf6b5a58cf587f8a85b7beca3caf4b6534bd7d0b diff --git a/SecureBootWatcher.Shared.Tests/obj/Debug/net8.0/SecureBootWatcher.Shared.Tests.GeneratedMSBuildEditorConfig.editorconfig b/SecureBootWatcher.Shared.Tests/obj/Debug/net8.0/SecureBootWatcher.Shared.Tests.GeneratedMSBuildEditorConfig.editorconfig deleted file mode 100644 index 0e61e0f..0000000 --- a/SecureBootWatcher.Shared.Tests/obj/Debug/net8.0/SecureBootWatcher.Shared.Tests.GeneratedMSBuildEditorConfig.editorconfig +++ /dev/null @@ -1,15 +0,0 @@ -is_global = true -build_property.TargetFramework = net8.0 -build_property.TargetPlatformMinVersion = -build_property.UsingMicrosoftNETSdkWeb = -build_property.ProjectTypeGuids = -build_property.InvariantGlobalization = -build_property.PlatformNeutralAssembly = -build_property.EnforceExtendedAnalyzerRules = -build_property._SupportedPlatformList = Linux,macOS,Windows -build_property.RootNamespace = SecureBootWatcher.Shared.Tests -build_property.ProjectDir = /home/runner/work/Nimbus.BootCertWatcher/Nimbus.BootCertWatcher/SecureBootWatcher.Shared.Tests/ -build_property.EnableComHosting = -build_property.EnableGeneratedComInterfaceComImportInterop = -build_property.EffectiveAnalysisLevelStyle = 8.0 -build_property.EnableCodeStyleSeverity = diff --git a/SecureBootWatcher.Shared.Tests/obj/Debug/net8.0/SecureBootWatcher.Shared.Tests.GlobalUsings.g.cs b/SecureBootWatcher.Shared.Tests/obj/Debug/net8.0/SecureBootWatcher.Shared.Tests.GlobalUsings.g.cs deleted file mode 100644 index 2cd3d38..0000000 --- a/SecureBootWatcher.Shared.Tests/obj/Debug/net8.0/SecureBootWatcher.Shared.Tests.GlobalUsings.g.cs +++ /dev/null @@ -1,9 +0,0 @@ -// -global using global::System; -global using global::System.Collections.Generic; -global using global::System.IO; -global using global::System.Linq; -global using global::System.Net.Http; -global using global::System.Threading; -global using global::System.Threading.Tasks; -global using global::Xunit; From 7487034a86b4005691ba9aa9a2eff8600716626b Mon Sep 17 00:00:00 2001 From: Roberto Gramellini Date: Mon, 10 Nov 2025 00:07:31 +0100 Subject: [PATCH 22/23] Update CertificateUpdateController.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Controllers/CertificateUpdateController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs b/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs index 216c92b..1c0b83f 100644 --- a/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs +++ b/SecureBootDashboard.Api/Controllers/CertificateUpdateController.cs @@ -119,7 +119,7 @@ public sealed record CertificateUpdateRequest /// [Required] [MaxLength(100)] - [RegularExpression(@"^[\w\-.]+$", ErrorMessage = "IssuedBy must be alphanumeric and may include dash, underscore, or dot.")] + [RegularExpression(@"^[\w\-.@]+$", ErrorMessage = "IssuedBy must be alphanumeric and may include dash, underscore, dot, or at-sign.")] public string? IssuedBy { get; init; } /// From 59fb7d52e1bd2d1dde6cc5f7c2459b9d819d21a7 Mon Sep 17 00:00:00 2001 From: Roberto Gramellini Date: Mon, 10 Nov 2025 00:07:51 +0100 Subject: [PATCH 23/23] Update CertificateUpdateService.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Services/CertificateUpdateService.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/SecureBootDashboard.Api/Services/CertificateUpdateService.cs b/SecureBootDashboard.Api/Services/CertificateUpdateService.cs index 982453b..00d5842 100644 --- a/SecureBootDashboard.Api/Services/CertificateUpdateService.cs +++ b/SecureBootDashboard.Api/Services/CertificateUpdateService.cs @@ -306,6 +306,13 @@ public async Task SendUpdateCommandAsync( } catch (Exception ex) { + // Rethrow critical exceptions + if (ex is OutOfMemoryException || + ex is StackOverflowException || + ex is ThreadAbortException) + { + throw; + } _logger.LogError(ex, "Unexpected error while creating command queue client"); return null; }