-
Notifications
You must be signed in to change notification settings - Fork 0
Add API endpoint for remote certificate update commands #18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Copilot
wants to merge
23
commits into
main
Choose a base branch
from
copilot/add-certificate-update-api
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
67e10fb
Initial plan
Copilot bb326f4
Initial plan for certificate update API endpoint
Copilot c28aa13
Add certificate update API endpoint with tests
Copilot a5b931a
Add comprehensive documentation for certificate update API
Copilot 5a368dc
Fix log forging security vulnerabilities
Copilot ecc7047
Update CertificateUpdateController.cs
robgrame c816fad
Update CERTIFICATE_UPDATE_API.md
robgrame 9e9d315
Update CertificateUpdateController.cs
robgrame a1fa9d8
Update CertificateUpdateController.cs
robgrame 8d29623
Update CertificateUpdateService.cs
robgrame 31430ae
Update Program.cs
robgrame 804ee9e
Update CertificateUpdateController.cs
robgrame 21020e2
Update CertificateUpdateController.cs
robgrame aac2168
Update Program.cs
robgrame f0fd1e9
Update CertificateUpdateController.cs
robgrame 386f0d7
Update CertificateUpdateService.cs
robgrame 1cefd18
Update CertificateUpdateService.cs
robgrame e6f1461
Update Program.cs
robgrame 7a38509
Update SecureBootDashboard.Api/Controllers/CertificateUpdateControlle…
robgrame 81df76a
Address code review feedback: improve exception handling, add validat…
Copilot c04dcde
Complete removal of all build artifact files from version control
Copilot 7487034
Update CertificateUpdateController.cs
robgrame 59fb7d5
Update CertificateUpdateService.cs
robgrame File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
152 changes: 152 additions & 0 deletions
152
SecureBootDashboard.Api.Tests/Controllers/CertificateUpdateControllerTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<ICertificateUpdateService> _mockUpdateService; | ||
| private readonly Mock<ILogger<CertificateUpdateController>> _mockLogger; | ||
| private readonly CertificateUpdateController _controller; | ||
|
|
||
| public CertificateUpdateControllerTests() | ||
| { | ||
| _mockUpdateService = new Mock<ICertificateUpdateService>(); | ||
| _mockLogger = new Mock<ILogger<CertificateUpdateController>>(); | ||
| _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<CertificateUpdateCommand>(), It.IsAny<CancellationToken>())) | ||
| .ReturnsAsync(expectedResult); | ||
|
|
||
| // Act | ||
| var result = await _controller.TriggerUpdateAsync(request, CancellationToken.None); | ||
|
|
||
| // Assert | ||
| var okResult = Assert.IsType<OkObjectResult>(result.Result); | ||
| var returnValue = Assert.IsType<CertificateUpdateResult>(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<CertificateUpdateCommand>(), It.IsAny<CancellationToken>())) | ||
| .ThrowsAsync(new Exception("Test exception")); | ||
|
|
||
| // Act | ||
| var result = await _controller.TriggerUpdateAsync(request, CancellationToken.None); | ||
|
|
||
| // Assert | ||
| var statusCodeResult = Assert.IsType<ObjectResult>(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<CancellationToken>())) | ||
| .ReturnsAsync(expectedStatus); | ||
|
|
||
| // Act | ||
| var result = await _controller.GetCommandStatusAsync(commandId, CancellationToken.None); | ||
|
|
||
| // Assert | ||
| var okResult = Assert.IsType<OkObjectResult>(result.Result); | ||
| var returnValue = Assert.IsType<CertificateUpdateCommandStatus>(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<CancellationToken>())) | ||
| .ReturnsAsync((CertificateUpdateCommandStatus?)null); | ||
|
|
||
| // Act | ||
| var result = await _controller.GetCommandStatusAsync(commandId, CancellationToken.None); | ||
|
|
||
| // Assert | ||
| Assert.IsType<NotFoundObjectResult>(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<CertificateUpdateCommand>(), It.IsAny<CancellationToken>())) | ||
| .Callback<CertificateUpdateCommand, CancellationToken>((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); | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 0 additions & 4 deletions
4
...reBootDashboard.Api.Tests/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs
This file was deleted.
Oops, something went wrong.
20 changes: 0 additions & 20 deletions
20
SecureBootDashboard.Api.Tests/obj/Debug/net8.0/SecureBootDashboard.Api.Tests.AssemblyInfo.cs
This file was deleted.
Oops, something went wrong.
1 change: 0 additions & 1 deletion
1
...shboard.Api.Tests/obj/Debug/net8.0/SecureBootDashboard.Api.Tests.AssemblyInfoInputs.cache
This file was deleted.
Oops, something went wrong.
15 changes: 0 additions & 15 deletions
15
.../obj/Debug/net8.0/SecureBootDashboard.Api.Tests.GeneratedMSBuildEditorConfig.editorconfig
This file was deleted.
Oops, something went wrong.
9 changes: 0 additions & 9 deletions
9
...eBootDashboard.Api.Tests/obj/Debug/net8.0/SecureBootDashboard.Api.Tests.GlobalUsings.g.cs
This file was deleted.
Oops, something went wrong.
75 changes: 75 additions & 0 deletions
75
SecureBootDashboard.Api/Configuration/CertificateUpdateServiceOptions.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| using System; | ||
|
|
||
| namespace SecureBootDashboard.Api.Configuration | ||
| { | ||
| /// <summary> | ||
| /// Configuration options for the Certificate Update Service. | ||
| /// </summary> | ||
| public sealed class CertificateUpdateServiceOptions | ||
| { | ||
| /// <summary> | ||
| /// Whether the certificate update service is enabled. | ||
| /// </summary> | ||
| public bool Enabled { get; set; } = false; | ||
|
|
||
| /// <summary> | ||
| /// Storage account queue service URI (e.g., https://mystorageaccount.queue.core.windows.net). | ||
| /// </summary> | ||
| public Uri? QueueServiceUri { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Queue name for certificate update commands. | ||
| /// </summary> | ||
| public string CommandQueueName { get; set; } = "secureboot-update-commands"; | ||
|
|
||
| /// <summary> | ||
| /// Authentication method: "ManagedIdentity", "AppRegistration", "Certificate", "DefaultAzureCredential", or "ConnectionString". | ||
| /// </summary> | ||
| public string AuthenticationMethod { get; set; } = "DefaultAzureCredential"; | ||
|
|
||
| /// <summary> | ||
| /// Connection string (only used if AuthenticationMethod is "ConnectionString"). | ||
| /// </summary> | ||
| public string? ConnectionString { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Application (Client) ID from Azure App Registration. | ||
| /// </summary> | ||
| public string? ClientId { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Directory (Tenant) ID. | ||
| /// </summary> | ||
| public string? TenantId { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Client Secret from App Registration. | ||
| /// </summary> | ||
| public string? ClientSecret { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Path to certificate file (.pfx or .pem). | ||
| /// </summary> | ||
| public string? CertificatePath { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Password for the certificate file. | ||
| /// </summary> | ||
| public string? CertificatePassword { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Certificate thumbprint for certificate store. | ||
| /// </summary> | ||
| public string? CertificateThumbprint { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Certificate store location. | ||
| /// </summary> | ||
| public string CertificateStoreLocation { get; set; } = "CurrentUser"; | ||
|
|
||
| /// <summary> | ||
| /// Certificate store name. | ||
| /// </summary> | ||
| public string CertificateStoreName { get; set; } = "My"; | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.