[backend] Reject requests if Host header doesn't match configured WebDomain or AccountDomain

This commit is contained in:
Laura Hausmann 2024-02-03 22:57:35 +01:00
parent ee449ec751
commit b72a22b9b0
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
8 changed files with 52 additions and 7 deletions

View file

@ -31,6 +31,7 @@ public sealed class Config {
public string UserAgent => $"Iceshrimp.NET/{Version} (https://{WebDomain})"; public string UserAgent => $"Iceshrimp.NET/{Version} (https://{WebDomain})";
public required int ListenPort { get; init; } = 3000; public required int ListenPort { get; init; } = 3000;
public required string ListenHost { get; init; } = "localhost";
public required string WebDomain { get; init; } public required string WebDomain { get; init; }
public required string AccountDomain { get; init; } public required string AccountDomain { get; init; }
} }

View file

@ -47,6 +47,7 @@ public static class ServiceExtensions {
services.AddSingleton<RequestBufferingMiddleware>(); services.AddSingleton<RequestBufferingMiddleware>();
services.AddSingleton<AuthorizationMiddleware>(); services.AddSingleton<AuthorizationMiddleware>();
services.AddSingleton<OauthAuthorizationMiddleware>(); services.AddSingleton<OauthAuthorizationMiddleware>();
services.AddSingleton<RequestVerificationMiddleware>();
// Hosted services = long running background tasks // Hosted services = long running background tasks
// Note: These need to be added as a singleton as well to ensure data consistency // Note: These need to be added as a singleton as well to ensure data consistency

View file

@ -10,6 +10,7 @@ public static class WebApplicationExtensions {
public static IApplicationBuilder UseCustomMiddleware(this IApplicationBuilder app) { public static IApplicationBuilder UseCustomMiddleware(this IApplicationBuilder app) {
// Caution: make sure these are in the correct order // Caution: make sure these are in the correct order
return app.UseMiddleware<ErrorHandlerMiddleware>() return app.UseMiddleware<ErrorHandlerMiddleware>()
.UseMiddleware<RequestVerificationMiddleware>()
.UseMiddleware<RequestBufferingMiddleware>() .UseMiddleware<RequestBufferingMiddleware>()
.UseMiddleware<AuthenticationMiddleware>() .UseMiddleware<AuthenticationMiddleware>()
.UseMiddleware<AuthorizationMiddleware>() .UseMiddleware<AuthorizationMiddleware>()

View file

@ -25,6 +25,9 @@ public class ErrorHandlerMiddleware(IOptions<Config.SecuritySection> options, IL
var verbosity = options.Value.ExceptionVerbosity; var verbosity = options.Value.ExceptionVerbosity;
if (e is GracefulException ce) { if (e is GracefulException ce) {
if (verbosity > ExceptionVerbosity.Basic && ce.OverrideBasic)
verbosity = ExceptionVerbosity.Basic;
ctx.Response.StatusCode = (int)ce.StatusCode; ctx.Response.StatusCode = (int)ce.StatusCode;
await ctx.Response.WriteAsJsonAsync(new ErrorResponse { await ctx.Response.WriteAsJsonAsync(new ErrorResponse {
StatusCode = ctx.Response.StatusCode, StatusCode = ctx.Response.StatusCode,
@ -58,10 +61,16 @@ public class ErrorHandlerMiddleware(IOptions<Config.SecuritySection> options, IL
} }
} }
public class GracefulException(HttpStatusCode statusCode, string error, string message, string? details = null) public class GracefulException(
HttpStatusCode statusCode,
string error,
string message,
string? details = null,
bool overrideBasic = false)
: Exception(message) { : Exception(message) {
public readonly string? Details = details; public readonly string? Details = details;
public readonly string Error = error; public readonly string Error = error;
public readonly bool OverrideBasic = overrideBasic;
public readonly HttpStatusCode StatusCode = statusCode; public readonly HttpStatusCode StatusCode = statusCode;
public GracefulException(HttpStatusCode statusCode, string message, string? details = null) : public GracefulException(HttpStatusCode statusCode, string message, string? details = null) :
@ -93,6 +102,11 @@ public class GracefulException(HttpStatusCode statusCode, string error, string m
public static GracefulException RecordNotFound() { public static GracefulException RecordNotFound() {
return new GracefulException(HttpStatusCode.NotFound, "Record not found"); return new GracefulException(HttpStatusCode.NotFound, "Record not found");
} }
public static GracefulException MisdirectedRequest() {
return new GracefulException(HttpStatusCode.MisdirectedRequest, HttpStatusCode.MisdirectedRequest.ToString(),
"This server is not configured to respond to this request.", null, true);
}
} }
public enum ExceptionVerbosity { public enum ExceptionVerbosity {

View file

@ -0,0 +1,27 @@
using Iceshrimp.Backend.Core.Configuration;
using Microsoft.Extensions.Options;
namespace Iceshrimp.Backend.Core.Middleware;
public class RequestVerificationMiddleware(IOptions<Config.InstanceSection> config, IHostEnvironment environment)
: IMiddleware {
private readonly bool _isDevelopment = environment.IsDevelopment();
public async Task InvokeAsync(HttpContext ctx, RequestDelegate next) {
if (!IsValid(ctx.Request) && !_isDevelopment)
throw GracefulException.MisdirectedRequest();
await next(ctx);
}
public bool IsValid(HttpRequest rq) {
if (rq.Host.Host == config.Value.WebDomain) return true;
if (rq.Host.Host == config.Value.AccountDomain && rq.Path.StartsWithSegments("/.well-known")) {
if (rq.Path == "/.well-known/webfinger") return true;
if (rq.Path == "/.well-known/host-meta") return true;
if (rq.Path == "/.well-known/nodeinfo") return true;
}
return false;
}
}

View file

@ -57,6 +57,6 @@ app.MapFallbackToPage("/Shared/FrontendSPA");
if (app.Environment.IsDevelopment()) app.UseViteDevMiddleware(); if (app.Environment.IsDevelopment()) app.UseViteDevMiddleware();
app.Urls.Clear(); app.Urls.Clear();
app.Urls.Add($"http://{config.WebDomain}:{config.ListenPort}"); app.Urls.Add($"http://{config.ListenHost}:{config.ListenPort}");
app.Run(); app.Run();

View file

@ -1,5 +1,6 @@
[Instance] [Instance]
ListenPort = 3000 ListenPort = 3000
ListenHost = localhost
;; Caution: changing these settings after initial setup *will* break federation ;; Caution: changing these settings after initial setup *will* break federation
WebDomain = shrimp.example.org WebDomain = shrimp.example.org