Azure 负载平衡器后面的.NET核心虚拟机上的SSL
我目前正在建立一个高可用性HA环境,两台运行Ubuntu的Azure虚拟机位于标准Azure负载平衡器后面。现在我知道标准负载平衡器只有第4层,这意味着它不能进行SSL卸载 这两个虚拟机都运行.NET核心Web API。显然,它们都需要SSL证书来处理来自负载平衡器的SSL连接 我知道我可以购买SSL证书,只需设置Kestrel在Web API本身上使用该证书,但我想要免费证书。我知道另一种选择是使用nginx服务器生成证书,然后将证书复制到Web API,但这意味着我需要每3个月重复一次该过程,这相当痛苦,因为这意味着我在使HA群集脱机以续订证书时会有停机时间 有人知道在负载平衡器后面的两个虚拟机上使用Lets加密的方法吗? 好吧,我就是这么说的。它要求我编写一个实用程序,使用DNS验证自动更新我的Lets加密证书。它使用Azure DNS或其他具有API的DNS提供商非常重要,因为您需要能够直接使用API或与提供商的其他接口修改DNS记录 我正在使用Azure DNS,它正在为我管理整个域,因此下面的代码是针对Azure DNS的,但您可以修改API以与您选择的任何具有某种API的提供商合作 第二部分是在我的高可用性HA集群中没有任何停机时间。所以我所做的是,将证书写入数据库,然后在启动VM时动态读取它。所以基本上每次Kestrel启动时,它都会从DB读取证书,然后使用它 密码 数据库模型 您需要将以下模型添加到数据库中,以便将实际的证书详细信息存储在某个位置Azure 负载平衡器后面的.NET核心虚拟机上的SSL,azure,ssl,load-balancing,azure-virtual-machine,azure-load-balancer,Azure,Ssl,Load Balancing,Azure Virtual Machine,Azure Load Balancer,我目前正在建立一个高可用性HA环境,两台运行Ubuntu的Azure虚拟机位于标准Azure负载平衡器后面。现在我知道标准负载平衡器只有第4层,这意味着它不能进行SSL卸载 这两个虚拟机都运行.NET核心Web API。显然,它们都需要SSL证书来处理来自负载平衡器的SSL连接 我知道我可以购买SSL证书,只需设置Kestrel在Web API本身上使用该证书,但我想要免费证书。我知道另一种选择是使用nginx服务器生成证书,然后将证书复制到Web API,但这意味着我需要每3个月重复一次该过程
public class Certificate
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
public string FullChainPem { get; set; }
public string CertificatePfx { get; set; }
public string CertificatePassword { get; set; }
public DateTime CertificateExpiry { get; set; }
public DateTime? CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
}
创建模型后,需要将其放置在上下文中,如下所示:
public DbSet<Certificate> Certificates { get; set; }
/// <summary>
/// Method that will renew the domain certificates and update the database with them
/// </summary>
public static void RenewCertificates() {
Console.WriteLine($ "[{DateTime.Now}] - Starting certificate renewal.");
//Instantiate variables
AcmeContext acme;
IAccountContext account;
//Try and get the setting value for ACME Key
var acmeKey = _db.Settings.FirstOrDefault(s = >s.Key == "ACME");
//Check if acme key is null
if (acmeKey == null) {
//Set the ACME servers to use
#if DEBUG
acme = new AcmeContext(WellKnownServers.LetsEncryptStagingV2);
#else
acme = new AcmeContext(WellKnownServers.LetsEncryptV2);
#endif
//Create the new account
account = acme.NewAccount("yourname@yourdomain.tld", true).Result;
//Save the key to the DB to be used
_db.Settings.Add(new Setting {
Key = "ACME",
Value = acme.AccountKey.ToPem()
});
//Save DB changes
_db.SaveChanges();
}
else {
//Get the account key from PEM
var accountKey = KeyFactory.FromPem(acmeKey.Value);
//Set the ACME servers to use
#if DEBUG
acme = new AcmeContext(WellKnownServers.LetsEncryptStagingV2, accountKey);
#else
acme = new AcmeContext(WellKnownServers.LetsEncryptV2, accountKey);
#endif
//Get the actual account
account = acme.Account().Result;
}
//Create an order for wildcard domain and normal domain
var order = acme.NewOrder(new[] {
"*.yourdomain.tld",
"yourdomain.tld"
}).Result;
//Generate the challenges for the domains
var authorizations = order.Authorizations().Result;
//Error flag
var hasFailed = false;
foreach(var authorization in authorizations) {
//Get the DNS challenge for the authorization
var dnsChallenge = authorization.Dns().Result;
//Get the DNS TXT
var dnsTxt = acme.AccountKey.DnsTxt(dnsChallenge.Token);
Console.WriteLine($ "[{DateTime.Now}] - Received DNS challenge data.");
//Set the DNS record
Azure.SetAcmeTxtRecord(dnsTxt);
Console.WriteLine($ "[{DateTime.Now}] - Updated DNS challenge data.");
Console.WriteLine($ "[{DateTime.Now}] - Waiting 1 minute before checking status.");
dnsChallenge.Validate();
//Wait 1 minute
Thread.Sleep(TimeSpan.FromMinutes(1));
//Check the DNS challenge
var valid = dnsChallenge.Validate().Result;
//If the verification fails set failed flag
if (valid.Status != ChallengeStatus.Valid) hasFailed = true;
}
//Check whether challenges failed
if (hasFailed) {
Console.WriteLine($ "[{DateTime.Now}] - DNS challenge(s) failed, retrying.");
//Recurse
RenewCertificates();
return;
}
else {
Console.WriteLine($ "[{DateTime.Now}] - DNS challenge(s) successful.");
//Generate a private key
var privateKey = KeyFactory.NewKey(KeyAlgorithm.ES256);
//Generate certificate
var cert = order.Generate(new CsrInfo {
CountryName = "ZA",
State = "Gauteng",
Locality = "Pretoria",
Organization = "Your Organization",
OrganizationUnit = "Production",
},
privateKey).Result;
Console.WriteLine($ "[{DateTime.Now}] - Certificate generated successfully.");
//Get the full chain
var fullChain = cert.ToPem();
//Generate password
var pass = Guid.NewGuid().ToString();
//Export the pfx
var pfxBuilder = cert.ToPfx(privateKey);
var pfx = pfxBuilder.Build("yourdomain.tld", pass);
//Create database entry
_db.Certificates.Add(new Certificate {
FullChainPem = fullChain,
CertificatePfx = Convert.ToBase64String(pfx),
CertificatePassword = pass,
CertificateExpiry = DateTime.Now.AddMonths(2)
});
//Save changes
_db.SaveChanges();
Console.WriteLine($ "[{DateTime.Now}] - Database updated with new certificate.");
Console.WriteLine($ "[{DateTime.Now}] - Restarting VMs.");
//Restart the VMS
Azure.RestartAllVms();
}
}
让我们加密实用程序
这就是解决方案的核心。它处理证书请求、质询、DNS验证,然后存储证书。它还将自动重新启动Azure中使用证书的每个VM实例,以便它们提取新证书
主要逻辑如下,它将检查证书是否需要更新
static void Main(string[] args) {
while (true) {
//Get the latest certificate in the DB for the servers
var lastCertificate = _db.Certificates.LastOrDefault();
//Check if the expiry date of last certificate is more than a month away
if (lastCertificate != null && (lastCertificate.CertificateExpiry - DateTime.Now).TotalDays > 31) {
//Log out some info
Console.WriteLine($ "[{DateTime.Now}] - Certificate still valid, sleeping for a day.");
//Sleep the thread
Thread.Sleep(TimeSpan.FromDays(1));
}
else {
//Renew the certificates
RenewCertificates();
}
}
}
好的,这是一个很大的过程,但是如果你把它分解,它实际上是非常简单的
创建帐户
获取帐户密钥
为域创建新订单
在所有组织中循环
对它们中的每一个执行DNS验证
生成证书
将证书保存到数据库
重新启动虚拟机
实际更新证书的方法如下:
public DbSet<Certificate> Certificates { get; set; }
/// <summary>
/// Method that will renew the domain certificates and update the database with them
/// </summary>
public static void RenewCertificates() {
Console.WriteLine($ "[{DateTime.Now}] - Starting certificate renewal.");
//Instantiate variables
AcmeContext acme;
IAccountContext account;
//Try and get the setting value for ACME Key
var acmeKey = _db.Settings.FirstOrDefault(s = >s.Key == "ACME");
//Check if acme key is null
if (acmeKey == null) {
//Set the ACME servers to use
#if DEBUG
acme = new AcmeContext(WellKnownServers.LetsEncryptStagingV2);
#else
acme = new AcmeContext(WellKnownServers.LetsEncryptV2);
#endif
//Create the new account
account = acme.NewAccount("yourname@yourdomain.tld", true).Result;
//Save the key to the DB to be used
_db.Settings.Add(new Setting {
Key = "ACME",
Value = acme.AccountKey.ToPem()
});
//Save DB changes
_db.SaveChanges();
}
else {
//Get the account key from PEM
var accountKey = KeyFactory.FromPem(acmeKey.Value);
//Set the ACME servers to use
#if DEBUG
acme = new AcmeContext(WellKnownServers.LetsEncryptStagingV2, accountKey);
#else
acme = new AcmeContext(WellKnownServers.LetsEncryptV2, accountKey);
#endif
//Get the actual account
account = acme.Account().Result;
}
//Create an order for wildcard domain and normal domain
var order = acme.NewOrder(new[] {
"*.yourdomain.tld",
"yourdomain.tld"
}).Result;
//Generate the challenges for the domains
var authorizations = order.Authorizations().Result;
//Error flag
var hasFailed = false;
foreach(var authorization in authorizations) {
//Get the DNS challenge for the authorization
var dnsChallenge = authorization.Dns().Result;
//Get the DNS TXT
var dnsTxt = acme.AccountKey.DnsTxt(dnsChallenge.Token);
Console.WriteLine($ "[{DateTime.Now}] - Received DNS challenge data.");
//Set the DNS record
Azure.SetAcmeTxtRecord(dnsTxt);
Console.WriteLine($ "[{DateTime.Now}] - Updated DNS challenge data.");
Console.WriteLine($ "[{DateTime.Now}] - Waiting 1 minute before checking status.");
dnsChallenge.Validate();
//Wait 1 minute
Thread.Sleep(TimeSpan.FromMinutes(1));
//Check the DNS challenge
var valid = dnsChallenge.Validate().Result;
//If the verification fails set failed flag
if (valid.Status != ChallengeStatus.Valid) hasFailed = true;
}
//Check whether challenges failed
if (hasFailed) {
Console.WriteLine($ "[{DateTime.Now}] - DNS challenge(s) failed, retrying.");
//Recurse
RenewCertificates();
return;
}
else {
Console.WriteLine($ "[{DateTime.Now}] - DNS challenge(s) successful.");
//Generate a private key
var privateKey = KeyFactory.NewKey(KeyAlgorithm.ES256);
//Generate certificate
var cert = order.Generate(new CsrInfo {
CountryName = "ZA",
State = "Gauteng",
Locality = "Pretoria",
Organization = "Your Organization",
OrganizationUnit = "Production",
},
privateKey).Result;
Console.WriteLine($ "[{DateTime.Now}] - Certificate generated successfully.");
//Get the full chain
var fullChain = cert.ToPem();
//Generate password
var pass = Guid.NewGuid().ToString();
//Export the pfx
var pfxBuilder = cert.ToPfx(privateKey);
var pfx = pfxBuilder.Build("yourdomain.tld", pass);
//Create database entry
_db.Certificates.Add(new Certificate {
FullChainPem = fullChain,
CertificatePfx = Convert.ToBase64String(pfx),
CertificatePassword = pass,
CertificateExpiry = DateTime.Now.AddMonths(2)
});
//Save changes
_db.SaveChanges();
Console.WriteLine($ "[{DateTime.Now}] - Database updated with new certificate.");
Console.WriteLine($ "[{DateTime.Now}] - Restarting VMs.");
//Restart the VMS
Azure.RestartAllVms();
}
}
我希望这能够帮助与我处境相同的其他人。也许你可以尝试为你的虚拟机提供帮助。你解决了这个问题吗?如果建议有帮助,请让我知道,或者你可以分享你的解决方案。@charlessu-我是对的,我会在一个小时左右将其作为答案发布在这里。@charlessu-如果你想看一下,我已经在下面发布了解决方案。是的,非常好。