1
0
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:
Shane Melton
2022-07-19 11:58:32 -07:00
committed by GitHub
parent 1764d2446e
commit 7d40b38352
39 changed files with 2331 additions and 1910 deletions

View File

@ -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>())
});
});
}
}
}