2020-05-13

Exchange 採用 Let's Encrypt 驗證時遇到 Multi CAS Role NLB 問題的處理

Exchange 扮演 CAS 腳色的機器一般都裝有兩台以上做 NLB 或 Round Robin
此時若在某一台 CAS 進行 Let's Encrypt Renew 的時候會遇到問題:

  • 建立了 .well-know 資料夾與驗證檔案
  • Let's Encrypt 來查詢卻問到別台 CAS
  • 別台 CAS 上面當然沒有 .well-know 資料夾與驗證檔案
  • 於是驗證失敗

為了解決這個問題,用了一些投機取巧、土炮的辦法
也許有更為正確的辦法,但目前沒想到或沒學到,所以就這樣做了,反正也行得通
原理很簡單,就是在開始 renew 之前先開始一個周期性的 robocopy /mir 同步每一台 CAS

採用的 Let's Encrypt ACMEv2 client 
https://www.win-acme.com/

以下是 Batch File 與 PowerShell Script

Updated: 2021.01.26
因應 Let's Encrypt 變更憑證發行單位名稱,增加變更 TLSCertificateName 的部份
Updated: 2021.07.09
如果有 Multi-Domain 且跟 Exchange Online 做 Hybrid,需要確認只有單一 TLSCertificateName
故將 TLSCertificateName 更新的部份加個參數做個判斷式,自己設定 True or False


以下假設 CAS 有兩台,分別為 CAS-01 與 CAS-02
並在 CAS-01 上面跑 Let's Encrypt 的 Renew

Sync.bat

md "C:\inetpub\wwwroot\.well-known"
for /l %%x in (1, 1, 120) do (
@ping 127.0.0.1 -n 2 -w 1000 > nul
robocopy /mir "C:\inetpub\wwwroot\.well-known" "\\CAS-02\c$\inetpub\wwwroot\.well-known"
)
Renew-Master.ps1 (在 CAS-01 上跑)

$PublishedURL = "mail.contoso.com"
$ExchangeServer = $("CAS-01","CAS-02")
$Domain = "contoso.com"
$WACSPath = "C:\Cert\"
$WACSEXEFileName = "wacs.exe"
$PFXPath = "C:\Cert\Cert\"
$RenewConnectorTLSCertificateName = $True;

cd $WACSPath
$DateTimeString = Get-Date -format yyyyMMdd_HHmmss
$TranscriptLog = (Get-Item -Path ".\" -Verbose).FullName + "\Logs\" + ("CertRenew_" + $DateTimeString + ".log")
start-transcript -path $TranscriptLog

write-host ""
write-host "*** Cert Renew"
write-host ""

Import-Module IISAdministration
$ConfigSection = Get-IISConfigSection -SectionPath "system.webServer/security/access" -Location "Default Web Site"
Set-IISConfigAttributeValue -AttributeName sslFlags -AttributeValue None -ConfigElement $ConfigSection

$IISDir = Set-WebConfigurationProperty -Location "Default Web Site/.well-known" -Filter 'system.webserver/security/access' -name "sslFlags" -Value None
$IISDirCeck = (Get-WebConfigurationProperty -Location "Default Web Site/.well-known" -Filter 'system.webserver/security/access' -name "sslFlags").Value

$CommandLine = "$WACSPath$WACSEXEFileName --renew --baseuri ""https://acme-v02.api.letsencrypt.org/"""
cmd /c $CommandLine

$ConfigSection = Get-IISConfigSection -SectionPath "system.webServer/security/access" -Location "Default Web Site"
Set-IISConfigAttributeValue -AttributeName sslFlags -AttributeValue Ssl -ConfigElement $ConfigSection

Add-PSSnapin Microsoft.Exchange.Management.PowerShell.SnapIn;

$PFXFullPath = "$PFXPath$PublishedURL.pfx"
$NewCertProperties = Get-PfxCertificate -FilePath $PFXFullPath | Select Thumbprint,NotAfter
$LastCertProperties = Get-ExchangeCertificate | where {$_.Subject -eq "CN=$PublishedURL"} | select Thumbprint,NotAfter

$NewCertThumbprint = $NewCertProperties.Thumbprint
$NewCertNotAfter = $NewCertProperties.NotAfter
$LastCertThumbprint = $LastCertProperties.Thumbprint
$LastCertNotAfter = $LastCertProperties.NotAfter

write-host
write-host "New  Cert Thumbprint is: $NewCertThumbprint"
write-host "New  Cert NotAfter is: $NewCertNotAfter"
write-host
write-host "Last Cert Thumbprint is: $LastCertThumbprint"
write-host "Last Cert NotAfter is: $LastCertNotAfter"
write-host

if ( ($NewCertNotAfter -lt $LastCertNotAfter) -or ($NewCertNotAfter -eq $LastCertNotAfter) ) {
write-host "New Cert's ExpireDate is Less than or Equal to the old one, Please check."
Stop-Transcript
exit;
}

if ($NewCertThumbprint -eq $LastCertThumbprint) {
write-host "Cert Doesn't Change, Program Close, Please check."
Stop-Transcript
exit;
}

write-host ""
write-host "*** Backup New Cert With Date"
write-host ""

Get-ChildItem -Path $PFXFullPath | Copy-Item -Destination $PFXFullPath.Replace(".pfx","_Expired_$($($NewCertProperties.NotAfter).tostring("yyyyMMdd")).pfx") -Force -Confirm:$false

write-host ""
write-host "*** Copy New Cert to Other Servers"
write-host ""

foreach ($ExServer in $ExchangeServer) {
if ($ExServer -ne $Env:ComputerName) {
write-host Copy Item from $PFXPath to \\$ExServer.$Domain\$($PFXPath.Replace(":","$"))
Get-ChildItem -Path $PFXPath | Copy-Item -Destination "\\$ExServer.$Domain\$($PFXPath.Replace(":","$"))" -Force -Confirm:$false
};
};

write-host ""
write-host "*** Import New Cert"
write-host ""

$CommandLine = 'C:\Windows\System32\certutil.exe -f -p "" -importpfx "'+$PFXFullPath+'" NoExport'
cmd /c $CommandLine

write-host ""
write-host "*** Enable New Cert on Exchange"
write-host ""

Enable-ExchangeCertificate -Thumbprint $NewCertThumbprint -Services POP,IMAP,IIS,SMTP -Force -Confirm:$false -ErrorAction Stop
Get-ExchangeCertificate | Format-List FriendlyName,Subject,CertificateDomains,Thumbprint,Services

write-host ""
write-host "*** Reset IIS"
write-host ""

iisreset

if ($RenewConnectorTLSCertificateName -eq $True) {
  write-host ""
  write-host "*** Renew ReceiveConnector and SendConnector TLSCertificateName"
  write-host ""

  $TLSCert=Get-ExchangeCertificate $NewCertThumbprint
  $TLSCertname="$($TLSCert.Issuer)$($TLSCert.Subject)"

  $ExchangeFrontendServer = ( $Env:ComputerName + '\Default Frontend ' +   $Env:ComputerName);

  write-host ('New ReceiveConnector TLSCertificateName: ' + $TLSCertname);
  write-host ('Last ReceiveConnector TLSCertificateName: ' + (Get-ReceiveConnector $ExchangeFrontendServer | select TLSCertificateName).TLSCertificateName);

  Set-ReceiveConnector -Identity $ExchangeFrontendServer -TLSCertificateName $TLSCertname
  Set-SendConnector -Identity "Outbound to Office 365" -TLSCertificateName $TLSCertname

  Restart-Service MSExchangeTransport
};

write-host ""
write-host "*** Remove Last Cert"
write-host ""

Remove-ExchangeCertificate -Thumbprint $LastCertThumbprint -Confirm:$false
Get-ExchangeCertificate | Format-List FriendlyName,Subject,CertificateDomains,Thumbprint,Services

Stop-Transcript
exit;

Renew-Slave.ps1 (在 CAS-02 上跑)

$PublishedURL = "mail.contoso.com"

$ExchangeServer = $("CAS-01","CAS-02")
$Domain = "contoso.com"
$WACSPath = "C:\Cert\"
$WACSEXEFileName = "wacs.exe"
$PFXPath = "C:\Cert\Cert\"

cd $WACSPath

$DateTimeString = Get-Date -format yyyyMMdd_HHmmss
$TranscriptLog = (Get-Item -Path ".\" -Verbose).FullName + "\Logs\" + ("CertRenew_" + $DateTimeString + ".log")
start-transcript -path $TranscriptLog

write-host ""

write-host "*** Looking for New Cert"
write-host ""

Import-Module IISAdministration

Add-PSSnapin Microsoft.Exchange.Management.PowerShell.SnapIn;

$PFXFullPath = "$PFXPath$PublishedURL.pfx"

$NewCertProperties = Get-PfxCertificate -FilePath $PFXFullPath | Select Thumbprint,NotAfter
$LastCertProperties = Get-ExchangeCertificate | where {$_.Subject -eq "CN=$PublishedURL"} | select Thumbprint,NotAfter

$NewCertThumbprint = $NewCertProperties.Thumbprint

$NewCertNotAfter = $NewCertProperties.NotAfter
$LastCertThumbprint = $LastCertProperties.Thumbprint
$LastCertNotAfter = $LastCertProperties.NotAfter

write-host 

write-host "New  Cert Thumbprint is: $NewCertThumbprint"
write-host "New  Cert NotAfter is: $NewCertNotAfter"
write-host 
write-host "Last Cert Thumbprint is: $LastCertThumbprint"
write-host "Last Cert NotAfter is: $LastCertNotAfter"
write-host 

if ( ($NewCertNotAfter -lt $LastCertNotAfter) -or ($NewCertNotAfter -eq $LastCertNotAfter) ) {

write-host "New Cert's ExpireDate is Less than or Equal to the old one, Please check."
Stop-Transcript
exit;
}

if ($NewCertThumbprint -eq $LastCertThumbprint) {

write-host "Cert Doesn't Change, Program Close, Please check."
Stop-Transcript
exit;
}

write-host ""

write-host "*** Import New Cert"
write-host ""

$CommandLine = 'C:\Windows\System32\certutil.exe -f -p "" -importpfx "'+$PFXFullPath+'" NoExport'

cmd /c $CommandLine

write-host ""

write-host "*** Enable New Cert on Exchange"
write-host ""

Enable-ExchangeCertificate -Thumbprint $NewCertThumbprint -Services POP,IMAP,IIS,SMTP -Force -Confirm:$false -ErrorAction Stop

Get-ExchangeCertificate | Format-List FriendlyName,Subject,CertificateDomains,Thumbprint,Services

write-host ""

write-host "*** Reset IIS"
write-host ""

iisreset
if ($RenewConnectorTLSCertificateName -eq $True) {
  write-host ""
  write-host "*** Renew ReceiveConnector and SendConnector TLSCertificateName"
  write-host ""

  $TLSCert=Get-ExchangeCertificate $NewCertThumbprint
  $TLSCertname="<I>$($TLSCert.Issuer)<S>$($TLSCert.Subject)"

  $ExchangeFrontendServer = ( $Env:ComputerName + '\Default Frontend ' + $Env:ComputerName);

  write-host ('New ReceiveConnector TLSCertificateName: ' + $TLSCertname);
  write-host ('Last ReceiveConnector TLSCertificateName: ' + (Get-ReceiveConnector $ExchangeFrontendServer | select TLSCertificateName).TLSCertificateName);

  Set-ReceiveConnector -Identity $ExchangeFrontendServer -TLSCertificateName $TLSCertname
  Set-SendConnector -Identity "Outbound to Office 365" -TLSCertificateName $TLSCertname

  Restart-Service MSExchangeTransport
};
write-host ""
write-host "*** Remove Last Cert"
write-host ""

Remove-ExchangeCertificate -Thumbprint $LastCertThumbprint -Confirm:$false

Get-ExchangeCertificate | Format-List FriendlyName,Subject,CertificateDomains,Thumbprint,Services

Stop-Transcript

exit;

Task Scheduler


先跑 Sync.bat (讓他在背景持續檢查,.well-know 裡面有檔案就複製到另一台)
C:\Windows\System32\cmd.exe /c "start cmd /c C:\Cert\Sync.bat"
再跑
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -command ". 'C:\Cert\ExchangeCertAutoRenewMaster.ps1'"


沒有留言:

張貼留言