case 4732 - trusted reverse proxies setting

New config setting AYANOVA_TRUSTED_PROXIES (IP addresses or CIDR networks)
for reverse proxies on another computer: their X-Forwarded-For is used, a
chain of trusted proxies is followed, invalid entries are warned at boot.
X-Forwarded-For from a proxy that is not trusted logs one warning per server
start naming the proxy's address. Without the setting nothing changes (only
a loopback proxy is trusted). Shown in server info; manual page, Linux install
note, changelog. F6 follow-up recorded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-25 12:58:01 -07:00
parent 344106ae27
commit c04de33af2
10 changed files with 100 additions and 1 deletions

View File

@@ -194,6 +194,7 @@ namespace AyaNova.Api.Controllers
AYANOVA_LOG_LEVEL = ServerBootConfig.AYANOVA_LOG_LEVEL,
AYANOVA_LOG_ENABLE_LOGGER_DIAGNOSTIC_LOG = ServerBootConfig.AYANOVA_LOG_ENABLE_LOGGER_DIAGNOSTIC_LOG,
AYANOVA_SMTP_ACCEPT_ANY_CERTIFICATE = ServerBootConfig.AYANOVA_SMTP_ACCEPT_ANY_CERTIFICATE,
AYANOVA_TRUSTED_PROXIES = ServerBootConfig.AYANOVA_TRUSTED_PROXIES,
serverinfo = ServerBootConfig.BOOT_DIAGNOSTIC_INFO,
dbserverinfo = ServerBootConfig.DBSERVER_DIAGNOSTIC_INFO
}));
@@ -317,6 +318,7 @@ namespace AyaNova.Api.Controllers
sbRet.AppendLine($"AYANOVA_LOG_LEVEL: {ServerBootConfig.AYANOVA_LOG_LEVEL}");
sbRet.AppendLine($"AYANOVA_LOG_ENABLE_LOGGER_DIAGNOSTIC_LOG: {ServerBootConfig.AYANOVA_LOG_ENABLE_LOGGER_DIAGNOSTIC_LOG}");
sbRet.AppendLine($"AYANOVA_SMTP_ACCEPT_ANY_CERTIFICATE: {ServerBootConfig.AYANOVA_SMTP_ACCEPT_ANY_CERTIFICATE}");
sbRet.AppendLine($"AYANOVA_TRUSTED_PROXIES: {ServerBootConfig.AYANOVA_TRUSTED_PROXIES}");
sbRet.AppendLine("#########################################################");
sbRet.AppendLine("SERVER DIAGNOSTICS");

View File

@@ -40,6 +40,7 @@ namespace AyaNova
}
private readonly ILogger _newLog;
private static int _untrustedProxyWarned = 0;
private string _connectionString = "";
private readonly Microsoft.AspNetCore.Hosting.IWebHostEnvironment _hostingEnvironment;
@@ -325,9 +326,37 @@ namespace AyaNova
ServiceProviderProvider.Provider = app.ApplicationServices;
//Enable ability to handle reverse proxy
app.UseForwardedHeaders(new ForwardedHeadersOptions
//case 4732: a proxy on this computer (loopback) is always trusted; proxies on other computers only when named in AYANOVA_TRUSTED_PROXIES
var forwardedHeadersOptions = new ForwardedHeadersOptions
{
ForwardedHeaders = Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders.XForwardedFor | Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders.XForwardedProto
};
if (!string.IsNullOrWhiteSpace(ServerBootConfig.AYANOVA_TRUSTED_PROXIES))
{
foreach (var entry in ServerBootConfig.AYANOVA_TRUSTED_PROXIES.Split(new[] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries))
{
if (entry.Contains('/') && System.Net.IPNetwork.TryParse(entry, out var network))
forwardedHeadersOptions.KnownIPNetworks.Add(network);
else if (!entry.Contains('/') && System.Net.IPAddress.TryParse(entry, out var address))
forwardedHeadersOptions.KnownProxies.Add(address);
else
_newLog.LogWarning($"AYANOVA_TRUSTED_PROXIES entry \"{entry}\" is not an IP address or network and is ignored");
}
//follow a chain of trusted proxies (e.g. a load balancer behind another proxy) back to the first address that is not trusted
forwardedHeadersOptions.ForwardLimit = null;
}
app.UseForwardedHeaders(forwardedHeadersOptions);
//case 4732: X-Forwarded-For from a proxy that is not trusted is ignored (no X-Original-For is set), so every user appears to come
//from the proxy's address and shares one failed login limit; say so once in the log
app.Use(async (context, next) =>
{
if (_untrustedProxyWarned == 0
&& context.Request.Headers.ContainsKey("X-Forwarded-For")
&& !context.Request.Headers.ContainsKey("X-Original-For")
&& System.Threading.Interlocked.Exchange(ref _untrustedProxyWarned, 1) == 0)
_newLog.LogWarning($"A request came through a reverse proxy at {(context.Connection.RemoteIpAddress.IsIPv4MappedToIPv6 ? context.Connection.RemoteIpAddress.MapToIPv4() : context.Connection.RemoteIpAddress)} that is not trusted, so users connecting through it all appear to come from that address and share one limit on failed login attempts. If this is your reverse proxy, add its address to the AYANOVA_TRUSTED_PROXIES setting");
await next.Invoke();
});

View File

@@ -107,6 +107,8 @@ namespace AyaNova.Util
//SECURITY
internal static string AYANOVA_SET_SUPERUSER_PW { get; set; }
//case 4732: reverse proxies on other computers whose X-Forwarded-For is trusted (addresses or networks, comma separated)
internal static string AYANOVA_TRUSTED_PROXIES { get; set; }
//EMAIL
//case 4724: the SMTP server's certificate is checked unless this is set (a mail server with a self-signed or internal certificate)
@@ -189,6 +191,8 @@ namespace AyaNova.Util
//backdoor back door password superuser reset
AYANOVA_SET_SUPERUSER_PW = config.GetValue<string>("AYANOVA_SET_SUPERUSER_PW");
AYANOVA_TRUSTED_PROXIES = config.GetValue<string>("AYANOVA_TRUSTED_PROXIES");
//REPORT RENDERING
//RENDER OVERRIDE URL FOR CORS ISSUES BEHIND IIS (case 4398)