mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 20:53:06 +00:00
Hardening: re-architecture to use mTLS for complete agent communication (agent endpoints consolidated)
This commit is contained in:
@@ -67,7 +67,17 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Network
|
||||
_model = hardwareInfo.Model;
|
||||
_serialNumber = hardwareInfo.SerialNumber;
|
||||
|
||||
_httpClient = new HttpClient
|
||||
// Use HttpClientHandler with client certificate for TLS-level mTLS negotiation.
|
||||
// Azure App Service extracts the cert from the TLS handshake and forwards it
|
||||
// in the X-ARR-ClientCert header to the backend function.
|
||||
var handler = new HttpClientHandler();
|
||||
if (_clientCertificate != null)
|
||||
{
|
||||
handler.ClientCertificates.Add(_clientCertificate);
|
||||
logger?.Info("Client certificate attached to TLS handler for mTLS negotiation");
|
||||
}
|
||||
|
||||
_httpClient = new HttpClient(handler)
|
||||
{
|
||||
Timeout = TimeSpan.FromSeconds(30)
|
||||
};
|
||||
@@ -107,8 +117,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Network
|
||||
// Add TenantId header so the backend can run security checks before parsing the body
|
||||
httpRequest.Headers.Add("X-Tenant-Id", request.TenantId);
|
||||
|
||||
// Add client certificate for device authentication (if available)
|
||||
AddClientCertificateHeader(httpRequest);
|
||||
// Add additional security headers for device authorization (client cert is at TLS layer, but we can add hardware info in headers for whitelist validation and Autopilot device verification)
|
||||
AddSecurityHeaders(httpRequest);
|
||||
|
||||
var httpResponse = await _httpClient.SendAsync(httpRequest);
|
||||
ThrowOnAuthFailure(httpResponse);
|
||||
@@ -128,7 +138,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Network
|
||||
var url = $"{_baseUrl}{Constants.ApiEndpoints.GetAgentConfig}?tenantId={Uri.EscapeDataString(tenantId)}";
|
||||
|
||||
var httpRequest = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
AddClientCertificateHeader(httpRequest);
|
||||
AddSecurityHeaders(httpRequest);
|
||||
|
||||
var response = await _httpClient.SendAsync(httpRequest);
|
||||
response.EnsureSuccessStatusCode();
|
||||
@@ -166,8 +176,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Network
|
||||
Content = new StringContent(json, Encoding.UTF8, "application/json")
|
||||
};
|
||||
|
||||
// Add device identification headers for all API calls
|
||||
AddClientCertificateHeader(httpRequest);
|
||||
// Add additional security headers for device authorization (client cert is at TLS layer, but we can add hardware info in headers for whitelist validation and Autopilot device verification)
|
||||
AddSecurityHeaders(httpRequest);
|
||||
|
||||
var response = await _httpClient.SendAsync(httpRequest);
|
||||
ThrowOnAuthFailure(response);
|
||||
@@ -237,25 +247,13 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Network
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds device identification headers for authentication and authorization
|
||||
/// Includes: Client certificate, manufacturer, model, serial number
|
||||
/// Adds additional security headers for device authorization.
|
||||
/// Client certificate is sent at TLS layer via HttpClientHandler.ClientCertificates.
|
||||
/// </summary>
|
||||
private void AddClientCertificateHeader(HttpRequestMessage request)
|
||||
private void AddSecurityHeaders(HttpRequestMessage request)
|
||||
{
|
||||
_logger?.Verbose("Adding security headers to request...");
|
||||
|
||||
// Add client certificate for device authentication
|
||||
if (_clientCertificate != null)
|
||||
{
|
||||
var certBytes = _clientCertificate.Export(X509ContentType.Cert);
|
||||
var certBase64 = Convert.ToBase64String(certBytes);
|
||||
request.Headers.Add("X-Client-Certificate", certBase64);
|
||||
_logger?.Verbose($" X-Client-Certificate: {_clientCertificate.Thumbprint.Substring(0, Math.Min(16, _clientCertificate.Thumbprint.Length))}...");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger?.Verbose(" X-Client-Certificate: NOT SENT (no certificate available)");
|
||||
}
|
||||
// Client certificate is sent at TLS layer via HttpClientHandler.ClientCertificates (mTLS)
|
||||
|
||||
// Add hardware information for whitelist validation
|
||||
if (!string.IsNullOrEmpty(_manufacturer))
|
||||
|
||||
@@ -1 +1 @@
|
||||
78
|
||||
80
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace AutopilotMonitor.Functions.Functions
|
||||
var response = req.CreateResponse(HttpStatusCode.OK);
|
||||
await response.WriteAsJsonAsync(new AgentConfigResponse
|
||||
{
|
||||
ConfigVersion = 7,
|
||||
ConfigVersion = 8, // mTLS migration: agent sends cert via TLS, routes moved under /api/agent/
|
||||
UploadIntervalSeconds = 30,
|
||||
SelfDestructOnComplete = tenantConfig.SelfDestructOnComplete ?? true,
|
||||
KeepLogFile = tenantConfig.KeepLogFile ?? false,
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace AutopilotMonitor.Functions.Functions
|
||||
|
||||
[Function("GetDiagnosticsUploadUrl")]
|
||||
public async Task<HttpResponseData> Run(
|
||||
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "diagnostics/upload-url")] HttpRequestData req)
|
||||
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "agent/upload-url")] HttpRequestData req)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace AutopilotMonitor.Functions.Functions
|
||||
|
||||
[Function("IngestEvents")]
|
||||
public async Task<IngestEventsOutput> Run(
|
||||
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "events/ingest")] HttpRequestData req)
|
||||
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "agent/ingest")] HttpRequestData req)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace AutopilotMonitor.Functions.Functions
|
||||
|
||||
[Function("RegisterSession")]
|
||||
public async Task<RegisterSessionOutput> Run(
|
||||
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "sessions/register")] HttpRequestData req)
|
||||
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "agent/register-session")] HttpRequestData req)
|
||||
{
|
||||
_logger.LogInformation("RegisterSession function processing request");
|
||||
|
||||
|
||||
@@ -42,10 +42,10 @@ public class AuthenticationMiddleware : IFunctionsWorkerMiddleware
|
||||
{
|
||||
"/api/health",
|
||||
"/api/platform-stats",
|
||||
"/api/sessions/register",
|
||||
"/api/events/ingest",
|
||||
"/api/agent/register-session",
|
||||
"/api/agent/ingest",
|
||||
"/api/agent/config",
|
||||
"/api/diagnostics/upload-url"
|
||||
"/api/agent/upload-url"
|
||||
};
|
||||
|
||||
public AuthenticationMiddleware(
|
||||
|
||||
@@ -57,6 +57,13 @@ namespace AutopilotMonitor.Functions.Security
|
||||
|
||||
try
|
||||
{
|
||||
// Azure App Service may URL-encode the X-ARR-ClientCert header value.
|
||||
// Decode before Base64 parsing.
|
||||
if (certificateBase64.Contains('%'))
|
||||
{
|
||||
certificateBase64 = Uri.UnescapeDataString(certificateBase64);
|
||||
}
|
||||
|
||||
// Parse certificate from Base64
|
||||
var certBytes = Convert.FromBase64String(certificateBase64);
|
||||
var certificate = new X509Certificate2(certBytes);
|
||||
|
||||
@@ -109,9 +109,15 @@ namespace AutopilotMonitor.Functions.Security
|
||||
}
|
||||
|
||||
// 1. Validate client certificate
|
||||
var certHeader = req.Headers.Contains("X-Client-Certificate")
|
||||
? req.Headers.GetValues("X-Client-Certificate").FirstOrDefault()
|
||||
: null;
|
||||
// Production: Azure App Service mTLS forwards client cert in X-ARR-ClientCert header.
|
||||
// Local dev fallback: X-Client-Certificate header for testing with Azure Functions Core Tools
|
||||
// (which does not support mTLS). This fallback never triggers in production because
|
||||
// requests without a TLS client cert are rejected by Azure before reaching the function.
|
||||
var certHeader = req.Headers.Contains("X-ARR-ClientCert")
|
||||
? req.Headers.GetValues("X-ARR-ClientCert").FirstOrDefault()
|
||||
: req.Headers.Contains("X-Client-Certificate")
|
||||
? req.Headers.GetValues("X-Client-Certificate").FirstOrDefault()
|
||||
: null;
|
||||
|
||||
var certValidation = CertificateValidator.ValidateCertificate(certHeader, _logger);
|
||||
if (!certValidation.IsValid)
|
||||
|
||||
@@ -67,12 +67,12 @@ namespace AutopilotMonitor.Shared
|
||||
/// </summary>
|
||||
public static class ApiEndpoints
|
||||
{
|
||||
public const string RegisterSession = "/api/sessions/register";
|
||||
public const string IngestEvents = "/api/events/ingest";
|
||||
public const string GetAgentConfig = "/api/agent/config";
|
||||
public const string RegisterSession = "/api/agent/register-session";
|
||||
public const string IngestEvents = "/api/agent/ingest";
|
||||
public const string GetAgentConfig = "/api/agent/config";
|
||||
public const string GatherRules = "/api/gather-rules";
|
||||
public const string AnalyzeRules = "/api/analyze-rules";
|
||||
public const string GetDiagnosticsUploadUrl = "/api/diagnostics/upload-url";
|
||||
public const string GetDiagnosticsUploadUrl = "/api/agent/upload-url";
|
||||
public const string BlockDevice = "/api/devices/block";
|
||||
public const string GetBlockedDevices = "/api/devices/blocked";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user