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