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

View File

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

View File

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

View File

@ -1,23 +1,22 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.StackExchangeRedis;
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;
public RedisCacheTicketStore(RedisCacheOptions options)
public DistributedCacheTicketStore(IDistributedCache distributedCache)
{
_cache = new RedisCache(options);
_cache = distributedCache;
}
public async Task<string> StoreAsync(AuthenticationTicket ticket)
{
var key = $"{_keyPrefix}{Guid.NewGuid()}";
var key = $"{KeyPrefix}{Guid.NewGuid()}";
await RenewAsync(key, ticket);
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,
IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> tokenDataFactory,
IFeatureService featureService,
[FromKeyedServices("persistent")]
IDistributedCache distributedCache,
IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder)
: base(userManager, deviceRepository, deviceService, userService, eventService,

View File

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

View File

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

View File

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

View File

@ -35,7 +35,6 @@ using Bit.Core.Vault.Services;
using Bit.Infrastructure.Dapper;
using Bit.Infrastructure.EntityFramework;
using DnsClient;
using Duende.IdentityServer.Configuration;
using IdentityModel;
using LaunchDarkly.Sdk.Server;
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.AddSession();
services.ConfigureApplicationCookie(configure => configure.CookieManager = new DistributedCacheCookieManager());
services.ConfigureExternalCookie(configure => configure.CookieManager = new DistributedCacheCookieManager());
services.AddSingleton<IPostConfigureOptions<CookieAuthenticationOptions>>(
svcs => new ConfigureOpenIdConnectDistributedOptions(
svcs.GetRequiredService<IHttpContextAccessor>(),
globalSettings,
svcs.GetRequiredService<IdentityServerOptions>())
);
services.AddSingleton<IPostConfigureOptions<CookieAuthenticationOptions>, ConfigureOpenIdConnectDistributedOptions>();
return services;
}