diff --git a/src/WebAppSSLManager/AzureHelper.cs b/src/WebAppSSLManager/AzureHelper.cs index f62abe2..4f003f7 100644 --- a/src/WebAppSSLManager/AzureHelper.cs +++ b/src/WebAppSSLManager/AzureHelper.cs @@ -136,8 +136,11 @@ public static async Task NeedsNewCertificateAsync() { ResourceConfiguration resource = await GetResourceConfigurationAsync(); + // Not filtering by issuer here: Let's Encrypt rotates its intermediate CA name over time + // (was "Let's Encrypt Authority X3", then "R3", now "R11"/"R12"/"R13"/"YR1", etc.), so any + // hardcoded issuer match goes stale. ExistingCertificates is already scoped to this hostname + // by GetResourceConfigurationAsync, so picking the latest-expiring one here is sufficient. IAppServiceCertificate existingCert = resource.ExistingCertificates? - .Where(c => c.Issuer.Contains(Constants.DefaultCA)) .OrderByDescending(c => c.ExpirationDate) .FirstOrDefault(); diff --git a/src/WebAppSSLManager/CertificatesHelper.cs b/src/WebAppSSLManager/CertificatesHelper.cs index d2ae73b..023c42c 100644 --- a/src/WebAppSSLManager/CertificatesHelper.cs +++ b/src/WebAppSSLManager/CertificatesHelper.cs @@ -125,6 +125,13 @@ public static async Task GetCertificateAsync() await AzureHelper.RemoveDNSVerificationTXTRecord(recordName); //to be sure we start clean await AzureHelper.CreateDNSVerificationTXTRecord(recordName, dnsTxt); + // Validating immediately after creating the TXT record races Azure DNS propagation: Let's + // Encrypt can check before the record is live, which fails the challenge and flips the + // authorization out of "pending" - so every retry after that first failure errors with + // "authorization must be pending" instead of actually retrying. Give DNS time to propagate first. + _logger.LogInformation($" Waiting {Settings.WaitTimeBeforeValidate.TotalSeconds}s for DNS propagation before validating..."); + await Task.Delay(Settings.WaitTimeBeforeValidate); + _logger.LogInformation($" Validating DNS authorization challenge. Can take up to 90 seconds..."); var validatedChallege = await dnsChallenge.Validate(); var waitUntil = DateTime.Now.AddSeconds(90); diff --git a/src/WebAppSSLManager/Models/Constants.cs b/src/WebAppSSLManager/Models/Constants.cs index 805de43..febe50f 100644 --- a/src/WebAppSSLManager/Models/Constants.cs +++ b/src/WebAppSSLManager/Models/Constants.cs @@ -13,5 +13,6 @@ public class Constants public const string DefaultCA = "Let's Encrypt Authority"; public const int DefaultBatchSize = 0; public static readonly TimeSpan DefaultTimeBeforeExpiryToRenewCertificate = TimeSpan.FromDays(30); + public static readonly TimeSpan DefaultWaitTimeBeforeValidate = TimeSpan.FromSeconds(15); } } diff --git a/src/WebAppSSLManager/Models/Settings.cs b/src/WebAppSSLManager/Models/Settings.cs index 6abce1a..9d39201 100644 --- a/src/WebAppSSLManager/Models/Settings.cs +++ b/src/WebAppSSLManager/Models/Settings.cs @@ -18,6 +18,7 @@ public static class Settings public static bool UseStaging { get; private set; } public static int BatchSize { get; private set; } public static TimeSpan TimeBeforeExpiryToRenew { get; private set; } + public static TimeSpan WaitTimeBeforeValidate { get; private set; } public static void Init(ILogger logger) { @@ -97,6 +98,14 @@ public static void Init(ILogger logger) _logger.LogWarning("TimeBeforeExpiryToRenew environment variable is null or invalid. Reverting to default"); TimeBeforeExpiryToRenew = Constants.DefaultTimeBeforeExpiryToRenewCertificate; } + + if (int.TryParse(Environment.GetEnvironmentVariable("WaitTimeBeforeValidateInSeconds"), out int waitSeconds) && waitSeconds >= 0) + WaitTimeBeforeValidate = TimeSpan.FromSeconds(waitSeconds); + else + { + _logger.LogWarning("WaitTimeBeforeValidateInSeconds environment variable is null or invalid. Reverting to default"); + WaitTimeBeforeValidate = Constants.DefaultWaitTimeBeforeValidate; + } } } }