(feature): dependencies update, improved logging, allign naming to k8s deployment

This commit is contained in:
Maksym Sadovnychyy 2025-11-01 22:17:31 +01:00
parent 399415c6b8
commit d59ded5cde
9 changed files with 115 additions and 65 deletions

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
namespace MaksIT.LetsEncrypt.Models.Responses;
public class AuthorizationChallengeChallenge
@ -10,4 +11,13 @@ public class AuthorizationChallengeChallenge
public string? Status { get; set; }
public string? Token { get; set; }
// New properties added to complete the model
public DateTime? Validated { get; set; }
public AuthorizationChallengeError? Error { get; set; }
public List<AuthorizationChallengeValidationRecord>? ValidationRecord { get; set; }
}

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MaksIT.LetsEncrypt.Models.Responses;
public class AuthorizationChallengeError {
public string Type { get; set; }
public string Detail { get; set; }
}

View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MaksIT.LetsEncrypt.Models.Responses;
public class AuthorizationChallengeValidationRecord {
public Uri? Url { get; set; }
public string? Hostname { get; set; }
public string? Port { get; set; }
public List<string>? AddressesResolved { get; set; }
public string? AddressUsed { get; set; }
}

View File

@ -391,7 +391,7 @@ public class LetsEncryptService : ILetsEncryptService {
if (challenge?.Url == null) {
_logger.LogError("Challenge URL is null");
return Result.InternalServerError();
return Result.InternalServerError("Challenge URL is null");
}
var request = new HttpRequestMessage(HttpMethod.Post, challenge.Url);
@ -712,6 +712,18 @@ public class LetsEncryptService : ILetsEncryptService {
throw new LetsEncrytException(problem, response);
}
if (response.Content.Headers.ContentType?.MediaType == GetContentType(ContentType.Json)) {
var authorizationChallengeChallenge = responseText.ToObject<AuthorizationChallengeChallenge>();
if (authorizationChallengeChallenge?.Status == "invalid") {
throw new LetsEncrytException(new Problem {
Type = authorizationChallengeChallenge.Error.Type,
Detail = authorizationChallengeChallenge.Error.Detail,
RawJson = responseText
}, response);
}
}
}
private SendResult<TResult> ProcessResponseContent<TResult>(HttpResponseMessage response, string responseText) {
@ -743,14 +755,14 @@ public class LetsEncryptService : ILetsEncryptService {
private Result HandleUnhandledException(Exception ex, string defaultMessage = "Let's Encrypt client unhandled exception") {
List<string> messages = new() { defaultMessage };
_logger.LogError(ex, messages.FirstOrDefault());
messages.Add(ex.Message);
ex.ExtractMessages().ForEach(m => messages.Add(m));
return Result.InternalServerError([.. messages]);
}
private Result<T?> HandleUnhandledException<T>(Exception ex, T? defaultValue = default, string defaultMessage = "Let's Encrypt client unhandled exception") {
List<string> messages = new() { defaultMessage };
_logger.LogError(ex, messages.FirstOrDefault());
messages.Add(ex.Message);
ex.ExtractMessages().ForEach(m => messages.Add(m));
return Result<T?>.InternalServerError(defaultValue, [.. messages]);
}
}

View File

@ -1,32 +0,0 @@
using System.Net;
namespace MaksIT.LetsEncryptServer.Middlewares {
public class GlobalExceptionMiddleware {
private readonly RequestDelegate _next;
private readonly ILogger<GlobalExceptionMiddleware> _logger;
public GlobalExceptionMiddleware(RequestDelegate next, ILogger<GlobalExceptionMiddleware> logger) {
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context) {
try {
await _next(context);
}
catch (Exception ex) {
_logger.LogError(ex, "An unhandled exception occurred.");
await HandleExceptionAsync(context);
}
}
private static Task HandleExceptionAsync(HttpContext context) {
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
context.Response.ContentType = "application/json";
var response = new { message = "An error occurred while processing your request." };
return context.Response.WriteAsJsonAsync(response);
}
}
}

View File

@ -1,9 +1,10 @@
using MaksIT.LetsEncryptServer;
using MaksIT.LetsEncrypt.Services;
using MaksIT.LetsEncryptServer.Services;
using MaksIT.LetsEncryptServer.BackgroundServices;
using MaksIT.LetsEncryptServer.Middlewares;
using MaksIT.Core.Webapi.Middlewares;
using MaksIT.Core.Logging;
using MaksIT.LetsEncrypt.Extensions;
using MaksIT.LetsEncrypt.Services;
using MaksIT.LetsEncryptServer;
using MaksIT.LetsEncryptServer.BackgroundServices;
using MaksIT.LetsEncryptServer.Services;
var builder = WebApplication.CreateBuilder(args);
@ -24,6 +25,9 @@ if (File.Exists(secretsPath)) {
var configurationSection = configuration.GetSection("Configuration");
var appSettings = configurationSection.Get<Configuration>() ?? throw new ArgumentNullException();
// Add logging
builder.Logging.AddConsoleLogger();
// Allow configurations to be available through IOptions<Configuration>
builder.Services.Configure<Configuration>(configurationSection);
@ -55,11 +59,8 @@ if (app.Environment.IsDevelopment()) {
app.UseSwaggerUI();
app.UseCors(builder => builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
}
else {
// app.UseMiddleware<GlobalExceptionMiddleware>();
}
app.UseMiddleware<GlobalExceptionMiddleware>();
app.UseMiddleware<ErrorHandlingMiddleware>();
app.UseAuthorization();

View File

@ -81,9 +81,11 @@ public class CertsFlowService : ICertsFlowService {
#region Internal methods
public async Task<Result<Guid?>> ConfigureClientAsync(bool isStaging) {
var sessionId = Guid.NewGuid();
var result = await _letsEncryptService.ConfigureClient(sessionId, isStaging);
if (!result.IsSuccess)
return result.ToResultOfType<Guid?>(default);
return Result<Guid?>.Ok(sessionId);
}
@ -91,17 +93,22 @@ public class CertsFlowService : ICertsFlowService {
RegistrationCache? cache = null;
if (accountId == null) {
accountId = Guid.NewGuid();
} else {
}
else {
var cacheResult = await _cacheService.LoadAccountFromCacheAsync(accountId.Value);
if (!cacheResult.IsSuccess || cacheResult.Value == null) {
accountId = Guid.NewGuid();
} else {
}
else {
cache = cacheResult.Value;
}
}
var result = await _letsEncryptService.Init(sessionId, accountId.Value, description, contacts, cache);
if (!result.IsSuccess)
return result.ToResultOfType<Guid?>(default);
return Result<Guid?>.Ok(accountId.Value);
}
@ -109,12 +116,15 @@ public class CertsFlowService : ICertsFlowService {
var orderResult = await _letsEncryptService.NewOrder(sessionId, hostnames, challengeType);
if (!orderResult.IsSuccess || orderResult.Value == null)
return orderResult.ToResultOfType<List<string>?>(_ => null);
var challenges = new List<string>();
foreach (var kvp in orderResult.Value) {
string[] splitToken = kvp.Value.Split('.');
File.WriteAllText(Path.Combine(_acmePath, splitToken[0]), kvp.Value);
challenges.Add(splitToken[0]);
}
return Result<List<string>?>.Ok(challenges);
}
@ -125,12 +135,15 @@ public class CertsFlowService : ICertsFlowService {
return result;
Thread.Sleep(1000);
}
var cacheResult = _letsEncryptService.GetRegistrationCache(sessionId);
if (!cacheResult.IsSuccess || cacheResult.Value == null)
return cacheResult;
var saveResult = await _cacheService.SaveToCacheAsync(cacheResult.Value.AccountId, cacheResult.Value);
if (!saveResult.IsSuccess)
return saveResult;
return Result.Ok();
}
@ -142,20 +155,25 @@ public class CertsFlowService : ICertsFlowService {
var cacheResult = _letsEncryptService.GetRegistrationCache(sessionId);
if (!cacheResult.IsSuccess || cacheResult.Value?.CachedCerts == null)
return cacheResult.ToResultOfType<Dictionary<string, string>?>(_ => null);
var results = new Dictionary<string, string>();
foreach (var hostname in hostnames) {
CertificateCache? cert;
if (cacheResult.Value.TryGetCachedCertificate(hostname, out cert)) {
var content = $"{cert.Cert}\n{cert.PrivatePem}";
results.Add(hostname, content);
}
}
var uploadResult = await _agentService.UploadCerts(results);
if (!uploadResult.IsSuccess)
return uploadResult.ToResultOfType<Dictionary<string, string>?>(default);
var reloadResult = await _agentService.ReloadService(_appSettings.Agent.ServiceToReload);
if (!reloadResult.IsSuccess)
return reloadResult.ToResultOfType<Dictionary<string, string>?>(default);
return Result<Dictionary<string, string>?>.Ok(results);
}
@ -165,12 +183,15 @@ public class CertsFlowService : ICertsFlowService {
if (!result.IsSuccess)
return result;
}
var cacheResult = _letsEncryptService.GetRegistrationCache(sessionId);
if (!cacheResult.IsSuccess || cacheResult.Value == null)
return cacheResult;
var saveResult = await _cacheService.SaveToCacheAsync(cacheResult.Value.AccountId, cacheResult.Value);
if (!saveResult.IsSuccess)
return saveResult;
return Result.Ok();
}
@ -194,6 +215,7 @@ public class CertsFlowService : ICertsFlowService {
if (!challengeResult.IsSuccess)
return challengeResult.ToResultOfType<Guid?>(default);
}
var getOrderResult = await GetOrderAsync(sessionId, hostnames);
if (!getOrderResult.IsSuccess)
return getOrderResult.ToResultOfType<Guid?>(default);
@ -222,9 +244,11 @@ public class CertsFlowService : ICertsFlowService {
var initResult = await InitAsync(sessionId, accountId, description, contacts);
if (!initResult.IsSuccess)
return initResult;
var revokeResult = await RevokeCertificatesAsync(sessionId, hostnames);
if (!revokeResult.IsSuccess)
return revokeResult;
return Result.Ok();
}
#endregion
@ -255,9 +279,11 @@ public class CertsFlowService : ICertsFlowService {
#region Acme Challenge REST methods
public Result<string?> AcmeChallenge(string fileName) {
DeleteExporedChallenges();
var challengePath = Path.Combine(_acmePath, fileName);
if(!File.Exists(challengePath))
if (!File.Exists(challengePath))
return Result<string?>.NotFound(null);
var fileContent = File.ReadAllText(Path.Combine(_acmePath, fileName));
return Result<string?>.Ok(fileContent);
}
@ -268,6 +294,7 @@ public class CertsFlowService : ICertsFlowService {
try {
var creationTime = File.GetCreationTime(file);
var timeDifference = currentDate - creationTime;
if (timeDifference.TotalDays > 1) {
File.Delete(file);
_logger.LogInformation($"Deleted file: {file}");

View File

@ -4,21 +4,21 @@ services:
ports:
- "8080:8080"
depends_on:
- letsencrypt-app
- letsencrypt-server
- certs-ui-client
- certs-ui-server
networks:
- maks-it
letsencrypt-app:
container_name: letsencrypt-app
certs-ui-client:
container_name: certs-ui-client
environment:
- ASPNETCORE_ENVIRONMENT=Development
- LETSENCRYPT_SERVER=http://localhost:8080
networks:
- maks-it
letsencrypt-server:
container_name: letsencrypt-server
certs-ui-server:
container_name: certs-ui-server
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_HTTP_PORTS=5000

View File

@ -1,18 +1,18 @@
services:
letsencrypt-app:
image: ${DOCKER_REGISTRY-}letsencrypt-app
build:
context: .
dockerfile: ClientApp/Dockerfile
reverse-proxy:
image: ${DOCKER_REGISTRY-}reverse-proxy
build:
context: .
dockerfile: ReverseProxy/Dockerfile
letsencrypt-server:
image: ${DOCKER_REGISTRY-}letsencrypt-server
certs-ui-client:
image: ${DOCKER_REGISTRY-}certs-ui-client
build:
context: .
dockerfile: ClientApp/Dockerfile
certs-ui-server:
image: ${DOCKER_REGISTRY-}certs-ui-server
build:
context: .
dockerfile: LetsEncryptServer/Dockerfile