[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 required int ListenPort { get; init; } = 3000;
public required string ListenHost { get; init; } = "localhost";
public required string WebDomain { get; init; }
public required string AccountDomain { get; init; }
}

View file

@ -32,7 +32,7 @@ public static class NoteQueryableExtensions {
) where T : IEntity {
if (pq.Limit is < 1)
throw GracefulException.BadRequest("Limit cannot be less than 1");
if (pq is { SinceId: not null, MinId: not null })
throw GracefulException.BadRequest("Can't use sinceId and minId params simultaneously");

View file

@ -47,6 +47,7 @@ public static class ServiceExtensions {
services.AddSingleton<RequestBufferingMiddleware>();
services.AddSingleton<AuthorizationMiddleware>();
services.AddSingleton<OauthAuthorizationMiddleware>();
services.AddSingleton<RequestVerificationMiddleware>();
// Hosted services = long running background tasks
// 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) {
// Caution: make sure these are in the correct order
return app.UseMiddleware<ErrorHandlerMiddleware>()
.UseMiddleware<RequestVerificationMiddleware>()
.UseMiddleware<RequestBufferingMiddleware>()
.UseMiddleware<AuthenticationMiddleware>()
.UseMiddleware<AuthorizationMiddleware>()

View file

@ -25,6 +25,9 @@ public class ErrorHandlerMiddleware(IOptions<Config.SecuritySection> options, IL
var verbosity = options.Value.ExceptionVerbosity;
if (e is GracefulException ce) {
if (verbosity > ExceptionVerbosity.Basic && ce.OverrideBasic)
verbosity = ExceptionVerbosity.Basic;
ctx.Response.StatusCode = (int)ce.StatusCode;
await ctx.Response.WriteAsJsonAsync(new ErrorResponse {
StatusCode = ctx.Response.StatusCode,
@ -58,11 +61,17 @@ 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) {
public readonly string? Details = details;
public readonly string Error = error;
public readonly HttpStatusCode StatusCode = statusCode;
public readonly string? Details = details;
public readonly string Error = error;
public readonly bool OverrideBasic = overrideBasic;
public readonly HttpStatusCode StatusCode = statusCode;
public GracefulException(HttpStatusCode statusCode, string message, string? details = null) :
this(statusCode, statusCode.ToString(), message, details) { }
@ -85,7 +94,7 @@ public class GracefulException(HttpStatusCode statusCode, string error, string m
public static GracefulException NotFound(string message, string? details = null) {
return new GracefulException(HttpStatusCode.NotFound, message, details);
}
public static GracefulException BadRequest(string message, string? details = null) {
return new GracefulException(HttpStatusCode.BadRequest, message, details);
}
@ -93,6 +102,11 @@ public class GracefulException(HttpStatusCode statusCode, string error, string m
public static GracefulException RecordNotFound() {
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 {

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();
app.Urls.Clear();
app.Urls.Add($"http://{config.WebDomain}:{config.ListenPort}");
app.Urls.Add($"http://{config.ListenHost}:{config.ListenPort}");
app.Run();

View file

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