mirror of
https://github.com/MAKS-IT-COM/maksit-certs-ui.git
synced 2025-12-31 04:00:03 +01:00
(feature): implementing cache persistance
This commit is contained in:
parent
a661489b4f
commit
4359d317c0
@ -12,6 +12,13 @@ public class CertificateCache {
|
||||
}
|
||||
|
||||
public class RegistrationCache {
|
||||
|
||||
/// <summary>
|
||||
/// Field used to identify cache by account id
|
||||
/// </summary>
|
||||
public Guid AccountId { get; set; }
|
||||
|
||||
|
||||
public Dictionary<string, CertificateCache>? CachedCerts { get; set; }
|
||||
public byte[]? AccountKey { get; set; }
|
||||
public string? Id { get; set; }
|
||||
|
||||
@ -17,8 +17,8 @@ namespace MaksIT.LetsEncrypt.Services;
|
||||
|
||||
public interface ILetsEncryptService {
|
||||
Task<IDomainResult> ConfigureClient(Guid sessionId, string url);
|
||||
Task<IDomainResult> Init(Guid sessionId, string[] contacts, RegistrationCache? registrationCache);
|
||||
RegistrationCache? GetRegistrationCache(Guid sessionId);
|
||||
Task<IDomainResult> Init(Guid sessionId,Guid accountId, string[] contacts, RegistrationCache? registrationCache);
|
||||
(RegistrationCache?, IDomainResult) GetRegistrationCache(Guid sessionId);
|
||||
(string?, IDomainResult) GetTermsOfServiceUri(Guid sessionId);
|
||||
Task<(Dictionary<string, string>?, IDomainResult)> NewOrder(Guid sessionId, string[] hostnames, string challengeType);
|
||||
Task<IDomainResult> CompleteChallenges(Guid sessionId);
|
||||
@ -30,7 +30,7 @@ public interface ILetsEncryptService {
|
||||
public class LetsEncryptService : ILetsEncryptService {
|
||||
private readonly ILogger<LetsEncryptService> _logger;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly IMemoryCache _cache;
|
||||
private readonly IMemoryCache _memoryCache;
|
||||
|
||||
public LetsEncryptService(
|
||||
ILogger<LetsEncryptService> logger,
|
||||
@ -38,13 +38,13 @@ public class LetsEncryptService : ILetsEncryptService {
|
||||
IMemoryCache cache) {
|
||||
_logger = logger;
|
||||
_httpClient = httpClient;
|
||||
_cache = cache;
|
||||
_memoryCache = cache;
|
||||
}
|
||||
|
||||
private State GetOrCreateState(Guid sessionId) {
|
||||
if (!_cache.TryGetValue(sessionId, out State state)) {
|
||||
if (!_memoryCache.TryGetValue(sessionId, out State state)) {
|
||||
state = new State();
|
||||
_cache.Set(sessionId, state, TimeSpan.FromHours(1));
|
||||
_memoryCache.Set(sessionId, state, TimeSpan.FromHours(1));
|
||||
}
|
||||
return state;
|
||||
}
|
||||
@ -74,7 +74,7 @@ public class LetsEncryptService : ILetsEncryptService {
|
||||
#endregion
|
||||
|
||||
#region Init
|
||||
public async Task<IDomainResult> Init(Guid sessionId, string[] contacts, RegistrationCache? cache) {
|
||||
public async Task<IDomainResult> Init(Guid sessionId, Guid accountId, string[] contacts, RegistrationCache? cache) {
|
||||
if (sessionId == Guid.Empty) {
|
||||
_logger.LogError("Invalid sessionId");
|
||||
return IDomainResult.Failed();
|
||||
@ -119,6 +119,8 @@ public class LetsEncryptService : ILetsEncryptService {
|
||||
}
|
||||
|
||||
state.Cache = new RegistrationCache {
|
||||
AccountId = accountId,
|
||||
|
||||
Location = account.Result.Location,
|
||||
AccountKey = accountKey.ExportCspBlob(true),
|
||||
Id = account.Result.Id,
|
||||
@ -135,12 +137,15 @@ public class LetsEncryptService : ILetsEncryptService {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public RegistrationCache? GetRegistrationCache(Guid sessionId) {
|
||||
public (RegistrationCache?, IDomainResult) GetRegistrationCache(Guid sessionId) {
|
||||
var state = GetOrCreateState(sessionId);
|
||||
return state.Cache;
|
||||
|
||||
if(state?.Cache == null)
|
||||
return IDomainResult.Failed<RegistrationCache?>();
|
||||
|
||||
return IDomainResult.Success(state.Cache);
|
||||
}
|
||||
|
||||
#region GetTermsOfService
|
||||
|
||||
18
src/LetsEncryptServer/BackgroundServices/AutoRenewal.cs
Normal file
18
src/LetsEncryptServer/BackgroundServices/AutoRenewal.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace LetsEncryptServer.BackgroundServices {
|
||||
public class AutoRenewal : BackgroundService {
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
|
||||
while (!stoppingToken.IsCancellationRequested) {
|
||||
// Your background task logic here
|
||||
Console.WriteLine("Background service is running.");
|
||||
|
||||
// Simulate some work by delaying for 5 seconds
|
||||
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
|
||||
}
|
||||
}
|
||||
|
||||
public override Task StopAsync(CancellationToken stoppingToken) {
|
||||
Console.WriteLine("Background service is stopping.");
|
||||
return base.StopAsync(stoppingToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -27,6 +27,7 @@ builder.Services.AddMemoryCache();
|
||||
|
||||
builder.Services.AddHttpClient<ILetsEncryptService, LetsEncryptService>();
|
||||
builder.Services.AddScoped<ICertsFlowService, CertsFlowService>();
|
||||
builder.Services.AddSingleton<ICacheService, CacheService>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
|
||||
123
src/LetsEncryptServer/Services/CacheService.cs
Normal file
123
src/LetsEncryptServer/Services/CacheService.cs
Normal file
@ -0,0 +1,123 @@
|
||||
using System.Text.Json;
|
||||
|
||||
using DomainResults.Common;
|
||||
|
||||
using MaksIT.LetsEncrypt.Entities;
|
||||
|
||||
namespace MaksIT.LetsEncryptServer.Services;
|
||||
|
||||
public interface ICacheService {
|
||||
Task<(RegistrationCache?, IDomainResult)> LoadFromCacheAsync(Guid accountId);
|
||||
Task<IDomainResult> SaveToCacheAsync(Guid accountId, RegistrationCache cache);
|
||||
Task<IDomainResult> DeleteFromCacheAsync(Guid accountId);
|
||||
}
|
||||
|
||||
public class CacheService : ICacheService, IDisposable {
|
||||
|
||||
private readonly ILogger<CacheService> _logger;
|
||||
private readonly string _cacheDirectory;
|
||||
private readonly SemaphoreSlim _cacheLock = new SemaphoreSlim(1, 1);
|
||||
|
||||
public CacheService(
|
||||
ILogger<CacheService> logger
|
||||
) {
|
||||
_logger = logger;
|
||||
_cacheDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "cache");
|
||||
|
||||
if (!Directory.Exists(_cacheDirectory)) {
|
||||
Directory.CreateDirectory(_cacheDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetCacheFilePath(Guid accountId) {
|
||||
return Path.Combine(_cacheDirectory, $"{accountId}.json");
|
||||
}
|
||||
|
||||
public async Task<(RegistrationCache?, IDomainResult)> LoadFromCacheAsync(Guid accountId) {
|
||||
var cacheFilePath = GetCacheFilePath(accountId);
|
||||
|
||||
await _cacheLock.WaitAsync();
|
||||
|
||||
try {
|
||||
if (!File.Exists(cacheFilePath)) {
|
||||
var message = $"Cache file not found for account {accountId}";
|
||||
_logger.LogWarning(message);
|
||||
|
||||
return IDomainResult.Failed<RegistrationCache>(message);
|
||||
}
|
||||
|
||||
var json = await File.ReadAllTextAsync(cacheFilePath);
|
||||
if (string.IsNullOrEmpty(json)) {
|
||||
var message = $"Cache file is empty for account {accountId}";
|
||||
_logger.LogWarning(message);
|
||||
|
||||
return IDomainResult.Failed<RegistrationCache>(message);
|
||||
}
|
||||
|
||||
var cache = JsonSerializer.Deserialize<RegistrationCache>(json);
|
||||
return IDomainResult.Success(cache);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
var message = "Error reading cache file for account {accountId}";
|
||||
_logger.LogError(ex, message);
|
||||
|
||||
return IDomainResult.Failed<RegistrationCache?>(message);
|
||||
}
|
||||
finally {
|
||||
_cacheLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IDomainResult> SaveToCacheAsync(Guid accountId, RegistrationCache cache) {
|
||||
var cacheFilePath = GetCacheFilePath(accountId);
|
||||
await _cacheLock.WaitAsync();
|
||||
|
||||
try {
|
||||
var json = JsonSerializer.Serialize(cache);
|
||||
await File.WriteAllTextAsync(cacheFilePath, json);
|
||||
|
||||
_logger.LogInformation($"Cache file saved for account {accountId}");
|
||||
|
||||
return DomainResult.Success();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
var message = "Error writing cache file for account {accountId}";
|
||||
_logger.LogError(ex, message);
|
||||
|
||||
return IDomainResult.Failed(message);
|
||||
}
|
||||
finally {
|
||||
_cacheLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IDomainResult> DeleteFromCacheAsync(Guid accountId) {
|
||||
var cacheFilePath = GetCacheFilePath(accountId);
|
||||
await _cacheLock.WaitAsync();
|
||||
|
||||
try {
|
||||
if (File.Exists(cacheFilePath)) {
|
||||
File.Delete(cacheFilePath);
|
||||
_logger.LogInformation($"Cache file deleted for account {accountId}");
|
||||
}
|
||||
else {
|
||||
_logger.LogWarning($"Cache file not found for account {accountId}");
|
||||
}
|
||||
|
||||
return IDomainResult.Success();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
var message = $"Error deleting cache file for account {accountId}";
|
||||
_logger.LogError(ex, message);
|
||||
|
||||
return IDomainResult.Failed(message);
|
||||
}
|
||||
finally {
|
||||
_cacheLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
_cacheLock?.Dispose();
|
||||
}
|
||||
}
|
||||
@ -32,17 +32,22 @@ public class CertsFlowService : ICertsFlowService {
|
||||
private readonly Configuration _appSettings;
|
||||
private readonly ILogger<CertsFlowService> _logger;
|
||||
private readonly ILetsEncryptService _letsEncryptService;
|
||||
private readonly string _acmePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "acme");
|
||||
private readonly ICacheService _cacheService;
|
||||
|
||||
private readonly string _acmePath;
|
||||
|
||||
public CertsFlowService(
|
||||
IOptions<Configuration> appSettings,
|
||||
ILogger<CertsFlowService> logger,
|
||||
ILetsEncryptService letsEncryptService
|
||||
ILetsEncryptService letsEncryptService,
|
||||
ICacheService cashService
|
||||
) {
|
||||
_appSettings = appSettings.Value;
|
||||
_logger = logger;
|
||||
_letsEncryptService = letsEncryptService;
|
||||
_cacheService = cashService;
|
||||
|
||||
_acmePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "acme");
|
||||
if (!Directory.Exists(_acmePath))
|
||||
Directory.CreateDirectory(_acmePath);
|
||||
}
|
||||
@ -70,12 +75,22 @@ public class CertsFlowService : ICertsFlowService {
|
||||
}
|
||||
|
||||
public async Task<(Guid?, IDomainResult)> InitAsync(Guid sessionId, Guid? accountId, InitRequest requestData) {
|
||||
var cache = default(RegistrationCache);
|
||||
RegistrationCache? cache = null;
|
||||
|
||||
if (accountId == null) {
|
||||
accountId = Guid.NewGuid();
|
||||
}
|
||||
else {
|
||||
var (loadedCache, loadCaceResutl) = await _cacheService.LoadFromCacheAsync(accountId.Value);
|
||||
if (!loadCaceResutl.IsSuccess || loadCaceResutl == null) {
|
||||
accountId = Guid.NewGuid();
|
||||
}
|
||||
else {
|
||||
cache = loadedCache;
|
||||
}
|
||||
}
|
||||
|
||||
var result = await _letsEncryptService.Init(sessionId, requestData.Contacts, cache);
|
||||
var result = await _letsEncryptService.Init(sessionId, accountId.Value, requestData.Contacts, cache);
|
||||
return result.IsSuccess ? IDomainResult.Success<Guid>(accountId.Value) : (null, result);
|
||||
}
|
||||
|
||||
@ -111,6 +126,15 @@ public class CertsFlowService : ICertsFlowService {
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
|
||||
// Persist the cache
|
||||
var (cache, getCacheResult) = _letsEncryptService.GetRegistrationCache(sessionId);
|
||||
if (!getCacheResult.IsSuccess || cache == null)
|
||||
return getCacheResult;
|
||||
|
||||
var saveResult = await _cacheService.SaveToCacheAsync(cache.AccountId, cache);
|
||||
if(!saveResult.IsSuccess)
|
||||
return saveResult;
|
||||
|
||||
return IDomainResult.Success();
|
||||
}
|
||||
|
||||
@ -178,98 +202,9 @@ public class CertsFlowService : ICertsFlowService {
|
||||
return IDomainResult.Success();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
abort ssl cert <certfile> : abort a transaction for a certificate file
|
||||
add acl [@<ver>] <acl> <pattern> : add an acl entry
|
||||
add map [@<ver>] <map> <key> <val> : add a map entry (payload supported instead of key/val)
|
||||
add ssl crt-list <list> <cert> [opts]* : add to crt-list file <list> a line <cert> or a payload
|
||||
clear acl [@<ver>] <acl> : clear the contents of this acl
|
||||
clear counters [all] : clear max statistics counters (or all counters)
|
||||
clear map [@<ver>] <map> : clear the contents of this map
|
||||
clear table <table> [<filter>]* : remove an entry from a table (filter: data/key)
|
||||
commit acl @<ver> <acl> : commit the ACL at this version
|
||||
commit map @<ver> <map> : commit the map at this version
|
||||
commit ssl cert <certfile> : commit a certificate file
|
||||
del acl <acl> [<key>|#<ref>] : delete acl entries matching <key>
|
||||
del map <map> [<key>|#<ref>] : delete map entries matching <key>
|
||||
del ssl cert <certfile> : delete an unused certificate file
|
||||
del ssl crt-list <list> <cert[:line]> : delete a line <cert> from crt-list file <list>
|
||||
disable agent : disable agent checks
|
||||
disable dynamic-cookie backend <bk> : disable dynamic cookies on a specific backend
|
||||
disable frontend <frontend> : temporarily disable specific frontend
|
||||
disable health : disable health checks
|
||||
disable server (DEPRECATED) : disable a server for maintenance (use 'set server' instead)
|
||||
enable agent : enable agent checks
|
||||
enable dynamic-cookie backend <bk> : enable dynamic cookies on a specific backend
|
||||
enable frontend <frontend> : re-enable specific frontend
|
||||
enable health : enable health checks
|
||||
enable server (DEPRECATED) : enable a disabled server (use 'set server' instead)
|
||||
get acl <acl> <value> : report the patterns matching a sample for an ACL
|
||||
get map <acl> <value> : report the keys and values matching a sample for a map
|
||||
get var <name> : retrieve contents of a process-wide variable
|
||||
get weight <bk>/<srv> : report a server's current weight
|
||||
new ssl cert <certfile> : create a new certificate file to be used in a crt-list or a directory
|
||||
operator : lower the level of the current CLI session to operator
|
||||
prepare acl <acl> : prepare a new version for atomic ACL replacement
|
||||
prepare map <acl> : prepare a new version for atomic map replacement
|
||||
set dynamic-cookie-key backend <bk> <k> : change a backend secret key for dynamic cookies
|
||||
set map <map> [<key>|#<ref>] <value> : modify a map entry
|
||||
set maxconn frontend <frontend> <value> : change a frontend's maxconn setting
|
||||
set maxconn global <value> : change the per-process maxconn setting
|
||||
set maxconn server <bk>/<srv> : change a server's maxconn setting
|
||||
set profiling <what> {auto|on|off} : enable/disable resource profiling (tasks,memory)
|
||||
set rate-limit <setting> <value> : change a rate limiting value
|
||||
set server <bk>/<srv> [opts] : change a server's state, weight, address or ssl
|
||||
set severity-output [none|number|string]: set presence of severity level in feedback information
|
||||
set ssl cert <certfile> <payload> : replace a certificate file
|
||||
set ssl ocsp-response <resp|payload> : update a certificate's OCSP Response from a base64-encode DER
|
||||
set ssl tls-key [id|file] <key> : set the next TLS key for the <id> or <file> listener to <key>
|
||||
set table <table> key <k> [data.* <v>]* : update or create a table entry's data
|
||||
set timeout [cli] <delay> : change a timeout setting
|
||||
set weight <bk>/<srv> (DEPRECATED) : change a server's weight (use 'set server' instead)
|
||||
show acl [@<ver>] <acl>] : report available acls or dump an acl's contents
|
||||
show activity : show per-thread activity stats (for support/developers)
|
||||
show backend : list backends in the current running config
|
||||
show cache : show cache status
|
||||
show cli level : display the level of the current CLI session
|
||||
show cli sockets : dump list of cli sockets
|
||||
show env [var] : dump environment variables known to the process
|
||||
show errors [<px>] [request|response] : report last request and/or response errors for each proxy
|
||||
show events [<sink>] [-w] [-n] : show event sink state
|
||||
show fd [num] : dump list of file descriptors in use or a specific one
|
||||
show info [desc|json|typed|float]* : report information about the running process
|
||||
show libs : show loaded object files and libraries
|
||||
show map [@ver] [map] : report available maps or dump a map's contents
|
||||
show peers [dict|-] [section] : dump some information about all the peers or this peers section
|
||||
show pools : report information about the memory pools usage
|
||||
show profiling [<what>|<#lines>|byaddr]*: show profiling state (all,status,tasks,memory)
|
||||
show resolvers [id] : dumps counters from all resolvers section and associated name servers
|
||||
show schema json : report schema used for stats
|
||||
show servers conn [<backend>] : dump server connections status (all or for a single backend)
|
||||
show servers state [<backend>] : dump volatile server information (all or for a single backend)
|
||||
show sess [id] : report the list of current sessions or dump this exact session
|
||||
show ssl cert [<certfile>] : display the SSL certificates used in memory, or the details of a file
|
||||
show ssl crt-list [-n] [<list>] : show the list of crt-lists or the content of a crt-list file <list>
|
||||
show startup-logs : report logs emitted during HAProxy startup
|
||||
show stat [desc|json|no-maint|typed|up]*: report counters for each proxy and server
|
||||
show table <table> [<filter>]* : report table usage stats or dump this table's contents (filter: data/key)
|
||||
show tasks : show running tasks
|
||||
show threads : show some threads debugging information
|
||||
show tls-keys [id|*] : show tls keys references or dump tls ticket keys when id specified
|
||||
show trace [<module>] : show live tracing state
|
||||
show version : show version of the current process
|
||||
shutdown frontend <frontend> : stop a specific frontend
|
||||
shutdown session [id] : kill a specific session
|
||||
shutdown sessions server <bk>/<srv> : kill sessions on a server
|
||||
trace [<module>|0] [cmd [args...]] : manage live tracing (empty to list, 0 to stop all)
|
||||
user : lower the level of the current CLI session to user
|
||||
help [<command>] : list matching or all commands
|
||||
prompt : toggle interactive mode with prompt
|
||||
quit : disconnect
|
||||
*/
|
||||
private IDomainResult NotifyHaproxy(IEnumerable<string> certFiles) {
|
||||
var server = _appSettings.Server;
|
||||
|
||||
try {
|
||||
using (var client = new TcpClient(server.Ip, server.SocketPort))
|
||||
using (var networkStream = client.GetStream())
|
||||
|
||||
Loading…
Reference in New Issue
Block a user