我使用每 90 天更新一次的 Let's Encrypt 证书,我想知道是否有一种方法可以在不重新启动 kestrel 服务器的情况下重新加载证书。
我在 github 上试过这个答案,但我就是不明白,或者我无法让它工作: https://github.com/aspnet/KestrelHttpServer/issues/2967
现在我有这个代码:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseKestrel(options =>
{
options.Listen(IPAddress.Any, 80);
options.Listen(IPAddress.Any, 443, listenOptions =>
{
listenOptions.UseHttps(httpsOptions =>
{
httpsOptions.ServerCertificateSelector = (context, dnsName) =>
{
var certificate = new X509Certificate2("C:\\Users\\jserra\\Desktop\\folder1\\Cert1.pfx", "`P@ssw0rd");
return certificate;
};
});
});
}).UseStartup<Startup>();
});
}
}
此代码为每个请求创建一个新的 var 证书。
所以我有 2 个证书,Cert1.pfx 和 Cert2.pfx。
首先我尝试发出请求,它加载 Cert1,然后在运行时使用文件资源管理器将 Cert1.pfx 重命名为 Cert1.pfx.old.
然后我将 Cert2.pfx 重命名为 Cert1.pfx,我提出了一个新的请求,这次服务器使用的是新证书。
这有意义吗?每个新请求都创建一个新的 var certificate = Cert1.pfx 好吗?
如果你有什么想法可以解决我的问题,我想阅读它,谢谢大家。
将证书放在静态变量中。将第二个 DateTime 存储在另一个静态变量中。当日期时间变量早于阈值时,从文件重新加载证书。
public static class CertificateCache
{
private const string certificatePath = "C:\\Users\\jserra\\Desktop\\folder1\\Cert1.pfx";
// password should be loaded from environment variable or configuration
private const string password = "`P@ssw0rd";
// cache certificate for one day
private static readonly TimeSpan cacheTime = TimeSpan.FromDays(1.0);
private static readonly object certificateLock = new();
private static X509Certificate2 certificate;
private static DateTime lastCertificateLoad;
public static X509Certificate2 GetCertificate()
{
if (DateTime.UtcNow - lastCertificateLoad > cacheTime)
{
lock (certificateLock)
{
// in case multiple callers got through the first if statement, check again
if (DateTime.UtcNow - lastCertificateLoad > cacheTime)
{
try
{
var oldCertificate = certificate;
certificate = new X509Certificate2(certificatePath, password);
lastCertificateLoad = DateTime.UtcNow;
// dispose of old certificate after 1 minute to eliminate memory leaks, this allows any connections using it to finish up cleanly
if (oldCertificate is not null)
{
Task.Run(async () =>
{
await Task.Delay(60000);
oldCertificate.Dispose();
});
}
}
catch (Exception ex)
{
// manual intervention probably required, send an email or high priority notification
}
}
}
}
return certificate;
}
}
我的解决方案使用来自 ASP.NET 和 PEM 证书的 IMemoryCache。
主要逻辑封装在
HttpsConfigurationService
:
public class HttpsConfigurationService : IHttpsConfigurationService
{
private readonly ILogger<HttpsConfigurationService> _logger;
private readonly IMemoryCache _memoryCache;
private readonly CertificateOptions _certificateOptions;
private const string CertificateKey = "HttpsCertificate";
public HttpsConfigurationService(ILogger<HttpsConfigurationService> logger,
IMemoryCache memoryCache, IOptions<CertificateOptions> certificateOptions)
{
_logger = logger;
_memoryCache = memoryCache;
_certificateOptions = certificateOptions.Value;
}
public void ConfigureKestrel(KestrelServerOptions serverOptions)
{
if(string.IsNullOrEmpty(_certificateOptions?.Path))
{
_logger.LogError("Certificate Path is missing");
return;
}
if (string.IsNullOrEmpty(_certificateOptions?.KeyPath))
{
_logger.LogError("Certificate KeyPath is missing");
return;
}
serverOptions.Listen(IPAddress.Any, 443, listenOptions =>
{
listenOptions.UseHttps(httpsOptions =>
{
httpsOptions.ServerCertificateSelector = (context, dnsName) =>
{
var certificate = _memoryCache.GetOrCreate(
CertificateKey,
cacheEntry =>
{
cacheEntry.AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(30);
var newCertificate = X509Certificate2.CreateFromPemFile(_certificateOptions.Path, _certificateOptions.KeyPath);
_logger.LogInformation("Certificate was loaded from the disk");
return newCertificate;
});
return certificate;
};
});
});
}
}
在
Program.cs
您可以使用此服务:
builder.ConfigureWebHost((b) =>
{
b.ConfigureKestrel((context, serverOptions) =>
{
var httpsConfigurationService = serverOptions.ApplicationServices.GetService<IHttpsConfigurationService>();
httpsConfigurationService.ConfigureKestrel(serverOptions);
});
});
不要忘记在
Startup.cs
中注册服务:
services.AddTransient<IHttpsConfigurationService, HttpsConfigurationService>();
对于配置,我创建了一个类
CertificateOptions
:
public class CertificateOptions
{
public string Path { get; set; }
public string KeyPath { get; set; }
}
并在
Startup.cs
中注册:
services.Configure<CertificateOptions>(config.GetSection("Kestrel:Certificates:Default"));