From c753c3185bb30ec2284de50bbe66714ff62865c5 Mon Sep 17 00:00:00 2001 From: "Simon J.K. Pedersen" Date: Fri, 19 Apr 2019 13:49:18 +0200 Subject: [PATCH] Updates to v2 --- LetsEncrypt.Azure.Core.V2/AcmeClient.cs | 17 ++-- LetsEncrypt.Azure.Core.V2/AzureHelper.cs | 7 +- .../AzureKeyVaultCertificateStore.cs | 51 +++++++++-- .../CertificateStores/FileSystemBase.cs | 23 ++++- .../CertificateStores/ICertificateStore.cs | 3 + .../CertificateStores/NullCertificateStore.cs | 28 ++++++ .../LetsencryptService.cs | 90 ++++++++++--------- .../LetsencryptServiceCollectionExtensions.cs | 80 +++++++++++++++++ LetsEncrypt.Azure.Core.V2/MessageHandler.cs | 72 +++++++++++++++ .../Models/AcmeDnsRequest.cs | 10 ++- .../Models/AzureServicePrincipal.cs | 1 + LetsEncrypt.Azure.Runner/App.cs | 63 ------------- LetsEncrypt.Azure.Runner/Program.cs | 4 +- 13 files changed, 315 insertions(+), 134 deletions(-) create mode 100644 LetsEncrypt.Azure.Core.V2/CertificateStores/NullCertificateStore.cs create mode 100644 LetsEncrypt.Azure.Core.V2/LetsencryptServiceCollectionExtensions.cs create mode 100644 LetsEncrypt.Azure.Core.V2/MessageHandler.cs delete mode 100644 LetsEncrypt.Azure.Runner/App.cs diff --git a/LetsEncrypt.Azure.Core.V2/AcmeClient.cs b/LetsEncrypt.Azure.Core.V2/AcmeClient.cs index c1b8e92..7c84c27 100644 --- a/LetsEncrypt.Azure.Core.V2/AcmeClient.cs +++ b/LetsEncrypt.Azure.Core.V2/AcmeClient.cs @@ -1,6 +1,7 @@ using Certes; using Certes.Acme; using Certes.Acme.Resource; +using LetsEncrypt.Azure.Core.V2.CertificateStores; using LetsEncrypt.Azure.Core.V2.DnsProviders; using LetsEncrypt.Azure.Core.V2.Models; using Microsoft.Extensions.Logging; @@ -18,15 +19,15 @@ public class AcmeClient { private readonly IDnsProvider dnsProvider; private readonly DnsLookupService dnsLookupService; - private readonly IFileSystem fileSystem; + private readonly ICertificateStore certificateStore; private readonly ILogger logger; - public AcmeClient(IDnsProvider dnsProvider, DnsLookupService dnsLookupService, IFileSystem fileSystem = null, ILogger logger = null) + public AcmeClient(IDnsProvider dnsProvider, DnsLookupService dnsLookupService, ICertificateStore certifcateStore, ILogger logger = null) { this.dnsProvider = dnsProvider; this.dnsLookupService = dnsLookupService; - this.fileSystem = fileSystem ?? new FileSystem(); + this.certificateStore = certifcateStore; this.logger = logger ?? NullLogger.Instance; } @@ -103,22 +104,22 @@ public async Task RequestDnsChallengeCertificate(IAcmeD private async Task GetOrCreateAcmeContext(Uri acmeDirectoryUri, string email) { AcmeContext acme = null; - string filename = $"account{email}--{acmeDirectoryUri.Host}.pem"; - if (! await fileSystem.Exists(filename)) + string filename = $"account{email}--{acmeDirectoryUri.Host}"; + var secret = await this.certificateStore.GetSecret(filename); + if (string.IsNullOrEmpty(secret)) { acme = new AcmeContext(acmeDirectoryUri); var account = acme.NewAccount(email, true); // Save the account key for later use var pemKey = acme.AccountKey.ToPem(); - await fileSystem.WriteAllText(filename, pemKey); + await certificateStore.SaveSecret(filename, pemKey); await Task.Delay(10000); //Wait a little before using the new account. acme = new AcmeContext(acmeDirectoryUri, acme.AccountKey, new AcmeHttpClient(acmeDirectoryUri, new HttpClient())); } else { - var pemKey = await fileSystem.ReadAllText(filename); - var accountKey = KeyFactory.FromPem(pemKey); + var accountKey = KeyFactory.FromPem(secret); acme = new AcmeContext(acmeDirectoryUri, accountKey, new AcmeHttpClient(acmeDirectoryUri, new HttpClient())); } diff --git a/LetsEncrypt.Azure.Core.V2/AzureHelper.cs b/LetsEncrypt.Azure.Core.V2/AzureHelper.cs index 2cf5992..67a13a8 100644 --- a/LetsEncrypt.Azure.Core.V2/AzureHelper.cs +++ b/LetsEncrypt.Azure.Core.V2/AzureHelper.cs @@ -1,8 +1,6 @@ using LetsEncrypt.Azure.Core.V2.Models; using Microsoft.Azure.Management.ResourceManager.Fluent.Authentication; using System; -using System.Collections.Generic; -using System.Text; namespace LetsEncrypt.Azure.Core.V2 { @@ -20,6 +18,11 @@ public static AzureCredentials GetAzureCredentials(AzureServicePrincipal service throw new ArgumentNullException(nameof(azureSubscription)); } + if (servicePrincipal.UseManagendIdentity) + { + return new AzureCredentials(new MSILoginInformation(MSIResourceType.AppService), Microsoft.Azure.Management.ResourceManager.Fluent.AzureEnvironment.FromName(azureSubscription.AzureRegion)); + } + return new AzureCredentials(servicePrincipal.ServicePrincipalLoginInformation, azureSubscription.Tenant, Microsoft.Azure.Management.ResourceManager.Fluent.AzureEnvironment.FromName(azureSubscription.AzureRegion)); } diff --git a/LetsEncrypt.Azure.Core.V2/CertificateStores/AzureKeyVaultCertificateStore.cs b/LetsEncrypt.Azure.Core.V2/CertificateStores/AzureKeyVaultCertificateStore.cs index ca56cbb..a80948e 100644 --- a/LetsEncrypt.Azure.Core.V2/CertificateStores/AzureKeyVaultCertificateStore.cs +++ b/LetsEncrypt.Azure.Core.V2/CertificateStores/AzureKeyVaultCertificateStore.cs @@ -1,11 +1,10 @@ using System; -using System.Collections.Generic; using System.Security.Cryptography.X509Certificates; -using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; using LetsEncrypt.Azure.Core.V2.Models; using Microsoft.Azure.KeyVault; -using Microsoft.Azure.Services.AppAuthentication; +using Microsoft.Azure.KeyVault.Models; namespace LetsEncrypt.Azure.Core.V2.CertificateStores { @@ -34,12 +33,17 @@ public AzureKeyVaultCertificateStore(IKeyVaultClient keyVaultClient, string vaul public async Task GetCertificate(string name, string password) { - // This retrieves the secret/certificate with the private key - var secret = await this.keyVaultClient.GetSecretAsync(this.vaultBaseUrl, name); - X509Certificate2 certificate = new X509Certificate2(Convert.FromBase64String(secret.Value), password); + var secretName = CleanName(name); + var secret = await GetSecret(name); + if (secret == null) + { + return null; + } + + X509Certificate2 certificate = new X509Certificate2(Convert.FromBase64String(secret), password); // This retrieves the secret/certificate without the private key - var certBundle = await this.keyVaultClient.GetCertificateAsync(this.vaultBaseUrl, name); + var certBundle = await this.keyVaultClient.GetCertificateAsync(this.vaultBaseUrl, secretName); var cert = new X509Certificate2(certBundle.Cer, password); return new CertificateInfo() @@ -56,7 +60,38 @@ public async Task GetCertificate(string name, string password) /// An asynchronous result. public Task SaveCertificate(CertificateInfo certificate) { - return this.keyVaultClient.ImportCertificateAsync(this.vaultBaseUrl, certificate.Name, certificate.PfxCertificate.ToString(), certificate.Password); + return this.keyVaultClient.ImportCertificateAsync(this.vaultBaseUrl, CleanName(certificate.Name), certificate.PfxCertificate.ToString(), certificate.Password); + } + + private string CleanName(string name) + { + Regex regex = new Regex("[^a-zA-Z0-9-]"); + return regex.Replace(name, ""); + } + + public async Task GetSecret(string name) + { + var secretName = CleanName(name); + // This retrieves the secret/certificate with the private key + SecretBundle secret = null; + try + { + secret = await this.keyVaultClient.GetSecretAsync(this.vaultBaseUrl, secretName); + } + catch (KeyVaultErrorException kvex) + { + if (kvex.Body.Error.Code == "SecretNotFound") + { + return null; + } + throw; + } + return secret.Value; + } + + public Task SaveSecret(string name, string secret) + { + return this.keyVaultClient.SetSecretAsync(this.vaultBaseUrl, CleanName(name), secret); } } } diff --git a/LetsEncrypt.Azure.Core.V2/CertificateStores/FileSystemBase.cs b/LetsEncrypt.Azure.Core.V2/CertificateStores/FileSystemBase.cs index b16a8e7..fd4cf3d 100644 --- a/LetsEncrypt.Azure.Core.V2/CertificateStores/FileSystemBase.cs +++ b/LetsEncrypt.Azure.Core.V2/CertificateStores/FileSystemBase.cs @@ -10,7 +10,7 @@ namespace LetsEncrypt.Azure.Core.V2.CertificateStores public abstract class FileSystemBase : ICertificateStore { private readonly IFileSystem fileSystem; - + private const string fileExtension = ".pfx"; public FileSystemBase(IFileSystem fileSystem) { @@ -20,9 +20,10 @@ public FileSystemBase(IFileSystem fileSystem) public async Task GetCertificate(string name, string password) { - if (! await this.fileSystem.Exists(name)) + var filename = name + fileExtension; + if (! await this.fileSystem.Exists(filename)) return null; - var pfx = await this.fileSystem.Read(name); + var pfx = await this.fileSystem.Read(filename); return new CertificateInfo() { PfxCertificate = pfx, @@ -34,7 +35,21 @@ public async Task GetCertificate(string name, string password) public Task SaveCertificate(CertificateInfo certificate) { - this.fileSystem.Write(certificate.Name, certificate.PfxCertificate); + this.fileSystem.Write(certificate.Name+fileExtension, certificate.PfxCertificate); + return Task.CompletedTask; + } + + public async Task GetSecret(string name) + { + var filename = name + fileExtension; + if (!await this.fileSystem.Exists(filename)) + return null; + return System.Text.Encoding.UTF8.GetString(await this.fileSystem.Read(filename)); + } + + public Task SaveSecret(string name, string secret) + { + this.fileSystem.Write(name + fileExtension, Encoding.UTF8.GetBytes(secret)); return Task.CompletedTask; } } diff --git a/LetsEncrypt.Azure.Core.V2/CertificateStores/ICertificateStore.cs b/LetsEncrypt.Azure.Core.V2/CertificateStores/ICertificateStore.cs index 06f9226..400c623 100644 --- a/LetsEncrypt.Azure.Core.V2/CertificateStores/ICertificateStore.cs +++ b/LetsEncrypt.Azure.Core.V2/CertificateStores/ICertificateStore.cs @@ -8,6 +8,9 @@ namespace LetsEncrypt.Azure.Core.V2.CertificateStores { public interface ICertificateStore { + Task GetSecret(string name); + Task SaveSecret(string name, string secret); + Task GetCertificate(string name, string password); Task SaveCertificate(CertificateInfo certificate); } diff --git a/LetsEncrypt.Azure.Core.V2/CertificateStores/NullCertificateStore.cs b/LetsEncrypt.Azure.Core.V2/CertificateStores/NullCertificateStore.cs new file mode 100644 index 0000000..faf86ec --- /dev/null +++ b/LetsEncrypt.Azure.Core.V2/CertificateStores/NullCertificateStore.cs @@ -0,0 +1,28 @@ +using System.Threading.Tasks; +using LetsEncrypt.Azure.Core.V2.Models; + +namespace LetsEncrypt.Azure.Core.V2.CertificateStores +{ + internal class NullCertificateStore : ICertificateStore + { + public Task GetCertificate(string name, string password) + { + return Task.FromResult(null); + } + + public Task GetSecret(string name) + { + return Task.FromResult(null); + } + + public Task SaveCertificate(CertificateInfo certificate) + { + return Task.CompletedTask; + } + + public Task SaveSecret(string name, string secret) + { + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/LetsEncrypt.Azure.Core.V2/LetsencryptService.cs b/LetsEncrypt.Azure.Core.V2/LetsencryptService.cs index 6fc28a2..630a62e 100644 --- a/LetsEncrypt.Azure.Core.V2/LetsencryptService.cs +++ b/LetsEncrypt.Azure.Core.V2/LetsencryptService.cs @@ -1,60 +1,62 @@ using LetsEncrypt.Azure.Core.V2.CertificateStores; -using LetsEncrypt.Azure.Core.V2.DnsProviders; using LetsEncrypt.Azure.Core.V2.Models; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using System; +using System.Threading.Tasks; namespace LetsEncrypt.Azure.Core.V2 { - public static class LetsencryptService + public class LetsencryptService { - public static IServiceCollection AddAcmeClient(this IServiceCollection serviceCollection, object dnsProviderConfig, string azureStorageConnectionString = null) where TDnsProvider : class, IDnsProvider - { - if (serviceCollection == null) - { - throw new ArgumentNullException(nameof(serviceCollection)); - } + private readonly AcmeClient acmeClient; + private readonly ICertificateStore certificateStore; + private readonly AzureWebAppService azureWebAppService; + private readonly ILogger logger; - if (dnsProviderConfig == null) - { - throw new ArgumentNullException(nameof(dnsProviderConfig)); - } - if (string.IsNullOrEmpty(azureStorageConnectionString)) - { - serviceCollection - .AddTransient() - .AddTransient(); - } - else + public LetsencryptService(AcmeClient acmeClient, ICertificateStore certificateStore, AzureWebAppService azureWebAppService, ILogger logger = null) + { + this.acmeClient = acmeClient; + this.certificateStore = certificateStore; + this.azureWebAppService = azureWebAppService; + this.logger = logger ?? NullLogger.Instance; + } + public async Task Run(AcmeDnsRequest acmeDnsRequest, int renewXNumberOfDaysBeforeExpiration) + { + try { - serviceCollection - .AddTransient(s => + CertificateInstallModel model = null; + + var certname = acmeDnsRequest.Host.Substring(2) + "-" + acmeDnsRequest.AcmeEnvironment.Name; + var cert = await certificateStore.GetCertificate(certname, acmeDnsRequest.PFXPassword); + if (cert == null || cert.Certificate.NotAfter < DateTime.UtcNow.AddDays(renewXNumberOfDaysBeforeExpiration)) //Cert doesnt exist or expires in less than renewXNumberOfDaysBeforeExpiration days, lets renew. + { + logger.LogInformation("Certificate store didn't contain certificate or certificate was expired starting renewing"); + model = await acmeClient.RequestDnsChallengeCertificate(acmeDnsRequest); + model.CertificateInfo.Name = certname; + await certificateStore.SaveCertificate(model.CertificateInfo); + } + else + { + logger.LogInformation("Certificate expires in more than {renewXNumberOfDaysBeforeExpiration} days, reusing certificate from certificate store", renewXNumberOfDaysBeforeExpiration); + model = new CertificateInstallModel() { - return new AzureBlobStorage(azureStorageConnectionString); - }) - .AddTransient(s => - { - return new AzureBlobStorage(azureStorageConnectionString); - }) - .AddTransient(); - } - return serviceCollection - .AddTransient() - .AddTransient() - .AddSingleton(dnsProviderConfig.GetType(), dnsProviderConfig) - .AddTransient(); - } + CertificateInfo = cert, + Host = acmeDnsRequest.Host + }; + } + await azureWebAppService.Install(model); - public static IServiceCollection AddAzureAppService(this IServiceCollection serviceCollection, params AzureWebAppSettings[] settings) - { - if (settings == null || settings.Length == 0) + logger.LogInformation("Removing expired certificates"); + var expired = azureWebAppService.RemoveExpired(); + logger.LogInformation("The following certificates was removed {Thumbprints}", string.Join(", ", expired.ToArray())); + + } + catch (Exception e) { - throw new ArgumentNullException(nameof(settings)); + logger.LogError(e, "Failed"); + throw; } - - return serviceCollection - .AddSingleton(settings) - .AddTransient(); } } } diff --git a/LetsEncrypt.Azure.Core.V2/LetsencryptServiceCollectionExtensions.cs b/LetsEncrypt.Azure.Core.V2/LetsencryptServiceCollectionExtensions.cs new file mode 100644 index 0000000..5751f54 --- /dev/null +++ b/LetsEncrypt.Azure.Core.V2/LetsencryptServiceCollectionExtensions.cs @@ -0,0 +1,80 @@ +using LetsEncrypt.Azure.Core.V2.CertificateStores; +using LetsEncrypt.Azure.Core.V2.DnsProviders; +using LetsEncrypt.Azure.Core.V2.Models; +using Microsoft.Azure.KeyVault; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Linq; + +namespace LetsEncrypt.Azure.Core.V2 +{ + public static class LetsencryptServiceCollectionExtensions + { + public static IServiceCollection AddAzureBlobStorageCertificateStore(this IServiceCollection serviceCollection, string azureStorageConnectionString) + { + return serviceCollection + .AddTransient(s => + { + return new AzureBlobStorage(azureStorageConnectionString); + }) + .AddTransient(s => + { + return new AzureBlobStorage(azureStorageConnectionString); + }) + .AddTransient(); + } + + public static IServiceCollection AddKeyVaultCertificateStore(this IServiceCollection serviceCollection, string vaultBaseUrl) + { + return serviceCollection + .AddTransient((serviceProvider) => + { + return new AzureKeyVaultCertificateStore(serviceProvider.GetService(), vaultBaseUrl); + }); + } + + public static IServiceCollection AddFileSystemCertificateStore(this IServiceCollection serviceCollection) + { + return serviceCollection + .AddTransient() + .AddTransient(); + } + + public static IServiceCollection AddAcmeClient(this IServiceCollection serviceCollection, object dnsProviderConfig) where TDnsProvider : class, IDnsProvider + { + if (serviceCollection == null) + { + throw new ArgumentNullException(nameof(serviceCollection)); + } + + if (dnsProviderConfig == null) + { + throw new ArgumentNullException(nameof(dnsProviderConfig)); + } + + if (!serviceCollection.Any(s => s.ServiceType == typeof(ICertificateStore))) + { + serviceCollection.AddTransient(); + } + + return serviceCollection + .AddTransient() + .AddTransient() + .AddSingleton(dnsProviderConfig.GetType(), dnsProviderConfig) + .AddTransient(); + } + + public static IServiceCollection AddAzureAppService(this IServiceCollection serviceCollection, params AzureWebAppSettings[] settings) + { + if (settings == null || settings.Length == 0) + { + throw new ArgumentNullException(nameof(settings)); + } + + return serviceCollection + .AddSingleton(settings) + .AddTransient() + .AddTransient(); + } + } +} diff --git a/LetsEncrypt.Azure.Core.V2/MessageHandler.cs b/LetsEncrypt.Azure.Core.V2/MessageHandler.cs new file mode 100644 index 0000000..d709e63 --- /dev/null +++ b/LetsEncrypt.Azure.Core.V2/MessageHandler.cs @@ -0,0 +1,72 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace LetsEncrypt.Azure.Core.V2 +{ + public abstract class MessageHandler : DelegatingHandler + { + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + var corrId = string.Format("{0}{1}", DateTime.Now.Ticks, Thread.CurrentThread.ManagedThreadId); + var requestInfo = string.Format("{0} {1}, headers {2}", request.Method, request.RequestUri, string.Join(",", request.Headers + .Where(s => !string.Equals(s.Key, "Authorization", StringComparison.InvariantCultureIgnoreCase)) + .Select(s => $"{s.Key} = {string.Join("|", s.Value)}") + )); + + byte[] requestMessage = null; + if (request.Content != null) + { + requestMessage = await request.Content.ReadAsByteArrayAsync(); + } + + await IncommingMessageAsync(corrId, requestInfo, requestMessage); + + var response = await base.SendAsync(request, cancellationToken); + + byte[] responseMessage = null; + if (response.Content != null) + { + responseMessage = await response.Content.ReadAsByteArrayAsync(); + } + + await OutgoingMessageAsync(corrId, requestInfo, responseMessage); + + return response; + } + + + protected abstract Task IncommingMessageAsync(string correlationId, string requestInfo, byte[] message); + protected abstract Task OutgoingMessageAsync(string correlationId, string requestInfo, byte[] message); + } + + + + public class MessageLoggingHandler : MessageHandler + { + private readonly ILogger logger; + + public MessageLoggingHandler(ILogger logger) + { + this.logger = logger; + } + protected override async Task IncommingMessageAsync(string correlationId, string requestInfo, byte[] message) + { + await Task.Run(() => + logger.LogInformation(string.Format("{0} - Request: {1}\r\n{2}", correlationId, requestInfo, message != null ? Encoding.UTF8.GetString(message) : String.Empty))); + } + + + protected override async Task OutgoingMessageAsync(string correlationId, string requestInfo, byte[] message) + { + await Task.Run(() => + logger.LogInformation(string.Format("{0} - Response: {1}\r\n{2}", correlationId, requestInfo, message != null ? Encoding.UTF8.GetString(message) : String.Empty))); + } + } +} diff --git a/LetsEncrypt.Azure.Core.V2/Models/AcmeDnsRequest.cs b/LetsEncrypt.Azure.Core.V2/Models/AcmeDnsRequest.cs index e5b5018..60958c3 100644 --- a/LetsEncrypt.Azure.Core.V2/Models/AcmeDnsRequest.cs +++ b/LetsEncrypt.Azure.Core.V2/Models/AcmeDnsRequest.cs @@ -71,7 +71,7 @@ public AcmeEnvironment(Uri uri) { this.BaseUri = uri; } - private string name; + protected string name; public string Name { get @@ -97,12 +97,16 @@ public string Name public class LetsEncryptStagingV2 : AcmeEnvironment { public LetsEncryptStagingV2() : base(WellKnownServers.LetsEncryptStagingV2) - { } + { + this.name = "staging"; + } } public class LetsEncryptV2 : AcmeEnvironment { public LetsEncryptV2() : base(WellKnownServers.LetsEncryptV2) - { } + { + this.name = "production"; + } } } diff --git a/LetsEncrypt.Azure.Core.V2/Models/AzureServicePrincipal.cs b/LetsEncrypt.Azure.Core.V2/Models/AzureServicePrincipal.cs index 5f20d44..f38f7fb 100644 --- a/LetsEncrypt.Azure.Core.V2/Models/AzureServicePrincipal.cs +++ b/LetsEncrypt.Azure.Core.V2/Models/AzureServicePrincipal.cs @@ -7,6 +7,7 @@ namespace LetsEncrypt.Azure.Core.V2.Models { public class AzureServicePrincipal { + public bool UseManagendIdentity { get; set; } public string ClientId { get; set; } public string ClientSecret { get; set; } public byte[] Certificate { get; set; } diff --git a/LetsEncrypt.Azure.Runner/App.cs b/LetsEncrypt.Azure.Runner/App.cs deleted file mode 100644 index 6d0196e..0000000 --- a/LetsEncrypt.Azure.Runner/App.cs +++ /dev/null @@ -1,63 +0,0 @@ -using LetsEncrypt.Azure.Core.V2; -using LetsEncrypt.Azure.Core.V2.CertificateStores; -using LetsEncrypt.Azure.Core.V2.Models; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using System; -using System.Threading.Tasks; - -namespace LetsEncrypt.Azure.Runner -{ - public class App - { - private readonly AcmeClient acmeClient; - private readonly ICertificateStore certificateStore; - private readonly AzureWebAppService azureWebAppService; - private readonly ILogger logger; - - public App(AcmeClient acmeClient, ICertificateStore certificateStore, AzureWebAppService azureWebAppService, ILogger logger = null) - { - this.acmeClient = acmeClient; - this.certificateStore = certificateStore; - this.azureWebAppService = azureWebAppService; - this.logger = logger ?? NullLogger.Instance; - } - public async Task Run(AcmeDnsRequest acmeDnsRequest, int renewXNumberOfDaysBeforeExpiration) - { - try - { - CertificateInstallModel model = null; - - var certname = acmeDnsRequest.Host.Substring(2) + "-" + acmeDnsRequest.AcmeEnvironment.Name + ".pfx"; - var cert = await certificateStore.GetCertificate(certname, acmeDnsRequest.PFXPassword); - if (cert == null || cert.Certificate.NotAfter < DateTime.UtcNow.AddDays(renewXNumberOfDaysBeforeExpiration)) //Cert doesnt exist or expires in less than 21 days, lets renew. - { - logger.LogInformation("Certificate store didn't contain certificate or certificate was expired starting renewing"); - model = await acmeClient.RequestDnsChallengeCertificate(acmeDnsRequest); - model.CertificateInfo.Name = certname; - await certificateStore.SaveCertificate(model.CertificateInfo); - } - else - { - logger.LogInformation("Certificate expires in more than {renewXNumberOfDaysBeforeExpiration} days, reusing certificate from certificate store", renewXNumberOfDaysBeforeExpiration); - model = new CertificateInstallModel() - { - CertificateInfo = cert, - Host = acmeDnsRequest.Host - }; - } - await azureWebAppService.Install(model); - - logger.LogInformation("Removing expired certificates"); - var expired = azureWebAppService.RemoveExpired(); - logger.LogInformation("The following certificates was removed {Thumbprints}", string.Join(", ", expired.ToArray())); - - } - catch (Exception e) - { - logger.LogError(e, "Failed"); - throw; - } - } - } -} diff --git a/LetsEncrypt.Azure.Runner/Program.cs b/LetsEncrypt.Azure.Runner/Program.cs index 16baff4..4c9f884 100644 --- a/LetsEncrypt.Azure.Runner/Program.cs +++ b/LetsEncrypt.Azure.Runner/Program.cs @@ -58,12 +58,12 @@ async static Task Main(string[] args) serviceCollection.AddAcmeClient(Configuration.GetSection("DnsSettings").Get(), azureStorageConnectionString); } - serviceCollection.AddTransient(); + serviceCollection.AddTransient(); var serviceProvider = serviceCollection.BuildServiceProvider(); var dnsRequest = Configuration.GetSection("AcmeDnsRequest").Get(); - var app = serviceProvider.GetService(); + var app = serviceProvider.GetService(); await app.Run(dnsRequest, Configuration.GetValue("RenewXNumberOfDaysBeforeExpiration") ?? 22); } }