无需重启服务器 kestrel 即可重新加载证书

问题描述 投票:0回答:2

我使用每 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 好吗?

如果你有什么想法可以解决我的问题,我想阅读它,谢谢大家。

c# asp.net-core ssl-certificate kestrel-http-server kestrel
2个回答
0
投票

将证书放在静态变量中。将第二个 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;
    }
}

0
投票

我的解决方案使用来自 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"));
© www.soinside.com 2019 - 2024. All rights reserved.