* Closes #2371

* Change the default to no known networks

* Address @Vital7 note

* Handle both IPv4 and IPv6 when mapped

This follows ASP.NET Core logic

* Refactor forwarded headers usage
This commit is contained in:
Łukasz Domeradzki 2021-07-12 13:40:23 +02:00 committed by GitHub
parent 485caab0f4
commit 13e9f1ac2a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 38 additions and 12 deletions

View file

@ -30,7 +30,9 @@ using ArchiSteamFarm.Core;
using ArchiSteamFarm.Helpers;
using ArchiSteamFarm.Storage;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
namespace ArchiSteamFarm.IPC.Integration {
@ -46,11 +48,18 @@ namespace ArchiSteamFarm.IPC.Integration {
private static Timer? ClearFailedAuthorizationsTimer;
private readonly ForwardedHeadersOptions ForwardedHeadersOptions;
private readonly RequestDelegate Next;
public ApiAuthenticationMiddleware(RequestDelegate next) {
public ApiAuthenticationMiddleware(RequestDelegate next, IOptions<ForwardedHeadersOptions> forwardedHeadersOptions) {
Next = next ?? throw new ArgumentNullException(nameof(next));
if (forwardedHeadersOptions == null) {
throw new ArgumentNullException(nameof(forwardedHeadersOptions));
}
ForwardedHeadersOptions = forwardedHeadersOptions.Value ?? throw new InvalidOperationException(nameof(forwardedHeadersOptions));
lock (FailedAuthorizations) {
ClearFailedAuthorizationsTimer ??= new Timer(
_ => FailedAuthorizations.Clear(),
@ -78,7 +87,7 @@ namespace ArchiSteamFarm.IPC.Integration {
await Next(context).ConfigureAwait(false);
}
private static async Task<HttpStatusCode> GetAuthenticationStatus(HttpContext context) {
private async Task<HttpStatusCode> GetAuthenticationStatus(HttpContext context) {
if (context == null) {
throw new ArgumentNullException(nameof(context));
}
@ -87,18 +96,34 @@ namespace ArchiSteamFarm.IPC.Integration {
throw new InvalidOperationException(nameof(ClearFailedAuthorizationsTimer));
}
string? ipcPassword = ASF.GlobalConfig != null ? ASF.GlobalConfig.IPCPassword : GlobalConfig.DefaultIPCPassword;
if (string.IsNullOrEmpty(ipcPassword)) {
return HttpStatusCode.OK;
}
IPAddress? clientIP = context.Connection.RemoteIpAddress;
if (clientIP == null) {
throw new InvalidOperationException(nameof(clientIP));
}
string? ipcPassword = ASF.GlobalConfig != null ? ASF.GlobalConfig.IPCPassword : GlobalConfig.DefaultIPCPassword;
if (string.IsNullOrEmpty(ipcPassword)) {
if (IPAddress.IsLoopback(clientIP)) {
return HttpStatusCode.OK;
}
if (ForwardedHeadersOptions.KnownNetworks.Count == 0) {
return HttpStatusCode.Forbidden;
}
if (clientIP.IsIPv4MappedToIPv6) {
IPAddress mappedClientIP = clientIP.MapToIPv4();
if (ForwardedHeadersOptions.KnownNetworks.Any(network => network.Contains(mappedClientIP))) {
return HttpStatusCode.OK;
}
}
return ForwardedHeadersOptions.KnownNetworks.Any(network => network.Contains(clientIP)) ? HttpStatusCode.OK : HttpStatusCode.Forbidden;
}
if (FailedAuthorizations.TryGetValue(clientIP, out byte attempts)) {
if (attempts >= MaxFailedAuthorizationAttempts) {
return HttpStatusCode.Forbidden;

View file

@ -148,12 +148,12 @@ namespace ArchiSteamFarm.IPC {
app.UseRouting();
#endif
// We want to protect our API with IPCPassword and additional security, this should be called after routing, so the middleware won't have to deal with API endpoints that do not exist
app.UseWhen(context => context.Request.Path.StartsWithSegments("/Api", StringComparison.OrdinalIgnoreCase), appBuilder => appBuilder.UseMiddleware<ApiAuthenticationMiddleware>());
string? ipcPassword = ASF.GlobalConfig != null ? ASF.GlobalConfig.IPCPassword : GlobalConfig.DefaultIPCPassword;
if (!string.IsNullOrEmpty(ipcPassword)) {
// We want to protect our API with IPCPassword, this should be called after routing, so the middleware won't have to deal with API endpoints that do not exist
app.UseWhen(context => context.Request.Path.StartsWithSegments("/Api", StringComparison.OrdinalIgnoreCase), appBuilder => appBuilder.UseMiddleware<ApiAuthenticationMiddleware>());
// We want to apply CORS policy in order to allow userscripts and other third-party integrations to communicate with ASF API, this should be called before response compression, but can't be due to how our flow works
// We apply CORS policy only with IPCPassword set as an extra authentication measure
app.UseCors();
@ -197,7 +197,8 @@ namespace ArchiSteamFarm.IPC {
HashSet<IPNetwork>? knownNetworks = null;
if (knownNetworksTexts?.Count > 0) {
knownNetworks = new HashSet<IPNetwork>(knownNetworksTexts.Count);
// Use specified known networks
knownNetworks = new HashSet<IPNetwork>();
foreach (string knownNetworkText in knownNetworksTexts) {
string[] addressParts = knownNetworkText.Split('/', StringSplitOptions.RemoveEmptyEntries);