mirror of
https://github.com/MAKS-IT-COM/maksit-certs-ui.git
synced 2026-05-16 04:48:12 +02:00
(feature): migrate from files to database, domain driven design, certs renew cooldown improvements
This commit is contained in:
parent
1b22b8688d
commit
3c9b432bd1
@ -1,300 +0,0 @@
|
|||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
|
|
||||||
using MaksIT.Core.Extensions;
|
|
||||||
|
|
||||||
using MaksIT.LetsEncrypt.Services;
|
|
||||||
using MaksIT.LetsEncrypt.Entities;
|
|
||||||
using MaksIT.LetsEncryptConsole.Services;
|
|
||||||
|
|
||||||
using MaksIT.SSHProvider;
|
|
||||||
|
|
||||||
namespace MaksIT.LetsEncryptConsole;
|
|
||||||
|
|
||||||
public interface IApp {
|
|
||||||
|
|
||||||
Task Run(string[] args);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public class App : IApp {
|
|
||||||
|
|
||||||
private readonly string _appPath = AppDomain.CurrentDomain.BaseDirectory;
|
|
||||||
|
|
||||||
private readonly ILogger<App> _logger;
|
|
||||||
private readonly Configuration _appSettings;
|
|
||||||
private readonly ILetsEncryptService _letsEncryptService;
|
|
||||||
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(
|
|
||||||
ILogger<App> logger,
|
|
||||||
IOptions<Configuration> appSettings,
|
|
||||||
ILetsEncryptService letsEncryptService,
|
|
||||||
ITerminalService terminalService
|
|
||||||
) {
|
|
||||||
_logger = logger;
|
|
||||||
_appSettings = appSettings.Value;
|
|
||||||
_letsEncryptService = letsEncryptService;
|
|
||||||
_terminalService = terminalService;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
_logger.LogInformation("Let's Encrypt client. Started...");
|
|
||||||
|
|
||||||
foreach (var env in _appSettings.Environments?.Where(x => x.Active) ?? new List<LetsEncryptEnvironment>()) {
|
|
||||||
|
|
||||||
_logger.LogInformation($"Let's Encrypt C# .Net Core Client, environment: {env.Name}");
|
|
||||||
|
|
||||||
//loop all customers
|
|
||||||
foreach (Customer customer in _appSettings.Customers?.Where(x => x.Active) ?? new List<Customer>()) {
|
|
||||||
|
|
||||||
_logger.LogInformation($"Managing customer: {customer.Id} - {customer.Name} {customer.LastName}");
|
|
||||||
|
|
||||||
//define cache folder
|
|
||||||
string cachePath = Path.Combine(_appPath, customer.Id, env.Name, "cache");
|
|
||||||
if (!Directory.Exists(cachePath)) {
|
|
||||||
Directory.CreateDirectory(cachePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
//check acme directory
|
|
||||||
var acmePath = Path.Combine(_appPath, customer.Id, env.Name, "acme");
|
|
||||||
if (!Directory.Exists(acmePath)) {
|
|
||||||
Directory.CreateDirectory(acmePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
//loop each customer website
|
|
||||||
foreach (Site site in customer.Sites?.Where(s => s.Active) ?? new List<Site>()) {
|
|
||||||
_logger.LogInformation($"Managing site: {site.Name}");
|
|
||||||
|
|
||||||
|
|
||||||
//create folder for ssl
|
|
||||||
string sslPath = Path.Combine(_appPath, customer.Id, env.Name, "ssl", site.Name);
|
|
||||||
if (!Directory.Exists(sslPath)) {
|
|
||||||
Directory.CreateDirectory(sslPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
var cacheFile = Path.Combine(cachePath, $"{site.Name}.lets-encrypt.cache.json");
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region LetsEncrypt client configuration and local registration cache initialization
|
|
||||||
_logger.LogInformation("1. Client Initialization...");
|
|
||||||
|
|
||||||
await _letsEncryptService.ConfigureClient(env.Url);
|
|
||||||
|
|
||||||
var registrationCache = (File.Exists(cacheFile)
|
|
||||||
? File.ReadAllText(cacheFile)
|
|
||||||
: null)
|
|
||||||
.ToObject<RegistrationCache>();
|
|
||||||
|
|
||||||
var initResult = await _letsEncryptService.Init(customer.Contacts, registrationCache);
|
|
||||||
if (!initResult.IsSuccess) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region LetsEncrypt terms of service
|
|
||||||
_logger.LogInformation($"Terms of service: {_letsEncryptService.GetTermsOfServiceUri()}");
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
// get cached certificate and check if it's valid
|
|
||||||
// if valid check if cert and key exists otherwise recreate
|
|
||||||
// else continue with new certificate request
|
|
||||||
var certRes = new CachedCertificateResult();
|
|
||||||
if (registrationCache != null && registrationCache.TryGetCachedCertificate(site.Name, out certRes)) {
|
|
||||||
|
|
||||||
File.WriteAllText(Path.Combine(sslPath, $"{site.Name}.crt"), certRes.Certificate);
|
|
||||||
|
|
||||||
if (certRes.PrivateKey != null)
|
|
||||||
File.WriteAllText(Path.Combine(sslPath, $"{site.Name}.key"), certRes.PrivateKey.ExportRSAPrivateKeyPem());
|
|
||||||
|
|
||||||
_logger.LogInformation("Certificate and Key exists and valid. Restored from cache.");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
|
|
||||||
|
|
||||||
//create new orders
|
|
||||||
#region LetsEncrypt new order
|
|
||||||
_logger.LogInformation("2. Client New Order...");
|
|
||||||
|
|
||||||
var (orders, newOrderResult) = await _letsEncryptService.NewOrder(site.Hosts, site.Challenge);
|
|
||||||
if (!newOrderResult.IsSuccess || orders == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
if (orders.Count > 0) {
|
|
||||||
switch (site.Challenge) {
|
|
||||||
case "http-01": {
|
|
||||||
//ensure to enable static file discovery on server in .well-known/acme-challenge
|
|
||||||
//and listen on 80 port
|
|
||||||
|
|
||||||
foreach (FileInfo file in new DirectoryInfo(acmePath).GetFiles())
|
|
||||||
file.Delete();
|
|
||||||
|
|
||||||
foreach (var result in orders) {
|
|
||||||
Console.WriteLine($"Key: {result.Key}, Value: {result.Value}");
|
|
||||||
string[] splitToken = result.Value.Split('.');
|
|
||||||
|
|
||||||
File.WriteAllText(Path.Combine(acmePath, splitToken[0]), result.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (FileInfo file in new DirectoryInfo(acmePath).GetFiles()) {
|
|
||||||
if (env?.SSH?.Active ?? false) {
|
|
||||||
UploadFiles(_logger, env.SSH, env.ACME.Linux.Path, file.Name, File.ReadAllBytes(file.FullName), env.ACME.Linux.Owner, env.ACME.Linux.ChangeMode);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "dns-01": {
|
|
||||||
//Manage DNS server MX record, depends from provider
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
default: {
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#region LetsEncrypt complete challenges
|
|
||||||
_logger.LogInformation("3. Client Complete Challange...");
|
|
||||||
var completeChallengesResult = await _letsEncryptService.CompleteChallenges();
|
|
||||||
if (!completeChallengesResult.IsSuccess) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
_logger.LogInformation("Challanges comleted.");
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
await Task.Delay(1000);
|
|
||||||
|
|
||||||
#region Download new certificate
|
|
||||||
_logger.LogInformation("4. Download certificate...");
|
|
||||||
var (certData, getCertResult) = await _letsEncryptService.GetCertificate(site.Name);
|
|
||||||
if (!getCertResult.IsSuccess || certData == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// not used in this scenario
|
|
||||||
// var (cert, key) = certData.Value;
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Persist cache
|
|
||||||
registrationCache = _letsEncryptService.GetRegistrationCache();
|
|
||||||
File.WriteAllText(cacheFile, registrationCache.ToJson());
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Save cert and key to filesystem
|
|
||||||
certRes = new CachedCertificateResult();
|
|
||||||
if (registrationCache.TryGetCachedCertificate(site.Name, out certRes)) {
|
|
||||||
|
|
||||||
File.WriteAllText(Path.Combine(sslPath, $"{site.Name}.crt"), certRes.Certificate);
|
|
||||||
|
|
||||||
if (certRes.PrivateKey != null)
|
|
||||||
File.WriteAllText(Path.Combine(sslPath, $"{site.Name}.key"), certRes.PrivateKey.ExportRSAPrivateKeyPem());
|
|
||||||
|
|
||||||
_logger.LogInformation("Certificate saved.");
|
|
||||||
|
|
||||||
foreach (FileInfo file in new DirectoryInfo(sslPath).GetFiles()) {
|
|
||||||
|
|
||||||
if (env?.SSH?.Active ?? false) {
|
|
||||||
UploadFiles(_logger, env.SSH, $"{env.SSL.Linux.Path}/{site.Name}", file.Name, File.ReadAllBytes(file.FullName), env.SSL.Linux.Owner, env.SSL.Linux.ChangeMode);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
_logger.LogError("Unable to get new cached certificate.");
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInformation($"Let's Encrypt client. Execution complete.");
|
|
||||||
}
|
|
||||||
catch (Exception ex) {
|
|
||||||
_logger.LogError(ex, $"Let's Encrypt client. Unhandled exception.");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void UploadFiles(
|
|
||||||
ILogger logger,
|
|
||||||
SSHClientSettings sshSettings,
|
|
||||||
string workDir,
|
|
||||||
string fileName,
|
|
||||||
byte[] bytes,
|
|
||||||
string owner,
|
|
||||||
string changeMode
|
|
||||||
) {
|
|
||||||
using var sshService = new SSHService(logger, sshSettings.Host, sshSettings.Port, sshSettings.Username, sshSettings.Password);
|
|
||||||
sshService.Connect();
|
|
||||||
|
|
||||||
sshService.RunSudoCommand(sshSettings.Password, $"mkdir {workDir}");
|
|
||||||
|
|
||||||
sshService.RunSudoCommand(sshSettings.Password, $"chown {owner} {workDir} -R");
|
|
||||||
sshService.RunSudoCommand(sshSettings.Password, $"chmod 777 {workDir} -R");
|
|
||||||
|
|
||||||
sshService.Upload($"{workDir}", fileName, bytes);
|
|
||||||
|
|
||||||
sshService.RunSudoCommand(sshSettings.Password, $"chown {owner} {workDir} -R");
|
|
||||||
sshService.RunSudoCommand(sshSettings.Password, $"chmod {changeMode} {workDir} -R");
|
|
||||||
|
|
||||||
//sshService.RunSudoCommand(sshSettings.Password, $"systemctl restart nginx");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,71 +0,0 @@
|
|||||||
|
|
||||||
namespace MaksIT.LetsEncryptConsole;
|
|
||||||
|
|
||||||
public class Configuration {
|
|
||||||
public LetsEncryptEnvironment[]? Environments { get; set; }
|
|
||||||
public Customer[]? Customers { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class OsWindows {
|
|
||||||
public string? Path { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class OsLinux {
|
|
||||||
public string? Path { get; set; }
|
|
||||||
|
|
||||||
public string? Owner { get; set; }
|
|
||||||
|
|
||||||
public string? ChangeMode { get; set; }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public class OsDependant {
|
|
||||||
public OsWindows? Windows { get; set; }
|
|
||||||
public OsLinux? Linux { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SSHClientSettings {
|
|
||||||
public bool Active { get; set; }
|
|
||||||
|
|
||||||
public string? Host { get; set; }
|
|
||||||
|
|
||||||
public int Port { get; set; }
|
|
||||||
|
|
||||||
public string? Username { get; set; }
|
|
||||||
|
|
||||||
public string? Password { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public class LetsEncryptEnvironment {
|
|
||||||
public bool Active { get; set; }
|
|
||||||
public string? Name { get; set; }
|
|
||||||
public string? Url { get; set; }
|
|
||||||
|
|
||||||
public OsDependant? ACME { get; set; }
|
|
||||||
public OsDependant? SSL { get; set; }
|
|
||||||
|
|
||||||
public SSHClientSettings? SSH { 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 Site {
|
|
||||||
public bool Active { get; set; }
|
|
||||||
public string? Name { get; set; }
|
|
||||||
public string[]? Hosts { get; set; }
|
|
||||||
public string? Challenge { get; set; }
|
|
||||||
}
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<RootNamespace>MaksIT.$(MSBuildProjectName.Replace(" ", "_"))</RootNamespace>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="7.0.0" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="7.0.0" />
|
|
||||||
|
|
||||||
|
|
||||||
<PackageReference Include="Serilog.Enrichers.Span" Version="3.1.0" />
|
|
||||||
<PackageReference Include="Serilog.Expressions" Version="3.4.1" />
|
|
||||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="7.0.0" />
|
|
||||||
<PackageReference Include="Serilog.Extensions.Logging" Version="7.0.0" />
|
|
||||||
<PackageReference Include="Serilog.Formatting.Compact" Version="1.1.0" />
|
|
||||||
<PackageReference Include="Serilog.Settings.Configuration" Version="7.0.0" />
|
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
|
|
||||||
|
|
||||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\Core\Core.csproj" />
|
|
||||||
<ProjectReference Include="..\LetsEncrypt\LetsEncrypt.csproj" />
|
|
||||||
<ProjectReference Include="..\SSHProvider\SSHProvider.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Update="appsettings.json">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
|
||||||
@ -1,69 +0,0 @@
|
|||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
using MaksIT.LetsEncryptConsole.Services;
|
|
||||||
using MaksIT.LetsEncrypt.Extensions;
|
|
||||||
|
|
||||||
namespace MaksIT.LetsEncryptConsole;
|
|
||||||
|
|
||||||
class Program {
|
|
||||||
private static readonly IConfiguration _configuration = InitConfig();
|
|
||||||
|
|
||||||
static void Main(string[] args) {
|
|
||||||
// create service collection
|
|
||||||
var services = new ServiceCollection();
|
|
||||||
ConfigureServices(services);
|
|
||||||
|
|
||||||
// create service provider
|
|
||||||
var serviceProvider = services.BuildServiceProvider();
|
|
||||||
|
|
||||||
// entry to run app
|
|
||||||
#pragma warning disable CS8602 // Dereference of a possibly null reference.
|
|
||||||
var app = serviceProvider.GetService<App>();
|
|
||||||
app.Run(args).Wait();
|
|
||||||
#pragma warning restore CS8602 // Dereference of a possibly null reference.
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ConfigureServices(IServiceCollection services) {
|
|
||||||
|
|
||||||
var configurationSection = _configuration.GetSection("Configuration");
|
|
||||||
services.Configure<Configuration>(configurationSection);
|
|
||||||
var appSettings = configurationSection.Get<Configuration>();
|
|
||||||
|
|
||||||
#region Configure logging
|
|
||||||
services.AddLogging(configure => {
|
|
||||||
configure.AddSerilog(new LoggerConfiguration()
|
|
||||||
.ReadFrom.Configuration(_configuration)
|
|
||||||
.CreateLogger());
|
|
||||||
});
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Services
|
|
||||||
services.RegisterLetsEncrypt();
|
|
||||||
services.AddSingleton<ITerminalService, TerminalService>();
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
// add app
|
|
||||||
services.AddSingleton<App>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IConfiguration InitConfig() {
|
|
||||||
var aspNetCoreEnvironment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
|
|
||||||
|
|
||||||
var configuration = new ConfigurationBuilder()
|
|
||||||
.SetBasePath(Directory.GetCurrentDirectory())
|
|
||||||
.AddEnvironmentVariables();
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(aspNetCoreEnvironment)
|
|
||||||
&& new FileInfo(Path.Combine(Directory.GetCurrentDirectory(), $"appsettings.{aspNetCoreEnvironment}.json")).Exists
|
|
||||||
)
|
|
||||||
configuration.AddJsonFile($"appsettings.{aspNetCoreEnvironment}.json", true);
|
|
||||||
else
|
|
||||||
configuration.AddJsonFile($"appsettings.json", true, true);
|
|
||||||
|
|
||||||
return configuration.Build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,140 +0,0 @@
|
|||||||
#ACMEv2 Client library
|
|
||||||
|
|
||||||
https://tools.ietf.org/html/draft-ietf-acme-acme-18
|
|
||||||
|
|
||||||
The following diagram illustrates the relations between resources on
|
|
||||||
an ACME server. For the most part, these relations are expressed by
|
|
||||||
URLs provided as strings in the resources' JSON representations.
|
|
||||||
Lines with labels in quotes indicate HTTP link relations.
|
|
||||||
|
|
||||||
directory
|
|
||||||
|
|
|
||||||
+--> new-nonce
|
|
||||||
|
|
|
||||||
+----------+----------+-----+-----+------------+
|
|
||||||
| | | | |
|
|
||||||
| | | | |
|
|
||||||
V V V V V
|
|
||||||
newAccount newAuthz newOrder revokeCert keyChange
|
|
||||||
| | |
|
|
||||||
| | |
|
|
||||||
V | V
|
|
||||||
account | order -----> cert
|
|
||||||
| |
|
|
||||||
| |
|
|
||||||
| V
|
|
||||||
+------> authz
|
|
||||||
| ^
|
|
||||||
| | "up"
|
|
||||||
V |
|
|
||||||
challenge
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
+-------------------+--------------------------------+--------------+
|
|
||||||
| Action | Request | Response |
|
|
||||||
+-------------------+--------------------------------+--------------+
|
|
||||||
| Get directory | GET directory | 200 |
|
|
||||||
| | | |
|
|
||||||
| Get nonce | HEAD newNonce | 200 |
|
|
||||||
| | | |
|
|
||||||
| Create account | POST newAccount | 201 -> |
|
|
||||||
| | | account |
|
|
||||||
| | | |
|
|
||||||
| Submit order | POST newOrder | 201 -> order |
|
|
||||||
| | | |
|
|
||||||
| Fetch challenges | POST-as-GET order's | 200 |
|
|
||||||
| | authorization urls | |
|
|
||||||
| | | |
|
|
||||||
| Respond to | POST authorization challenge | 200 |
|
|
||||||
| challenges | urls | |
|
|
||||||
| | | |
|
|
||||||
| Poll for status | POST-as-GET order | 200 |
|
|
||||||
| | | |
|
|
||||||
| Finalize order | POST order's finalize url | 200 |
|
|
||||||
| | | |
|
|
||||||
| Poll for status | POST-as-GET order | 200 |
|
|
||||||
| | | |
|
|
||||||
| Download | POST-as-GET order's | 200 |
|
|
||||||
| certificate | certificate url | |
|
|
||||||
+-------------------+--------------------------------+--------------+
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pending
|
|
||||||
|
|
|
||||||
| Receive
|
|
||||||
| response
|
|
||||||
V
|
|
||||||
processing <-+
|
|
||||||
| | | Server retry or
|
|
||||||
| | | client retry request
|
|
||||||
| +----+
|
|
||||||
|
|
|
||||||
|
|
|
||||||
Successful | Failed
|
|
||||||
validation | validation
|
|
||||||
+---------+---------+
|
|
||||||
| |
|
|
||||||
V V
|
|
||||||
valid invalid
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
https://community.letsencrypt.org/t/acme-client-finalized-order-stuck-on-ready-state/165196
|
|
||||||
|
|
||||||
The following table illustrates a typical sequence of requests
|
|
||||||
required to establish a new account with the server, prove control of
|
|
||||||
an identifier, issue a certificate, and fetch an updated certificate
|
|
||||||
some time after issuance. The "->" is a mnemonic for a Location
|
|
||||||
header field pointing to a created resource.
|
|
||||||
|
|
||||||
+-------------------+--------------------------------+--------------+
|
|
||||||
| Action | Request | Response |
|
|
||||||
+-------------------+--------------------------------+--------------+
|
|
||||||
| Get directory | GET directory | 200 |
|
|
||||||
| | | |
|
|
||||||
| Get nonce | HEAD newNonce | 200 |
|
|
||||||
| | | |
|
|
||||||
| Create account | POST newAccount | 201 -> |
|
|
||||||
| | | account |
|
|
||||||
| | | |
|
|
||||||
| Submit order | POST newOrder | 201 -> order |
|
|
||||||
| | | |
|
|
||||||
| Fetch challenges | POST-as-GET order's | 200 |
|
|
||||||
| | authorization urls | |
|
|
||||||
| | | |
|
|
||||||
| Respond to | POST authorization challenge | 200 |
|
|
||||||
| challenges | urls | |
|
|
||||||
| | | |
|
|
||||||
| Poll for status | POST-as-GET order | 200 |
|
|
||||||
| | | |
|
|
||||||
| Finalize order | POST order's finalize url | 200 |
|
|
||||||
| | | |
|
|
||||||
| Poll for status | POST-as-GET order | 200 |
|
|
||||||
| | | |
|
|
||||||
| Download | POST-as-GET order's | 200 |
|
|
||||||
| certificate | certificate url | |
|
|
||||||
+-------------------+--------------------------------+--------------+
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|Level|Usage|
|
|
||||||
|-----|-----|
|
|
||||||
|Verbose|Verbose is the noisiest level, rarely (if ever) enabled for a production app.|
|
|
||||||
|Debug|Debug is used for internal system events that are not necessarily observable from the outside, but useful when determining how something happened.|
|
|
||||||
|Information|Information events describe things happening in the system that correspond to its responsibilities and functions. Generally these are the observable actions the system can perform.|
|
|
||||||
|Warning|When service is degraded, endangered, or may be behaving outside of its expected parameters, Warning level events are used.|
|
|
||||||
|Error|When functionality is unavailable or expectations broken, an Error event is used.|
|
|
||||||
|Fatal|The most critical level, Fatal events demand immediate attention.|
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
namespace MaksIT.LetsEncryptConsole.Services;
|
|
||||||
|
|
||||||
public interface ITerminalService {
|
|
||||||
void Exec(string cmd);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TerminalService : ITerminalService {
|
|
||||||
|
|
||||||
public void Exec(string cmd) {
|
|
||||||
var escapedArgs = cmd.Replace("\"", "\\\"");
|
|
||||||
|
|
||||||
var pc = new Process {
|
|
||||||
StartInfo = new ProcessStartInfo {
|
|
||||||
RedirectStandardOutput = true,
|
|
||||||
UseShellExecute = false,
|
|
||||||
CreateNoWindow = true,
|
|
||||||
WindowStyle = ProcessWindowStyle.Hidden,
|
|
||||||
FileName = "/bin/bash",
|
|
||||||
Arguments = $"-c \"{escapedArgs}\""
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pc.Start();
|
|
||||||
pc.WaitForExit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,150 +0,0 @@
|
|||||||
{
|
|
||||||
"Serilog": {
|
|
||||||
"Using": [ "Serilog.Settings.Configuration", "Serilog.Expressions", "Serilog.Sinks.Console" ],
|
|
||||||
"MinimumLevel": "Information",
|
|
||||||
"Enrich": [ "FromLogContext", "WithMachineName", "WithProcessId", "WithThreadId" ],
|
|
||||||
"WriteTo": [
|
|
||||||
{
|
|
||||||
"Name": "Console",
|
|
||||||
"Args": {
|
|
||||||
"restrictedToMinimumLevel": "Information",
|
|
||||||
//"formatter": "Serilog.Formatting.Compact.RenderedCompactJsonFormatter, Serilog.Formatting.Compact"
|
|
||||||
|
|
||||||
"theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console",
|
|
||||||
"outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} <s:{SourceContext}>{NewLine}{Exception}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Configuration": {
|
|
||||||
"Environments": [
|
|
||||||
{
|
|
||||||
"Active": false,
|
|
||||||
"Name": "StagingV2",
|
|
||||||
"Url": "https://acme-staging-v02.api.letsencrypt.org/directory",
|
|
||||||
|
|
||||||
"Cache": "staging_cache",
|
|
||||||
"ACME": {
|
|
||||||
"Linux": {
|
|
||||||
"Path": "/var/www/html/acme-challenge",
|
|
||||||
"Ower": "nginx:nginx",
|
|
||||||
"ChangeMode": "775"
|
|
||||||
},
|
|
||||||
"Windows": {
|
|
||||||
"Path": "C:\\inetpub\\www\\acme-challenge"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"SSL": {
|
|
||||||
"Linux": {
|
|
||||||
"Path": "/var/www/ssl/staging",
|
|
||||||
"Owner": "nginx:nginx",
|
|
||||||
"ChangeMode": "775"
|
|
||||||
},
|
|
||||||
"Windows": {
|
|
||||||
"Path": "C:\\inetpub\\www\\ssl\\staging"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"SSH": {
|
|
||||||
"Active": true,
|
|
||||||
"Host": "192.168.2.10",
|
|
||||||
"Port": 22,
|
|
||||||
"Username": "",
|
|
||||||
"Password": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Active": true,
|
|
||||||
"Name": "ProductionV2",
|
|
||||||
"Url": "https://acme-v02.api.letsencrypt.org/directory",
|
|
||||||
|
|
||||||
"Cache": "production_cache",
|
|
||||||
"ACME": {
|
|
||||||
"Linux": {
|
|
||||||
"Path": "/var/www/html/acme-challenge",
|
|
||||||
"Owner": "nginx:nginx",
|
|
||||||
"ChangeMode": "775"
|
|
||||||
},
|
|
||||||
"Windows": {
|
|
||||||
"Path": "C:\\inetpub\\www\\acme-challenge"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"SSL": {
|
|
||||||
"Linux": {
|
|
||||||
"Path": "/var/www/ssl",
|
|
||||||
"Owner": "nginx:nginx",
|
|
||||||
"ChangeMode": "775"
|
|
||||||
},
|
|
||||||
"Windows": {
|
|
||||||
"Path": "C:\\inetpub\\www\\ssl"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"SSH": {
|
|
||||||
"Active": true,
|
|
||||||
"Host": "192.168.2.10",
|
|
||||||
"Port": 22,
|
|
||||||
"Username": "",
|
|
||||||
"Password": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
"Customers": [
|
|
||||||
{
|
|
||||||
"Id": "9b4c8584-dc83-4388-b45f-2942e34dca9d",
|
|
||||||
"Active": true,
|
|
||||||
"Contacts": [ "maksym.sadovnychyy@gmail.com" ],
|
|
||||||
"Name": "Maksym",
|
|
||||||
"LastName": "Sadovnychyy",
|
|
||||||
|
|
||||||
"Sites": [
|
|
||||||
{
|
|
||||||
"Active": true,
|
|
||||||
"Name": "maks-it.com",
|
|
||||||
"Hosts": [
|
|
||||||
"maks-it.com",
|
|
||||||
"www.maks-it.com",
|
|
||||||
|
|
||||||
"git.maks-it.com",
|
|
||||||
"www.git.maks-it.com",
|
|
||||||
|
|
||||||
"hcr.maks-it.com",
|
|
||||||
"www.hcr.maks-it.com",
|
|
||||||
|
|
||||||
"vlt.maks-it.com",
|
|
||||||
"www.vlt.maks-it.com",
|
|
||||||
|
|
||||||
"obj.maks-it.com",
|
|
||||||
"www.obj.maks-it.com",
|
|
||||||
|
|
||||||
"s3.maks-it.com",
|
|
||||||
"www.s3.maks-it.com"
|
|
||||||
],
|
|
||||||
"Challenge": "http-01"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "46337ef5-d69b-4332-b6ef-67959dfb3c2c",
|
|
||||||
"Active": false,
|
|
||||||
"Contacts": [
|
|
||||||
"maksym.sadovnychyy@gmail.com",
|
|
||||||
"anastasiia.pavlovskaia@gmail.com"
|
|
||||||
],
|
|
||||||
"Name": "Anastasiia",
|
|
||||||
"LastName": "Pavlovskaia",
|
|
||||||
|
|
||||||
"Sites": [
|
|
||||||
{
|
|
||||||
"Active": true,
|
|
||||||
"Name": "nastyarey.com",
|
|
||||||
"Hosts": [
|
|
||||||
"nastyarey.com",
|
|
||||||
"www.nastyarey.com"
|
|
||||||
],
|
|
||||||
"Challenge": "http-01"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user