Initial Commit
This commit is contained in:
54
PatchProbe.Cli/Services/EnrollmentService.cs
Normal file
54
PatchProbe.Cli/Services/EnrollmentService.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System.Net.Http.Json;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Win32;
|
||||
using PatchProbe.Cli.Auth;
|
||||
using PatchProbe.Engine.Contracts.ApiModels;
|
||||
|
||||
namespace PatchProbe.Cli.Services;
|
||||
|
||||
internal sealed class EnrollmentService(
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IDeviceCredentialStore credentialStore,
|
||||
ILogger<EnrollmentService> logger)
|
||||
{
|
||||
public async Task EnrollAsync(string serverUrl, string enrollmentKey, CancellationToken ct = default)
|
||||
{
|
||||
using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
|
||||
var publicKeySpki = Convert.ToBase64String(ecdsa.ExportSubjectPublicKeyInfo());
|
||||
var privateKeyPkcs8 = Convert.ToBase64String(ecdsa.ExportPkcs8PrivateKey());
|
||||
|
||||
var requestBody = new EnrollmentRequest(
|
||||
EnrollmentKey: enrollmentKey,
|
||||
MachineName: Environment.MachineName,
|
||||
DeviceFingerprint: GetMachineFingerprint(),
|
||||
PublicKeySpki: publicKeySpki);
|
||||
|
||||
var http = httpClientFactory.CreateClient();
|
||||
var url = $"{serverUrl.TrimEnd('/')}/api/enrollments";
|
||||
|
||||
logger.LogInformation("Enrolling device with server at {Url}", url);
|
||||
|
||||
var response = await http.PostAsJsonAsync(url, requestBody, ct);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<EnrollmentResponse>(cancellationToken: ct)
|
||||
?? throw new InvalidOperationException("Server returned an empty enrollment response.");
|
||||
|
||||
credentialStore.Save(new DeviceCredentials(
|
||||
DeviceId: result.DeviceId,
|
||||
PrivateKeyPkcs8: privateKeyPkcs8,
|
||||
ServerUrl: serverUrl));
|
||||
|
||||
logger.LogInformation("Enrollment complete — Device ID: {DeviceId}", result.DeviceId);
|
||||
}
|
||||
|
||||
private static string GetMachineFingerprint()
|
||||
{
|
||||
// MachineGuid is a stable, per-install GUID set by Windows Setup.
|
||||
using var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Cryptography");
|
||||
var guid = key?.GetValue("MachineGuid")?.ToString() ?? Environment.MachineName;
|
||||
return Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(guid))).ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
60
PatchProbe.Cli/Services/PayloadUploader.cs
Normal file
60
PatchProbe.Cli/Services/PayloadUploader.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PatchProbe.Cli.Auth;
|
||||
using PatchProbe.Shared.Contracts;
|
||||
using PatchProbe.Shared.Models;
|
||||
using PatchProbe.Shared.Serialization;
|
||||
|
||||
namespace PatchProbe.Cli.Services;
|
||||
|
||||
internal sealed class PayloadUploader(
|
||||
ILogger<PayloadUploader> logger,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IRequestAuthenticator authenticator) : IPayloadUploader
|
||||
{
|
||||
public async Task UploadAsync(PatchProbePayload payload, string? serverUrl = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(serverUrl))
|
||||
{
|
||||
await PostToServerAsync(payload, serverUrl, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
await WriteLocalAsync(payload, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
Task IPayloadUploader.UploadAsync(PatchProbePayload payload, CancellationToken cancellationToken) =>
|
||||
UploadAsync(payload, null, cancellationToken);
|
||||
|
||||
private async Task PostToServerAsync(PatchProbePayload payload, string serverUrl, CancellationToken cancellationToken)
|
||||
{
|
||||
var url = serverUrl.TrimEnd('/') + "/api/scans";
|
||||
logger.LogInformation("Uploading scan to {Url}", url);
|
||||
|
||||
var json = PayloadSerializer.Serialize(payload);
|
||||
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, url);
|
||||
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
|
||||
await authenticator.AuthenticateAsync(request, json, cancellationToken);
|
||||
|
||||
var client = httpClientFactory.CreateClient();
|
||||
var response = await client.SendAsync(request, cancellationToken);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
logger.LogInformation("Scan uploaded successfully (HTTP {Status})", (int)response.StatusCode);
|
||||
}
|
||||
|
||||
private async Task WriteLocalAsync(PatchProbePayload payload, CancellationToken cancellationToken)
|
||||
{
|
||||
var outputDir = Path.Combine(AppContext.BaseDirectory, "output");
|
||||
Directory.CreateDirectory(outputDir);
|
||||
|
||||
var fileName = $"patchprobe_{payload.Collector.CollectedAt:yyyyMMdd_HHmmss}_{payload.Collector.MachineName}.json";
|
||||
var filePath = Path.Combine(outputDir, fileName);
|
||||
|
||||
await PayloadSerializer.SerializeToFileAsync(payload, filePath, cancellationToken);
|
||||
logger.LogInformation("Payload written to {FilePath}", filePath);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user