Skip to content

Commit

Permalink
Closes #2371 (#2372)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
JustArchi authored Jul 12, 2021
1 parent 485caab commit 13e9f1a
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 11 deletions.
39 changes: 32 additions & 7 deletions ArchiSteamFarm/IPC/Integration/ApiAuthenticationMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
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 {
Expand All @@ -46,11 +48,18 @@ internal sealed class ApiAuthenticationMiddleware {

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(),
Expand Down Expand Up @@ -78,7 +87,7 @@ public async Task InvokeAsync(HttpContext context) {
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));
}
Expand All @@ -87,16 +96,32 @@ private static async Task<HttpStatusCode> GetAuthenticationStatus(HttpContext co
throw new InvalidOperationException(nameof(ClearFailedAuthorizationsTimer));
}

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)) {
return HttpStatusCode.OK;
}
if (IPAddress.IsLoopback(clientIP)) {
return HttpStatusCode.OK;
}

IPAddress? clientIP = context.Connection.RemoteIpAddress;
if (ForwardedHeadersOptions.KnownNetworks.Count == 0) {
return HttpStatusCode.Forbidden;
}

if (clientIP == null) {
throw new InvalidOperationException(nameof(clientIP));
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)) {
Expand Down
9 changes: 5 additions & 4 deletions ArchiSteamFarm/IPC/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,12 +148,12 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
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();
Expand Down Expand Up @@ -197,7 +197,8 @@ public void ConfigureServices(IServiceCollection services) {
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);
Expand Down

0 comments on commit 13e9f1a

Please sign in to comment.