diff --git a/FastGithub.Dns/DnsHostedService.cs b/FastGithub.Dns/DnsHostedService.cs index db2426a..5c268c6 100644 --- a/FastGithub.Dns/DnsHostedService.cs +++ b/FastGithub.Dns/DnsHostedService.cs @@ -61,16 +61,20 @@ namespace FastGithub.Dns { SystemDnsUtil.DnsSetPrimitive(IPAddress.Loopback); SystemDnsUtil.DnsFlushResolverCache(); - this.logger.LogInformation($"设置为本机主DNS成功"); + this.logger.LogInformation($"设置成本机主DNS成功"); } catch (Exception ex) { - this.logger.LogWarning($"设置为本机主DNS为{IPAddress.Loopback}失败:{ex.Message}"); + this.logger.LogWarning($"设置成本机主DNS为{IPAddress.Loopback}失败:{ex.Message}"); } } + else if (OperatingSystem.IsLinux()) + { + this.logger.LogWarning($"不支持自动设置本机DNS,手工添加{IPAddress.Loopback}做为/etc/resolv.conf的第一条记录"); + } else { - this.logger.LogWarning($"不支持自动设置DNS,请根据你的系统平台情况修改主DNS为{IPAddress.Loopback}"); + this.logger.LogWarning($"不支持自动设置本机DNS,请手工添加{IPAddress.Loopback}做为连接网络的DNS的第一条记录"); } foreach (var item in this.dnsValidators) diff --git a/FastGithub.DomainResolve/TomlUtil.cs b/FastGithub.DomainResolve/TomlUtil.cs index 97541b5..0b9f380 100644 --- a/FastGithub.DomainResolve/TomlUtil.cs +++ b/FastGithub.DomainResolve/TomlUtil.cs @@ -20,7 +20,7 @@ namespace FastGithub.DomainResolve /// public static Task SetListensAsync(string tomlPath, IPEndPoint endpoint, CancellationToken cancellationToken = default) { - return SetAsync(tomlPath, "listen_addresses", $"['{endpoint}']"); + return SetAsync(tomlPath, "listen_addresses", $"['{endpoint}']", cancellationToken); } /// diff --git a/FastGithub.ReverseProxy/CertService.cs b/FastGithub.ReverseProxy/CertService.cs new file mode 100644 index 0000000..75a1656 --- /dev/null +++ b/FastGithub.ReverseProxy/CertService.cs @@ -0,0 +1,165 @@ +using FastGithub.Configuration; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography.X509Certificates; + +namespace FastGithub.ReverseProxy +{ + /// + /// 证书服务 + /// + sealed class CertService + { + private const string CAPATH = "CACert"; + private readonly IMemoryCache serverCertCache; + private readonly ILogger logger; + + /// + /// 私钥长度 + /// + public int KeySizeBits { get; } = 2048; + + /// + /// 获取证书文件路径 + /// + public string CaCerFilePath { get; } = $"{CAPATH}/{nameof(FastGithub)}.cer"; + + /// + /// 获取私钥文件路径 + /// + public string CaKeyFilePath { get; } = $"{CAPATH}/{nameof(FastGithub)}.key"; + + /// + /// 证书服务 + /// + /// + public CertService( + IMemoryCache serverCertCache, + ILogger logger) + { + this.serverCertCache = serverCertCache; + this.logger = logger; + + Directory.CreateDirectory(CAPATH); + } + + /// + /// 获取颁发给指定域名的证书 + /// + /// + /// + public X509Certificate2 GetServerCert(string? domain) + { + var key = $"{nameof(CertService)}:{domain}"; + return this.serverCertCache.GetOrCreate(key, GetOrCreateCert); + + // 生成域名的1年证书 + X509Certificate2 GetOrCreateCert(ICacheEntry entry) + { + var domains = GetDomains(domain).Distinct(); + var validFrom = DateTime.Today.AddDays(-1); + var validTo = DateTime.Today.AddYears(1); + + entry.SetAbsoluteExpiration(validTo); + return CertGenerator.GenerateByCa(domains, this.KeySizeBits, validFrom, validTo, this.CaCerFilePath, this.CaKeyFilePath); + } + } + + /// + /// 获取域名 + /// + /// + /// + private static IEnumerable GetDomains(string? domain) + { + if (string.IsNullOrEmpty(domain) == false) + { + yield return domain; + yield break; + } + + yield return LocalMachine.Name; + foreach (var address in LocalMachine.GetAllIPv4Addresses()) + { + yield return address.ToString(); + } + } + + /// + /// 生成10年的根证书 + /// + public bool GenerateCaCert() + { + if (File.Exists(this.CaCerFilePath) && File.Exists(this.CaKeyFilePath)) + { + return false; + } + + File.Delete(this.CaCerFilePath); + File.Delete(this.CaKeyFilePath); + + var validFrom = DateTime.Today.AddDays(-1); + var validTo = DateTime.Today.AddYears(10); + CertGenerator.GenerateBySelf(new[] { nameof(FastGithub) }, this.KeySizeBits, validFrom, validTo, this.CaCerFilePath, this.CaKeyFilePath); + return true; + } + + /// + /// 安装根证书 + /// + public void InstallCaCert() + { + if (OperatingSystem.IsWindows()) + { + this.InstallCaCertAtWindows(); + } + else if (OperatingSystem.IsLinux()) + { + this.logger.LogWarning($"不支持自动安装证书{this.CaCerFilePath}:请手工安装证书然后设置信任证书"); + } + else if (OperatingSystem.IsMacOS()) + { + this.logger.LogWarning($"不支持自动安装证书{this.CaCerFilePath}:请手工安装证书然后设置信任证书"); + } + else + { + this.logger.LogWarning($"不支持自动安装证书{this.CaCerFilePath}:请根据你的系统平台手工安装和信任证书"); + } + } + + /// + /// 安装根证书 + /// + private void InstallCaCertAtWindows() + { + try + { + using var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine); + store.Open(OpenFlags.ReadWrite); + + var caCert = new X509Certificate2(this.CaCerFilePath); + var subjectName = caCert.Subject[3..]; + foreach (var item in store.Certificates.Find(X509FindType.FindBySubjectName, subjectName, false)) + { + if (item.Thumbprint != caCert.Thumbprint) + { + store.Remove(item); + } + } + if (store.Certificates.Find(X509FindType.FindByThumbprint, caCert.Thumbprint, true).Count == 0) + { + store.Add(caCert); + } + store.Close(); + } + catch (Exception ex) + { + this.logger.LogWarning($"安装证书{this.CaCerFilePath}失败:请手动安装到“将所有的证书都放入下载存储”\\“受信任的根证书颁发机构”", ex); + } + } + } +} diff --git a/FastGithub.ReverseProxy/KestrelServerOptionsExtensions.cs b/FastGithub.ReverseProxy/KestrelServerOptionsExtensions.cs index 77a50dd..aeb380e 100644 --- a/FastGithub.ReverseProxy/KestrelServerOptionsExtensions.cs +++ b/FastGithub.ReverseProxy/KestrelServerOptionsExtensions.cs @@ -2,17 +2,12 @@ using FastGithub.ReverseProxy; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Server.Kestrel.Core; -using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; using System; -using System.Collections.Generic; -using System.IO; using System.Linq; using System.Net; using System.Net.NetworkInformation; -using System.Security.Cryptography.X509Certificates; namespace FastGithub { @@ -21,20 +16,12 @@ namespace FastGithub /// public static class KestrelServerOptionsExtensions { - /// - /// 服务器证书缓存 - /// - private static readonly IMemoryCache serverCertCache = new MemoryCache(Options.Create(new MemoryCacheOptions())); - /// /// 监听http的反向代理 /// /// public static void ListenHttpReverseProxy(this KestrelServerOptions kestrel) { - var loggerFactory = kestrel.ApplicationServices.GetRequiredService(); - var logger = loggerFactory.CreateLogger($"{nameof(FastGithub)}.{nameof(ReverseProxy)}"); - const int HTTP_PORT = 80; if (OperatingSystem.IsWindows()) { @@ -43,7 +30,9 @@ namespace FastGithub if (CanTcpListen(HTTP_PORT) == false) { - logger.LogWarning($"由于tcp端口{HTTP_PORT}已经被其它进程占用,{nameof(FastGithub)}无法进行http反向代理"); + var loggerFactory = kestrel.ApplicationServices.GetRequiredService(); + var logger = loggerFactory.CreateLogger($"{nameof(FastGithub)}.{nameof(ReverseProxy)}"); + logger.LogWarning($"由于tcp端口{HTTP_PORT}已经被其它进程占用,http反向代理功能将受限"); } else { @@ -57,17 +46,6 @@ namespace FastGithub /// public static void ListenHttpsReverseProxy(this KestrelServerOptions kestrel) { - var loggerFactory = kestrel.ApplicationServices.GetRequiredService(); - var logger = loggerFactory.CreateLogger($"{nameof(FastGithub)}.{nameof(ReverseProxy)}"); - - const string CAPATH = "CACert"; - Directory.CreateDirectory(CAPATH); - var caPublicCerPath = $"{CAPATH}/{nameof(FastGithub)}.cer"; - var caPrivateKeyPath = $"{CAPATH}/{nameof(FastGithub)}.key"; - - GeneratorCaCert(caPublicCerPath, caPrivateKeyPath); - InstallCaCert(caPublicCerPath, logger); - const int HTTPS_PORT = 443; if (OperatingSystem.IsWindows()) { @@ -76,15 +54,17 @@ namespace FastGithub if (CanTcpListen(HTTPS_PORT) == false) { - logger.LogError($"由于tcp端口{HTTPS_PORT}已经被其它进程占用,{nameof(FastGithub)}无法进行https反向代理"); - } - else - { - kestrel.Listen(IPAddress.Any, HTTPS_PORT, listen => - listen.UseHttps(https => - https.ServerCertificateSelector = (ctx, domain) => - GetServerCert(domain, caPublicCerPath, caPrivateKeyPath))); + throw new FastGithubException($"由于tcp端口{HTTPS_PORT}已经被其它进程占用,{nameof(FastGithub)}无法进行必须的https反向代理"); } + + var certService = kestrel.ApplicationServices.GetRequiredService(); + certService.GenerateCaCert(); + certService.InstallCaCert(); + + kestrel.Listen(IPAddress.Any, HTTPS_PORT, listen => + listen.UseHttps(https => + https.ServerCertificateSelector = (ctx, domain) => + certService.GetServerCert(domain))); } /// @@ -97,110 +77,5 @@ namespace FastGithub var tcpListeners = IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners(); return tcpListeners.Any(item => item.Port == port) == false; } - - /// - /// 生成根证书 - /// 10年 - /// - /// - /// - private static void GeneratorCaCert(string caPublicCerPath, string caPrivateKeyPath) - { - if (File.Exists(caPublicCerPath) && File.Exists(caPublicCerPath)) - { - return; - } - - File.Delete(caPublicCerPath); - File.Delete(caPrivateKeyPath); - - var validFrom = DateTime.Today.AddDays(-1); - var validTo = DateTime.Today.AddYears(10); - CertGenerator.GenerateBySelf(new[] { nameof(FastGithub) }, 2048, validFrom, validTo, caPublicCerPath, caPrivateKeyPath); - } - - - /// - /// 安装根证书 - /// - /// - /// - private static void InstallCaCert(string caPublicCerPath, ILogger logger) - { - if (OperatingSystem.IsWindows() == false) - { - logger.LogWarning($"不支持自动安装证书{caPublicCerPath}:请手动安装证书到根证书颁发机构"); - return; - } - - try - { - using var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine); - store.Open(OpenFlags.ReadWrite); - - var caCert = new X509Certificate2(caPublicCerPath); - var subjectName = caCert.Subject[3..]; - foreach (var item in store.Certificates.Find(X509FindType.FindBySubjectName, subjectName, false)) - { - if (item.Thumbprint != caCert.Thumbprint) - { - store.Remove(item); - } - } - if (store.Certificates.Find(X509FindType.FindByThumbprint, caCert.Thumbprint, true).Count == 0) - { - store.Add(caCert); - } - store.Close(); - } - catch (Exception) - { - logger.LogWarning($"安装证书{caPublicCerPath}失败:请手动安装到“将所有的证书都放入下载存储”\\“受信任的根证书颁发机构”"); - } - } - - /// - /// 获取颁发给指定域名的证书 - /// - /// - /// - /// - /// - private static X509Certificate2 GetServerCert(string? domain, string caPublicCerPath, string caPrivateKeyPath) - { - return serverCertCache.GetOrCreate(domain ?? string.Empty, GetOrCreateCert); - - // 生成域名的1年证书 - X509Certificate2 GetOrCreateCert(ICacheEntry entry) - { - var host = (string)entry.Key; - var domains = GetDomains(host).Distinct(); - var validFrom = DateTime.Today.AddDays(-1); - var validTo = DateTime.Today.AddYears(1); - - entry.SetAbsoluteExpiration(validTo); - return CertGenerator.GenerateByCa(domains, 2048, validFrom, validTo, caPublicCerPath, caPrivateKeyPath); - } - } - - /// - /// 获取域名 - /// - /// - /// - private static IEnumerable GetDomains(string host) - { - if (string.IsNullOrEmpty(host) == false) - { - yield return host; - yield break; - } - - yield return LocalMachine.Name; - foreach (var address in LocalMachine.GetAllIPv4Addresses()) - { - yield return address.ToString(); - } - } } } diff --git a/FastGithub.ReverseProxy/ReverseProxyServiceCollectionExtensions.cs b/FastGithub.ReverseProxy/ReverseProxyServiceCollectionExtensions.cs index 069a745..1e1bddd 100644 --- a/FastGithub.ReverseProxy/ReverseProxyServiceCollectionExtensions.cs +++ b/FastGithub.ReverseProxy/ReverseProxyServiceCollectionExtensions.cs @@ -18,6 +18,7 @@ namespace FastGithub return services .AddMemoryCache() .AddHttpForwarder() + .AddSingleton() .AddSingleton() .AddSingleton(); }