Add spam rate limits for public invoice endpoints (Fix #3782) (#3889)

This commit is contained in:
Nicolas Dorier
2022-06-21 12:33:20 +09:00
committed by GitHub
parent 9d41a52d3b
commit 0aa7dacbca
7 changed files with 33 additions and 30 deletions

View File

@@ -1,4 +1,4 @@
 <Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" /> <Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
<Import Project="../Build/Common.csproj" /> <Import Project="../Build/Common.csproj" />
@@ -71,7 +71,7 @@
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NicolasDorier.CommandLine" Version="1.0.0.2" /> <PackageReference Include="NicolasDorier.CommandLine" Version="1.0.0.2" />
<PackageReference Include="NicolasDorier.CommandLine.Configuration" Version="1.0.0.3" /> <PackageReference Include="NicolasDorier.CommandLine.Configuration" Version="1.0.0.3" />
<PackageReference Include="NicolasDorier.RateLimits" Version="1.1.0" /> <PackageReference Include="NicolasDorier.RateLimits" Version="1.2.3" />
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="1.0.0.18" /> <PackageReference Include="NicolasDorier.StandardConfiguration" Version="1.0.0.18" />
<PackageReference Include="Serilog" Version="2.9.0" /> <PackageReference Include="Serilog" Version="2.9.0" />
<PackageReference Include="Serilog.AspNetCore" Version="3.2.0" /> <PackageReference Include="Serilog.AspNetCore" Version="3.2.0" />

View File

@@ -35,7 +35,7 @@ namespace BTCPayServer.Controllers.Greenfield
private readonly SettingsRepository _settingsRepository; private readonly SettingsRepository _settingsRepository;
private readonly EventAggregator _eventAggregator; private readonly EventAggregator _eventAggregator;
private readonly IPasswordValidator<ApplicationUser> _passwordValidator; private readonly IPasswordValidator<ApplicationUser> _passwordValidator;
private readonly RateLimitService _throttleService; private readonly IRateLimitService _throttleService;
private readonly BTCPayServerOptions _options; private readonly BTCPayServerOptions _options;
private readonly IAuthorizationService _authorizationService; private readonly IAuthorizationService _authorizationService;
private readonly UserService _userService; private readonly UserService _userService;
@@ -46,7 +46,7 @@ namespace BTCPayServer.Controllers.Greenfield
PoliciesSettings policiesSettings, PoliciesSettings policiesSettings,
EventAggregator eventAggregator, EventAggregator eventAggregator,
IPasswordValidator<ApplicationUser> passwordValidator, IPasswordValidator<ApplicationUser> passwordValidator,
RateLimitService throttleService, IRateLimitService throttleService,
BTCPayServerOptions options, BTCPayServerOptions options,
IAuthorizationService authorizationService, IAuthorizationService authorizationService,
UserService userService, UserService userService,

View File

@@ -18,6 +18,7 @@ using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using NBitpayClient; using NBitpayClient;
using NicolasDorier.RateLimits;
using static BTCPayServer.Controllers.UIAppsController; using static BTCPayServer.Controllers.UIAppsController;
namespace BTCPayServer.Controllers namespace BTCPayServer.Controllers
@@ -116,6 +117,7 @@ namespace BTCPayServer.Controllers
[IgnoreAntiforgeryToken] [IgnoreAntiforgeryToken]
[EnableCors(CorsPolicies.All)] [EnableCors(CorsPolicies.All)]
[DomainMappingConstraint(AppType.PointOfSale)] [DomainMappingConstraint(AppType.PointOfSale)]
[RateLimitsFilter(ZoneLimits.PublicInvoices, Scope = RateLimitsScope.RemoteAddress)]
public async Task<IActionResult> ViewPointOfSale(string appId, public async Task<IActionResult> ViewPointOfSale(string appId,
PosViewType viewType, PosViewType viewType,
[ModelBinder(typeof(InvariantDecimalModelBinder))] decimal? amount, [ModelBinder(typeof(InvariantDecimalModelBinder))] decimal? amount,
@@ -292,6 +294,7 @@ namespace BTCPayServer.Controllers
[IgnoreAntiforgeryToken] [IgnoreAntiforgeryToken]
[EnableCors(CorsPolicies.All)] [EnableCors(CorsPolicies.All)]
[DomainMappingConstraintAttribute(AppType.Crowdfund)] [DomainMappingConstraintAttribute(AppType.Crowdfund)]
[RateLimitsFilter(ZoneLimits.PublicInvoices, Scope = RateLimitsScope.RemoteAddress)]
public async Task<IActionResult> ContributeToCrowdfund(string appId, ContributeToCrowdfund request, CancellationToken cancellationToken) public async Task<IActionResult> ContributeToCrowdfund(string appId, ContributeToCrowdfund request, CancellationToken cancellationToken)
{ {

View File

@@ -10,6 +10,7 @@ using BTCPayServer.Plugins.PayButton.Models;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using NicolasDorier.RateLimits;
namespace BTCPayServer.Controllers namespace BTCPayServer.Controllers
{ {
@@ -38,6 +39,7 @@ namespace BTCPayServer.Controllers
[Route("api/v1/invoices")] [Route("api/v1/invoices")]
[IgnoreAntiforgeryToken] [IgnoreAntiforgeryToken]
[EnableCors(CorsPolicies.All)] [EnableCors(CorsPolicies.All)]
[RateLimitsFilter(ZoneLimits.PublicInvoices, Scope = RateLimitsScope.RemoteAddress)]
public async Task<IActionResult> PayButtonHandle([FromForm] PayButtonViewModel model, CancellationToken cancellationToken) public async Task<IActionResult> PayButtonHandle([FromForm] PayButtonViewModel model, CancellationToken cancellationToken)
{ {
var store = await _StoreRepository.FindStore(model.StoreId); var store = await _StoreRepository.FindStore(model.StoreId);

View File

@@ -437,28 +437,7 @@ namespace BTCPayServer.Hosting
{ {
options.AddPolicy(CorsPolicies.All, p => p.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin()); options.AddPolicy(CorsPolicies.All, p => p.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin());
}); });
services.AddSingleton(provider => services.AddRateLimits();
{
var btcPayEnv = provider.GetService<BTCPayServerEnvironment>();
var rateLimits = new RateLimitService();
if (btcPayEnv.IsDeveloping)
{
rateLimits.SetZone($"zone={ZoneLimits.Login} rate=1000r/min burst=100 nodelay");
rateLimits.SetZone($"zone={ZoneLimits.Register} rate=1000r/min burst=100 nodelay");
rateLimits.SetZone($"zone={ZoneLimits.PayJoin} rate=1000r/min burst=100 nodelay");
rateLimits.SetZone($"zone={ZoneLimits.Shopify} rate=1000r/min burst=100 nodelay");
rateLimits.SetZone($"zone={ZoneLimits.ForgotPassword} rate=5r/d burst=3 nodelay");
}
else
{
rateLimits.SetZone($"zone={ZoneLimits.Login} rate=5r/min burst=3 nodelay");
rateLimits.SetZone($"zone={ZoneLimits.Register} rate=2r/min burst=2 nodelay");
rateLimits.SetZone($"zone={ZoneLimits.PayJoin} rate=5r/min burst=3 nodelay");
rateLimits.SetZone($"zone={ZoneLimits.Shopify} rate=20r/min burst=3 nodelay");
rateLimits.SetZone($"zone={ZoneLimits.ForgotPassword} rate=5r/d burst=5 nodelay");
}
return rateLimits;
});
services.AddLogging(logBuilder => services.AddLogging(logBuilder =>
{ {
var debugLogFile = BTCPayServerOptions.GetDebugLog(configuration); var debugLogFile = BTCPayServerOptions.GetDebugLog(configuration);

View File

@@ -33,6 +33,7 @@ using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
using NicolasDorier.RateLimits;
namespace BTCPayServer.Hosting namespace BTCPayServer.Hosting
{ {
@@ -207,28 +208,45 @@ namespace BTCPayServer.Hosting
IServiceProvider prov, IServiceProvider prov,
BTCPayServerOptions options, BTCPayServerOptions options,
IOptions<DataDirectories> dataDirectories, IOptions<DataDirectories> dataDirectories,
ILoggerFactory loggerFactory) ILoggerFactory loggerFactory,
IRateLimitService rateLimits)
{ {
Logs.Configure(loggerFactory); Logs.Configure(loggerFactory);
Logs.Configuration.LogInformation($"Root Path: {options.RootPath}"); Logs.Configuration.LogInformation($"Root Path: {options.RootPath}");
if (options.RootPath.Equals("/", StringComparison.OrdinalIgnoreCase)) if (options.RootPath.Equals("/", StringComparison.OrdinalIgnoreCase))
{ {
ConfigureCore(app, env, prov, dataDirectories); ConfigureCore(app, env, prov, dataDirectories, rateLimits);
} }
else else
{ {
app.Map(options.RootPath, appChild => app.Map(options.RootPath, appChild =>
{ {
ConfigureCore(appChild, env, prov, dataDirectories); ConfigureCore(appChild, env, prov, dataDirectories, rateLimits);
}); });
} }
} }
private void ConfigureCore(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider prov, IOptions<DataDirectories> dataDirectories) private void ConfigureCore(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider prov, IOptions<DataDirectories> dataDirectories, IRateLimitService rateLimits)
{ {
if (env.IsDevelopment()) if (env.IsDevelopment())
{ {
app.UseDeveloperExceptionPage(); app.UseDeveloperExceptionPage();
rateLimits.SetZone($"zone={ZoneLimits.Login} rate=1000r/min burst=100 nodelay");
rateLimits.SetZone($"zone={ZoneLimits.PublicInvoices} rate=1000r/min burst=100 nodelay");
rateLimits.SetZone($"zone={ZoneLimits.Register} rate=1000r/min burst=100 nodelay");
rateLimits.SetZone($"zone={ZoneLimits.PayJoin} rate=1000r/min burst=100 nodelay");
rateLimits.SetZone($"zone={ZoneLimits.Shopify} rate=1000r/min burst=100 nodelay");
rateLimits.SetZone($"zone={ZoneLimits.ForgotPassword} rate=5r/d burst=3 nodelay");
} }
else
{
rateLimits.SetZone($"zone={ZoneLimits.Login} rate=5r/min burst=3 nodelay");
rateLimits.SetZone($"zone={ZoneLimits.PublicInvoices} rate=4r/min burst=10 delay=3");
rateLimits.SetZone($"zone={ZoneLimits.Register} rate=2r/min burst=2 nodelay");
rateLimits.SetZone($"zone={ZoneLimits.PayJoin} rate=5r/min burst=3 nodelay");
rateLimits.SetZone($"zone={ZoneLimits.Shopify} rate=20r/min burst=3 nodelay");
rateLimits.SetZone($"zone={ZoneLimits.ForgotPassword} rate=5r/d burst=5 nodelay");
}
app.UseHeadersOverride(); app.UseHeadersOverride();
var forwardingOptions = new ForwardedHeadersOptions() var forwardingOptions = new ForwardedHeadersOptions()
{ {

View File

@@ -7,5 +7,6 @@ namespace BTCPayServer
public const string PayJoin = "PayJoin"; public const string PayJoin = "PayJoin";
public const string Shopify = nameof(Shopify); public const string Shopify = nameof(Shopify);
public const string ForgotPassword = "forgotpassword"; public const string ForgotPassword = "forgotpassword";
public const string PublicInvoices = "publicinvoices";
} }
} }