Dev #24
@@ -0,0 +1,71 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
|
||||||
|
namespace UniVerse.Api.Middleware;
|
||||||
|
|
||||||
|
public sealed class LocalNetworksOnlyMiddleware
|
||||||
|
{
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
private readonly ILogger<LocalNetworksOnlyMiddleware> _logger;
|
||||||
|
|
||||||
|
public LocalNetworksOnlyMiddleware(RequestDelegate next, ILogger<LocalNetworksOnlyMiddleware> logger)
|
||||||
|
{
|
||||||
|
_next = next;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InvokeAsync(HttpContext context)
|
||||||
|
{
|
||||||
|
var remoteIpAddress = context.Connection.RemoteIpAddress;
|
||||||
|
|
||||||
|
if (remoteIpAddress is null || !IsLocalNetwork(remoteIpAddress))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Blocked metrics request from non-local address {RemoteIpAddress}", remoteIpAddress);
|
||||||
|
context.Response.StatusCode = StatusCodes.Status403Forbidden;
|
||||||
|
await context.Response.WriteAsync("Metrics endpoint is available only from local networks.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _next(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsLocalNetwork(IPAddress ipAddress)
|
||||||
|
{
|
||||||
|
if (IPAddress.IsLoopback(ipAddress))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ipAddress.IsIPv4MappedToIPv6)
|
||||||
|
{
|
||||||
|
ipAddress = ipAddress.MapToIPv4();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ipAddress.AddressFamily switch
|
||||||
|
{
|
||||||
|
AddressFamily.InterNetwork => IsPrivateOrLinkLocalIPv4(ipAddress),
|
||||||
|
AddressFamily.InterNetworkV6 => IsPrivateOrLinkLocalIPv6(ipAddress),
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsPrivateOrLinkLocalIPv4(IPAddress ipAddress)
|
||||||
|
{
|
||||||
|
var bytes = ipAddress.GetAddressBytes();
|
||||||
|
|
||||||
|
return bytes[0] == 10
|
||||||
|
|| bytes[0] == 127
|
||||||
|
|| (bytes[0] == 192 && bytes[1] == 168)
|
||||||
|
|| (bytes[0] == 172 && bytes[1] is >= 16 and <= 31)
|
||||||
|
|| (bytes[0] == 169 && bytes[1] == 254);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsPrivateOrLinkLocalIPv6(IPAddress ipAddress)
|
||||||
|
{
|
||||||
|
var bytes = ipAddress.GetAddressBytes();
|
||||||
|
|
||||||
|
return ipAddress.IsIPv6LinkLocal
|
||||||
|
|| ipAddress.IsIPv6SiteLocal
|
||||||
|
|| (bytes[0] & 0xfe) == 0xfc;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using Microsoft.OpenApi;
|
using Microsoft.OpenApi;
|
||||||
|
using Prometheus;
|
||||||
using Quartz;
|
using Quartz;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using UniVerse.Api.BackgroundServices;
|
using UniVerse.Api.BackgroundServices;
|
||||||
@@ -221,6 +222,7 @@ if (app.Environment.IsDevelopment())
|
|||||||
app.UseCors();
|
app.UseCors();
|
||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
app.UseHttpMetrics();
|
||||||
if (app.Environment.IsDevelopment())
|
if (app.Environment.IsDevelopment())
|
||||||
{
|
{
|
||||||
app.UseAntiforgery();
|
app.UseAntiforgery();
|
||||||
@@ -228,4 +230,10 @@ if (app.Environment.IsDevelopment())
|
|||||||
}
|
}
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
|
|
||||||
|
// Restrict Prometheus scrape endpoint to local and private networks.
|
||||||
|
app.UseWhen(
|
||||||
|
context => context.Request.Path.StartsWithSegments("/metrics", StringComparison.OrdinalIgnoreCase),
|
||||||
|
branch => branch.UseMiddleware<LocalNetworksOnlyMiddleware>());
|
||||||
|
app.MapMetrics();
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
|
||||||
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.1" />
|
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.1" />
|
||||||
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.18.1" />
|
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.18.1" />
|
||||||
|
<PackageReference Include="prometheus-net.AspNetCore" Version="8.2.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
Reference in New Issue
Block a user