mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 12:42:38 +00:00
fix bootstrap rate limit, fix download timeout
This commit is contained in:
@@ -1 +1 @@
|
||||
752
|
||||
754
|
||||
|
||||
+6
-2
@@ -513,7 +513,9 @@ namespace AutopilotMonitor.Functions.DataAccess.TableStorage
|
||||
{ "DiagnosticsGlobalLogPathsJson", config.DiagnosticsGlobalLogPathsJson },
|
||||
{ "NvdApiKey", config.NvdApiKey },
|
||||
{ "VulnerabilityCorrelationEnabled", config.VulnerabilityCorrelationEnabled },
|
||||
{ "VulnerabilityDataLastSyncUtc", config.VulnerabilityDataLastSyncUtc }
|
||||
{ "VulnerabilityDataLastSyncUtc", config.VulnerabilityDataLastSyncUtc },
|
||||
{ "MaxDiagnosticsDownloadSizeMB", config.MaxDiagnosticsDownloadSizeMB },
|
||||
{ "DiagnosticsDownloadTimeoutSeconds", config.DiagnosticsDownloadTimeoutSeconds }
|
||||
};
|
||||
|
||||
return entity;
|
||||
@@ -535,7 +537,9 @@ namespace AutopilotMonitor.Functions.DataAccess.TableStorage
|
||||
DiagnosticsGlobalLogPathsJson = entity.GetString("DiagnosticsGlobalLogPathsJson"),
|
||||
NvdApiKey = entity.GetString("NvdApiKey"),
|
||||
VulnerabilityCorrelationEnabled = entity.GetBoolean("VulnerabilityCorrelationEnabled") ?? true,
|
||||
VulnerabilityDataLastSyncUtc = entity.GetString("VulnerabilityDataLastSyncUtc")
|
||||
VulnerabilityDataLastSyncUtc = entity.GetString("VulnerabilityDataLastSyncUtc"),
|
||||
MaxDiagnosticsDownloadSizeMB = entity.GetInt32("MaxDiagnosticsDownloadSizeMB") ?? 500,
|
||||
DiagnosticsDownloadTimeoutSeconds = entity.GetInt32("DiagnosticsDownloadTimeoutSeconds") ?? 120
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+44
-3
@@ -23,15 +23,18 @@ namespace AutopilotMonitor.Functions.Functions.Diagnostics
|
||||
{
|
||||
private readonly ILogger<DiagnosticsDownloadFunction> _logger;
|
||||
private readonly TenantConfigurationService _configService;
|
||||
private readonly AdminConfigurationService _adminConfigService;
|
||||
private readonly TelemetryClient _telemetryClient;
|
||||
|
||||
public DiagnosticsDownloadFunction(
|
||||
ILogger<DiagnosticsDownloadFunction> logger,
|
||||
TenantConfigurationService configService,
|
||||
AdminConfigurationService adminConfigService,
|
||||
TelemetryClient telemetryClient)
|
||||
{
|
||||
_logger = logger;
|
||||
_configService = configService;
|
||||
_adminConfigService = adminConfigService;
|
||||
_telemetryClient = telemetryClient;
|
||||
}
|
||||
|
||||
@@ -98,14 +101,41 @@ namespace AutopilotMonitor.Functions.Functions.Diagnostics
|
||||
blobUrl = $"{containerSasUrl.TrimEnd('/')}/{blobName}";
|
||||
}
|
||||
|
||||
// Load admin configuration for download limits
|
||||
var adminConfig = await _adminConfigService.GetConfigurationAsync();
|
||||
var maxSizeBytes = (long)adminConfig.MaxDiagnosticsDownloadSizeMB * 1024 * 1024;
|
||||
var timeoutSeconds = adminConfig.DiagnosticsDownloadTimeoutSeconds;
|
||||
|
||||
using var cts = timeoutSeconds > 0
|
||||
? new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds))
|
||||
: new CancellationTokenSource();
|
||||
|
||||
// Download blob server-side and stream to client
|
||||
var sw = Stopwatch.StartNew();
|
||||
var blobClient = new BlobClient(new Uri(blobUrl));
|
||||
var download = await blobClient.DownloadStreamingAsync();
|
||||
var download = await blobClient.DownloadStreamingAsync(cancellationToken: cts.Token);
|
||||
sw.Stop();
|
||||
|
||||
var contentLength = download.Value.Details.ContentLength;
|
||||
|
||||
// Enforce size limit before streaming (fast reject)
|
||||
if (maxSizeBytes > 0 && contentLength > maxSizeBytes)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"DiagnosticsDownload: Blob {BlobName} for tenant {TenantId} rejected — size {SizeBytes} bytes exceeds limit {MaxSizeBytes} bytes",
|
||||
blobName, tenantId, contentLength, maxSizeBytes);
|
||||
|
||||
download.Value.Content.Dispose();
|
||||
|
||||
var tooLarge = req.CreateResponse(HttpStatusCode.RequestEntityTooLarge);
|
||||
await tooLarge.WriteAsJsonAsync(new
|
||||
{
|
||||
success = false,
|
||||
message = $"Diagnostics package size ({contentLength / (1024 * 1024)} MB) exceeds the maximum allowed size ({adminConfig.MaxDiagnosticsDownloadSizeMB} MB)."
|
||||
});
|
||||
return tooLarge;
|
||||
}
|
||||
|
||||
_logger.LogInformation(
|
||||
"DiagnosticsDownload: Proxying blob {BlobName} for tenant {TenantId}, size {SizeBytes} bytes, fetch took {DurationMs}ms",
|
||||
blobName, tenantId, contentLength, sw.ElapsedMilliseconds);
|
||||
@@ -125,7 +155,7 @@ namespace AutopilotMonitor.Functions.Functions.Diagnostics
|
||||
["DurationMs"] = sw.ElapsedMilliseconds
|
||||
});
|
||||
|
||||
// Stream blob content to client
|
||||
// Stream blob content to client (with timeout)
|
||||
var response = req.CreateResponse(HttpStatusCode.OK);
|
||||
response.Headers.Add("Content-Type", "application/octet-stream");
|
||||
response.Headers.Add("Content-Disposition", $"attachment; filename=\"{blobName}\"");
|
||||
@@ -134,7 +164,7 @@ namespace AutopilotMonitor.Functions.Functions.Diagnostics
|
||||
response.Headers.Add("Content-Length", contentLength.ToString());
|
||||
}
|
||||
|
||||
await download.Value.Content.CopyToAsync(response.Body);
|
||||
await download.Value.Content.CopyToAsync(response.Body, cts.Token);
|
||||
return response;
|
||||
}
|
||||
catch (Azure.RequestFailedException ex) when (ex.Status == 404)
|
||||
@@ -144,6 +174,17 @@ namespace AutopilotMonitor.Functions.Functions.Diagnostics
|
||||
await notFoundResponse.WriteAsJsonAsync(new { success = false, message = "Diagnostics package not found." });
|
||||
return notFoundResponse;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_logger.LogWarning("DiagnosticsDownload: Operation timed out or was cancelled for requested blob download");
|
||||
var timeoutResponse = req.CreateResponse(HttpStatusCode.GatewayTimeout);
|
||||
await timeoutResponse.WriteAsJsonAsync(new
|
||||
{
|
||||
success = false,
|
||||
message = "Diagnostics download timed out. The file may be too large or the connection is too slow."
|
||||
});
|
||||
return timeoutResponse;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error proxying diagnostics download");
|
||||
|
||||
@@ -173,6 +173,24 @@ namespace AutopilotMonitor.Functions.Security
|
||||
_logger.LogInformation("Bootstrap token auth accepted for tenant {TenantId} (code {ShortCode})",
|
||||
tenantId, bootstrapSession.ShortCode);
|
||||
|
||||
// Rate limit check for bootstrap auth (DoS protection)
|
||||
var bsRateLimitValue = config.CustomRateLimitRequestsPerMinute ?? config.RateLimitRequestsPerMinute;
|
||||
var bsRateLimitResult = _rateLimitService.CheckRateLimit(
|
||||
$"bootstrap:{bootstrapTokenHeader}",
|
||||
bsRateLimitValue
|
||||
);
|
||||
|
||||
if (!bsRateLimitResult.IsAllowed)
|
||||
{
|
||||
return new SecurityValidationResult
|
||||
{
|
||||
IsValid = false,
|
||||
StatusCode = HttpStatusCode.TooManyRequests,
|
||||
ErrorMessage = "Rate limit exceeded",
|
||||
RateLimitResult = bsRateLimitResult
|
||||
};
|
||||
}
|
||||
|
||||
return new SecurityValidationResult
|
||||
{
|
||||
IsValid = true,
|
||||
@@ -180,7 +198,8 @@ namespace AutopilotMonitor.Functions.Security
|
||||
BootstrapShortCode = bootstrapSession.ShortCode,
|
||||
Manufacturer = bsManufacturer,
|
||||
Model = bsModel,
|
||||
SerialNumber = bsSerial
|
||||
SerialNumber = bsSerial,
|
||||
RateLimitResult = bsRateLimitResult
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -141,6 +141,21 @@ namespace AutopilotMonitor.Shared.Models
|
||||
}
|
||||
}
|
||||
|
||||
// ===== DIAGNOSTICS DOWNLOAD LIMITS =====
|
||||
|
||||
/// <summary>
|
||||
/// Maximum allowed diagnostics download size in MB.
|
||||
/// Blobs exceeding this are rejected before streaming (413).
|
||||
/// 0 = no limit. Default: 500 MB.
|
||||
/// </summary>
|
||||
public int MaxDiagnosticsDownloadSizeMB { get; set; } = 500;
|
||||
|
||||
/// <summary>
|
||||
/// Timeout in seconds for the entire diagnostics download+stream operation.
|
||||
/// 0 = no timeout. Default: 120 seconds.
|
||||
/// </summary>
|
||||
public int DiagnosticsDownloadTimeoutSeconds { get; set; } = 120;
|
||||
|
||||
// ===== MCP ACCESS CONTROL =====
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user