diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..1ff0c42
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,63 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln merge=binary
+#*.csproj merge=binary
+#*.vbproj merge=binary
+#*.vcxproj merge=binary
+#*.vcproj merge=binary
+#*.dbproj merge=binary
+#*.fsproj merge=binary
+#*.lsproj merge=binary
+#*.wixproj merge=binary
+#*.modelproj merge=binary
+#*.sqlproj merge=binary
+#*.wwaproj merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg binary
+#*.png binary
+#*.gif binary
+
+###############################################################################
+# diff behavior for common document formats
+#
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the
+# entries below.
+###############################################################################
+#*.doc diff=astextplain
+#*.DOC diff=astextplain
+#*.docx diff=astextplain
+#*.DOCX diff=astextplain
+#*.dot diff=astextplain
+#*.DOT diff=astextplain
+#*.pdf diff=astextplain
+#*.PDF diff=astextplain
+#*.rtf diff=astextplain
+#*.RTF diff=astextplain
diff --git a/.gitignore b/.gitignore
index 3f39de8..4ce6fdd 100644
Binary files a/.gitignore and b/.gitignore differ
diff --git a/Dockerfile b/Dockerfile
index f106dcd..e5e7b34 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM microsoft/dotnet:2.1-sdk-alpine AS build
+FROM microsoft/dotnet:2.2-sdk-alpine AS build
# Set the working directory witin the container
WORKDIR /src
@@ -17,8 +17,8 @@ RUN dotnet publish -c release ./src/LetsEncrypt.Azure.Runner/LetsEncrypt.Azure.R
# Build runtime image
-FROM microsoft/dotnet:2.1-aspnetcore-runtime-alpine AS app
+FROM microsoft/dotnet:2.2-aspnetcore-runtime-alpine AS app
WORKDIR /app
-COPY --from=build /src/src/LetsEncrypt.Azure.Runner/bin/release/netcoreapp2.1/publish .
+COPY --from=build /src/src/LetsEncrypt.Azure.Runner/bin/release/netcoreapp2.2/publish .
ENTRYPOINT ["dotnet", "LetsEncrypt.Azure.Runner.dll"]
\ No newline at end of file
diff --git a/examples/LetsEncrypt.Azure.FunctionV2/Helper.cs b/examples/LetsEncrypt.Azure.FunctionV2/Helper.cs
index 37efe73..f816e18 100644
--- a/examples/LetsEncrypt.Azure.FunctionV2/Helper.cs
+++ b/examples/LetsEncrypt.Azure.FunctionV2/Helper.cs
@@ -7,6 +7,8 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
+using System.ComponentModel.DataAnnotations;
+using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace LetsEncrypt.Azure.FunctionV2
@@ -14,11 +16,11 @@ namespace LetsEncrypt.Azure.FunctionV2
public class Helper
{
///
- /// Requests a Let's Encrypt wild card certificate using DNS challenge.
+ /// Requests a Let's Encrypt wild card certificate using DNS challenge.
/// The DNS provider used is Azure DNS.
/// The certificate is saved to Azure Key Vault.
- /// The Certificate is finally install to an Azure App Service.
- /// Configuration values are stored in Environment Variables.
+ /// The Certificate is finally install to an Azure App Service.
+ /// Configuration values are stored in Environment Variables.
///
///
///
@@ -26,7 +28,7 @@ public static async Task InstallOrRenewCertificate(ILogger log)
{
var vaultBaseUrl = $"https://{Environment.GetEnvironmentVariable("Vault")}.vault.azure.net/";
log.LogInformation("C# HTTP trigger function processed a request.");
- var Configuration = new ConfigurationBuilder()
+ var configuration = new ConfigurationBuilder()
.AddAzureKeyVault(vaultBaseUrl) //Use MSI to get token
.AddEnvironmentVariables()
.Build();
@@ -34,33 +36,43 @@ public static async Task InstallOrRenewCertificate(ILogger log)
//Create the Key Vault client
var kvClient = new KeyVaultClient((authority, resource, scope) => tokenProvider.KeyVaultTokenCallback(authority, resource, scope), new MessageLoggingHandler(log));
+ ValidationContext validationContext;
IServiceCollection serviceCollection = new ServiceCollection();
-
- serviceCollection.AddSingleton(log)
- .Configure(options => options.MinLevel = LogLevel.Information);
- var certificateConsumer = Configuration.GetValue("CertificateConsumer");
+ serviceCollection
+ .AddSingleton(log)
+ .Configure(options => options.MinLevel = LogLevel.Information);
+ var certificateConsumer = configuration.GetValue("CertificateConsumer");
if (string.IsNullOrEmpty(certificateConsumer))
{
- serviceCollection.AddAzureAppService(Configuration.GetSection("AzureAppService").Get());
+ var webAppSettings = configuration.GetSection("AzureAppService").Get();
+ validationContext = new ValidationContext(webAppSettings);
+ Validator.ValidateObject(webAppSettings, validationContext);
+ serviceCollection.AddAzureAppService(webAppSettings);
}
else if (certificateConsumer.Equals("NullCertificateConsumer"))
{
serviceCollection.AddNullCertificateConsumer();
}
- serviceCollection.AddSingleton(kvClient)
- .AddKeyVaultCertificateStore(vaultBaseUrl);
-
+ serviceCollection
+ .AddSingleton(kvClient)
+ .AddKeyVaultCertificateStore(vaultBaseUrl);
- serviceCollection.AddAcmeClient(Configuration.GetSection("DnsSettings").Get());
+ var dnsProviderConfig = configuration.GetSection("DnsSettings").Get();
+ validationContext = new ValidationContext(dnsProviderConfig);
+ Validator.ValidateObject(dnsProviderConfig, validationContext);
+ serviceCollection
+ .AddAcmeClient(dnsProviderConfig);
var serviceProvider = serviceCollection.BuildServiceProvider();
var app = serviceProvider.GetService();
- var dnsRequest = Configuration.GetSection("AcmeDnsRequest").Get();
+ var dnsRequest = configuration.GetSection("AcmeDnsRequest").Get();
+ validationContext = new ValidationContext(dnsRequest);
+ Validator.ValidateObject(dnsRequest, validationContext);
- await app.Run(dnsRequest, Configuration.GetValue("RenewXNumberOfDaysBeforeExpiration") ?? 22);
+ await app.Run(dnsRequest, configuration.GetValue("RenewXNumberOfDaysBeforeExpiration") ?? 22);
}
}
}
diff --git a/examples/LetsEncrypt.Azure.FunctionV2/LetsEncrypt.Azure.FunctionV2.csproj b/examples/LetsEncrypt.Azure.FunctionV2/LetsEncrypt.Azure.FunctionV2.csproj
index 7377a86..687ee97 100644
--- a/examples/LetsEncrypt.Azure.FunctionV2/LetsEncrypt.Azure.FunctionV2.csproj
+++ b/examples/LetsEncrypt.Azure.FunctionV2/LetsEncrypt.Azure.FunctionV2.csproj
@@ -1,11 +1,12 @@
- netcoreapp2.1
+ netcoreapp2.2
v2
+
-
+
diff --git a/examples/LetsEncrypt.Azure.FunctionV2/RequestWildcardCertificate.cs b/examples/LetsEncrypt.Azure.FunctionV2/RequestWildcardCertificate.cs
index ab1c2b3..d00ae14 100644
--- a/examples/LetsEncrypt.Azure.FunctionV2/RequestWildcardCertificate.cs
+++ b/examples/LetsEncrypt.Azure.FunctionV2/RequestWildcardCertificate.cs
@@ -30,11 +30,11 @@ public static async Task Run(
await Helper.InstallOrRenewCertificate(log);
return new OkResult();
- } catch(Exception ex)
+ }
+ catch (Exception ex)
{
log.LogError(ex.ToString());
return new ExceptionResult(ex, true);
-
}
}
}
diff --git a/src/LetsEncrypt.Azure.Core.Test/AcmeClientTest.cs b/src/LetsEncrypt.Azure.Core.Test/AcmeClientTest.cs
index 1792606..c70cb3a 100644
--- a/src/LetsEncrypt.Azure.Core.Test/AcmeClientTest.cs
+++ b/src/LetsEncrypt.Azure.Core.Test/AcmeClientTest.cs
@@ -20,25 +20,20 @@ public class AcmeClientTest
{
private readonly ILogger logger;
- public AcmeClientTest()
- {
+ public AcmeClientTest() { }
- }
+ public AcmeClientTest(ILogger logger) => this.logger = logger;
- public AcmeClientTest(ILogger logger)
- {
- this.logger = logger;
- }
[TestMethod]
public async Task TestEndToEndAzure()
{
- var config = TestHelper.AzureDnsSettings;
+ var settings = TestHelper.AzureDnsSettings;
- var manager = new AcmeClient(new AzureDnsProvider(config), new DnsLookupService(), null, this.logger);
+ var manager = new AcmeClient(new AzureDnsProvider(settings), new DnsLookupService(), new NullCertificateStore(), this.logger);
- var dnsRequest = new AcmeDnsRequest()
+ IAcmeDnsRequest dnsRequest = new AcmeDnsRequest()
{
- Host = "*.ai4bots.com",
+ Hosts = "*.ai4bots.com",
PFXPassword = "Pass@word",
RegistrationEmail = "mail@sjkp.dk",
AcmeEnvironment = new LetsEncryptStagingV2(),
@@ -56,32 +51,32 @@ public async Task TestEndToEndAzure()
Assert.IsNotNull(res);
- File.WriteAllBytes($"{dnsRequest.Host.Substring(2)}.pfx", res.CertificateInfo.PfxCertificate);
-
- var pass = new System.Security.SecureString();
- Array.ForEach(dnsRequest.PFXPassword.ToCharArray(), c =>
+ string hostsPlusSeparated = AcmeClient.GetHostsPlusSeparated(dnsRequest.Hosts);
+ File.WriteAllBytes($"{hostsPlusSeparated}.pfx", res.CertificateInfo.PfxCertificate);
+ using (var pass = new System.Security.SecureString())
{
- pass.AppendChar(c);
- });
- File.WriteAllBytes($"exported-{dnsRequest.Host.Substring(2)}.pfx", res.CertificateInfo.Certificate.Export(System.Security.Cryptography.X509Certificates.X509ContentType.Pkcs12, pass));
+ Array.ForEach(dnsRequest.PFXPassword.ToCharArray(), c =>
+ {
+ pass.AppendChar(c);
+ });
+ File.WriteAllBytes($"exported-{hostsPlusSeparated}.pfx", res.CertificateInfo.Certificate.Export(System.Security.Cryptography.X509Certificates.X509ContentType.Pkcs12, pass));
+ var certService = new AzureWebAppService(new[] { TestHelper.AzureWebAppSettings });
- var certService = new AzureWebAppService(new[] { TestHelper.AzureWebAppSettings });
-
- await certService.Install(res);
+ await certService.Install(res);
+ }
}
[TestMethod]
public async Task TestEndToEndUnoEuro()
{
-
var dnsProvider = TestHelper.UnoEuroDnsProvider;
var manager = new AcmeClient(dnsProvider, new DnsLookupService(), new NullCertificateStore());
var dnsRequest = new AcmeDnsRequest()
{
- Host = "*.tiimo.dk",
+ Hosts = "*.tiimo.dk",
PFXPassword = "Pass@word",
RegistrationEmail = "mail@sjkp.dk",
AcmeEnvironment = new LetsEncryptStagingV2(),
@@ -99,7 +94,7 @@ public async Task TestEndToEndUnoEuro()
Assert.IsNotNull(res);
- File.WriteAllBytes($"{dnsRequest.Host.Substring(2)}.pfx", res.CertificateInfo.PfxCertificate);
+ File.WriteAllBytes($"{dnsRequest.Hosts.Substring(2)}.pfx", res.CertificateInfo.PfxCertificate);
}
@@ -113,7 +108,7 @@ public async Task TestEndToEndGoDaddy()
var dnsRequest = new AcmeDnsRequest()
{
- Host = "*.åbningstider.info",
+ Hosts = "*.åbningstider.info",
PFXPassword = "Pass@word",
RegistrationEmail = "mail@sjkp.dk",
AcmeEnvironment = new LetsEncryptStagingV2(),
@@ -131,7 +126,7 @@ public async Task TestEndToEndGoDaddy()
Assert.IsNotNull(res);
- File.WriteAllBytes($"{dnsRequest.Host.Substring(2)}.pfx", res.CertificateInfo.PfxCertificate);
+ File.WriteAllBytes($"{dnsRequest.Hosts.Substring(2)}.pfx", res.CertificateInfo.PfxCertificate);
var certService = new AzureWebAppService(new[] { TestHelper.AzureWebAppSettings });
diff --git a/src/LetsEncrypt.Azure.Core.Test/AzureDnsServiceTest.cs b/src/LetsEncrypt.Azure.Core.Test/AzureDnsServiceTest.cs
index 71c000e..e3ec386 100644
--- a/src/LetsEncrypt.Azure.Core.Test/AzureDnsServiceTest.cs
+++ b/src/LetsEncrypt.Azure.Core.Test/AzureDnsServiceTest.cs
@@ -1,9 +1,11 @@
using LetsEncrypt.Azure.Core.V2;
using LetsEncrypt.Azure.Core.V2.DnsProviders;
+using LetsEncrypt.Azure.Core.V2.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.Rest.Azure.Authentication;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
+using System.Linq;
using System.Threading.Tasks;
namespace Letsencrypt.Azure.Core.Test
@@ -11,21 +13,24 @@ namespace Letsencrypt.Azure.Core.Test
[TestClass]
public class AzureDnsServiceTest
{
+ private const string Domain = "ai4bots.com";
+ private const string HostName = "*." + Domain;
+
[TestMethod]
public async Task AzureDnsTest()
{
var config = TestHelper.AzureDnsSettings;
-
+
var service = new AzureDnsProvider(config);
var id = Guid.NewGuid().ToString();
- await service.PersistChallenge("_acme-challenge", id);
+ await service.PersistChallenge(zoneName: Domain, recordSetName: "_acme-challenge", recordValue: id);
- var exists = await new DnsLookupService().Exists("*.ai4bots.com", id, service.MinimumTtl);
+ var exists = await new DnsLookupService().Exists(HostName, id, service.MinimumTtl);
Assert.IsTrue(exists);
- await service.Cleanup("_acme-challenge");
- }
+ await service.Cleanup(Domain, "_acme-challenge");
+ }
}
}
diff --git a/src/LetsEncrypt.Azure.Core.Test/GoDaddyDnsProviderTest.cs b/src/LetsEncrypt.Azure.Core.Test/GoDaddyDnsProviderTest.cs
index 09c163f..68b5549 100644
--- a/src/LetsEncrypt.Azure.Core.Test/GoDaddyDnsProviderTest.cs
+++ b/src/LetsEncrypt.Azure.Core.Test/GoDaddyDnsProviderTest.cs
@@ -20,7 +20,7 @@ public class GoDaddyDnsProviderTest
public GoDaddyDnsProviderTest()
{
- this.Configuration = new ConfigurationBuilder()
+ this.Configuration = new ConfigurationBuilder()
.AddUserSecrets()
.Build();
@@ -37,13 +37,13 @@ public GoDaddyDnsProviderTest()
public async Task TestPersistChallenge()
{
var id = Guid.NewGuid().ToString();
- await DnsService.PersistChallenge("_acme-challenge", id);
+ await DnsService.PersistChallenge(zoneName: Domain, recordSetName: "_acme-challenge", recordValue: id);
var exists = await new DnsLookupService().Exists("*." + Domain, id);
Assert.IsTrue(exists);
- await DnsService.Cleanup("_acme-challenge");
+ await DnsService.Cleanup(Domain, "_acme-challenge");
}
}
}
diff --git a/src/LetsEncrypt.Azure.Core.Test/Letsencrypt.Azure.Core.Test.csproj b/src/LetsEncrypt.Azure.Core.Test/Letsencrypt.Azure.Core.Test.csproj
index 6690da8..6b98097 100644
--- a/src/LetsEncrypt.Azure.Core.Test/Letsencrypt.Azure.Core.Test.csproj
+++ b/src/LetsEncrypt.Azure.Core.Test/Letsencrypt.Azure.Core.Test.csproj
@@ -1,22 +1,25 @@
- netcoreapp2.1
+ netcoreapp2.2
+ true
+ win-x64
false
5F9376DE-25E6-4FFF-8462-D95812FCE06C
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
@@ -27,5 +30,5 @@
Always
-
+
diff --git a/src/LetsEncrypt.Azure.Core.Test/LoggingTest.cs b/src/LetsEncrypt.Azure.Core.Test/LoggingTest.cs
index 557213d..5d301e0 100644
--- a/src/LetsEncrypt.Azure.Core.Test/LoggingTest.cs
+++ b/src/LetsEncrypt.Azure.Core.Test/LoggingTest.cs
@@ -12,13 +12,13 @@ public class LoggingTest
public async Task TestLogging()
{
ILoggerFactory loggerFactory = new LoggerFactory()
- .AddConsole()
+ .AddConsole()
.AddDebug();
var logger = loggerFactory.CreateLogger();
logger.LogInformation("Initial message");
-
+
var client = new AcmeClientTest(logger);
await client.TestEndToEndAzure();
- }
+ }
}
}
diff --git a/src/LetsEncrypt.Azure.Core.Test/TestHelper.cs b/src/LetsEncrypt.Azure.Core.Test/TestHelper.cs
index 679e0b2..a223e07 100644
--- a/src/LetsEncrypt.Azure.Core.Test/TestHelper.cs
+++ b/src/LetsEncrypt.Azure.Core.Test/TestHelper.cs
@@ -28,10 +28,11 @@ static TestHelper()
clientId = config["clientId"];
secret = config["clientSecret"];
}
+
public static AzureDnsSettings AzureDnsSettings
{
get
- {
+ {
return new AzureDnsSettings("dns", "ai4bots.com", AzureServicePrincipal, new AzureSubscription()
{
AzureRegion = "AzureGlobalCloud",
@@ -46,8 +47,8 @@ public static UnoEuroDnsProvider UnoEuroDnsProvider
get
{
var config = new ConfigurationBuilder()
- .AddUserSecrets()
- .Build();
+ .AddUserSecrets()
+ .Build();
return new UnoEuroDnsProvider(new UnoEuroDnsSettings()
{
@@ -67,11 +68,11 @@ public static UnoEuroDnsProvider UnoEuroDnsProvider
public static AzureWebAppSettings AzureWebAppSettings
{
get
- {
+ {
return new AzureWebAppSettings("webappcfmv5fy7lcq7o", "LetsEncrypt-SiteExtension2", AzureServicePrincipal, new AzureSubscription()
{
Tenant = tenantId,
- SubscriptionId = "3f09c367-93e0-4b61-bbe5-dcb5c686bf8a",
+ SubscriptionId = subscriptionId,
AzureRegion = "AzureGlobalCloud"
});
}
diff --git a/src/LetsEncrypt.Azure.Core.Test/UnoEuroDnsProviderTest.cs b/src/LetsEncrypt.Azure.Core.Test/UnoEuroDnsProviderTest.cs
index 7f54776..294d54c 100644
--- a/src/LetsEncrypt.Azure.Core.Test/UnoEuroDnsProviderTest.cs
+++ b/src/LetsEncrypt.Azure.Core.Test/UnoEuroDnsProviderTest.cs
@@ -18,19 +18,20 @@ public async Task CreateRecord()
var config = new ConfigurationBuilder()
.AddUserSecrets()
.Build();
-
+
+ string domain = config["domain"];
var dnsProvider = new UnoEuroDnsProvider(new UnoEuroDnsSettings()
{
AccountName = config["accountName"],
ApiKey = config["apiKey"],
- Domain = config["domain"]
+ Domain = domain
});
//Test create new
- await dnsProvider.PersistChallenge("_acme-challenge", Guid.NewGuid().ToString());
+ await dnsProvider.PersistChallenge(zoneName: domain, recordSetName: "_acme-challenge", recordValue: Guid.NewGuid().ToString());
//Test Update existing
- await dnsProvider.PersistChallenge("_acme-challenge", Guid.NewGuid().ToString());
+ await dnsProvider.PersistChallenge(zoneName: domain, recordSetName: "_acme-challenge", recordValue: Guid.NewGuid().ToString());
//Test clean up
- await dnsProvider.Cleanup("_acme-challenge");
+ await dnsProvider.Cleanup(domain, "_acme-challenge");
}
@@ -39,14 +40,16 @@ public async Task UnoEuroDnsTest()
{
var service = TestHelper.UnoEuroDnsProvider;
- var id = Guid.NewGuid().ToString();
- await service.PersistChallenge("_acme-challenge", id);
+ const string Domain = "tiimo.dk";
+ const string HostName = "*." + Domain;
+ var id = Guid.NewGuid().ToString();
+ await service.PersistChallenge(zoneName: Domain, recordSetName: "_acme-challenge", recordValue: id);
- var exists = await new DnsLookupService().Exists("*.tiimo.dk", id, service.MinimumTtl);
+ var exists = await new DnsLookupService().Exists(HostName, id, service.MinimumTtl);
Assert.IsTrue(exists);
- await service.Cleanup("_acme-challenge");
+ await service.Cleanup(Domain, "_acme-challenge");
}
diff --git a/src/LetsEncrypt.Azure.Core.V2/AcmeClient.cs b/src/LetsEncrypt.Azure.Core.V2/AcmeClient.cs
index a237df6..e10e414 100644
--- a/src/LetsEncrypt.Azure.Core.V2/AcmeClient.cs
+++ b/src/LetsEncrypt.Azure.Core.V2/AcmeClient.cs
@@ -7,16 +7,20 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
+using System.Threading;
using System.Threading.Tasks;
namespace LetsEncrypt.Azure.Core.V2
{
public class AcmeClient
{
+ private readonly HttpClient http = new HttpClient();
private readonly IDnsProvider dnsProvider;
private readonly DnsLookupService dnsLookupService;
private readonly ICertificateStore certificateStore;
@@ -28,12 +32,12 @@ public AcmeClient(IDnsProvider dnsProvider, DnsLookupService dnsLookupService, I
this.dnsProvider = dnsProvider;
this.dnsLookupService = dnsLookupService;
this.certificateStore = certifcateStore;
- this.logger = logger ?? NullLogger.Instance;
-
+ this.logger = logger ?? NullLogger.Instance;
}
+
///
- /// Request a certificate from lets encrypt using the DNS challenge, placing the challenge record in Azure DNS.
- /// The certifiacte is not assigned, but just returned.
+ /// Request a certificate from lets encrypt using the DNS challenge, placing the challenge record in Azure DNS.
+ /// The certifiacte is not assigned, but just returned.
///
///
///
@@ -44,33 +48,49 @@ public async Task RequestDnsChallengeCertificate(IAcmeD
var acmeContext = await GetOrCreateAcmeContext(acmeConfig.AcmeEnvironment.BaseUri, acmeConfig.RegistrationEmail);
var idn = new IdnMapping();
- var order = await acmeContext.NewOrder(new[] { "*." + idn.GetAscii(acmeConfig.Host.Substring(2)) });
- var a = await order.Authorizations();
- var authz = a.First();
- var challenge = await authz.Dns();
- var dnsTxt = acmeContext.AccountKey.DnsTxt(challenge.Token);
- logger.LogInformation("Got DNS challenge token {Token}", dnsTxt);
+ var orderHosts = (from host in acmeConfig.Hosts
+ let asciiHost = idn.GetAscii(host)
+ let asciiDomain = asciiHost.StartsWith("*.")
+ ? asciiHost.Substring(2)
+ : asciiHost
+ select (Host: asciiHost, Domain: asciiDomain))
+ .ToImmutableArray();
+ var order = await acmeContext.NewOrder(orderHosts.Select(h => h.Host).ToImmutableArray());
+ var authorizations = (await order.Authorizations()).ToImmutableArray();
+ var tasks = new List(authorizations.Length);
+ var dnsTxts = new List(authorizations.Length);
+ // TODO: Consider parallelizing
+ for (var i = 0; i < authorizations.Length; i++) /*tasks.Add(Task.Factory.StartNew(async state =>*/
+ {
+ //var (authorization, host) = (Tuple)state;
+ var (authorization, zoneName) = (authorizations[i], orderHosts[i].Domain);
+ var challenge = await authorization.Dns();
+ var dnsTxt = acmeContext.AccountKey.DnsTxt(challenge.Token);
+ logger.LogInformation("Got DNS challenge token {Token}", dnsTxt);
- ///add dns entry
- await this.dnsProvider.PersistChallenge("_acme-challenge", dnsTxt);
+ ///add dns entry
+ await this.dnsProvider.PersistChallenge(zoneName, "_acme-challenge", dnsTxt);
+ await Task.Delay(500);
- if (!(await this.dnsLookupService.Exists(acmeConfig.Host, dnsTxt, this.dnsProvider.MinimumTtl)))
- {
- throw new TimeoutException($"Unable to validate that _acme-challenge was stored in txt _acme-challenge record after {this.dnsProvider.MinimumTtl} seconds");
- }
+ if (!(await this.dnsLookupService.Exists(zoneName, dnsTxt, this.dnsProvider.MinimumTtl)))
+ {
+ throw new TimeoutException($"Unable to validate that _acme-challenge was stored in txt _acme-challenge record after {this.dnsProvider.MinimumTtl} seconds");
+ }
-
- Challenge chalResp = await challenge.Validate();
- while (chalResp.Status == ChallengeStatus.Pending || chalResp.Status == ChallengeStatus.Processing)
- {
- logger.LogInformation("Dns challenge response status {ChallengeStatus} more info at {ChallengeStatusUrl} retrying in 5 sec", chalResp.Status, chalResp.Url.ToString());
- await Task.Delay(5000);
- chalResp = await challenge.Resource();
- }
+ Challenge chalResp = await challenge.Validate();
+ while (chalResp.Status == ChallengeStatus.Pending || chalResp.Status == ChallengeStatus.Processing)
+ {
+ logger.LogInformation("Dns challenge response status {ChallengeStatus} more info at {ChallengeStatusUrl} retrying in 5 sec", chalResp.Status, chalResp.Url.ToString());
+ await Task.Delay(5000);
+ chalResp = await challenge.Resource();
+ }
- logger.LogInformation("Finished validating dns challenge token, response was {ChallengeStatus} more info at {ChallengeStatusUrl}", chalResp.Status, chalResp.Url);
+ logger.LogInformation("Finished validating dns challenge token, response was {ChallengeStatus} more info at {ChallengeStatusUrl}", chalResp.Status, chalResp.Url);
+ }/*, Tuple.Create(authorizations[i], orderHosts[i].BaseHost), CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default).Unwrap());
+ await Task.WhenAll(tasks);
+ tasks.Clear()*/;
- var privateKey = await GetOrCreateKey(acmeConfig.AcmeEnvironment.BaseUri, acmeConfig.Host);
+ var privateKey = await GetOrCreateKey(acmeConfig.AcmeEnvironment.BaseUri, acmeConfig.Hosts);
var cert = await order.Generate(new Certes.CsrInfo
{
CountryName = acmeConfig.CsrInfo.CountryName,
@@ -83,27 +103,39 @@ public async Task RequestDnsChallengeCertificate(IAcmeD
var certPem = cert.ToPem();
+ string hostsPlusSeparated = GetHostsPlusSeparated(acmeConfig.Hosts);
var pfxBuilder = cert.ToPfx(privateKey);
- var pfx = pfxBuilder.Build(acmeConfig.Host, acmeConfig.PFXPassword);
+ var pfx = pfxBuilder.Build(hostsPlusSeparated, acmeConfig.PFXPassword);
- await this.dnsProvider.Cleanup(dnsTxt);
+ for (var i = 0; i < dnsTxts.Count; i++)
+ {
+ tasks.Add(this.dnsProvider.Cleanup(orderHosts[i].Domain, dnsTxts[i]));
+ }
+ await Task.WhenAll(tasks);
+ tasks.Clear();
return new CertificateInstallModel()
{
CertificateInfo = new CertificateInfo()
{
+#pragma warning disable DF0100 // Marks return values that hides the IDisposable implementation of return value.
Certificate = new X509Certificate2(pfx, acmeConfig.PFXPassword, X509KeyStorageFlags.DefaultKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable),
- Name = $"{acmeConfig.Host} {DateTime.Now}",
+#pragma warning restore DF0100 // Marks return values that hides the IDisposable implementation of return value.
+ Name = $"{acmeConfig.Hosts} {DateTime.Now}",
Password = acmeConfig.PFXPassword,
PfxCertificate = pfx
},
- Host = acmeConfig.Host
+ Hosts = acmeConfig.Hosts
};
}
- private async Task GetOrCreateKey(Uri acmeDirectory, string host)
+ internal static string GetHostsPlusSeparated(ImmutableArray hosts)
+ => string.Join("+", hosts.Select(h => h.StartsWith("*") ? h.Substring(1) : h));
+
+ private async Task GetOrCreateKey(Uri acmeDirectory, ImmutableArray hosts)
{
- string secretName = $"privatekey{host}--{acmeDirectory.Host}";
+ string hostsPlusSeparated = GetHostsPlusSeparated(hosts);
+ string secretName = $"privatekey-{hostsPlusSeparated}--{acmeDirectory.Host}";
var key = await this.certificateStore.GetSecret(secretName);
if (string.IsNullOrEmpty(key))
{
@@ -129,19 +161,15 @@ private async Task GetOrCreateAcmeContext(Uri acmeDirectoryUri, str
var pemKey = acme.AccountKey.ToPem();
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()));
+ acme = new AcmeContext(acmeDirectoryUri, acme.AccountKey, new AcmeHttpClient(acmeDirectoryUri, http));
}
else
{
var accountKey = KeyFactory.FromPem(secret);
- acme = new AcmeContext(acmeDirectoryUri, accountKey, new AcmeHttpClient(acmeDirectoryUri, new HttpClient()));
+ acme = new AcmeContext(acmeDirectoryUri, accountKey, new AcmeHttpClient(acmeDirectoryUri, http));
}
return acme;
}
-
-
-
-
}
}
diff --git a/src/LetsEncrypt.Azure.Core.V2/AssemblyInfo.cs b/src/LetsEncrypt.Azure.Core.V2/AssemblyInfo.cs
new file mode 100644
index 0000000..5f323da
--- /dev/null
+++ b/src/LetsEncrypt.Azure.Core.V2/AssemblyInfo.cs
@@ -0,0 +1,3 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Letsencrypt.Azure.Core.Test")]
\ No newline at end of file
diff --git a/src/LetsEncrypt.Azure.Core.V2/AzureBlobStorage.cs b/src/LetsEncrypt.Azure.Core.V2/AzureBlobStorage.cs
index 5b24f6a..4d62271 100644
--- a/src/LetsEncrypt.Azure.Core.V2/AzureBlobStorage.cs
+++ b/src/LetsEncrypt.Azure.Core.V2/AzureBlobStorage.cs
@@ -1,5 +1,5 @@
-using Microsoft.WindowsAzure.Storage;
-using Microsoft.WindowsAzure.Storage.Blob;
+using Microsoft.Azure.Storage;
+using Microsoft.Azure.Storage.Blob;
using System.IO;
using System.Threading.Tasks;
@@ -26,9 +26,7 @@ private async Task GetBlob(string v)
var container = client.GetContainerReference("letsencrypt");
await container.CreateIfNotExistsAsync();
-
var blob = container.GetBlockBlobReference(v);
-
return blob;
}
@@ -52,7 +50,7 @@ public async Task Read(string v)
{
await data.CopyToAsync(ms);
return ms.ToArray();
- }
+ }
}
public async Task Write(string v, byte[] data)
diff --git a/src/LetsEncrypt.Azure.Core.V2/AzureHelper.cs b/src/LetsEncrypt.Azure.Core.V2/AzureHelper.cs
index ff892b9..af79d71 100644
--- a/src/LetsEncrypt.Azure.Core.V2/AzureHelper.cs
+++ b/src/LetsEncrypt.Azure.Core.V2/AzureHelper.cs
@@ -22,18 +22,16 @@ public static AzureCredentials GetAzureCredentials(AzureServicePrincipal service
}
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));
}
-
public static RestClient GetRestClient(AzureServicePrincipal servicePrincipal, AzureSubscription azureSubscription)
- {
+ {
var credentials = GetAzureCredentials(servicePrincipal, azureSubscription);
return RestClient
.Configure()
diff --git a/src/LetsEncrypt.Azure.Core.V2/CertificateConsumers/AzureWebAppService.cs b/src/LetsEncrypt.Azure.Core.V2/CertificateConsumers/AzureWebAppService.cs
index 44c6915..f2b418b 100644
--- a/src/LetsEncrypt.Azure.Core.V2/CertificateConsumers/AzureWebAppService.cs
+++ b/src/LetsEncrypt.Azure.Core.V2/CertificateConsumers/AzureWebAppService.cs
@@ -21,9 +21,11 @@ public AzureWebAppService(AzureWebAppSettings[] settings, ILogger.Instance;
}
+
public async Task Install(ICertificateInstallModel model)
{
- logger.LogInformation("Starting installation of certificate {Thumbprint} for {Host}", model.CertificateInfo.Certificate.Thumbprint, model.Host);
+ string hostsComaSeparated = string.Join(",", model.Hosts);
+ logger.LogInformation("Starting installation of certificate {Thumbprint} for {Host}", model.CertificateInfo.Certificate.Thumbprint, hostsComaSeparated);
var cert = model.CertificateInfo;
foreach (var setting in this.settings)
{
@@ -42,13 +44,21 @@ public async Task Install(ICertificateInstallModel model)
var existingCerts = await appServiceManager.AppServiceCertificates.ListByResourceGroupAsync(setting.ServicePlanResourceGroupName ?? setting.ResourceGroupName);
if (existingCerts.All(_ => _.Thumbprint != cert.Certificate.Thumbprint))
{
- await appServiceManager.AppServiceCertificates.Define(model.Host + "-" + cert.Certificate.Thumbprint).WithRegion(s.RegionName).WithExistingResourceGroup(setting.ServicePlanResourceGroupName ?? setting.ResourceGroupName).WithPfxByteArray(model.CertificateInfo.PfxCertificate).WithPfxPassword(model.CertificateInfo.Password).CreateAsync();
+ await appServiceManager
+ .AppServiceCertificates
+ .Define($"{hostsComaSeparated}-{cert.Certificate.Thumbprint}")
+ .WithRegion(s.RegionName)
+ .WithExistingResourceGroup(setting.ServicePlanResourceGroupName ?? setting.ResourceGroupName)
+ .WithPfxByteArray(model.CertificateInfo.PfxCertificate)
+ .WithPfxPassword(model.CertificateInfo.Password)
+ .CreateAsync();
}
-
-
var sslStates = siteOrSlot.HostNameSslStates;
- var domainSslMappings = new List>(sslStates.Where(_ => _.Key.Contains($".{model.Host.Substring(2)}")));
+ var domainSslMappings = new List>(
+ sslStates.Where(_ =>
+ model.Hosts.Any(h => _.Key == h
+ || _.Key.Contains($".{h}"))));
if (domainSslMappings.Any())
{
@@ -75,6 +85,7 @@ public async Task Install(ICertificateInstallModel model)
}
}
}
+ // TODO: Catch errors one by one
catch (Exception e)
{
logger.LogCritical(e, "Unable to install certificate for '{WebApp}'", setting.WebAppName);
@@ -114,8 +125,6 @@ private async Task RemoveCertificate(IAppServiceManager webSiteClient, IAppServi
{
await webSiteClient.AppServiceCertificates.DeleteByResourceGroupAsync(setting.ServicePlanResourceGroupName ?? setting.ResourceGroupName, s.Name);
}
-
-
}
}
diff --git a/src/LetsEncrypt.Azure.Core.V2/DnsLookupService.cs b/src/LetsEncrypt.Azure.Core.V2/DnsLookupService.cs
index 55504c5..9843652 100644
--- a/src/LetsEncrypt.Azure.Core.V2/DnsLookupService.cs
+++ b/src/LetsEncrypt.Azure.Core.V2/DnsLookupService.cs
@@ -3,6 +3,7 @@
using Microsoft.Extensions.Logging.Abstractions;
using System;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using System.Text;
@@ -19,56 +20,45 @@ public DnsLookupService(ILogger logger = null)
this.logger = logger ?? NullLogger.Instance;
}
- public async Task Exists(string hostname, string dnsTxt, int timeout = 60)
+ public async Task Exists(string zoneName, string dnsTxt, int timeout = 60)
{
- logger.LogInformation("Starting dns precheck validation for hostname: {HostName} challenge: {Challenge} and timeout {Timeout}", hostname, dnsTxt, timeout);
+ logger.LogInformation("Starting dns precheck validation for hostname: {HostName} challenge: {Challenge} and timeout {Timeout}", zoneName, dnsTxt, timeout);
var idn = new IdnMapping();
- hostname = idn.GetAscii(GetNoneWildcardDomain(hostname));
- var dnsClient = GetDnsClient(hostname);
+ zoneName = idn.GetAscii(zoneName);
+ var dnsClient = GetDnsClient(zoneName);
var startTime = DateTime.UtcNow;
- string queriedDns = "";
- //Lets encrypt checks a random authoritative server, thus we need to ensure that all respond with the challenge.
- foreach (var ns in dnsClient.NameServers)
+ bool result = false;
+ do
{
- logger.LogInformation("Validating dns challenge exists on name server {NameServer}", ns.ToString());
- do
+ var servers = dnsClient.NameServers.Select(s => s.Endpoint.Address).ToImmutableArray();
+ logger.LogInformation("Validating dns challenge exists on name servers {NameServers}", string.Join(", ", servers));
+ var dnsRes = dnsClient.QueryServer(servers, $"_acme-challenge.{zoneName}", QueryType.TXT);
+ result = dnsRes.Answers.TxtRecords().FirstOrDefault()?.Text.Any(r => r == dnsTxt) ?? false;
+ if (!result)
{
- var dnsRes = dnsClient.QueryServer(new[] { ns.Endpoint.Address }, $"_acme-challenge.{hostname}", QueryType.TXT);
- queriedDns = dnsRes.Answers.TxtRecords().FirstOrDefault()?.Text.FirstOrDefault();
- if (queriedDns != dnsTxt)
- {
- logger.LogInformation("Challenge record was {existingTxt} should have been {Challenge}, retrying again in 5 seconds", queriedDns, dnsTxt);
- await Task.Delay(5000);
- }
+ logger.LogInformation("Challenge record missing, retrying again in 5 seconds");
+ await Task.Delay(5000);
+ }
- } while (queriedDns != dnsTxt && (DateTime.UtcNow - startTime).TotalSeconds < timeout);
- }
+ } while (!result && (DateTime.UtcNow - startTime).TotalSeconds < timeout);
- return queriedDns == dnsTxt;
+ return result;
}
- private static LookupClient GetDnsClient(params string[] hostnames)
+ private static LookupClient GetDnsClient(string zoneName)
{
-
LookupClient generalClient = new LookupClient();
- LookupClient dnsClient = null;
generalClient.UseCache = false;
- foreach (var hostname in hostnames)
- {
- var ns = generalClient.Query(hostname, QueryType.NS);
- var ip = ns.Answers.NsRecords().Select(s => generalClient.GetHostEntry(s.NSDName.Value));
-
- dnsClient = new LookupClient(ip.SelectMany(i => i.AddressList).Where(s => s.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork).ToArray());
- dnsClient.UseCache = false;
-
- }
+ var ns = generalClient.Query(zoneName, QueryType.NS);
+ var ip = ns.Answers.NsRecords().Select(s => generalClient.GetHostEntry(s.NSDName.Value));
- return dnsClient;
- }
+ var nameServers = ip.SelectMany(i => i.AddressList)
+ .Where(s => s.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
+ .ToArray();
+ LookupClient dnsClient = new LookupClient(nameServers);
+ dnsClient.UseCache = false;
- public static string GetNoneWildcardDomain(string hostname)
- {
- return hostname.Replace("*.", "");
+ return dnsClient;
}
}
}
diff --git a/src/LetsEncrypt.Azure.Core.V2/DnsProviders/AzureDnsProvider.cs b/src/LetsEncrypt.Azure.Core.V2/DnsProviders/AzureDnsProvider.cs
index 5c9fa73..a81db5c 100644
--- a/src/LetsEncrypt.Azure.Core.V2/DnsProviders/AzureDnsProvider.cs
+++ b/src/LetsEncrypt.Azure.Core.V2/DnsProviders/AzureDnsProvider.cs
@@ -18,31 +18,32 @@ public class AzureDnsProvider : IDnsProvider
public AzureDnsProvider(AzureDnsSettings settings)
{
var restClient = AzureHelper.GetRestClient(settings.AzureServicePrincipal, settings.AzureSubscription);
-
-
+
+#pragma warning disable DF0020 // Marks undisposed objects assinged to a field, originated in an object creation.
this.client = new DnsManagementClient(restClient);
+#pragma warning restore DF0020 // Marks undisposed objects assinged to a field, originated in an object creation.
this.client.SubscriptionId = settings.AzureSubscription.SubscriptionId;
this.settings = settings;
}
public int MinimumTtl => 60;
- public async Task Cleanup(string recordSetName)
+ public async Task Cleanup(string recordSetName, string zoneName)
{
- var existingRecords = await SafeGetExistingRecords(recordSetName);
+ var existingRecords = await SafeGetExistingRecords(recordSetName, zoneName);
- await this.client.RecordSets.DeleteAsync(this.settings.ResourceGroupName, this.settings.ZoneName, GetRelativeRecordSetName(recordSetName), RecordType.TXT);
+ await this.client.RecordSets.DeleteAsync(this.settings.ResourceGroupName, zoneName, GetRelativeRecordSetName(recordSetName, zoneName), RecordType.TXT);
}
- public async Task PersistChallenge(string recordSetName, string recordValue)
+ public async Task PersistChallenge(string zoneName, string recordSetName, string recordValue)
{
List records = new List()
{
new TxtRecord() { Value = new[] { recordValue } }
};
- if ((await client.RecordSets.ListByTypeAsync(settings.ResourceGroupName, settings.ZoneName, RecordType.TXT)).Any())
+ if ((await client.RecordSets.ListByTypeAsync(settings.ResourceGroupName, zoneName, RecordType.TXT)).Any())
{
- var existingRecords = await SafeGetExistingRecords(recordSetName);
+ var existingRecords = await SafeGetExistingRecords(recordSetName, zoneName);
if (existingRecords != null)
{
if (existingRecords.TxtRecords.Any(s => s.Value.Contains(recordValue)))
@@ -55,23 +56,21 @@ public async Task PersistChallenge(string recordSetName, string recordValue)
}
}
}
- await this.client.RecordSets.CreateOrUpdateAsync(this.settings.ResourceGroupName, this.settings.ZoneName, GetRelativeRecordSetName(recordSetName), RecordType.TXT, new RecordSetInner()
+ await this.client.RecordSets.CreateOrUpdateAsync(this.settings.ResourceGroupName, zoneName, GetRelativeRecordSetName(recordSetName, zoneName), RecordType.TXT, new RecordSetInner()
{
TxtRecords = records,
TTL = MinimumTtl
});
}
- private string GetRelativeRecordSetName(string dnsTxt)
- {
- return dnsTxt.Replace($".{this.settings.ZoneName}", "");
- }
+ private string GetRelativeRecordSetName(string dnsTxt, string zoneName)
+ => dnsTxt.Replace($".{zoneName}", "");
- private async Task SafeGetExistingRecords(string recordSetName)
+ private async Task SafeGetExistingRecords(string recordSetName, string zoneName)
{
try
{
- return await client.RecordSets.GetAsync(settings.ResourceGroupName, settings.ZoneName, GetRelativeRecordSetName(recordSetName), RecordType.TXT);
+ return await client.RecordSets.GetAsync(settings.ResourceGroupName, zoneName, GetRelativeRecordSetName(recordSetName, zoneName), RecordType.TXT);
}
catch (CloudException cex)
diff --git a/src/LetsEncrypt.Azure.Core.V2/DnsProviders/GoDaddyDnsProvider.cs b/src/LetsEncrypt.Azure.Core.V2/DnsProviders/GoDaddyDnsProvider.cs
index 49a2d58..98457f3 100644
--- a/src/LetsEncrypt.Azure.Core.V2/DnsProviders/GoDaddyDnsProvider.cs
+++ b/src/LetsEncrypt.Azure.Core.V2/DnsProviders/GoDaddyDnsProvider.cs
@@ -21,12 +21,12 @@ public GoDaddyDnsProvider(GoDaddyDnsSettings settings)
public int MinimumTtl => 600;
- public Task Cleanup(string recordSetName)
+ public Task Cleanup(string zoneName, string recordSetName)
{
return Task.FromResult(0);
}
- public async Task PersistChallenge(string recordSetName, string recordValue)
+ public async Task PersistChallenge(string zoneName, string recordSetName, string recordValue)
{
var body = await httpClient.GetStringAsync($"records/TXT/{recordSetName}");
var acmeChallengeRecord = JsonConvert.DeserializeObject(body);
@@ -40,10 +40,12 @@ public async Task PersistChallenge(string recordSetName, string recordValue)
type = "TXT"
}};
- var res = await this.httpClient.PutAsync($"records/TXT/{recordSetName}", new StringContent(JsonConvert.SerializeObject(acmeChallengeRecord), Encoding.UTF8, "application/json"));
- body = await res.Content.ReadAsStringAsync();
- res.EnsureSuccessStatusCode();
-
+ using (var stringContent = new StringContent(JsonConvert.SerializeObject(acmeChallengeRecord), Encoding.UTF8, "application/json"))
+ using (var res = await this.httpClient.PutAsync($"records/TXT/{recordSetName}", stringContent))
+ {
+ body = await res.Content.ReadAsStringAsync();
+ res.EnsureSuccessStatusCode();
+ }
}
public class GoDaddyDnsSettings
@@ -54,7 +56,6 @@ public class GoDaddyDnsSettings
public string Domain { get; set; }
}
-
public class DnsRecord
{
public string data { get; set; }
@@ -62,6 +63,5 @@ public class DnsRecord
public int ttl { get; set; }
public string type { get; set; }
}
-
}
}
diff --git a/src/LetsEncrypt.Azure.Core.V2/DnsProviders/IDnsProvider.cs b/src/LetsEncrypt.Azure.Core.V2/DnsProviders/IDnsProvider.cs
index 3827fb7..36e8e9c 100644
--- a/src/LetsEncrypt.Azure.Core.V2/DnsProviders/IDnsProvider.cs
+++ b/src/LetsEncrypt.Azure.Core.V2/DnsProviders/IDnsProvider.cs
@@ -4,8 +4,8 @@ namespace LetsEncrypt.Azure.Core.V2.DnsProviders
{
public interface IDnsProvider
{
- Task PersistChallenge(string recordSetName, string recordValue);
- Task Cleanup(string recordSetName);
+ Task PersistChallenge(string zoneName, string recordSetName, string recordValue);
+ Task Cleanup(string recordSetName, string zoneName);
///
/// The minimum ttl value in seconds, that the provider supports.
diff --git a/src/LetsEncrypt.Azure.Core.V2/DnsProviders/UnoEuroDnsProvider.cs b/src/LetsEncrypt.Azure.Core.V2/DnsProviders/UnoEuroDnsProvider.cs
index ad6d7d0..e139ce5 100644
--- a/src/LetsEncrypt.Azure.Core.V2/DnsProviders/UnoEuroDnsProvider.cs
+++ b/src/LetsEncrypt.Azure.Core.V2/DnsProviders/UnoEuroDnsProvider.cs
@@ -15,21 +15,25 @@ public class UnoEuroDnsProvider : IDnsProvider
public UnoEuroDnsProvider(UnoEuroDnsSettings settings)
{
+#pragma warning disable DF0020 // Marks undisposed objects assinged to a field, originated in an object creation.
this.httpClient = new HttpClient();
- httpClient.BaseAddress = new Uri($"https://api.unoeuro.com/1/{settings.AccountName}/{settings.ApiKey}/my/products/{settings.Domain}/dns/records/");
+#pragma warning restore DF0020 // Marks undisposed objects assinged to a field, originated in an object creation.
+ httpClient.BaseAddress = new Uri($"https://api.unoeuro.com/1/{settings.AccountName}/{settings.ApiKey}/my/products/{settings.Domain}/dns/records/");
}
- public async Task Cleanup(string recordSetName)
+ public async Task Cleanup(string zoneName, string recordSetName)
{
DnsRecord acmeChallengeRecord = await GetRecord(recordSetName);
if (acmeChallengeRecord != null)
- {
- var res = await this.httpClient.DeleteAsync($"{acmeChallengeRecord.record_id}");
- res.EnsureSuccessStatusCode();
- }
+ using (var res = await this.httpClient.DeleteAsync($"{acmeChallengeRecord.record_id}"))
+ {
+#pragma warning disable DF0001 // Marks undisposed anonymous objects from method invocations.
+ res.EnsureSuccessStatusCode();
+#pragma warning restore DF0001 // Marks undisposed anonymous objects from method invocations.
+ }
}
- public async Task PersistChallenge(string recordSetName, string recordValue)
+ public async Task PersistChallenge(string zoneName, string recordSetName, string recordValue)
{
DnsRecord acmeChallengeRecord = await GetRecord(recordSetName);
@@ -44,10 +48,14 @@ public async Task PersistChallenge(string recordSetName, string recordValue)
data = recordValue,
acmeChallengeRecord.priority
};
- StringContent content = CreateRequestBody(update);
- var res = await httpClient.PutAsync($"{acmeChallengeRecord.record_id}", content);
- var s = res.Content.ReadAsStringAsync();
- res.EnsureSuccessStatusCode();
+ using (StringContent content = CreateRequestBody(update))
+ using (var res = await httpClient.PutAsync($"{acmeChallengeRecord.record_id}", content))
+ {
+ var s = res.Content.ReadAsStringAsync();
+#pragma warning disable DF0001 // Marks undisposed anonymous objects from method invocations.
+ res.EnsureSuccessStatusCode();
+#pragma warning restore DF0001 // Marks undisposed anonymous objects from method invocations.
+ }
}
else
{
@@ -59,9 +67,13 @@ public async Task PersistChallenge(string recordSetName, string recordValue)
data = recordValue,
priority = 0
};
- //Create
- var res = await httpClient.PostAsync("", CreateRequestBody(acmeChallengeRecord));
- res.EnsureSuccessStatusCode();
+ using (StringContent content = CreateRequestBody(acmeChallengeRecord))
+ using (var res = await httpClient.PostAsync("", content))
+ {
+#pragma warning disable DF0001 // Marks undisposed anonymous objects from method invocations.
+ res.EnsureSuccessStatusCode();
+#pragma warning restore DF0001 // Marks undisposed anonymous objects from method invocations.
+ }
}
}
diff --git a/src/LetsEncrypt.Azure.Core.V2/LetsEncrypt.Azure.Core.V2.csproj b/src/LetsEncrypt.Azure.Core.V2/LetsEncrypt.Azure.Core.V2.csproj
index 0fd82ed..052fe78 100644
--- a/src/LetsEncrypt.Azure.Core.V2/LetsEncrypt.Azure.Core.V2.csproj
+++ b/src/LetsEncrypt.Azure.Core.V2/LetsEncrypt.Azure.Core.V2.csproj
@@ -2,27 +2,23 @@
netstandard2.0
- Library for easy retrieval of Let's Encrypt wildcard certificates using version 2 api. Support easy install to Azure Web Apps and storage in Azure Key Vault or Blob Storage.
+ Library for easy retrieval of Let's Encrypt wildcard certificates using version 2 api. Support easy install to Azure Web Apps and storage in Azure Key Vault or Blob Storage.
false
-
+
-
-
-
-
-
-
-
-
-
-
-
-
- C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6\System.ComponentModel.DataAnnotations.dll
-
+
+
+
+
+
+
+
+
+
+
diff --git a/src/LetsEncrypt.Azure.Core.V2/LetsencryptService.cs b/src/LetsEncrypt.Azure.Core.V2/LetsencryptService.cs
index 461cf74..2d77cb4 100644
--- a/src/LetsEncrypt.Azure.Core.V2/LetsencryptService.cs
+++ b/src/LetsEncrypt.Azure.Core.V2/LetsencryptService.cs
@@ -4,6 +4,7 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System;
+using System.Linq;
using System.Threading.Tasks;
namespace LetsEncrypt.Azure.Core.V2
@@ -13,7 +14,6 @@ public class LetsencryptService
private readonly AcmeClient acmeClient;
private readonly ICertificateStore certificateStore;
private readonly ICertificateConsumer certificateConsumer;
- private readonly AzureWebAppService azureWebAppService;
private readonly ILogger logger;
public LetsencryptService(AcmeClient acmeClient, ICertificateStore certificateStore, ICertificateConsumer certificateConsumer, ILogger logger = null)
@@ -23,13 +23,15 @@ public LetsencryptService(AcmeClient acmeClient, ICertificateStore certificateSt
this.certificateConsumer = certificateConsumer;
this.logger = logger ?? NullLogger.Instance;
}
- public async Task Run(AcmeDnsRequest acmeDnsRequest, int renewXNumberOfDaysBeforeExpiration)
+
+ public async Task Run(IAcmeDnsRequest acmeDnsRequest, int renewXNumberOfDaysBeforeExpiration)
{
try
{
CertificateInstallModel model = null;
-
- var certname = acmeDnsRequest.Host.Substring(2) + "-" + acmeDnsRequest.AcmeEnvironment.Name;
+
+ string hostsPlusSeparated = AcmeClient.GetHostsPlusSeparated(acmeDnsRequest.Hosts);
+ var certname = $"{hostsPlusSeparated}-{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.
{
@@ -44,7 +46,7 @@ public async Task Run(AcmeDnsRequest acmeDnsRequest, int renewXNumberOfDaysBefor
model = new CertificateInstallModel()
{
CertificateInfo = cert,
- Host = acmeDnsRequest.Host
+ Hosts = acmeDnsRequest.Hosts
};
}
await certificateConsumer.Install(model);
@@ -52,7 +54,7 @@ public async Task Run(AcmeDnsRequest acmeDnsRequest, int renewXNumberOfDaysBefor
logger.LogInformation("Removing expired certificates");
var expired = await certificateConsumer.CleanUp();
logger.LogInformation("The following certificates was removed {Thumbprints}", string.Join(", ", expired.ToArray()));
-
+
}
catch (Exception e)
{
diff --git a/src/LetsEncrypt.Azure.Core.V2/LetsencryptServiceCollectionExtensions.cs b/src/LetsEncrypt.Azure.Core.V2/LetsencryptServiceCollectionExtensions.cs
index 5b8408b..cdddeaf 100644
--- a/src/LetsEncrypt.Azure.Core.V2/LetsencryptServiceCollectionExtensions.cs
+++ b/src/LetsEncrypt.Azure.Core.V2/LetsencryptServiceCollectionExtensions.cs
@@ -60,21 +60,19 @@ public static IServiceCollection AddAcmeClient(this IServiceCollec
return serviceCollection
.AddTransient()
- .AddTransient()
+ .AddTransient()
.AddSingleton(dnsProviderConfig.GetType(), dnsProviderConfig)
- .AddTransient();
+ .AddTransient();
}
public static IServiceCollection AddNullCertificateConsumer(this IServiceCollection serviceCollection)
{
-
+
return serviceCollection
.AddTransient()
.AddTransient();
}
-
-
public static IServiceCollection AddAzureAppService(this IServiceCollection serviceCollection, params AzureWebAppSettings[] settings)
{
if (settings == null || settings.Length == 0)
diff --git a/src/LetsEncrypt.Azure.Core.V2/MessageHandler.cs b/src/LetsEncrypt.Azure.Core.V2/MessageHandler.cs
index d709e63..435f102 100644
--- a/src/LetsEncrypt.Azure.Core.V2/MessageHandler.cs
+++ b/src/LetsEncrypt.Azure.Core.V2/MessageHandler.cs
@@ -35,19 +35,17 @@ protected override async Task SendAsync(HttpRequestMessage
{
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;
@@ -56,13 +54,13 @@ 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(() =>
diff --git a/src/LetsEncrypt.Azure.Core.V2/Models/AcmeDnsRequest.cs b/src/LetsEncrypt.Azure.Core.V2/Models/AcmeDnsRequest.cs
index 60958c3..ea44911 100644
--- a/src/LetsEncrypt.Azure.Core.V2/Models/AcmeDnsRequest.cs
+++ b/src/LetsEncrypt.Azure.Core.V2/Models/AcmeDnsRequest.cs
@@ -1,47 +1,101 @@
using Certes.Acme;
using System;
using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
using System.Text;
namespace LetsEncrypt.Azure.Core.V2.Models
{
+ public class DomainComparer : IComparer
+ {
+ public int Compare(string x, string y)
+ {
+ var xParts = x.Split('.').Reverse().ToImmutableArray();
+ var yParts = y.Split('.').Reverse().ToImmutableArray();
+ var result = xParts.Zip(yParts, (xPart, yPart) => string.Compare(xPart, yPart)).FirstOrDefault(r => r != 0);
+ if (result == 0)
+ result = xParts.Length - yParts.Length;
+ return result;
+ }
+ }
+
public class AcmeDnsRequest : IAcmeDnsRequest
{
+ private readonly static IComparer domainComparer = new DomainComparer();
+
///
- /// The email to register with lets encrypt with. Will recieve notifications on expiring certificates.
+ /// The email to register with lets encrypt with. Will recieve notifications on expiring certificates.
///
+ [Required]
public string RegistrationEmail { get; set; }
///
- /// The ACME environment, use or or provide you own ACME compatible endpoint by implementing .
+ /// The ACME environment, use or or provide you
+ /// own ACME compatible endpoint by implementing .
///
+ [Required]
public AcmeEnvironment AcmeEnvironment { get; set; }
+ private string hosts;
+ private ImmutableArray hostsList = ImmutableArray.Empty;
+ private ImmutableArray domainsList = ImmutableArray.Empty;
+
///
- /// The host name to request a certificate for e.g. *.example.com
+ /// The host names to request a certificate for delimited by coma e.g. *.example1.com,*.example2.com
///
- public string Host { get; set; }
+ [Required]
+ public string Hosts
+ {
+ get { return hosts; }
+ set
+ {
+ string RemoveWildcard(string host) => host.StartsWith("*.") ? host.Substring(2) : host;
+
+ this.hosts = value;
+ string[] hosts = this.hosts.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
+ hostsList = hosts.OrderBy(h => h, domainComparer).Distinct().ToImmutableArray();
+ domainsList = hostsList
+ .Select(RemoveWildcard)
+ .Distinct()
+ .ToImmutableArray();
+ }
+ }
+
+ ImmutableArray IAcmeDnsRequest.Hosts => hostsList;
+ ImmutableArray IAcmeDnsRequest.Domains => domainsList;
+
+ [Required]
public string PFXPassword { get; set; }
+ [Required]
public CsrInfo CsrInfo { get; set; }
}
public interface IAcmeDnsRequest
{
///
- /// The email to register with lets encrypt with. Will recieve notifications on expiring certificates.
+ /// The email to register with lets encrypt with. Will recieve notifications on expiring certificates.
///
string RegistrationEmail { get; }
+
///
- /// The ACME environment, use or or provide you own ACME compatible endpoint by implementing .
+ /// The ACME environment, use or or provide you own ACME
+ /// compatible endpoint by implementing .
///
AcmeEnvironment AcmeEnvironment { get; }
///
- /// The host name to request a certificate for e.g. *.example.com
+ /// A list of host names to request a certificate for without a wildcard symbol e.g. example.com
+ ///
+ ImmutableArray Hosts { get; }
+
+ ///
+ /// A list of domain zones for which certificate will be requested
///
- string Host { get; }
+ ImmutableArray Domains { get; }
string PFXPassword { get; }
@@ -51,10 +105,15 @@ public interface IAcmeDnsRequest
public class CsrInfo
{
public string CountryName { get; set; }
+
public string State { get; set; }
+
public string Locality { get; set; }
+
public string Organization { get; set; }
- public string OrganizationUnit { get; set; }
+
+ public string OrganizationUnit { get; set; }
+
public string CommonName { get; set; }
}
@@ -62,22 +121,15 @@ public class AcmeEnvironment
{
public Uri BaseUri { get; set; }
- public AcmeEnvironment()
- {
+ public AcmeEnvironment() { }
- }
+ public AcmeEnvironment(Uri uri) { this.BaseUri = uri; }
- public AcmeEnvironment(Uri uri)
- {
- this.BaseUri = uri;
- }
protected string name;
+
public string Name
{
- get
- {
- return name;
- }
+ get => name;
set
{
if ("production".Equals(value, StringComparison.InvariantCultureIgnoreCase))
@@ -97,16 +149,12 @@ public string Name
public class LetsEncryptStagingV2 : AcmeEnvironment
{
public LetsEncryptStagingV2() : base(WellKnownServers.LetsEncryptStagingV2)
- {
- this.name = "staging";
- }
+ => this.name = "staging";
}
public class LetsEncryptV2 : AcmeEnvironment
{
public LetsEncryptV2() : base(WellKnownServers.LetsEncryptV2)
- {
- this.name = "production";
- }
+ => this.name = "production";
}
}
diff --git a/src/LetsEncrypt.Azure.Core.V2/Models/AzureDnsSettings.cs b/src/LetsEncrypt.Azure.Core.V2/Models/AzureDnsSettings.cs
index 629e341..ff8b5e0 100644
--- a/src/LetsEncrypt.Azure.Core.V2/Models/AzureDnsSettings.cs
+++ b/src/LetsEncrypt.Azure.Core.V2/Models/AzureDnsSettings.cs
@@ -1,33 +1,35 @@
using System;
using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.ComponentModel.DataAnnotations;
using System.Text;
namespace LetsEncrypt.Azure.Core.V2.Models
{
public class AzureDnsSettings
- {
+ {
public AzureDnsSettings()
{
this.RelativeRecordSetName = "@";
}
- public AzureDnsSettings(string resourceGroupName, string zoneName, AzureServicePrincipal servicePrincipal, AzureSubscription azureSubscription, string relativeRecordName = "@")
+ public AzureDnsSettings(string resourceGroupName, AzureServicePrincipal servicePrincipal, AzureSubscription azureSubscription, string relativeRecordName = "@")
{
this.AzureSubscription = azureSubscription;
this.AzureServicePrincipal = servicePrincipal;
this.ResourceGroupName = resourceGroupName;
- this.ZoneName = zoneName;
this.RelativeRecordSetName = resourceGroupName;
}
- public AzureServicePrincipal AzureServicePrincipal {get;set;}
- public AzureSubscription AzureSubscription { get; set; }
-
- public string ResourceGroupName { get; set; }
+ [Required]
+ public AzureServicePrincipal AzureServicePrincipal { get; set; }
- public string RelativeRecordSetName { get; set; }
+ [Required]
+ public AzureSubscription AzureSubscription { get; set; }
- public string ZoneName { get; set; }
+ [Required]
+ public string ResourceGroupName { get; set; }
+ public string RelativeRecordSetName { get; set; }
}
}
diff --git a/src/LetsEncrypt.Azure.Core.V2/Models/AzureServicePrincipal.cs b/src/LetsEncrypt.Azure.Core.V2/Models/AzureServicePrincipal.cs
index f38f7fb..3382e58 100644
--- a/src/LetsEncrypt.Azure.Core.V2/Models/AzureServicePrincipal.cs
+++ b/src/LetsEncrypt.Azure.Core.V2/Models/AzureServicePrincipal.cs
@@ -5,7 +5,7 @@
namespace LetsEncrypt.Azure.Core.V2.Models
{
- public class AzureServicePrincipal
+ public class AzureServicePrincipal
{
public bool UseManagendIdentity { get; set; }
public string ClientId { get; set; }
diff --git a/src/LetsEncrypt.Azure.Core.V2/Models/AzureSubscription.cs b/src/LetsEncrypt.Azure.Core.V2/Models/AzureSubscription.cs
index 76ef381..9b14f12 100644
--- a/src/LetsEncrypt.Azure.Core.V2/Models/AzureSubscription.cs
+++ b/src/LetsEncrypt.Azure.Core.V2/Models/AzureSubscription.cs
@@ -1,13 +1,18 @@
-namespace LetsEncrypt.Azure.Core.V2.Models
+using System.ComponentModel.DataAnnotations;
+
+namespace LetsEncrypt.Azure.Core.V2.Models
{
public class AzureSubscription
{
+ [Required]
public string Tenant { get; set; }
+ [Required]
public string SubscriptionId { get; set; }
///
/// Should be AzureGlobalCloud, AzureChinaCloud, AzureUSGovernment or AzureGermanCloud
///
- public string AzureRegion { get; set; }
+ [Required]
+ public string AzureRegion { get; set; }
}
}
\ No newline at end of file
diff --git a/src/LetsEncrypt.Azure.Core.V2/Models/AzureWebAppSettings.cs b/src/LetsEncrypt.Azure.Core.V2/Models/AzureWebAppSettings.cs
index e9276df..1037b1e 100644
--- a/src/LetsEncrypt.Azure.Core.V2/Models/AzureWebAppSettings.cs
+++ b/src/LetsEncrypt.Azure.Core.V2/Models/AzureWebAppSettings.cs
@@ -1,11 +1,11 @@
-namespace LetsEncrypt.Azure.Core.V2.Models
+using System.ComponentModel.DataAnnotations;
+
+namespace LetsEncrypt.Azure.Core.V2.Models
{
public class AzureWebAppSettings
{
- public AzureWebAppSettings()
- {
+ public AzureWebAppSettings() { }
- }
public AzureWebAppSettings(string webappName, string resourceGroup, AzureServicePrincipal servicePrincipal, AzureSubscription azureSubscription, string siteSlotName = null, string servicePlanResourceGroupName = null, bool useIPBasedSSL = false)
{
this.WebAppName = webappName;
@@ -16,7 +16,11 @@ public AzureWebAppSettings(string webappName, string resourceGroup, AzureService
this.ServicePlanResourceGroupName = servicePlanResourceGroupName;
this.UseIPBasedSSL = useIPBasedSSL;
}
+
+ [Required]
public string WebAppName { get; set; }
+
+ [Required]
public string ResourceGroupName { get; set; }
public string ServicePlanResourceGroupName { get; set; }
@@ -25,8 +29,10 @@ public AzureWebAppSettings(string webappName, string resourceGroup, AzureService
public bool UseIPBasedSSL { get; set; }
+ [Required]
public AzureServicePrincipal AzureServicePrincipal { get; set; }
+ [Required]
public AzureSubscription AzureSubscription { get; set; }
}
}
diff --git a/src/LetsEncrypt.Azure.Core.V2/Models/CertificateInstallModel.cs b/src/LetsEncrypt.Azure.Core.V2/Models/CertificateInstallModel.cs
index 7b68b05..ffdeeb8 100644
--- a/src/LetsEncrypt.Azure.Core.V2/Models/CertificateInstallModel.cs
+++ b/src/LetsEncrypt.Azure.Core.V2/Models/CertificateInstallModel.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Text;
namespace LetsEncrypt.Azure.Core.V2.Models
@@ -12,24 +13,18 @@ public class CertificateInstallModel : ICertificateInstallModel
///
/// Certificate info.
///
- public CertificateInfo CertificateInfo
- {
- get; set;
- }
+ public CertificateInfo CertificateInfo { get; set; }
///
- /// The primary host name.
+ /// The primary host name.
///
- public string Host
- {
- get; set;
- }
+ public ImmutableArray Hosts { get; set; }
}
public interface ICertificateInstallModel
{
CertificateInfo CertificateInfo { get; set; }
- string Host { get; set; }
- }
+ ImmutableArray Hosts { get; set; }
+ }
}
diff --git a/src/LetsEncrypt.Azure.ResourceGroup/Templates/letsencrypt.functionapp.renewer.json b/src/LetsEncrypt.Azure.ResourceGroup/Templates/letsencrypt.functionapp.renewer.json
index 7a0c588..9ff8d87 100644
--- a/src/LetsEncrypt.Azure.ResourceGroup/Templates/letsencrypt.functionapp.renewer.json
+++ b/src/LetsEncrypt.Azure.ResourceGroup/Templates/letsencrypt.functionapp.renewer.json
@@ -36,12 +36,6 @@
"description": "The name of the web application that should have the SSL Cert assigned"
}
},
- "dnsZoneName": {
- "type": "string",
- "metadata": {
- "description": "The DNS zone name in azure, e.g. yourwebsite.com"
- }
- },
"dnsResourceGroupName": {
"type": "string",
"metadata": {
@@ -97,10 +91,10 @@
},
"defaultValue": "staging"
},
- "certificateDomain": {
+ "certificateDomains": {
"type": "string",
"metadata": {
- "description": "The domain name to request a certificate for e.g. *.yourdomain.com"
+ "description": "The coma separated domain names to request a certificate for e.g. *.yourdomain.com"
}
},
"pfxPass": {
@@ -261,7 +255,6 @@
"AzureAppService__AzureSubscription__Tenant": "[subscription().tenantId]",
"AzureAppService__AzureSubscription__SubscriptionId": "[subscription().subscriptionId]",
"AzureAppService__AzureSubscription__AzureRegion": "AzureGlobalCloud",
- "DnsSettings__ZoneName": "[parameters('dnsZoneName')]",
"DnsSettings__ResourceGroupName": "[parameters('dnsResourceGroupName')]",
"DnsSettings__AzureServicePrincipal__UseManagendIdentity": "true",
"DnsSettings__AzureSubscription__Tenant": "[subscription().tenantId]",
@@ -270,7 +263,7 @@
"AcmeDnsRequest__CsrInfo__Organization": "[parameters('csrOrganisation')]",
"AcmeDnsRequest__RegistrationEmail": "[parameters('acmeRegistrationEmail')]",
"AcmeDnsRequest__AcmeEnvironment__Name": "[parameters('acmeEnvironment')]",
- "AcmeDnsRequest__Host": "[parameters('certificateDomain')]",
+ "AcmeDnsRequest__Hosts": "[parameters('certificateDomains')]",
"AcmeDnsRequest__PFXPassword": "[concat('@Microsoft.KeyVault(SecretUri=', reference(resourceId('Microsoft.KeyVault/vaults/secrets', parameters('vaultName'), variables('pfxPass'))).secretUriWithVersion, ')')]",
"APPINSIGHTS_INSTRUMENTATIONKEY": "[reference(concat('microsoft.insights/components/', variables('appInsightsName'))).InstrumentationKey]",
"WEBSITE_RUN_FROM_PACKAGE": "[parameters('runFromPackage')]",
diff --git a/src/LetsEncrypt.Azure.ResourceGroup/Templates/letsencrypt.functionapp.renewer.parameters.json b/src/LetsEncrypt.Azure.ResourceGroup/Templates/letsencrypt.functionapp.renewer.parameters.json
index 272758d..d54b839 100644
--- a/src/LetsEncrypt.Azure.ResourceGroup/Templates/letsencrypt.functionapp.renewer.parameters.json
+++ b/src/LetsEncrypt.Azure.ResourceGroup/Templates/letsencrypt.functionapp.renewer.parameters.json
@@ -7,11 +7,10 @@
"hostingPlanName": { "value": "letsencryptrunner" },
"targetWebAppResourceGroupName": { "value": "LetsEncrypt.Wildcard.Function" },
"targetWebAppName": { "value": "sjkpletsencrypt" },
- "dnsZoneName": { "value": "ai4bots.com" },
"dnsResourceGroupName": { "value": "dns" },
"csrOrganisation": { "value": "sjkp" },
"acmeRegistrationEmail": { "value": "mail@sjkp.dk" },
- "certificateDomain": { "value": "*.ai4bots.com" },
+ "certificateDomains": { "value": "*.ai4bots.com" },
"runFromPackage": { "value": "https://letsencryptazure.blob.core.windows.net/releases/126.zip" }
}
}
diff --git a/src/LetsEncrypt.Azure.ResourceGroup/bin/Debug/staging/LetsEncrypt.Azure.ResourceGroup/Templates/letsencrypt.azure.core.json b/src/LetsEncrypt.Azure.ResourceGroup/bin/Debug/staging/LetsEncrypt.Azure.ResourceGroup/Templates/letsencrypt.azure.core.json
deleted file mode 100644
index fc24e36..0000000
--- a/src/LetsEncrypt.Azure.ResourceGroup/bin/Debug/staging/LetsEncrypt.Azure.ResourceGroup/Templates/letsencrypt.azure.core.json
+++ /dev/null
@@ -1,194 +0,0 @@
-{
- "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
- "contentVersion": "1.0.0.0",
- "parameters": {
- "keyVaultName": {
- "type": "string",
- "metadata": {
- "description": "Name of the Vault"
- }
- },
- "tenantId": {
- "type": "string",
- "metadata": {
- "description": "Tenant Id of the subscription. Get using Get-AzureRmSubscription cmdlet or Get Subscription API"
- }
- },
- "objectId": {
- "type": "string",
- "metadata": {
- "description": "Object Id of the AD user. Get using Get-AzureRmADUser or Get-AzureRmADServicePrincipal cmdlets"
- }
- },
- "keysPermissions": {
- "type": "array",
- "defaultValue": ["all"],
- "metadata": {
- "description": "Permissions to keys in the vault. Valid values are: all, create, import, update, get, list, delete, backup, restore, encrypt, decrypt, wrapkey, unwrapkey, sign, and verify."
- }
- },
- "secretsPermissions": {
- "type": "array",
- "defaultValue": ["all"],
- "metadata": {
- "description": "Permissions to secrets in the vault. Valid values are: all, get, set, list, and delete."
- }
- },
- "skuName": {
- "type": "string",
- "defaultValue": "Standard",
- "allowedValues": [
- "Standard",
- "Premium"
- ],
- "metadata": {
- "description": "SKU for the vault"
- }
- },
- "enableVaultForDeployment": {
- "type": "bool",
- "defaultValue": false,
- "allowedValues": [
- true,
- false
- ],
- "metadata": {
- "description": "Specifies if the vault is enabled for a VM deployment"
- }
- },
- "enableVaultForDiskEncryption": {
- "type": "bool",
- "defaultValue": false,
- "allowedValues": [
- true,
- false
- ],
- "metadata": {
- "description": "Specifies if the azure platform has access to the vault for enabling disk encryption scenarios."
- }
- },
- "enabledForTemplateDeployment": {
- "type": "bool",
- "defaultValue": false,
- "allowedValues": [
- true,
- false
- ],
- "metadata": {
- "description": "Specifies whether Azure Resource Manager is permitted to retrieve secrets from the key vault."
- }
- },
- "appName": {
- "type": "string",
- "metadata": {
- "description": "The name of the function app that you wish to create."
- }
- },
- "storageAccountType": {
- "type": "string",
- "defaultValue": "Standard_LRS",
- "allowedValues": [
- "Standard_LRS",
- "Standard_GRS",
- "Standard_ZRS",
- "Premium_LRS"
- ],
- "metadata": {
- "description": "Storage Account type"
- }
- }
- },
- "variables": {
- "functionAppName": "[parameters('appName')]",
- "hostingPlanName": "[parameters('appName')]",
- "storageAccountName": "[concat(uniquestring(resourceGroup().id), 'azfunctions')]",
- "armResourceProvideServicePrincipalId": "abfa0a7c-a6b6-4736-8310-5855508787cd"
- },
- "resources": [
- {
- "type": "Microsoft.KeyVault/vaults",
- "name": "[parameters('keyVaultName')]",
- "apiVersion": "2015-06-01",
- "location": "[resourceGroup().location]",
- "properties": {
- "enabledForDeployment": "[parameters('enableVaultForDeployment')]",
- "enabledForDiskEncryption": "[parameters('enableVaultForDiskEncryption')]",
- "enabledForTemplateDeployment": "[parameters('enabledForTemplateDeployment')]",
- "tenantId": "[parameters('tenantId')]",
- "accessPolicies": [
- {
- "tenantId": "[parameters('tenantId')]",
- "objectId": "[parameters('objectId')]",
- "permissions": {
- "keys": "[parameters('keysPermissions')]",
- "secrets": "[parameters('secretsPermissions')]"
- }
- },
- {
- "tenantId": "[parameters('tenantId')]",
- "objectId": "[variables('armResourceProvideServicePrincipalId')]",
- "permissions": {
- "keys": "[parameters('keysPermissions')]",
- "secrets": "[parameters('secretsPermissions')]"
- }
- }
- ],
- "sku": {
- "name": "[parameters('skuName')]",
- "family": "A"
- }
- }
- },
- {
- "type": "Microsoft.Storage/storageAccounts",
- "name": "[variables('storageAccountName')]",
- "apiVersion": "2015-06-15",
- "location": "[resourceGroup().location]",
- "properties": {
- "accountType": "[parameters('storageAccountType')]"
- }
- },
- {
- "type": "Microsoft.Web/serverfarms",
- "apiVersion": "2015-04-01",
- "name": "[variables('hostingPlanName')]",
- "location": "[resourceGroup().location]",
- "properties": {
- "name": "[variables('hostingPlanName')]",
- "computeMode": "Dynamic",
- "sku": "Dynamic"
- }
- },
- {
- "apiVersion": "2015-08-01",
- "type": "Microsoft.Web/sites",
- "name": "[variables('functionAppName')]",
- "location": "[resourceGroup().location]",
- "kind": "functionapp",
- "properties": {
- "name": "[variables('functionAppName')]",
- "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]"
- },
- "dependsOn": [
- "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
- "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
- ],
- "resources": [
- {
- "apiVersion": "2016-03-01",
- "name": "appsettings",
- "type": "config",
- "dependsOn": [
- "[resourceId('Microsoft.Web/sites', variables('functionAppName'))]",
- "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
- ],
- "properties": {
- "AzureWebJobsStorage": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listkeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2015-05-01-preview').key1,';')]",
- "AzureWebJobsDashboard": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listkeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2015-05-01-preview').key1,';')]",
- "FUNCTIONS_EXTENSION_VERSION": "latest"
- }
- }
- ]
- }
- ]
-}
\ No newline at end of file
diff --git a/src/LetsEncrypt.Azure.ResourceGroup/bin/Debug/staging/LetsEncrypt.Azure.ResourceGroup/Templates/letsencrypt.azure.core.parameters.json b/src/LetsEncrypt.Azure.ResourceGroup/bin/Debug/staging/LetsEncrypt.Azure.ResourceGroup/Templates/letsencrypt.azure.core.parameters.json
deleted file mode 100644
index 9fb9226..0000000
--- a/src/LetsEncrypt.Azure.ResourceGroup/bin/Debug/staging/LetsEncrypt.Azure.ResourceGroup/Templates/letsencrypt.azure.core.parameters.json
+++ /dev/null
@@ -1,40 +0,0 @@
-{
- "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
- "contentVersion": "1.0.0.0",
- "parameters": {
- "keyVaultName": {
- "value": "letsencrypt-vault"
- },
- "tenantId": {
- "value": ""
- },
- "objectId": {
- "value": ""
- },
- "keysPermissions": {
- "value": [
- "all"
- ]
- },
- "secretsPermissions": {
- "value": [
- "all"
- ]
- },
- "skuName": {
- "value": "Standard"
- },
- "enableVaultForDeployment": {
- "value": false
- },
- "enableVaultForDiskEncryption": {
- "value": false
- },
- "enabledForTemplateDeployment": {
- "value": false
- },
- "appName": {
- "value": "letsencryptfunctionapp"
- }
- }
-}
\ No newline at end of file
diff --git a/src/LetsEncrypt.Azure.Runner/LetsEncrypt.Azure.Runner.csproj b/src/LetsEncrypt.Azure.Runner/LetsEncrypt.Azure.Runner.csproj
index 1e03c06..27f050f 100644
--- a/src/LetsEncrypt.Azure.Runner/LetsEncrypt.Azure.Runner.csproj
+++ b/src/LetsEncrypt.Azure.Runner/LetsEncrypt.Azure.Runner.csproj
@@ -2,15 +2,15 @@
Exe
- netcoreapp2.1
+ netcoreapp2.2
7.2
-
-
-
-
+
+
+
+
diff --git a/src/LetsEncrypt.Azure.Runner/Program.cs b/src/LetsEncrypt.Azure.Runner/Program.cs
index f4289df..596a302 100644
--- a/src/LetsEncrypt.Azure.Runner/Program.cs
+++ b/src/LetsEncrypt.Azure.Runner/Program.cs
@@ -22,7 +22,7 @@ async static Task Main(string[] args)
.Build();
var azureAppSettings = new AzureWebAppSettings[] { };
-
+
if (Configuration.GetSection("AzureAppService").Exists())
{
azureAppSettings = new[] { Configuration.GetSection("AzureAppService").Get() };
@@ -43,16 +43,18 @@ async static Task Main(string[] args)
c.AddConsole();
//c.AddDebug();
})
- .Configure(options => options.MinLevel = LogLevel.Information)
+ .Configure(options => options.MinLevel = LogLevel.Information)
.AddAzureAppService(azureAppSettings);
if (Configuration.GetSection("DnsSettings").Get().ShopperId != null)
{
serviceCollection.AddAcmeClient(Configuration.GetSection("DnsSettings").Get());
- } else if (Configuration.GetSection("DnsSettings").Get().AccountName != null)
+ }
+ else if (Configuration.GetSection("DnsSettings").Get().AccountName != null)
{
serviceCollection.AddAcmeClient(Configuration.GetSection("DnsSettings").Get());
- } else if (Configuration.GetSection("DnsSettings").Get().ResourceGroupName != null)
+ }
+ else if (Configuration.GetSection("DnsSettings").Get().ResourceGroupName != null)
{
serviceCollection.AddAcmeClient(Configuration.GetSection("DnsSettings").Get());
}