mirror of
https://github.com/MAKS-IT-COM/maksit-certs-ui.git
synced 2025-12-31 12:10:03 +01:00
(feature): .net version upgrade, containerization
This commit is contained in:
parent
f311a655cc
commit
150b8e76fc
30
src/.dockerignore
Normal file
30
src/.dockerignore
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
**/.classpath
|
||||||
|
**/.dockerignore
|
||||||
|
**/.env
|
||||||
|
**/.git
|
||||||
|
**/.gitignore
|
||||||
|
**/.project
|
||||||
|
**/.settings
|
||||||
|
**/.toolstarget
|
||||||
|
**/.vs
|
||||||
|
**/.vscode
|
||||||
|
**/*.*proj.user
|
||||||
|
**/*.dbmdl
|
||||||
|
**/*.jfm
|
||||||
|
**/azds.yaml
|
||||||
|
**/bin
|
||||||
|
**/charts
|
||||||
|
**/docker-compose*
|
||||||
|
**/Dockerfile*
|
||||||
|
**/node_modules
|
||||||
|
**/npm-debug.log
|
||||||
|
**/obj
|
||||||
|
**/secrets.dev.yaml
|
||||||
|
**/values.dev.yaml
|
||||||
|
LICENSE
|
||||||
|
README.md
|
||||||
|
!**/.gitignore
|
||||||
|
!.git/HEAD
|
||||||
|
!.git/config
|
||||||
|
!.git/packed-refs
|
||||||
|
!.git/refs/heads/**
|
||||||
@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<RootNamespace>MaksIT.$(MSBuildProjectName.Replace(" ", "_"))</RootNamespace>
|
<RootNamespace>MaksIT.$(MSBuildProjectName.Replace(" ", "_"))</RootNamespace>
|
||||||
|
|||||||
@ -9,11 +9,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LetsEncryptConsole", "LetsE
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core", "Core\Core.csproj", "{27A58A5F-B52A-44F2-9639-84C6F02EA75D}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core", "Core\Core.csproj", "{27A58A5F-B52A-44F2-9639-84C6F02EA75D}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SSHProvider", "SSHProvider\SSHProvider.csproj", "{B6556305-D728-4368-A22C-93079C236808}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SSHProvider", "SSHProvider\SSHProvider.csproj", "{B6556305-D728-4368-A22C-93079C236808}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{3374FDB1-C95E-4103-8E14-5BBF0BDC4E9D}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{3374FDB1-C95E-4103-8E14-5BBF0BDC4E9D}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SSHProviderTests", "Tests\SSHSerivceTests\SSHProviderTests.csproj", "{3937760A-FFB3-4A8C-ABD1-CDDCE1D977C4}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SSHProviderTests", "Tests\SSHSerivceTests\SSHProviderTests.csproj", "{3937760A-FFB3-4A8C-ABD1-CDDCE1D977C4}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LetsEncryptServer", "LetsEncryptServer\LetsEncryptServer.csproj", "{B5F39E04-C2E3-49BF-82C2-9DEBAA949E3D}"
|
||||||
|
EndProject
|
||||||
|
Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{0233E43F-435D-4309-B20C-ECD4BFBD2E63}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
@ -41,6 +45,14 @@ Global
|
|||||||
{3937760A-FFB3-4A8C-ABD1-CDDCE1D977C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{3937760A-FFB3-4A8C-ABD1-CDDCE1D977C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{3937760A-FFB3-4A8C-ABD1-CDDCE1D977C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{3937760A-FFB3-4A8C-ABD1-CDDCE1D977C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{3937760A-FFB3-4A8C-ABD1-CDDCE1D977C4}.Release|Any CPU.Build.0 = Release|Any CPU
|
{3937760A-FFB3-4A8C-ABD1-CDDCE1D977C4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{B5F39E04-C2E3-49BF-82C2-9DEBAA949E3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{B5F39E04-C2E3-49BF-82C2-9DEBAA949E3D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B5F39E04-C2E3-49BF-82C2-9DEBAA949E3D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{B5F39E04-C2E3-49BF-82C2-9DEBAA949E3D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{0233E43F-435D-4309-B20C-ECD4BFBD2E63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{0233E43F-435D-4309-B20C-ECD4BFBD2E63}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{0233E43F-435D-4309-B20C-ECD4BFBD2E63}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{0233E43F-435D-4309-B20C-ECD4BFBD2E63}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
@ -1,18 +1,18 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<RootNamespace>MaksIT.$(MSBuildProjectName.Replace(" ", "_"))</RootNamespace>
|
<RootNamespace>MaksIT.$(MSBuildProjectName.Replace(" ", "_"))</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="DomainResult.Common" Version="3.1.0" />
|
<PackageReference Include="DomainResult.Common" Version="3.2.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@ -23,44 +23,18 @@ using DomainResults.Common;
|
|||||||
namespace MaksIT.LetsEncrypt.Services;
|
namespace MaksIT.LetsEncrypt.Services;
|
||||||
|
|
||||||
public interface ILetsEncryptService {
|
public interface ILetsEncryptService {
|
||||||
|
Task<(AcmeDirectory?, IDomainResult)> ConfigureClient(string url);
|
||||||
Task<IDomainResult> ConfigureClient(string url);
|
Task<(RegistrationCache?, IDomainResult)> Init(Uri newAccount, Uri newNonce, string[] contacts);
|
||||||
|
Task<((Order?, Dictionary<string, string>?, List<AuthorizationChallenge>?), IDomainResult)> NewOrder(Uri newOrder, Uri newNonce, byte[] accountKeyBytes, string location, string[] hostnames, string challengeType);
|
||||||
Task<IDomainResult> Init(string[] contacts, RegistrationCache? registrationCache);
|
Task<IDomainResult> CompleteChallenges(Uri newNonce, byte[] accountKeyBytes, string location, Order currentOrder, List<AuthorizationChallenge> _challenges);
|
||||||
|
Task<(Order?, IDomainResult)> GetOrder(Uri newOrder, Uri newNonce, byte[] accountKeyBytes, string location, string[] hostnames);
|
||||||
RegistrationCache? GetRegistrationCache();
|
Task<(Dictionary<string, CertificateCache>?, IDomainResult)> GetCertificate(Uri newOrder, Uri newNonce, byte[] accountKeyBytes, Order currentOrder, string location, string [] subjects);
|
||||||
|
|
||||||
(string?, IDomainResult) GetTermsOfServiceUri();
|
|
||||||
|
|
||||||
|
|
||||||
Task<(Dictionary<string, string>?, IDomainResult)> NewOrder(string[] hostnames, string challengeType);
|
|
||||||
Task<IDomainResult> CompleteChallenges();
|
|
||||||
Task<IDomainResult> GetOrder(string[] hostnames);
|
|
||||||
Task<((X509Certificate2 Cert, RSA PrivateKey)?, IDomainResult)> GetCertificate(string subject);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public class LetsEncryptService : ILetsEncryptService {
|
public class LetsEncryptService : ILetsEncryptService {
|
||||||
|
|
||||||
//private static readonly JsonSerializerSettings jsonSettings = new JsonSerializerSettings {
|
|
||||||
// NullValueHandling = NullValueHandling.Ignore,
|
|
||||||
// Formatting = Formatting.Indented
|
|
||||||
//};
|
|
||||||
|
|
||||||
private readonly ILogger<LetsEncryptService> _logger;
|
private readonly ILogger<LetsEncryptService> _logger;
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
private HttpClient _httpClient;
|
|
||||||
|
|
||||||
private IJwsService? _jwsService;
|
|
||||||
private AcmeDirectory? _directory;
|
|
||||||
private RegistrationCache? _cache;
|
|
||||||
|
|
||||||
private string? _nonce;
|
|
||||||
|
|
||||||
private List<AuthorizationChallenge> _challenges = new List<AuthorizationChallenge>();
|
|
||||||
private Order? _currentOrder;
|
|
||||||
|
|
||||||
public LetsEncryptService(
|
public LetsEncryptService(
|
||||||
ILogger<LetsEncryptService> logger,
|
ILogger<LetsEncryptService> logger,
|
||||||
@ -71,27 +45,28 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="url"></param>
|
/// <param name="url"></param>
|
||||||
/// <param name="contacts"></param>
|
/// <param name="contacts"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<IDomainResult> ConfigureClient(string url) {
|
public async Task<(AcmeDirectory?, IDomainResult)> ConfigureClient(string url) {
|
||||||
try {
|
try {
|
||||||
_httpClient.BaseAddress ??= new Uri(url);
|
_httpClient.BaseAddress ??= new Uri(url);
|
||||||
|
|
||||||
var (directory, getAcmeDirectoryResult) = await SendAsync<AcmeDirectory>(HttpMethod.Get, new Uri("directory", UriKind.Relative), false, null);
|
var (directory, getAcmeDirectoryResult) = await SendAsync<AcmeDirectory>(HttpMethod.Get, new Uri("directory", UriKind.Relative), false, null, null, null, null);
|
||||||
if (!getAcmeDirectoryResult.IsSuccess)
|
if (!getAcmeDirectoryResult.IsSuccess)
|
||||||
return getAcmeDirectoryResult;
|
return (null, getAcmeDirectoryResult);
|
||||||
|
|
||||||
_directory = directory.Result;
|
var result = directory?.Result;
|
||||||
|
|
||||||
return IDomainResult.Success();
|
return IDomainResult.Success(result);
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
_logger.LogError(ex, "Let's Encrypt client unhandled exception");
|
_logger.LogError(ex, "Let's Encrypt client unhandled exception");
|
||||||
return IDomainResult.CriticalDependencyError();
|
return IDomainResult.CriticalDependencyError<AcmeDirectory>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,27 +76,14 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
/// <param name="contacts"></param>
|
/// <param name="contacts"></param>
|
||||||
/// <param name="token"></param>
|
/// <param name="token"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<IDomainResult> Init(string? [] contacts, RegistrationCache? cache) {
|
public async Task<(RegistrationCache?, IDomainResult)> Init(Uri newAccount, Uri newNonce, string[] contacts) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
_logger.LogInformation($"Executing {nameof(Init)}...");
|
_logger.LogInformation($"Executing {nameof(Init)}...");
|
||||||
|
|
||||||
if (contacts == null || contacts.Length == 0)
|
|
||||||
return IDomainResult.Failed();
|
|
||||||
|
|
||||||
if (_directory == null)
|
|
||||||
return IDomainResult.Failed();
|
|
||||||
|
|
||||||
var accountKey = new RSACryptoServiceProvider(4096);
|
var accountKey = new RSACryptoServiceProvider(4096);
|
||||||
|
var jwsService = new JwsService(accountKey);
|
||||||
if (cache != null && cache.AccountKey != null) {
|
|
||||||
_cache = cache;
|
|
||||||
accountKey.ImportCspBlob(cache.AccountKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
// New Account request
|
|
||||||
_jwsService = new JwsService(accountKey);
|
|
||||||
|
|
||||||
|
|
||||||
var letsEncryptOrder = new Account {
|
var letsEncryptOrder = new Account {
|
||||||
@ -129,59 +91,32 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
Contacts = contacts.Select(contact => $"mailto:{contact}").ToArray()
|
Contacts = contacts.Select(contact => $"mailto:{contact}").ToArray()
|
||||||
};
|
};
|
||||||
|
|
||||||
var (account, postAccuntResult) = await SendAsync<Account>(HttpMethod.Post, _directory.NewAccount, false, letsEncryptOrder);
|
var (account, postAccuntResult) = await SendAsync<Account>(HttpMethod.Post, newAccount, false, letsEncryptOrder, accountKey, null, newNonce);
|
||||||
_jwsService.SetKeyId(account.Result.Location.ToString());
|
if (!postAccuntResult.IsSuccess || account == null)
|
||||||
|
return (null, postAccuntResult);
|
||||||
|
|
||||||
|
// Probably non necessary here
|
||||||
|
// jwsService.SetKeyId(account.Result.Location.ToString());
|
||||||
|
|
||||||
if (account.Result.Status != "valid") {
|
if (account.Result.Status != "valid") {
|
||||||
_logger.LogError($"Account status is not valid, was: {account.Result.Status} \r\n {account.ResponseText}");
|
_logger.LogError($"Account status is not valid, was: {account.Result.Status} \r\n {account.ResponseText}");
|
||||||
return IDomainResult.Failed();
|
return IDomainResult.Failed<RegistrationCache>();
|
||||||
}
|
}
|
||||||
|
|
||||||
_cache = new RegistrationCache {
|
var cache = new RegistrationCache {
|
||||||
Location = account.Result.Location,
|
Location = account.Result.Location,
|
||||||
AccountKey = accountKey.ExportCspBlob(true),
|
AccountKey = accountKey.ExportCspBlob(true),
|
||||||
Id = account.Result.Id,
|
Id = account.Result.Id,
|
||||||
Key = account.Result.Key
|
Key = account.Result.Key
|
||||||
};
|
};
|
||||||
|
|
||||||
return IDomainResult.Success();
|
return IDomainResult.Success(cache);
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
var message = "Let's Encrypt client unhandled exception";
|
var message = "Let's Encrypt client unhandled exception";
|
||||||
|
|
||||||
_logger.LogError(ex, message);
|
_logger.LogError(ex, message);
|
||||||
return IDomainResult.CriticalDependencyError(message);
|
return IDomainResult.CriticalDependencyError<RegistrationCache>(message);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public RegistrationCache? GetRegistrationCache() =>
|
|
||||||
_cache;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Just retrive terms of service
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="token"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public (string?, IDomainResult) GetTermsOfServiceUri() {
|
|
||||||
try {
|
|
||||||
|
|
||||||
_logger.LogInformation($"Executing {nameof(GetTermsOfServiceUri)}...");
|
|
||||||
|
|
||||||
if (_directory == null) {
|
|
||||||
return IDomainResult.Failed<string?>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return IDomainResult.Success(_directory.Meta.TermsOfService);
|
|
||||||
}
|
|
||||||
catch (Exception ex) {
|
|
||||||
var message = "Let's Encrypt client unhandled exception";
|
|
||||||
|
|
||||||
_logger.LogError(ex, message);
|
|
||||||
return IDomainResult.CriticalDependencyError<string?>(message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,12 +135,19 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
/// <param name="challengeType"></param>
|
/// <param name="challengeType"></param>
|
||||||
/// <param name="token"></param>
|
/// <param name="token"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<(Dictionary<string, string>?, IDomainResult)> NewOrder(string[] hostnames, string challengeType) {
|
public async Task<((Order?, Dictionary<string, string>?, List<AuthorizationChallenge>?), IDomainResult)> NewOrder(Uri newOrder, Uri newNonce, byte[] accountKeyBytes, string location, string[] hostnames, string challengeType) {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
var accountKey = new RSACryptoServiceProvider(4096);
|
||||||
|
accountKey.ImportCspBlob(accountKeyBytes);
|
||||||
|
|
||||||
|
var jwsService = new JwsService(accountKey);
|
||||||
|
|
||||||
_logger.LogInformation($"Executing {nameof(NewOrder)}...");
|
_logger.LogInformation($"Executing {nameof(NewOrder)}...");
|
||||||
|
|
||||||
_challenges.Clear();
|
var currentOrder = default(Order);
|
||||||
|
var results = new Dictionary<string, string>();
|
||||||
|
var challenges = new List<AuthorizationChallenge>();
|
||||||
|
|
||||||
var letsEncryptOrder = new Order {
|
var letsEncryptOrder = new Order {
|
||||||
Expires = DateTime.UtcNow.AddDays(2),
|
Expires = DateTime.UtcNow.AddDays(2),
|
||||||
@ -215,41 +157,41 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
}).ToArray()
|
}).ToArray()
|
||||||
};
|
};
|
||||||
|
|
||||||
var (order, postNewOrderResult) = await SendAsync<Order>(HttpMethod.Post, _directory.NewOrder, false, letsEncryptOrder);
|
var (order, postNewOrderResult) = await SendAsync<Order>(HttpMethod.Post, newOrder, false, letsEncryptOrder, accountKey, location, newNonce);
|
||||||
if (!postNewOrderResult.IsSuccess) {
|
if (!postNewOrderResult.IsSuccess) {
|
||||||
return (null, postNewOrderResult);
|
return ((null, null, null), postNewOrderResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (order.Result.Status == "ready")
|
if (order.Result.Status == "ready")
|
||||||
return IDomainResult.Success(new Dictionary<string, string>());
|
return IDomainResult.Success((currentOrder, results, challenges));
|
||||||
|
|
||||||
if (order.Result.Status != "pending") {
|
if (order.Result.Status != "pending") {
|
||||||
_logger.LogError($"Created new order and expected status 'pending', but got: {order.Result.Status} \r\n {order.Result}");
|
_logger.LogError($"Created new order and expected status 'pending', but got: {order.Result.Status} \r\n {order.Result}");
|
||||||
return IDomainResult.Failed<Dictionary<string, string>?>();
|
return IDomainResult.Failed<(Order?, Dictionary<string, string>?, List<AuthorizationChallenge>?)>();
|
||||||
}
|
}
|
||||||
|
|
||||||
_currentOrder = order.Result;
|
currentOrder = order.Result;
|
||||||
|
|
||||||
var results = new Dictionary<string, string>();
|
|
||||||
foreach (var item in order.Result.Authorizations) {
|
|
||||||
|
|
||||||
var (challengeResponse, postAuthorisationChallengeResult) = await SendAsync<AuthorizationChallengeResponse>(HttpMethod.Post, item, true, null);
|
foreach (var item in currentOrder.Authorizations) {
|
||||||
|
|
||||||
|
var (challengeResponse, postAuthorisationChallengeResult) = await SendAsync<AuthorizationChallengeResponse>(HttpMethod.Post, item, true, null, accountKey, location, newNonce);
|
||||||
if (!postAuthorisationChallengeResult.IsSuccess) {
|
if (!postAuthorisationChallengeResult.IsSuccess) {
|
||||||
return (null, postAuthorisationChallengeResult);
|
return ((null, null, null), postAuthorisationChallengeResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (challengeResponse.Result.Status == "valid")
|
if (challengeResponse.Result.Status == "valid")
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (challengeResponse.Result.Status != "pending") {
|
if (challengeResponse.Result.Status != "pending") {
|
||||||
_logger.LogError($"Expected autorization status 'pending', but got: {order.Result.Status} \r\n {challengeResponse.ResponseText}");
|
_logger.LogError($"Expected autorization status 'pending', but got: {currentOrder.Status} \r\n {challengeResponse.ResponseText}");
|
||||||
return IDomainResult.Failed<Dictionary<string, string>?>();
|
return IDomainResult.Failed<(Order?, Dictionary<string, string>?, List<AuthorizationChallenge>?)>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var challenge = challengeResponse.Result.Challenges.First(x => x.Type == challengeType);
|
var challenge = challengeResponse.Result.Challenges.First(x => x.Type == challengeType);
|
||||||
_challenges.Add(challenge);
|
challenges.Add(challenge);
|
||||||
|
|
||||||
var keyToken = _jwsService.GetKeyAuthorization(challenge.Token);
|
var keyToken = jwsService.GetKeyAuthorization(challenge.Token);
|
||||||
|
|
||||||
switch (challengeType) {
|
switch (challengeType) {
|
||||||
|
|
||||||
@ -263,7 +205,7 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
|
|
||||||
case "dns-01": {
|
case "dns-01": {
|
||||||
using (var sha256 = SHA256.Create()) {
|
using (var sha256 = SHA256.Create()) {
|
||||||
var dnsToken = _jwsService.Base64UrlEncoded(sha256.ComputeHash(Encoding.UTF8.GetBytes(keyToken)));
|
var dnsToken = jwsService.Base64UrlEncoded(sha256.ComputeHash(Encoding.UTF8.GetBytes(keyToken)));
|
||||||
results[challengeResponse.Result.Identifier.Value] = dnsToken;
|
results[challengeResponse.Result.Identifier.Value] = dnsToken;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -290,13 +232,14 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return IDomainResult.Success(results);
|
// TODO: reurn challenges
|
||||||
|
return IDomainResult.Success((currentOrder, results, challenges));
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
var message = "Let's Encrypt client unhandled exception";
|
var message = "Let's Encrypt client unhandled exception";
|
||||||
|
|
||||||
_logger.LogError(ex, message);
|
_logger.LogError(ex, message);
|
||||||
return IDomainResult.CriticalDependencyError<Dictionary<string, string>?>(message);
|
return IDomainResult.CriticalDependencyError<(Order?, Dictionary<string, string>?, List<AuthorizationChallenge>?)>(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,18 +248,22 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
/// <exception cref="InvalidOperationException"></exception>
|
/// <exception cref="InvalidOperationException"></exception>
|
||||||
public async Task<IDomainResult> CompleteChallenges() {
|
public async Task<IDomainResult> CompleteChallenges(Uri newNonce, byte[] accountKeyBytes, string location, Order currentOrder, List<AuthorizationChallenge> challenges) {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
var accountKey = new RSACryptoServiceProvider(4096);
|
||||||
|
accountKey.ImportCspBlob(accountKeyBytes);
|
||||||
|
var jwsService = new JwsService(accountKey);
|
||||||
|
|
||||||
_logger.LogInformation($"Executing {nameof(CompleteChallenges)}...");
|
_logger.LogInformation($"Executing {nameof(CompleteChallenges)}...");
|
||||||
|
|
||||||
if (_currentOrder?.Identifiers == null) {
|
if (currentOrder?.Identifiers == null) {
|
||||||
return IDomainResult.Failed();
|
return IDomainResult.Failed();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var index = 0; index < _challenges.Count; index++) {
|
for (var index = 0; index < challenges.Count; index++) {
|
||||||
|
|
||||||
var challenge = _challenges[index];
|
var challenge = challenges[index];
|
||||||
|
|
||||||
var start = DateTime.UtcNow;
|
var start = DateTime.UtcNow;
|
||||||
|
|
||||||
@ -325,7 +272,7 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
|
|
||||||
switch (challenge.Type) {
|
switch (challenge.Type) {
|
||||||
case "dns-01": {
|
case "dns-01": {
|
||||||
authorizeChallenge.KeyAuthorization = _jwsService.GetKeyAuthorization(challenge.Token);
|
authorizeChallenge.KeyAuthorization = jwsService.GetKeyAuthorization(challenge.Token);
|
||||||
//var (result, responseText) = await SendAsync<AuthorizationChallengeResponse>(HttpMethod.Post, challenge.Url, authorizeChallenge, token);
|
//var (result, responseText) = await SendAsync<AuthorizationChallengeResponse>(HttpMethod.Post, challenge.Url, authorizeChallenge, token);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -335,7 +282,7 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (authChallenge, postAuthChallengeResult) = await SendAsync<AuthorizationChallengeResponse>(HttpMethod.Post, challenge.Url, false, "{}");
|
var (authChallenge, postAuthChallengeResult) = await SendAsync<AuthorizationChallengeResponse>(HttpMethod.Post, challenge.Url, false, "{}", accountKey, location, newNonce);
|
||||||
if (!postAuthChallengeResult.IsSuccess) {
|
if (!postAuthChallengeResult.IsSuccess) {
|
||||||
return postAuthChallengeResult;
|
return postAuthChallengeResult;
|
||||||
}
|
}
|
||||||
@ -344,7 +291,7 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
if (authChallenge.Result.Status != "pending") {
|
if (authChallenge.Result.Status != "pending") {
|
||||||
_logger.LogError($"Failed autorization of {_currentOrder.Identifiers[index].Value} \r\n {authChallenge.ResponseText}");
|
_logger.LogError($"Failed autorization of {currentOrder.Identifiers[index].Value} \r\n {authChallenge.ResponseText}");
|
||||||
return IDomainResult.Failed();
|
return IDomainResult.Failed();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,9 +317,11 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="hostnames"></param>
|
/// <param name="hostnames"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<IDomainResult> GetOrder(string[] hostnames) {
|
public async Task<(Order?, IDomainResult)> GetOrder(Uri newOrder, Uri newNonce, byte[] accountKeyBytes, string location, string[] hostnames) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
var accountKey = new RSACryptoServiceProvider(4096);
|
||||||
|
accountKey.ImportCspBlob(accountKeyBytes);
|
||||||
|
|
||||||
_logger.LogInformation($"Executing {nameof(GetOrder)}");
|
_logger.LogInformation($"Executing {nameof(GetOrder)}");
|
||||||
|
|
||||||
@ -384,19 +333,19 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
}).ToArray()
|
}).ToArray()
|
||||||
};
|
};
|
||||||
|
|
||||||
var (order, postOrderResult) = await SendAsync<Order>(HttpMethod.Post, _directory.NewOrder, false, letsEncryptOrder);
|
var (order, postOrderResult) = await SendAsync<Order>(HttpMethod.Post, newOrder, false, letsEncryptOrder, accountKey, location, newNonce);
|
||||||
if (!postOrderResult.IsSuccess)
|
if (!postOrderResult.IsSuccess)
|
||||||
return postOrderResult;
|
return (null, postOrderResult);
|
||||||
|
|
||||||
_currentOrder = order.Result;
|
var currentOrder = order.Result;
|
||||||
|
|
||||||
return IDomainResult.Success();
|
return IDomainResult.Success(currentOrder);
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
var message = "Let's Encrypt client unhandled exception";
|
var message = "Let's Encrypt client unhandled exception";
|
||||||
|
|
||||||
_logger.LogError(ex, message);
|
_logger.LogError(ex, message);
|
||||||
return IDomainResult.CriticalDependencyError(message);
|
return IDomainResult.CriticalDependencyError<Order?>(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -406,13 +355,26 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
/// <param name="subject"></param>
|
/// <param name="subject"></param>
|
||||||
/// <returns>Cert and Private key</returns>
|
/// <returns>Cert and Private key</returns>
|
||||||
/// <exception cref="InvalidOperationException"></exception>
|
/// <exception cref="InvalidOperationException"></exception>
|
||||||
public async Task<((X509Certificate2 Cert, RSA PrivateKey)?, IDomainResult)> GetCertificate(string subject) {
|
public async Task<(Dictionary<string, CertificateCache>?, IDomainResult)> GetCertificate(Uri newOrder, Uri newNonce, byte[] accountKeyBytes, Order currentOrder, string location, string [] subjects) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
var accountKey = new RSACryptoServiceProvider(4096);
|
||||||
|
accountKey.ImportCspBlob(accountKeyBytes);
|
||||||
|
|
||||||
|
var jwsService = new JwsService(accountKey);
|
||||||
|
|
||||||
|
|
||||||
_logger.LogInformation($"Executing {nameof(GetCertificate)}...");
|
_logger.LogInformation($"Executing {nameof(GetCertificate)}...");
|
||||||
|
|
||||||
if (_currentOrder == null) {
|
var cachedCerts = new Dictionary<string, CertificateCache>();
|
||||||
return IDomainResult.Failed<(X509Certificate2 Cert, RSA PrivateKey)?>();
|
|
||||||
|
|
||||||
|
foreach (var subject in subjects) {
|
||||||
|
|
||||||
|
|
||||||
|
if (currentOrder == null) {
|
||||||
|
return IDomainResult.Failed<Dictionary<string, CertificateCache>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var key = new RSACryptoServiceProvider(4096);
|
var key = new RSACryptoServiceProvider(4096);
|
||||||
@ -420,13 +382,13 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
||||||
|
|
||||||
var san = new SubjectAlternativeNameBuilder();
|
var san = new SubjectAlternativeNameBuilder();
|
||||||
foreach (var host in _currentOrder.Identifiers)
|
foreach (var host in currentOrder.Identifiers)
|
||||||
san.AddDnsName(host.Value);
|
san.AddDnsName(host.Value);
|
||||||
|
|
||||||
csr.CertificateExtensions.Add(san.Build());
|
csr.CertificateExtensions.Add(san.Build());
|
||||||
|
|
||||||
var letsEncryptOrder = new FinalizeRequest {
|
var letsEncryptOrder = new FinalizeRequest {
|
||||||
Csr = _jwsService.Base64UrlEncoded(csr.CreateSigningRequest())
|
Csr = jwsService.Base64UrlEncoded(csr.CreateSigningRequest())
|
||||||
};
|
};
|
||||||
|
|
||||||
Uri? certificateUrl = default;
|
Uri? certificateUrl = default;
|
||||||
@ -436,16 +398,16 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
|
|
||||||
while (certificateUrl == null) {
|
while (certificateUrl == null) {
|
||||||
// https://community.letsencrypt.org/t/breaking-changes-in-asynchronous-order-finalization-api/195882
|
// https://community.letsencrypt.org/t/breaking-changes-in-asynchronous-order-finalization-api/195882
|
||||||
await GetOrder(_currentOrder.Identifiers.Select(x => x.Value).ToArray());
|
await GetOrder(newOrder, newNonce, accountKeyBytes, location, currentOrder.Identifiers.Select(x => x.Value).ToArray());
|
||||||
|
|
||||||
if (_currentOrder.Status == "ready") {
|
if (currentOrder.Status == "ready") {
|
||||||
var (order, postOrderResult) = await SendAsync<Order>(HttpMethod.Post, _currentOrder.Finalize, false, letsEncryptOrder);
|
var (order, postOrderResult) = await SendAsync<Order>(HttpMethod.Post, currentOrder.Finalize, false, letsEncryptOrder, accountKey, location, newNonce);
|
||||||
if (!postOrderResult.IsSuccess || order?.Result == null)
|
if (!postOrderResult.IsSuccess || order?.Result == null)
|
||||||
return (null, postOrderResult);
|
return (null, postOrderResult);
|
||||||
|
|
||||||
|
|
||||||
if (order.Result.Status == "processing") {
|
if (order.Result.Status == "processing") {
|
||||||
(order, postOrderResult) = await SendAsync<Order>(HttpMethod.Post, _currentOrder.Location, true, null);
|
(order, postOrderResult) = await SendAsync<Order>(HttpMethod.Post, currentOrder.Location, true, null, accountKey, location, newNonce);
|
||||||
if (!postOrderResult.IsSuccess || order?.Result == null)
|
if (!postOrderResult.IsSuccess || order?.Result == null)
|
||||||
return (null, postOrderResult);
|
return (null, postOrderResult);
|
||||||
}
|
}
|
||||||
@ -461,31 +423,28 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
var (pem, postPemResult) = await SendAsync<string>(HttpMethod.Post, certificateUrl, true, null);
|
var (pem, postPemResult) = await SendAsync<string>(HttpMethod.Post, certificateUrl, true, null, accountKey, location, newNonce);
|
||||||
if (!postPemResult.IsSuccess || pem?.Result == null)
|
if (!postPemResult.IsSuccess || pem?.Result == null)
|
||||||
return (null, postPemResult);
|
return (null, postPemResult);
|
||||||
|
|
||||||
|
|
||||||
if (_cache == null) {
|
|
||||||
_logger.LogError($"{nameof(_cache)} is null");
|
|
||||||
return IDomainResult.Failed<(X509Certificate2 Cert, RSA PrivateKey)?>();
|
|
||||||
}
|
|
||||||
|
|
||||||
_cache.CachedCerts ??= new Dictionary<string, CertificateCache>();
|
cachedCerts.Add(subject, new CertificateCache {
|
||||||
_cache.CachedCerts[subject] = new CertificateCache {
|
|
||||||
Cert = pem.Result,
|
Cert = pem.Result,
|
||||||
Private = key.ExportCspBlob(true)
|
Private = key.ExportCspBlob(true)
|
||||||
};
|
});
|
||||||
|
|
||||||
var cert = new X509Certificate2(Encoding.UTF8.GetBytes(pem.Result));
|
//var cert = new X509Certificate2(Encoding.UTF8.GetBytes(pem.Result));
|
||||||
|
|
||||||
return IDomainResult.Success((cert, key));
|
}
|
||||||
|
|
||||||
|
return IDomainResult.Success(cachedCerts);
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
var message = "Let's Encrypt client unhandled exception";
|
var message = "Let's Encrypt client unhandled exception";
|
||||||
|
|
||||||
_logger.LogError(ex, message);
|
_logger.LogError(ex, message);
|
||||||
return IDomainResult.CriticalDependencyError< (X509Certificate2 Cert, RSA PrivateKey)?>(message);
|
return IDomainResult.CriticalDependencyError<Dictionary<string, CertificateCache>?>(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -513,16 +472,13 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="token"></param>
|
/// <param name="token"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private async Task<(string?, IDomainResult)> NewNonce() {
|
private async Task<(string?, IDomainResult)> NewNonce(Uri newNonce) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
_logger.LogInformation($"Executing {nameof(NewNonce)}...");
|
_logger.LogInformation($"Executing {nameof(NewNonce)}...");
|
||||||
|
|
||||||
if (_directory == null)
|
var result = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, newNonce));
|
||||||
IDomainResult.Failed();
|
|
||||||
|
|
||||||
var result = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, _directory.NewNonce));
|
|
||||||
return IDomainResult.Success(result.Headers.GetValues("Replay-Nonce").First());
|
return IDomainResult.Success(result.Headers.GetValues("Replay-Nonce").First());
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -543,31 +499,32 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
/// <param name="requestModel"></param>
|
/// <param name="requestModel"></param>
|
||||||
/// <param name="token"></param>
|
/// <param name="token"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private async Task<(SendResult<TResult>?, IDomainResult)> SendAsync<TResult>(HttpMethod method, Uri uri, bool isPostAsGet, object? requestModel) {
|
private async Task<(SendResult<TResult>?, IDomainResult)> SendAsync<TResult>(HttpMethod method, Uri uri, bool isPostAsGet, object? requestModel, RSACryptoServiceProvider? accountKey, string? location, Uri? newNonce) {
|
||||||
try {
|
try {
|
||||||
|
var _nonce = default(string?);
|
||||||
|
|
||||||
_logger.LogInformation($"Executing {nameof(SendAsync)}...");
|
_logger.LogInformation($"Executing {nameof(SendAsync)}...");
|
||||||
|
|
||||||
//if (_jwsService == null) {
|
|
||||||
// _logger.LogError($"{nameof(_jwsService)} is null");
|
|
||||||
// return IDomainResult.Failed<SendResult<TResult>?>();
|
|
||||||
//}
|
|
||||||
|
|
||||||
var request = new HttpRequestMessage(method, uri);
|
var request = new HttpRequestMessage(method, uri);
|
||||||
|
|
||||||
if (uri.OriginalString != "directory") {
|
if (uri.OriginalString != "directory") {
|
||||||
var (nonce, newNonceResult) = await NewNonce();
|
var (nonce, newNonceResult) = await NewNonce(newNonce);
|
||||||
if (!newNonceResult.IsSuccess || nonce == null) {
|
if (!newNonceResult.IsSuccess || nonce == null) {
|
||||||
return (null, newNonceResult);
|
return (null, newNonceResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
_nonce = nonce;
|
_nonce = nonce;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
_nonce = default;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requestModel != null || isPostAsGet) {
|
if (requestModel != null || isPostAsGet) {
|
||||||
|
|
||||||
|
if (accountKey == null)
|
||||||
|
return IDomainResult.Failed<SendResult<TResult>?>();
|
||||||
|
|
||||||
|
var jwsService = new JwsService(accountKey);
|
||||||
|
if(location != null)
|
||||||
|
jwsService.SetKeyId(location);
|
||||||
|
|
||||||
var jwsHeader = new JwsHeader {
|
var jwsHeader = new JwsHeader {
|
||||||
Url = uri,
|
Url = uri,
|
||||||
};
|
};
|
||||||
@ -576,8 +533,8 @@ public class LetsEncryptService : ILetsEncryptService {
|
|||||||
jwsHeader.Nonce = _nonce;
|
jwsHeader.Nonce = _nonce;
|
||||||
|
|
||||||
var encodedMessage = isPostAsGet
|
var encodedMessage = isPostAsGet
|
||||||
? _jwsService.Encode(jwsHeader)
|
? jwsService.Encode(jwsHeader)
|
||||||
: _jwsService.Encode(requestModel, jwsHeader);
|
: jwsService.Encode(requestModel, jwsHeader);
|
||||||
|
|
||||||
var json = encodedMessage.ToJson();
|
var json = encodedMessage.ToJson();
|
||||||
|
|
||||||
|
|||||||
@ -16,6 +16,9 @@ public interface IApp {
|
|||||||
Task Run(string[] args);
|
Task Run(string[] args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public class App : IApp {
|
public class App : IApp {
|
||||||
|
|
||||||
private readonly string _appPath = AppDomain.CurrentDomain.BaseDirectory;
|
private readonly string _appPath = AppDomain.CurrentDomain.BaseDirectory;
|
||||||
@ -25,6 +28,10 @@ public class App : IApp {
|
|||||||
private readonly ILetsEncryptService _letsEncryptService;
|
private readonly ILetsEncryptService _letsEncryptService;
|
||||||
private readonly ITerminalService _terminalService;
|
private readonly ITerminalService _terminalService;
|
||||||
|
|
||||||
|
private static readonly string _registerAccount = "--register-account";
|
||||||
|
private static readonly string _server = "--server";
|
||||||
|
private static readonly string _mail = "-m";
|
||||||
|
|
||||||
public App(
|
public App(
|
||||||
ILogger<App> logger,
|
ILogger<App> logger,
|
||||||
IOptions<Configuration> appSettings,
|
IOptions<Configuration> appSettings,
|
||||||
@ -39,6 +46,37 @@ public class App : IApp {
|
|||||||
|
|
||||||
public async Task Run(string[] args) {
|
public async Task Run(string[] args) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var parsedArgs = args.Select(x => x.Split(' ')).ToDictionary(x => x[0].Trim(), x => x[1].Trim());
|
||||||
|
|
||||||
|
if (parsedArgs.ContainsKey(_registerAccount)) {
|
||||||
|
_logger.LogInformation("Registring accoount");
|
||||||
|
|
||||||
|
if(!parsedArgs.ContainsKey(_server))
|
||||||
|
throw new ArgumentNullException("Server is required");
|
||||||
|
|
||||||
|
if(!parsedArgs.ContainsKey(_mail))
|
||||||
|
throw new ArgumentNullException("Mail is required");
|
||||||
|
|
||||||
|
var mail = parsedArgs[_mail];
|
||||||
|
|
||||||
|
if (parsedArgs[_server] == "staging")
|
||||||
|
await _letsEncryptService.ConfigureClient("https://acme-staging-v02.api.letsencrypt.org/");
|
||||||
|
else if(parsedArgs[_server] == "production")
|
||||||
|
await _letsEncryptService.ConfigureClient("https://acme-v02.api.letsencrypt.org/");
|
||||||
|
else
|
||||||
|
throw new ArgumentException("Invalid server");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
_logger.LogInformation("Let's Encrypt client. Started...");
|
_logger.LogInformation("Let's Encrypt client. Started...");
|
||||||
|
|
||||||
|
|||||||
36
src/LetsEncryptServer/Configuration.cs
Normal file
36
src/LetsEncryptServer/Configuration.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
namespace LetsEncryptServer {
|
||||||
|
|
||||||
|
public class Site {
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public required string[] Hosts { get; set; }
|
||||||
|
public required string Challenge { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Customer {
|
||||||
|
private string? _id;
|
||||||
|
public string Id {
|
||||||
|
get => _id ?? string.Empty;
|
||||||
|
set => _id = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Active { get; set; }
|
||||||
|
public string[]? Contacts { get; set; }
|
||||||
|
public string? Name { get; set; }
|
||||||
|
public string? LastName { get; set; }
|
||||||
|
public Site[]? Sites { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Server {
|
||||||
|
public required string Address { get; set; }
|
||||||
|
public required string PrivateKey { get; set; }
|
||||||
|
public required string Path { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Configuration {
|
||||||
|
public required string Production { get; set; }
|
||||||
|
public required string Staging { get; set; }
|
||||||
|
public required Server Server { get; set; }
|
||||||
|
|
||||||
|
public Customer[]? Customers { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
225
src/LetsEncryptServer/Controllers/CertsFlowController.cs
Normal file
225
src/LetsEncryptServer/Controllers/CertsFlowController.cs
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
using DomainResults.Mvc;
|
||||||
|
using MaksIT.LetsEncrypt.Entities;
|
||||||
|
using MaksIT.LetsEncrypt.Models.Responses;
|
||||||
|
using MaksIT.LetsEncrypt.Services;
|
||||||
|
using MaksIT.LetsEncryptServer.Models.Requests;
|
||||||
|
using Microsoft.AspNetCore.Identity.Data;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using System.IO;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace LetsEncryptServer.Controllers;
|
||||||
|
|
||||||
|
public class LetsEncryptSession {
|
||||||
|
public RegistrationCache? RegistrationCache { get; set; }
|
||||||
|
public Order? CurrentOrder { get; set; }
|
||||||
|
public List<AuthorizationChallenge>? Challenges { get; set; }
|
||||||
|
public string[] Hostnames { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("[controller]")]
|
||||||
|
public class CertsFlowController : ControllerBase {
|
||||||
|
|
||||||
|
private readonly Configuration _appSettings;
|
||||||
|
private readonly IMemoryCache _memoryCache;
|
||||||
|
private readonly ILetsEncryptService _letsEncryptService;
|
||||||
|
|
||||||
|
private readonly string _acmePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "acme");
|
||||||
|
private readonly string _certPath = Path.Combine();
|
||||||
|
|
||||||
|
MemoryCacheEntryOptions _cacheEntryOptions = new MemoryCacheEntryOptions {
|
||||||
|
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5),
|
||||||
|
SlidingExpiration = TimeSpan.FromMinutes(2)
|
||||||
|
};
|
||||||
|
|
||||||
|
public CertsFlowController(
|
||||||
|
IOptions<Configuration> appSettings,
|
||||||
|
IMemoryCache memoryCache,
|
||||||
|
ILetsEncryptService letsEncryptService
|
||||||
|
) {
|
||||||
|
_memoryCache = memoryCache;
|
||||||
|
_appSettings = appSettings.Value;
|
||||||
|
_letsEncryptService = letsEncryptService;
|
||||||
|
|
||||||
|
if (!Directory.Exists(_acmePath))
|
||||||
|
Directory.CreateDirectory(_acmePath);
|
||||||
|
|
||||||
|
Console.WriteLine(_acmePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("[action]")]
|
||||||
|
public async Task<IActionResult> TermsOfService() {
|
||||||
|
var (config, configResult) = await _letsEncryptService.ConfigureClient("https://acme-staging-v02.api.letsencrypt.org/directory");
|
||||||
|
|
||||||
|
if (!configResult.IsSuccess || config == null)
|
||||||
|
return configResult.ToActionResult();
|
||||||
|
|
||||||
|
return Ok(config.Meta.TermsOfService);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[HttpPost("[action]")]
|
||||||
|
public async Task<IActionResult> Init([FromBody] InitRequest requestData) {
|
||||||
|
|
||||||
|
var (config, configResult) = await _letsEncryptService.ConfigureClient("https://acme-staging-v02.api.letsencrypt.org/directory");
|
||||||
|
if (!configResult.IsSuccess || config == null)
|
||||||
|
return configResult.ToActionResult();
|
||||||
|
|
||||||
|
var (cache, cacheResult) = await _letsEncryptService.Init(config.NewAccount, config.NewNonce, requestData.Contacts);
|
||||||
|
if(!cacheResult.IsSuccess || cache == null)
|
||||||
|
return cacheResult.ToActionResult();
|
||||||
|
|
||||||
|
var cacheData = new LetsEncryptSession {
|
||||||
|
RegistrationCache = cache,
|
||||||
|
};
|
||||||
|
|
||||||
|
var accountId = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
_memoryCache.Set(accountId, cacheData, _cacheEntryOptions);
|
||||||
|
|
||||||
|
return Ok(accountId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("[action]/{accountId}")]
|
||||||
|
public async Task<IActionResult> NewOrder(string accountId, [FromBody] NewOrderRequest requestData) {
|
||||||
|
|
||||||
|
var cacheData = (LetsEncryptSession?)_memoryCache.Get(accountId);
|
||||||
|
if (cacheData?.RegistrationCache?.AccountKey == null)
|
||||||
|
return BadRequest();
|
||||||
|
|
||||||
|
var (config, configResult) = await _letsEncryptService.ConfigureClient("https://acme-staging-v02.api.letsencrypt.org/directory");
|
||||||
|
if (!configResult.IsSuccess || config == null)
|
||||||
|
return configResult.ToActionResult();
|
||||||
|
|
||||||
|
|
||||||
|
var (orderData, newOrderResult) = await _letsEncryptService.NewOrder(
|
||||||
|
config.NewOrder,
|
||||||
|
config.NewNonce,
|
||||||
|
cacheData.RegistrationCache.AccountKey,
|
||||||
|
cacheData.RegistrationCache.Location.ToString(),
|
||||||
|
requestData.Hostnames,
|
||||||
|
requestData.ChallengeType);
|
||||||
|
|
||||||
|
if (!newOrderResult.IsSuccess)
|
||||||
|
return newOrderResult.ToActionResult();
|
||||||
|
|
||||||
|
var(currentOrder, results, challenges) = orderData;
|
||||||
|
|
||||||
|
if (results?.Count == 0)
|
||||||
|
return StatusCode(500);
|
||||||
|
|
||||||
|
// TODO: save results to disk
|
||||||
|
var fullPaths = new List<string>();
|
||||||
|
foreach (var result in results) {
|
||||||
|
string[] splitToken = result.Value.Split('.');
|
||||||
|
|
||||||
|
System.IO.File.WriteAllText(Path.Combine(_acmePath, splitToken[0]), result.Value);
|
||||||
|
|
||||||
|
fullPaths.Add(splitToken[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheData.CurrentOrder = currentOrder;
|
||||||
|
cacheData.Challenges = challenges;
|
||||||
|
cacheData.Hostnames = requestData.Hostnames;
|
||||||
|
|
||||||
|
_memoryCache.Set(accountId, cacheData, _cacheEntryOptions);
|
||||||
|
|
||||||
|
return Ok(fullPaths);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("[action]/{accountId}")]
|
||||||
|
public async Task<IActionResult> CompleteChallenges(string accountId) {
|
||||||
|
|
||||||
|
var cacheData = (LetsEncryptSession?)_memoryCache.Get(accountId);
|
||||||
|
if (cacheData?.RegistrationCache?.AccountKey == null)
|
||||||
|
return BadRequest();
|
||||||
|
|
||||||
|
var (config, configResult) = await _letsEncryptService.ConfigureClient("https://acme-staging-v02.api.letsencrypt.org/directory");
|
||||||
|
if (!configResult.IsSuccess || config == null)
|
||||||
|
return configResult.ToActionResult();
|
||||||
|
|
||||||
|
var challengeResult = await _letsEncryptService.CompleteChallenges(
|
||||||
|
config.NewNonce,
|
||||||
|
cacheData.RegistrationCache.AccountKey,
|
||||||
|
cacheData.RegistrationCache.Location.ToString(),
|
||||||
|
cacheData.CurrentOrder,
|
||||||
|
cacheData.Challenges
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!challengeResult.IsSuccess)
|
||||||
|
return challengeResult.ToActionResult();
|
||||||
|
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("[action]/{accountId}")]
|
||||||
|
public async Task<IActionResult> GetOrder(string accountId) {
|
||||||
|
|
||||||
|
var cacheData = (LetsEncryptSession?)_memoryCache.Get(accountId);
|
||||||
|
if (cacheData?.RegistrationCache?.AccountKey == null)
|
||||||
|
return BadRequest();
|
||||||
|
|
||||||
|
var (config, configResult) = await _letsEncryptService.ConfigureClient("https://acme-staging-v02.api.letsencrypt.org/directory");
|
||||||
|
if (!configResult.IsSuccess || config == null)
|
||||||
|
return configResult.ToActionResult();
|
||||||
|
|
||||||
|
|
||||||
|
var (currentOrder, currentOrderResult) = await _letsEncryptService.GetOrder(
|
||||||
|
config.NewOrder,
|
||||||
|
config.NewNonce,
|
||||||
|
cacheData.RegistrationCache.AccountKey,
|
||||||
|
cacheData.RegistrationCache.Location.ToString(),
|
||||||
|
cacheData.Hostnames
|
||||||
|
);
|
||||||
|
|
||||||
|
if(!currentOrderResult.IsSuccess)
|
||||||
|
return currentOrderResult.ToActionResult();
|
||||||
|
|
||||||
|
cacheData.CurrentOrder = currentOrder;
|
||||||
|
|
||||||
|
_memoryCache.Set(accountId, cacheData, _cacheEntryOptions);
|
||||||
|
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("[action]/{accountId}")]
|
||||||
|
public async Task<IActionResult> GetCertificate(string accountId) {
|
||||||
|
|
||||||
|
var cacheData = (LetsEncryptSession?)_memoryCache.Get(accountId);
|
||||||
|
if (cacheData?.RegistrationCache?.AccountKey == null)
|
||||||
|
return BadRequest();
|
||||||
|
|
||||||
|
var (config, configResult) = await _letsEncryptService.ConfigureClient("https://acme-staging-v02.api.letsencrypt.org/directory");
|
||||||
|
if (!configResult.IsSuccess || config == null)
|
||||||
|
return configResult.ToActionResult();
|
||||||
|
|
||||||
|
var (cachedCerts, certsResult) = await _letsEncryptService.GetCertificate(
|
||||||
|
config.NewOrder,
|
||||||
|
config.NewNonce,
|
||||||
|
cacheData.RegistrationCache.AccountKey,
|
||||||
|
cacheData.CurrentOrder,
|
||||||
|
cacheData.RegistrationCache.Location.ToString(),
|
||||||
|
cacheData.Hostnames
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!certsResult.IsSuccess || cachedCerts == null)
|
||||||
|
return certsResult.ToActionResult();
|
||||||
|
|
||||||
|
// TODO: write certs to filesystem
|
||||||
|
foreach (var (subject, cachedCert) in cachedCerts) {
|
||||||
|
var cert = new X509Certificate2(Encoding.UTF8.GetBytes(cachedCert.Cert));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!certsResult.IsSuccess)
|
||||||
|
return BadRequest();
|
||||||
|
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
39
src/LetsEncryptServer/Controllers/WellKnownController.cs
Normal file
39
src/LetsEncryptServer/Controllers/WellKnownController.cs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
using MaksIT.LetsEncrypt.Services;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace LetsEncryptServer.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route(".well-known")]
|
||||||
|
public class WellKnownController : ControllerBase {
|
||||||
|
|
||||||
|
private readonly Configuration _appSettings;
|
||||||
|
private readonly ILetsEncryptService _letsEncryptService;
|
||||||
|
|
||||||
|
private readonly string _acmePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "acme");
|
||||||
|
|
||||||
|
public WellKnownController(
|
||||||
|
IOptions<Configuration> appSettings,
|
||||||
|
ILetsEncryptService letsEncryptService
|
||||||
|
) {
|
||||||
|
_appSettings = appSettings.Value;
|
||||||
|
_letsEncryptService = letsEncryptService;
|
||||||
|
|
||||||
|
if (!Directory.Exists(_acmePath))
|
||||||
|
Directory.CreateDirectory(_acmePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[HttpGet("acme-challenge/{fileName}")]
|
||||||
|
public IActionResult AcmeChallenge(string fileName) {
|
||||||
|
|
||||||
|
var fileContent = System.IO.File.ReadAllText(Path.Combine(_acmePath, fileName));
|
||||||
|
if (fileContent == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
return Ok(fileContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
24
src/LetsEncryptServer/Dockerfile
Normal file
24
src/LetsEncryptServer/Dockerfile
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
|
||||||
|
USER app
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||||
|
ARG BUILD_CONFIGURATION=Release
|
||||||
|
WORKDIR /src
|
||||||
|
COPY ["LetsEncryptServer/LetsEncryptServer.csproj", "LetsEncryptServer/"]
|
||||||
|
RUN dotnet restore "./LetsEncryptServer/LetsEncryptServer.csproj"
|
||||||
|
COPY . .
|
||||||
|
WORKDIR "/src/LetsEncryptServer"
|
||||||
|
RUN dotnet build "./LetsEncryptServer.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||||
|
|
||||||
|
FROM build AS publish
|
||||||
|
ARG BUILD_CONFIGURATION=Release
|
||||||
|
RUN dotnet publish "./LetsEncryptServer.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||||
|
|
||||||
|
FROM base AS final
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=publish /app/publish .
|
||||||
|
ENTRYPOINT ["dotnet", "LetsEncryptServer.dll"]
|
||||||
27
src/LetsEncryptServer/LetsEncryptServer.csproj
Normal file
27
src/LetsEncryptServer/LetsEncryptServer.csproj
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
|
<DockerComposeProjectPath>..\docker-compose.dcproj</DockerComposeProjectPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="DomainResult" Version="3.2.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.20.1" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\LetsEncrypt\LetsEncrypt.csproj" />
|
||||||
|
<ProjectReference Include="..\SSHProvider\SSHProvider.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Models\Responses\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
6
src/LetsEncryptServer/LetsEncryptServer.http
Normal file
6
src/LetsEncryptServer/LetsEncryptServer.http
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
@LetsEncryptServer_HostAddress = http://localhost:5016
|
||||||
|
|
||||||
|
GET {{LetsEncryptServer_HostAddress}}/weatherforecast/
|
||||||
|
Accept: application/json
|
||||||
|
|
||||||
|
###
|
||||||
11
src/LetsEncryptServer/Models/Requests/InitRequest.cs
Normal file
11
src/LetsEncryptServer/Models/Requests/InitRequest.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MaksIT.LetsEncryptServer.Models.Requests {
|
||||||
|
public class InitRequest {
|
||||||
|
public string[] Contacts { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/LetsEncryptServer/Models/Requests/NewOrderRequest.cs
Normal file
7
src/LetsEncryptServer/Models/Requests/NewOrderRequest.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace MaksIT.LetsEncryptServer.Models.Requests {
|
||||||
|
public class NewOrderRequest {
|
||||||
|
public string[] Hostnames { get; set; }
|
||||||
|
|
||||||
|
public string ChallengeType { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/LetsEncryptServer/Program.cs
Normal file
28
src/LetsEncryptServer/Program.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using MaksIT.LetsEncrypt.Services;
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
// Add services to the container.
|
||||||
|
|
||||||
|
builder.Services.AddControllers();
|
||||||
|
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||||
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
|
builder.Services.AddSwaggerGen();
|
||||||
|
|
||||||
|
builder.Services.AddMemoryCache();
|
||||||
|
|
||||||
|
builder.Services.AddHttpClient<ILetsEncryptService, LetsEncryptService>();
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
// Configure the HTTP request pipeline.
|
||||||
|
if (app.Environment.IsDevelopment()) {
|
||||||
|
app.UseSwagger();
|
||||||
|
app.UseSwaggerUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
app.MapControllers();
|
||||||
|
|
||||||
|
app.Run();
|
||||||
40
src/LetsEncryptServer/Properties/launchSettings.json
Normal file
40
src/LetsEncryptServer/Properties/launchSettings.json
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
},
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"applicationUrl": "http://localhost:5016"
|
||||||
|
},
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Container (Dockerfile)": {
|
||||||
|
"commandName": "Docker",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_HTTP_PORTS": "8080"
|
||||||
|
},
|
||||||
|
"publishAllPorts": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:10248",
|
||||||
|
"sslPort": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
src/LetsEncryptServer/appsettings.Development.json
Normal file
8
src/LetsEncryptServer/appsettings.Development.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/LetsEncryptServer/appsettings.json
Normal file
16
src/LetsEncryptServer/appsettings.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
|
||||||
|
"Configuration": {
|
||||||
|
"Production": "https://acme-v02.api.letsencrypt.org/directory",
|
||||||
|
"Staging": "https://acme-staging-v02.api.letsencrypt.org/directory",
|
||||||
|
|
||||||
|
"ServerPath": "/etc/haproxy/certs"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,16 +1,16 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="DomainResult.Common" Version="3.1.0" />
|
<PackageReference Include="DomainResult.Common" Version="3.2.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
|
||||||
<PackageReference Include="SSH.NET" Version="2020.0.2" />
|
<PackageReference Include="SSH.NET" Version="2024.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
@ -10,35 +10,35 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||||
<PackageReference Include="xunit" Version="2.5.0" />
|
<PackageReference Include="xunit" Version="2.8.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.0">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
|
||||||
|
|
||||||
<PackageReference Include="Serilog.Enrichers.Span" Version="3.1.0" />
|
<PackageReference Include="Serilog.Enrichers.Span" Version="3.1.0" />
|
||||||
<PackageReference Include="Serilog.Expressions" Version="3.4.1" />
|
<PackageReference Include="Serilog.Expressions" Version="4.0.0" />
|
||||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="7.0.0" />
|
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
|
||||||
<PackageReference Include="Serilog.Extensions.Logging" Version="7.0.0" />
|
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
|
||||||
<PackageReference Include="Serilog.Formatting.Compact" Version="1.1.0" />
|
<PackageReference Include="Serilog.Formatting.Compact" Version="2.0.0" />
|
||||||
<PackageReference Include="Serilog.Settings.Configuration" Version="7.0.0" />
|
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
19
src/docker-compose.dcproj
Normal file
19
src/docker-compose.dcproj
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="15.0" Sdk="Microsoft.Docker.Sdk">
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<ProjectVersion>2.1</ProjectVersion>
|
||||||
|
<DockerTargetOS>Linux</DockerTargetOS>
|
||||||
|
<DockerPublishLocally>False</DockerPublishLocally>
|
||||||
|
<ProjectGuid>0233e43f-435d-4309-b20c-ecd4bfbd2e63</ProjectGuid>
|
||||||
|
<DockerLaunchAction>LaunchBrowser</DockerLaunchAction>
|
||||||
|
<DockerServiceUrl>{Scheme}://localhost:{ServicePort}/swagger</DockerServiceUrl>
|
||||||
|
<DockerServiceName>letsencryptserver</DockerServiceName>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="docker-compose.override.yml">
|
||||||
|
<DependentUpon>docker-compose.yml</DependentUpon>
|
||||||
|
</None>
|
||||||
|
<None Include="docker-compose.yml" />
|
||||||
|
<None Include=".dockerignore" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
9
src/docker-compose.override.yml
Normal file
9
src/docker-compose.override.yml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
version: '3.4'
|
||||||
|
|
||||||
|
services:
|
||||||
|
letsencryptserver:
|
||||||
|
environment:
|
||||||
|
- ASPNETCORE_ENVIRONMENT=Development
|
||||||
|
- ASPNETCORE_HTTP_PORTS=8080
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
8
src/docker-compose.yml
Normal file
8
src/docker-compose.yml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
version: '3.4'
|
||||||
|
|
||||||
|
services:
|
||||||
|
letsencryptserver:
|
||||||
|
image: ${DOCKER_REGISTRY-}letsencryptserver
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: LetsEncryptServer/Dockerfile
|
||||||
11
src/launchSettings.json
Normal file
11
src/launchSettings.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"Docker Compose": {
|
||||||
|
"commandName": "DockerCompose",
|
||||||
|
"commandVersion": "1.0",
|
||||||
|
"serviceActions": {
|
||||||
|
"letsencryptserver": "StartDebugging"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user