mirror of
https://github.com/bitwarden/server.git
synced 2025-07-21 09:31:34 -05:00
[PS-93] Distributed Ip rate limiting (#2060)
* Upgrade AspNetCoreRateLimiter and enable redis distributed cache for rate limiting. - Upgrades AspNetCoreRateLimiter to 4.0.2, which required updating NewtonSoft.Json to 13.0.1. - Replaces Microsoft.Extensions.Caching.Redis with Microsoft.Extensions.Caching.StackExchangeRedis as the original was deprecated and conflicted with the latest AspNetCoreRateLimiter - Adds startup task to Program.cs for Api/Identity projects to support AspNetCoreRateLimiters breaking changes for seeding its stores. - Adds a Redis connection string option to GlobalSettings Signed-off-by: Shane Melton <smelton@bitwarden.com> * Cleanup Redis distributed cache registration - Add new AddDistributedCache service collection extension to add either a Memory or Redis distributed cache. - Remove distributed cache registration from Identity service collection extension. - Add IpRateLimitSeedStartupService.cs to run at application startup to seed the Ip rate limiting policies. Signed-off-by: Shane Melton <smelton@bitwarden.com> * Add caching configuration to SSO Startup.cs Signed-off-by: Shane Melton <smelton@bitwarden.com> * Add ProjectName as an instance name for Redis options Signed-off-by: Shane Melton <smelton@bitwarden.com> * Use distributed cache in CustomIpRateLimitMiddleware.cs Signed-off-by: Shane Melton <smelton@bitwarden.com> * Undo changes to Program.cs and launchSettings.json * Move new service collection extensions to SharedWeb * Upgrade Caching.StackExchangeRedis package to v6 * Cleanup and fix leftover merge conflicts * Remove use of Newtonsoft.Json in distributed cache extensions * Cleanup more formatting * Fix formatting * Fix startup issue caused by merge and fix integration test Signed-off-by: Shane Melton <smelton@bitwarden.com> * Linting fix Signed-off-by: Shane Melton <smelton@bitwarden.com>
This commit is contained in:
@ -1,8 +1,11 @@
|
||||
using System.Reflection;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using AspNetCoreRateLimit;
|
||||
using AspNetCoreRateLimit.Redis;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.HostedServices;
|
||||
using Bit.Core.Identity;
|
||||
using Bit.Core.IdentityServer;
|
||||
using Bit.Core.Models.Business.Tokenables;
|
||||
@ -27,13 +30,17 @@ using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc.Localization;
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
using Microsoft.Extensions.Caching.StackExchangeRedis;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Serilog.Context;
|
||||
using StackExchange.Redis;
|
||||
using NoopRepos = Bit.Core.Repositories.Noop;
|
||||
using Role = Bit.Core.Entities.Role;
|
||||
using TableStorageRepos = Bit.Core.Repositories.TableStorage;
|
||||
|
||||
namespace Bit.SharedWeb.Utilities
|
||||
@ -566,16 +573,6 @@ namespace Bit.SharedWeb.Utilities
|
||||
|
||||
public static IServiceCollection AddDistributedIdentityServices(this IServiceCollection services, GlobalSettings globalSettings)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(globalSettings.IdentityServer?.RedisConnectionString))
|
||||
{
|
||||
services.AddDistributedMemoryCache();
|
||||
}
|
||||
else
|
||||
{
|
||||
services.AddDistributedRedisCache(options =>
|
||||
options.Configuration = globalSettings.IdentityServer.RedisConnectionString);
|
||||
}
|
||||
|
||||
services.AddOidcStateDataFormatterCache();
|
||||
services.AddSession();
|
||||
services.ConfigureApplicationCookie(configure => configure.CookieManager = new DistributedCacheCookieManager());
|
||||
@ -600,5 +597,59 @@ namespace Bit.SharedWeb.Utilities
|
||||
options.TimestampDriftTolerance = 300000;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds either an in-memory or distributed IP rate limiter depending if a Redis connection string is available.
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <param name="globalSettings"></param>
|
||||
public static void AddIpRateLimiting(this IServiceCollection services,
|
||||
GlobalSettings globalSettings)
|
||||
{
|
||||
services.AddHostedService<IpRateLimitSeedStartupService>();
|
||||
services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
|
||||
|
||||
if (string.IsNullOrEmpty(globalSettings.Redis.ConnectionString))
|
||||
{
|
||||
services.AddInMemoryRateLimiting();
|
||||
}
|
||||
else
|
||||
{
|
||||
services.AddRedisRateLimiting(); // Requires a registered IConnectionMultiplexer
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an implementation of <see cref="IDistributedCache"/> to the service collection. Uses a memory
|
||||
/// cache if self hosted or no Redis connection string is available in GlobalSettings.
|
||||
/// </summary>
|
||||
public static void AddDistributedCache(
|
||||
this IServiceCollection services,
|
||||
GlobalSettings globalSettings)
|
||||
{
|
||||
if (globalSettings.SelfHosted || string.IsNullOrEmpty(globalSettings.Redis.ConnectionString))
|
||||
{
|
||||
services.AddDistributedMemoryCache();
|
||||
return;
|
||||
}
|
||||
|
||||
// Register the IConnectionMultiplexer explicitly so it can be accessed via DI
|
||||
// (e.g. for the IP rate limiting store)
|
||||
services.AddSingleton<IConnectionMultiplexer>(
|
||||
_ => ConnectionMultiplexer.Connect(globalSettings.Redis.ConnectionString));
|
||||
|
||||
// Explicitly register IDistributedCache to re-use existing IConnectionMultiplexer
|
||||
// to reduce the number of redundant connections to the Redis instance
|
||||
services.AddSingleton<IDistributedCache>(s =>
|
||||
{
|
||||
return new RedisCache(new RedisCacheOptions
|
||||
{
|
||||
// Use "ProjectName:" as an instance name to namespace keys and avoid conflicts between projects
|
||||
InstanceName = $"{globalSettings.ProjectName}:",
|
||||
ConnectionMultiplexerFactory = () =>
|
||||
Task.FromResult(s.GetRequiredService<IConnectionMultiplexer>())
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user