1
0
mirror of https://github.com/bitwarden/server.git synced 2025-01-22 21:51:22 +01:00

[PM-6196] Cleanup distributed cache for identity (#3704)

* cleanup distributed cache for identity

* removed unused using

* use persistent IDistributedCache
This commit is contained in:
Kyle Spearrin 2024-02-09 07:43:28 -05:00 committed by GitHub
parent b1967aa8a7
commit 6174df0874
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 45 additions and 111 deletions

View File

@ -65,7 +65,7 @@ public class Startup
} }
// Authentication // Authentication
services.AddDistributedIdentityServices(globalSettings); services.AddDistributedIdentityServices();
services.AddAuthentication() services.AddAuthentication()
.AddCookie(AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme); .AddCookie(AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme);
services.AddSsoServices(globalSettings); services.AddSsoServices(globalSettings);

View File

@ -1,8 +1,8 @@
using Bit.Core.Settings; using Duende.IdentityServer.Configuration;
using Duende.IdentityServer.Configuration;
using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.Caching.StackExchangeRedis; using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
namespace Bit.Core.IdentityServer; namespace Bit.Core.IdentityServer;
@ -10,15 +10,18 @@ namespace Bit.Core.IdentityServer;
public class ConfigureOpenIdConnectDistributedOptions : IPostConfigureOptions<CookieAuthenticationOptions> public class ConfigureOpenIdConnectDistributedOptions : IPostConfigureOptions<CookieAuthenticationOptions>
{ {
private readonly IdentityServerOptions _idsrv; private readonly IdentityServerOptions _idsrv;
private readonly IHttpContextAccessor _httpContextAccessor; private readonly IDistributedCache _distributedCache;
private readonly GlobalSettings _globalSettings; private readonly IDataProtectionProvider _dataProtectionProvider;
public ConfigureOpenIdConnectDistributedOptions(IHttpContextAccessor httpContextAccessor, GlobalSettings globalSettings, public ConfigureOpenIdConnectDistributedOptions(
[FromKeyedServices("persistent")]
IDistributedCache distributedCache,
IDataProtectionProvider dataProtectionProvider,
IdentityServerOptions idsrv) IdentityServerOptions idsrv)
{ {
_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
_globalSettings = globalSettings;
_idsrv = idsrv; _idsrv = idsrv;
_distributedCache = distributedCache;
_dataProtectionProvider = dataProtectionProvider;
} }
public void PostConfigure(string name, CookieAuthenticationOptions options) public void PostConfigure(string name, CookieAuthenticationOptions options)
@ -34,19 +37,7 @@ public class ConfigureOpenIdConnectDistributedOptions : IPostConfigureOptions<Co
options.Cookie.Name = AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme; options.Cookie.Name = AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme;
options.Cookie.IsEssential = true; options.Cookie.IsEssential = true;
options.Cookie.SameSite = _idsrv.Authentication.CookieSameSiteMode; options.Cookie.SameSite = _idsrv.Authentication.CookieSameSiteMode;
options.TicketDataFormat = new DistributedCacheTicketDataFormatter(_httpContextAccessor, name); options.TicketDataFormat = new DistributedCacheTicketDataFormatter(_distributedCache, _dataProtectionProvider, name);
options.SessionStore = new DistributedCacheTicketStore(_distributedCache);
if (string.IsNullOrWhiteSpace(_globalSettings.IdentityServer?.RedisConnectionString))
{
options.SessionStore = new MemoryCacheTicketStore();
}
else
{
var redisOptions = new RedisCacheOptions
{
Configuration = _globalSettings.IdentityServer.RedisConnectionString,
};
options.SessionStore = new RedisCacheTicketStore(redisOptions);
}
} }
} }

View File

@ -58,7 +58,7 @@ public class DistributedCacheCookieManager : ICookieManager
} }
private IDistributedCache GetCache(HttpContext context) => private IDistributedCache GetCache(HttpContext context) =>
context.RequestServices.GetRequiredService<IDistributedCache>(); context.RequestServices.GetRequiredKeyedService<IDistributedCache>("persistent");
private string GetKey(string key, string id) => $"{CacheKeyPrefix}-{key}-{id}"; private string GetKey(string key, string id) => $"{CacheKeyPrefix}-{key}-{id}";

View File

@ -1,32 +1,32 @@
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
namespace Bit.Core.IdentityServer; namespace Bit.Core.IdentityServer;
public class DistributedCacheTicketDataFormatter : ISecureDataFormat<AuthenticationTicket> public class DistributedCacheTicketDataFormatter : ISecureDataFormat<AuthenticationTicket>
{ {
private readonly IHttpContextAccessor _httpContext; private const string CacheKeyPrefix = "ticket-data";
private readonly string _name;
public DistributedCacheTicketDataFormatter(IHttpContextAccessor httpContext, string name) private readonly IDistributedCache _distributedCache;
private readonly IDataProtector _dataProtector;
private readonly string _prefix;
public DistributedCacheTicketDataFormatter(
IDistributedCache distributedCache,
IDataProtectionProvider dataProtectionProvider,
string name)
{ {
_httpContext = httpContext; _distributedCache = distributedCache;
_name = name; _dataProtector = dataProtectionProvider.CreateProtector(CacheKeyPrefix, name);
_prefix = $"{CacheKeyPrefix}-{name}";
} }
private string CacheKeyPrefix => "ticket-data";
private IDistributedCache Cache => _httpContext.HttpContext.RequestServices.GetRequiredService<IDistributedCache>();
private IDataProtector Protector => _httpContext.HttpContext.RequestServices.GetRequiredService<IDataProtectionProvider>()
.CreateProtector(CacheKeyPrefix, _name);
public string Protect(AuthenticationTicket data) => Protect(data, null); public string Protect(AuthenticationTicket data) => Protect(data, null);
public string Protect(AuthenticationTicket data, string purpose) public string Protect(AuthenticationTicket data, string purpose)
{ {
var key = Guid.NewGuid().ToString(); var key = Guid.NewGuid().ToString();
var cacheKey = $"{CacheKeyPrefix}-{_name}-{purpose}-{key}"; var cacheKey = $"{_prefix}-{purpose}-{key}";
var expiresUtc = data.Properties.ExpiresUtc ?? var expiresUtc = data.Properties.ExpiresUtc ??
DateTimeOffset.UtcNow.AddMinutes(15); DateTimeOffset.UtcNow.AddMinutes(15);
@ -35,9 +35,9 @@ public class DistributedCacheTicketDataFormatter : ISecureDataFormat<Authenticat
options.SetAbsoluteExpiration(expiresUtc); options.SetAbsoluteExpiration(expiresUtc);
var ticket = TicketSerializer.Default.Serialize(data); var ticket = TicketSerializer.Default.Serialize(data);
Cache.Set(cacheKey, ticket, options); _distributedCache.Set(cacheKey, ticket, options);
return Protector.Protect(key); return _dataProtector.Protect(key);
} }
public AuthenticationTicket Unprotect(string protectedText) => Unprotect(protectedText, null); public AuthenticationTicket Unprotect(string protectedText) => Unprotect(protectedText, null);
@ -49,9 +49,9 @@ public class DistributedCacheTicketDataFormatter : ISecureDataFormat<Authenticat
} }
// Decrypt the key and retrieve the data from the cache. // Decrypt the key and retrieve the data from the cache.
var key = Protector.Unprotect(protectedText); var key = _dataProtector.Unprotect(protectedText);
var cacheKey = $"{CacheKeyPrefix}-{_name}-{purpose}-{key}"; var cacheKey = $"{_prefix}-{purpose}-{key}";
var ticket = Cache.Get(cacheKey); var ticket = _distributedCache.Get(cacheKey);
if (ticket == null) if (ticket == null)
{ {

View File

@ -1,23 +1,22 @@
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.StackExchangeRedis;
namespace Bit.Core.IdentityServer; namespace Bit.Core.IdentityServer;
public class RedisCacheTicketStore : ITicketStore public class DistributedCacheTicketStore : ITicketStore
{ {
private const string _keyPrefix = "auth-"; private const string KeyPrefix = "auth-";
private readonly IDistributedCache _cache; private readonly IDistributedCache _cache;
public RedisCacheTicketStore(RedisCacheOptions options) public DistributedCacheTicketStore(IDistributedCache distributedCache)
{ {
_cache = new RedisCache(options); _cache = distributedCache;
} }
public async Task<string> StoreAsync(AuthenticationTicket ticket) public async Task<string> StoreAsync(AuthenticationTicket ticket)
{ {
var key = $"{_keyPrefix}{Guid.NewGuid()}"; var key = $"{KeyPrefix}{Guid.NewGuid()}";
await RenewAsync(key, ticket); await RenewAsync(key, ticket);
return key; return key;

View File

@ -1,53 +0,0 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.Extensions.Caching.Memory;
namespace Bit.Core.IdentityServer;
public class MemoryCacheTicketStore : ITicketStore
{
private const string _keyPrefix = "auth-";
private readonly IMemoryCache _cache;
public MemoryCacheTicketStore()
{
_cache = new MemoryCache(new MemoryCacheOptions());
}
public async Task<string> StoreAsync(AuthenticationTicket ticket)
{
var key = $"{_keyPrefix}{Guid.NewGuid()}";
await RenewAsync(key, ticket);
return key;
}
public Task RenewAsync(string key, AuthenticationTicket ticket)
{
var options = new MemoryCacheEntryOptions();
var expiresUtc = ticket.Properties.ExpiresUtc;
if (expiresUtc.HasValue)
{
options.SetAbsoluteExpiration(expiresUtc.Value);
}
else
{
options.SetSlidingExpiration(TimeSpan.FromMinutes(15));
}
_cache.Set(key, ticket, options);
return Task.FromResult(0);
}
public Task<AuthenticationTicket> RetrieveAsync(string key)
{
_cache.TryGetValue(key, out AuthenticationTicket ticket);
return Task.FromResult(ticket);
}
public Task RemoveAsync(string key)
{
_cache.Remove(key);
return Task.FromResult(0);
}
}

View File

@ -46,6 +46,7 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque
IPolicyService policyService, IPolicyService policyService,
IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> tokenDataFactory, IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> tokenDataFactory,
IFeatureService featureService, IFeatureService featureService,
[FromKeyedServices("persistent")]
IDistributedCache distributedCache, IDistributedCache distributedCache,
IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder) IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder)
: base(userManager, deviceRepository, deviceService, userService, eventService, : base(userManager, deviceRepository, deviceService, userService, eventService,

View File

@ -49,6 +49,7 @@ public class ResourceOwnerPasswordValidator : BaseRequestValidator<ResourceOwner
IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> tokenDataFactory, IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> tokenDataFactory,
IFeatureService featureService, IFeatureService featureService,
ISsoConfigRepository ssoConfigRepository, ISsoConfigRepository ssoConfigRepository,
[FromKeyedServices("persistent")]
IDistributedCache distributedCache, IDistributedCache distributedCache,
IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder) IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder)
: base(userManager, deviceRepository, deviceService, userService, eventService, : base(userManager, deviceRepository, deviceService, userService, eventService,

View File

@ -50,6 +50,7 @@ public class WebAuthnGrantValidator : BaseRequestValidator<ExtensionGrantValidat
IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> tokenDataFactory, IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> tokenDataFactory,
IDataProtectorTokenFactory<WebAuthnLoginAssertionOptionsTokenable> assertionOptionsDataProtector, IDataProtectorTokenFactory<WebAuthnLoginAssertionOptionsTokenable> assertionOptionsDataProtector,
IFeatureService featureService, IFeatureService featureService,
[FromKeyedServices("persistent")]
IDistributedCache distributedCache, IDistributedCache distributedCache,
IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder, IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder,
IAssertWebAuthnLoginCredentialCommand assertWebAuthnLoginCredentialCommand IAssertWebAuthnLoginCredentialCommand assertWebAuthnLoginCredentialCommand

View File

@ -91,7 +91,7 @@ public class Startup
// Authentication // Authentication
services services
.AddDistributedIdentityServices(globalSettings) .AddDistributedIdentityServices()
.AddAuthentication() .AddAuthentication()
.AddCookie(AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme) .AddCookie(AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme)
.AddOpenIdConnect("sso", "Single Sign On", options => .AddOpenIdConnect("sso", "Single Sign On", options =>

View File

@ -35,7 +35,6 @@ using Bit.Core.Vault.Services;
using Bit.Infrastructure.Dapper; using Bit.Infrastructure.Dapper;
using Bit.Infrastructure.EntityFramework; using Bit.Infrastructure.EntityFramework;
using DnsClient; using DnsClient;
using Duende.IdentityServer.Configuration;
using IdentityModel; using IdentityModel;
using LaunchDarkly.Sdk.Server; using LaunchDarkly.Sdk.Server;
using LaunchDarkly.Sdk.Server.Interfaces; using LaunchDarkly.Sdk.Server.Interfaces;
@ -632,18 +631,13 @@ public static class ServiceCollectionExtensions
}); });
} }
public static IServiceCollection AddDistributedIdentityServices(this IServiceCollection services, GlobalSettings globalSettings) public static IServiceCollection AddDistributedIdentityServices(this IServiceCollection services)
{ {
services.AddOidcStateDataFormatterCache(); services.AddOidcStateDataFormatterCache();
services.AddSession(); services.AddSession();
services.ConfigureApplicationCookie(configure => configure.CookieManager = new DistributedCacheCookieManager()); services.ConfigureApplicationCookie(configure => configure.CookieManager = new DistributedCacheCookieManager());
services.ConfigureExternalCookie(configure => configure.CookieManager = new DistributedCacheCookieManager()); services.ConfigureExternalCookie(configure => configure.CookieManager = new DistributedCacheCookieManager());
services.AddSingleton<IPostConfigureOptions<CookieAuthenticationOptions>>( services.AddSingleton<IPostConfigureOptions<CookieAuthenticationOptions>, ConfigureOpenIdConnectDistributedOptions>();
svcs => new ConfigureOpenIdConnectDistributedOptions(
svcs.GetRequiredService<IHttpContextAccessor>(),
globalSettings,
svcs.GetRequiredService<IdentityServerOptions>())
);
return services; return services;
} }