Skip to content
This repository was archived by the owner on Apr 24, 2025. It is now read-only.

Commit 5894e19

Browse files
authored
Optimize cert loading/rewewal checks on server startup (#205)
* fix: check if certificate needs renewal immediately on startup instead waiting 24 hours * fix: optimize loading intermediate certificate change and reduce unnecessary warnings about invalid certs
1 parent e5f5089 commit 5894e19

File tree

5 files changed

+34
-13
lines changed

5 files changed

+34
-13
lines changed

src/LettuceEncrypt/Internal/AcmeStates/CheckForRenewalState.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,6 @@ public override async Task<IAcmeState> MoveNextAsync(CancellationToken cancellat
4242
return MoveTo<TerminalState>();
4343
}
4444

45-
await Task.Delay(checkPeriod.Value, cancellationToken);
46-
4745
var domainNames = _options.Value.DomainNames;
4846
if (_logger.IsEnabled(LogLevel.Debug))
4947
{
@@ -60,6 +58,8 @@ public override async Task<IAcmeState> MoveNextAsync(CancellationToken cancellat
6058
return MoveTo<BeginCertificateCreationState>();
6159
}
6260
}
61+
62+
await Task.Delay(checkPeriod.Value, cancellationToken);
6363
}
6464

6565
return MoveTo<TerminalState>();

src/LettuceEncrypt/Internal/CertificateSelector.cs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,18 @@ public CertificateSelector(IOptions<LettuceEncryptOptions> options, ILogger<Cert
3434

3535
public virtual void Add(X509Certificate2 certificate)
3636
{
37-
PreloadIntermediateCertificates(certificate);
38-
37+
var preloaded = false;
3938
foreach (var dnsName in X509CertificateHelpers.GetAllDnsNames(certificate))
4039
{
41-
AddWithDomainName(_certs, dnsName, certificate);
40+
var selectedCert = AddWithDomainName(_certs, dnsName, certificate);
41+
42+
// Call preload once per certificate, but only if the cetificate is actually selected to be used
43+
// for this domain. This is a small optimization which avoids preloading on a cert that may not be used.
44+
if (!preloaded && selectedCert == certificate)
45+
{
46+
preloaded = true;
47+
PreloadIntermediateCertificates(selectedCert);
48+
}
4249
}
4350
}
4451

@@ -55,10 +62,17 @@ public void ClearChallengeCert(string domainName)
5562
_challengeCerts.TryRemove(domainName, out _);
5663
}
5764

58-
private void AddWithDomainName(ConcurrentDictionary<string, X509Certificate2> certs, string domainName,
65+
/// <summary>
66+
/// Registers the certificate for usage with domain unless there is already a newer cert for this domain.
67+
/// </summary>
68+
/// <param name="certs"></param>
69+
/// <param name="domainName"></param>
70+
/// <param name="certificate"></param>
71+
/// <returns>The certificate current selected to be used for this domain</returns>
72+
private X509Certificate2 AddWithDomainName(ConcurrentDictionary<string, X509Certificate2> certs, string domainName,
5973
X509Certificate2 certificate)
6074
{
61-
certs.AddOrUpdate(
75+
return certs.AddOrUpdate(
6276
domainName,
6377
certificate,
6478
(_, currentCert) =>

src/LettuceEncrypt/Internal/StartupCertificateLoader.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Security.Cryptography.X509Certificates;
57
using System.Threading;
68
using System.Threading.Tasks;
79
using Microsoft.Extensions.Hosting;
@@ -23,14 +25,17 @@ public StartupCertificateLoader(
2325

2426
public async Task StartAsync(CancellationToken cancellationToken)
2527
{
28+
var allCerts = new List<X509Certificate2>();
2629
foreach (var certSource in _certSources)
2730
{
2831
var certs = await certSource.GetCertificatesAsync(cancellationToken);
32+
allCerts.AddRange(certs);
33+
}
2934

30-
foreach (var cert in certs)
31-
{
32-
_selector.Add(cert);
33-
}
35+
// Add newer certificates first. This avoid potentially unnecessary cert validations on older certificates
36+
foreach (var cert in allCerts.OrderByDescending(c => c.NotAfter))
37+
{
38+
_selector.Add(cert);
3439
}
3540
}
3641

src/LettuceEncrypt/releasenotes.props

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ Bug fixes:
1414
Other:
1515
* Update package to target .NET Core 3.1 as 3.0 is no longer supported by Microsoft
1616

17-
Bug patch 1.1.1:
17+
Fixes in patch 1.1.1:
1818
* Fix infinite loop waiting for verification of domain ownership
19+
* Check for certificates that new renewal immediately on server startup instead of waiting 24 hours
20+
* Optimize loading intermediate certificate change and reduce unnecessary warnings about invalid certs
1921
</PackageReleaseNotes>
2022
<PackageReleaseNotes Condition="'$(VersionPrefix)' == '1.0.1'">
2123
* Fix bug in detecting Kestrel in .NET 5

test/LettuceEncrypt.UnitTests/StartupCertificateLoaderTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public class StartupCertificateLoaderTests
1818
[Fact]
1919
public async Task ItLoadsAllCertsIntoSelector()
2020
{
21-
var testCert = new X509Certificate2();
21+
var testCert = TestUtils.CreateTestCert("test1.natemcmaster.com");
2222
IEnumerable<X509Certificate2> certs = new[] { testCert };
2323

2424
var selector = new Mock<CertificateSelector>(

0 commit comments

Comments
 (0)