feat(observability): add diagnostic dashboards (#27)
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
using System.Reflection;
|
||||
using FinalFactory.Rendezvous.Contracts;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace FinalFactory.Rendezvous.Server.Diagnostics;
|
||||
|
||||
internal static class DiagnosticDashboardEndpoints
|
||||
{
|
||||
private const string ContentSecurityPolicy =
|
||||
"default-src 'none'; base-uri 'none'; connect-src 'self'; "
|
||||
+ "font-src 'self'; form-action 'none'; frame-ancestors 'none'; "
|
||||
+ "img-src 'self'; manifest-src 'none'; object-src 'none'; "
|
||||
+ "script-src 'self'; style-src 'self'";
|
||||
private static readonly byte[] Index = ReadAsset("index.html");
|
||||
private static readonly byte[] Script = ReadAsset("app.mjs");
|
||||
private static readonly byte[] Styles = ReadAsset("styles.css");
|
||||
|
||||
public static IEndpointRouteBuilder MapDiagnosticDashboardEndpoints(
|
||||
this IEndpointRouteBuilder endpoints)
|
||||
{
|
||||
RouteGroupBuilder dashboard = endpoints.MapGroup("/diagnostics")
|
||||
.ExcludeFromDescription();
|
||||
dashboard.MapGet("", ServeIndex);
|
||||
dashboard.MapGet("/app.mjs", ServeScript);
|
||||
dashboard.MapGet("/styles.css", ServeStyles);
|
||||
dashboard.MapGet("/config.json", ServeConfiguration);
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
private static IResult ServeIndex(
|
||||
HttpContext context,
|
||||
[FromServices] IOptions<DiagnosticDashboardOptions> configured) =>
|
||||
ServeAsset(context, configured.Value, Index, "text/html; charset=utf-8");
|
||||
|
||||
private static IResult ServeScript(
|
||||
HttpContext context,
|
||||
[FromServices] IOptions<DiagnosticDashboardOptions> configured) =>
|
||||
ServeAsset(context, configured.Value, Script, "text/javascript; charset=utf-8");
|
||||
|
||||
private static IResult ServeStyles(
|
||||
HttpContext context,
|
||||
[FromServices] IOptions<DiagnosticDashboardOptions> configured) =>
|
||||
ServeAsset(context, configured.Value, Styles, "text/css; charset=utf-8");
|
||||
|
||||
private static IResult ServeConfiguration(
|
||||
HttpContext context,
|
||||
[FromServices] IOptions<DiagnosticDashboardOptions> configured)
|
||||
{
|
||||
DiagnosticDashboardOptions options = configured.Value;
|
||||
if (!options.Enabled)
|
||||
{
|
||||
return Results.NotFound();
|
||||
}
|
||||
|
||||
ApplySecurityHeaders(context.Response);
|
||||
return Results.Json(new
|
||||
{
|
||||
contractVersion = ContractLimits.ContractVersion,
|
||||
pollIntervalSeconds = options.PollIntervalSeconds,
|
||||
maximumRenderedSessions = options.MaximumRenderedSessions,
|
||||
scopes = options.Scopes.Select(static scope => new
|
||||
{
|
||||
gameId = scope.GameId,
|
||||
environmentId = scope.EnvironmentId,
|
||||
protocolVersions = scope.ProtocolVersions,
|
||||
regions = scope.Regions,
|
||||
visibility = "public",
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
private static IResult ServeAsset(
|
||||
HttpContext context,
|
||||
DiagnosticDashboardOptions options,
|
||||
byte[] content,
|
||||
string contentType)
|
||||
{
|
||||
if (!options.Enabled)
|
||||
{
|
||||
return Results.NotFound();
|
||||
}
|
||||
|
||||
ApplySecurityHeaders(context.Response);
|
||||
return Results.Bytes(content, contentType);
|
||||
}
|
||||
|
||||
private static void ApplySecurityHeaders(HttpResponse response)
|
||||
{
|
||||
response.Headers.CacheControl = "no-store";
|
||||
response.Headers["Content-Security-Policy"] = ContentSecurityPolicy;
|
||||
response.Headers["X-Content-Type-Options"] = "nosniff";
|
||||
response.Headers["X-Frame-Options"] = "DENY";
|
||||
response.Headers["Cross-Origin-Opener-Policy"] = "same-origin";
|
||||
response.Headers["Cross-Origin-Resource-Policy"] = "same-origin";
|
||||
response.Headers["Permissions-Policy"] =
|
||||
"camera=(), geolocation=(), microphone=(), payment=(), usb=()";
|
||||
response.Headers["Referrer-Policy"] = "no-referrer";
|
||||
}
|
||||
|
||||
private static byte[] ReadAsset(string fileName)
|
||||
{
|
||||
Assembly assembly = typeof(DiagnosticDashboardEndpoints).Assembly;
|
||||
string resourceName = $"FinalFactory.Rendezvous.Server.Diagnostics.Assets.{fileName}";
|
||||
using Stream source = assembly.GetManifestResourceStream(resourceName)
|
||||
?? throw new InvalidOperationException($"Missing embedded dashboard asset {fileName}.");
|
||||
using MemoryStream destination = new();
|
||||
source.CopyTo(destination);
|
||||
return destination.ToArray();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user