2018-07-20

Exchange 2016 採用 Let's Encrypt 憑證的方法與 Auto Renew

Let's Encrypt 真是進年度最偉大的服務了
上次用 Let's Encrypt 佈署 Remote Desktop Gateway 後, 這次要來佈署 Exchange 2016

Updated:
2025.02.24
Let's Encrypt 已經改用 https://www.win-acme.com/
PowerShell Script 更新加入 Exchange Online Hybrid 的部分 (TLS Issuer Name)


採用的版本是 LetsEncryptWinSimple.v1.9.11.2 (直接下載連結)
解壓縮後我把他放到 Exchange Server 的 C:\Cert\LetsEncryptWinSimple.v1.9.11.2 路徑
接著執行 letsencrypt.exe



這裡我選 M





這裡我選 4 , 因為要輸入多個 FQDN

接著輸入連接 Exchange 用的主要 FQDN: webmail.contoso.com
以及 Auto Discover 用的 FQDN: autodiscover.contoso.com
選擇以 webmail.contoso.com.tw 為憑證的主要 FQDN









選擇驗證方式, 這裡我選 3 將驗證檔案放到指定的路徑去














指定的路徑放到 IIS 的 Root 去
順便讓他自己幫我們設定好 web.config
然後在驗證成功取得憑證後不需要自動 Run 任何 Script














在上面這張圖片按下 Enter 之前, 有一些動作必須要先完成
安裝 Exchange 2016 後 IIS 預設不會接受 HTTP 無加密的連線, 只接受 HTTPS
所以為了要讓 Let's Encrypt 能透過 HTTP 的方式驗證我們的合法性
必須先將 HTTP 打開

到 IIS 主控台 -> Default Web Site 點選右邊的 SSL Settings
把 Require SSL 的勾勾拿掉 (要到憑證後記得要勾回來)

之後按下 Enter, 經過一些連線驗證後就可以取得憑證了
憑證會放在 C:\ProgramData\win-acme\httpsacme-v01.api.letsencrypt.org
下圖當然不會成功了, 因為範例寫 contoso.com













Auto Renew 的部份:

$OldPublishedURL = "webmail.contoso.com"
$NewPublishedURL = "webmail.contoso.com"
$CertPassword = "P@##w0rd"
$ExchangeServer = $("EX-01","EX-02")
$Domain = "Contoso.com"
$WACSPath = "C:\Cert\"
$WACSEXEFileName = "wacs.exe"
$PFXPath = "C:\Cert\Cert\"

# For Exchange Online Hybrid
$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

# Remove Firewall Rule Blocking TCP 80 for Let's Encrypt Authentication
    $FWGroupName = "CertTools";
    $Remove = New-NetFirewallRule -DisplayName "RemovePrepare" -Group $FWGroupName -Direction "inBound" -Program "C:\windows\system32\calc.exe" -Action Block -RemoteAddress $BlockIPs
    Remove-NetFirewallRule -Group $FWGroupName -Confirm:$False
# Remove Firewall Rule Blocking TCP 80 for Let's Encrypt Authentication

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

# Add Firewall Rule Back for Blocking TCP 80
    $BlockIPs = @("0.0.0.1-9.255.255.255", "11.0.0.0-172.15.255.255", "172.32.0.0-192.167.255.255", "192.169.0.0-255.255.255.255")
    New-NetFirewallRule -DisplayName "Block 80 Port inBound" -Group $FWGroupName -Direction "inBound" -Protocol "TCP" -LocalPort 80 -Action Block -RemoteAddress $BlockIPs
# Add Firewall Rule Back for Blocking TCP 80

$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 + ($NewPublishedURL -Replace '\*','_') + ".pfx")
$NewCertProperties = (Get-PfxData -FilePath $PFXFullPath -Password (ConvertTo-SecureString -String $CertPassword -Force -AsPlainText)).EndEntityCertificates | Select Thumbprint,NotAfter

$LastCerts = Get-ExchangeCertificate | where {$_.Subject -eq "CN=$OldPublishedURL"} | select Thumbprint,NotAfter

$LastCertProperties = $NewCertProperties;
foreach ($LastCert in $LastCerts) {
    if ($LastCert.Thumbprint -ne $NewCertProperties.Thumbprint) {
        $LastCertProperties = $LastCert;
    };
};

$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 "' + $CertPassword + '" -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

Import-Module WebAdministration
$IISSSLBindings = Get-ChildItem IIS:SSLBindings
$BackEndWebCertThumbprint = ($IISSSLBindings | where {$_.Sites.Value -eq 'Exchange Back End'}).Thumbprint;
write-host ('BackEnd Web Cert: ' + $BackEndWebCertThumbprint)
$BackEndWebCertThumbprint
if ( ($BackEndWebCertThumbprint) -ne $NewCertThumbprint ) {
    (((Get-ChildItem IIS:\Sites) | where {$_.Name -eq 'Exchange Back End'}).Bindings.Collection | where {$_.protocol -eq 'https' -and $_.bindingInformation -eq '*:444:'}).AddSslCertificate($NewCertThumbprint, "My")
    
write-host ('Update BackEnd Web Cert: ' + $NewCertThumbprint)
};

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

iisreset

# Section for Exchange Online Hybrid
    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);

            $NewReceiveConnectorTLSCertificateName = $TLSCertname;
            $LastDefaultReceiveConnectorTLSCertificateName = (Get-ReceiveConnector $ExchangeFrontendServer | select TLSCertificateName).TLSCertificateName;

            write-host ('New Default ReceiveConnector TLSCertificateName : ' + $NewReceiveConnectorTLSCertificateName);
            write-host ('Last Default ReceiveConnector TLSCertificateName: ' + $LastDefaultReceiveConnectorTLSCertificateName);

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

            if ($LastCertThumbprint -ne $Null) {
                Get-ChildItem -Path "Cert:\LocalMachine\My" | where {$_.Thumbprint -eq $LastCertThumbprint} | Remove-Item -Confirm:$false
            };
            Set-ReceiveConnector -Identity $ExchangeFrontendServer -TLSCertificateName $TLSCertname
            
        # ----------
        
            $ClientFrontendServer = ( $Env:ComputerName + '\Client Frontend ' + $Env:ComputerName);

            $LastClientReceiveConnectorTLSCertificateName = (Get-ReceiveConnector $ClientFrontendServer | select TLSCertificateName).TLSCertificateName;

            write-host ('New Client ReceiveConnector TLSCertificateName : ' + $NewReceiveConnectorTLSCertificateName);
            write-host ('Last Client ReceiveConnector TLSCertificateName: ' + $LastClientReceiveConnectorTLSCertificateName);

            Set-ReceiveConnector -Identity $ClientFrontendServer -TLSCertificateName $TLSCertname
            
        # ----------
        
        Get-SendConnector | Where {$_.Identity -like "Outbound to Office 365*"} | Set-SendConnector -TLSCertificateName $TLSCertname
        Restart-Service MSExchangeTransport
    };
# Section for Exchange Online Hybrid

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

Stop-Transcript
exit;

1 則留言:

  1. 您好方便請教以下訊息:
    [WARN] Invalid SiteId '', should be a number
    [WARN] No valid sites selected
    [EROR] NullReferenceException: 並未將物件參考設定為物件的執行個體。
    請問該如何修正呢?誠摯感謝

    回覆刪除