2020-05-19

使用 PowerShell 自動佈署基於 Let's Encrypt 公開憑證的 Microsoft RDS (VDI/RemoteApp) 環境


使用 Win-ACME 來更新憑證

以下 Script 基於僅有兩台 RDWeb/Gateway 色合一的 FrontEnd Server
僅做到將憑證更新完畢匯入系統,派送新的 RDCB 憑證指紋 (Thumbprints) 參考這篇文章

環境預設禁止 RDWeb/Gateway 主機主動對 Internet 連線
也禁止 Internet 對 RDWeb/Gateway 的 TCP Port 80 連線
因此開始 Renew 之前會先打開防火牆,此外也會先更新 TrustedRootCA

由於此例將 RDWeb、RDGateway、RDCB 全部都用公開憑證
所以必須要將 RDCB HA URL 的 Internet IP 也指到 RDWeb / RDGateway 這台上讓外網連線
實際上 RDCB 不需要對外,所以外網 DNS 對應哪個 IP 沒差
  • 2020.06.10 Updated
    • Script 最後將憑證指紋寫入 Thumbprint.txt 檔存放在 RDWeb 的 wwwroot
      這樣就可以另外寫一隻 Script 以 http get 的方式抓取 寫入 GPO
    • 另外,為了彌補 GPO 更新的時間差
      在憑證即將到期的 15 日內,且為周六或周日
      才執行 RDS 系統更新憑證的指令

# -----------------
# Configuration

$WACSPath = "C:\Cert\"

$WACSEXEFileName = "wacs.exe"
$PFXPath = ($WACSPath + "Cert\")
$RDWebGWURL = "Contoso.com"
$RDCBHAURL = "RDCB.Contoso.com"
$DefaultActivedRDCBMaster = "RDCB-01.Contoso.com"
$TrustedRootCAPath = ($WACSPath + "TrustedRootCA\")
$TrustedRootCAFile = "roots.sst"
$BlockIPs = @("1.0.0.0-9.255.255.255", "11.0.0.0-172.15.255.255", "172.33.0.0-192.167.255.255", "192.169.0.0-255.255.255.255")

# Get Password SecureString by the following Command
# ConvertFrom-SecureString -SecureString (Read-Host -AsSecureString) -Key (1..16)

$CertPasswordPlain = "Password SecureString"
$CertPassword = ConvertTo-SecureString -String $CertPasswordPlain -Key (1..16)

# Assume this Script run on RDWeb-01.Contoso.com

$SecondRDWebGW = "RDWeb-02.Contoso.com"
$SecondRDWebGWAdmin = "Administrator"
$SecondRDWebGWPasswordPlain = "Password SecureString"
$SecondRDWebGWPassword = ConvertTo-SecureString -String $SecondRDWebGWPasswordPlain -Key (1..16)

# -----------------
# Make Sure Cert Working Path Exists

if (-not ( Test-Path -Path 'C:\Cert' -PathType Container )) {

md "C:\Cert"
};
if (-not ( Test-Path -Path (('\\' + $SecondRDWebGW + '\' + $WACSPath) -Replace 'C:','C$') -PathType Container )) {
md (('\\' + $SecondRDWebGW + '\' + $WACSPath) -Replace 'C:','C$') 
};

if (-not ( Test-Path -Path 'C:\Cert\TrustedRootCA' -PathType Container )) {

md "C:\Cert\TrustedRootCA"
};
if (-not ( Test-Path -Path (('\\' + $SecondRDWebGW + '\' + $TrustedRootCAPath) -Replace 'C:','C$') -PathType Container )) {
md (('\\' + $SecondRDWebGW + '\' + $TrustedRootCAPath) -Replace 'C:','C$') 
};

# -----------------

# Change Path to WACS Path

Set-Location -Path $WACSPath


# -----------------

# Start Logging

$DateTimeString = Get-Date -format ("yyyyMMdd HHmmss")

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

# -----------------

# Allow Programs to Connect to Internet

write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' UnBlock Programs Connecting to Internet')

$RemovePrepare = New-NetFirewallRule -DisplayName "RemovePrepare" -Group "Block Connection To Internet" -Direction Outbound -Action Block -RemoteAddress $BlockIPs
$RemoveFirewallRule = Remove-NetFirewallRule -Group "Block Connection To Internet" -Confirm:$False

# -----------------

# Update Trusted Root CA

write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' Downloading Trusted Root CA')

if (Test-Path ($TrustedRootCAPath + $TrustedRootCAFile) -PathType Leaf) {
Remove-Item ($TrustedRootCAPath + $TrustedRootCAFile)
};
Start-Process -wait -FilePath "C:\Windows\System32\certutil.exe" -ArgumentList ('-generateSSTFromWU ' + ($TrustedRootCAPath + $TrustedRootCAFile))

if (-not (Test-Path ($TrustedRootCAPath + $TrustedRootCAFile) -PathType Leaf)) {

write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' Can not Update Trusted Root CA')
Stop-Transcript
exit;
};

write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' Updating Local Trusted Root CA')

$UpdateTrustedRootCA = (Get-ChildItem -Path ($TrustedRootCAPath + $TrustedRootCAFile)) | Import-Certificate -CertStoreLocation Cert:\LocalMachine\Root

copy-item ($TrustedRootCAPath + $TrustedRootCAFile) (('\\' + $SecondRDWebGW + '\' + ($TrustedRootCAPath + $TrustedRootCAFile)) -Replace 'C:','C$')


# -----------------

# Update Trusted Root CA for Second RDWebGW Server
write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' Updating Remote Trusted Root CA')
$Command = {
param($TrustedRootCAPath,$TrustedRootCAFile)
$UpdateTrustedRootCA = (Get-ChildItem -Path ($TrustedRootCAPath + $TrustedRootCAFile)) | Import-Certificate -CertStoreLocation Cert:\LocalMachine\Root
}
$Cred = New-Object System.Management.Automation.PSCredential -ArgumentList $SecondRDWebGWAdmin,$SecondRDWebGWPassword
Invoke-Command -ComputerName $SecondRDWebGW -ArgumentList $TrustedRootCAPath,$TrustedRootCAFile -ScriptBlock $Command -credential $Cred

# -----------------

# Allow TCP Port 80 Connection From Internet

$RemovePrepare = New-NetFirewallRule -DisplayName "RemovePrepare" -Group "TCP80BlockFromInternet" -Direction Inbound -Action Block -RemoteAddress $BlockIPs

$RemoveFirewallRule = Remove-NetFirewallRule -Group "TCP80BlockFromInternet" -Confirm:$False

# -----------------
# Allow TCP Port 80 Connection From Internet for Second RDWebGW Server
$Command = {
$RemovePrepare = New-NetFirewallRule -DisplayName "RemovePrepare" -Group "TCP80BlockFromInternet" -Direction Inbound -Action Block -RemoteAddress $BlockIPs
$RemoveFirewallRule = Remove-NetFirewallRule -Group "TCP80BlockFromInternet" -Confirm:$False
}
$Cred = New-Object System.Management.Automation.PSCredential -ArgumentList $SecondRDWebGWAdmin,$SecondRDWebGWPassword
Invoke-Command -ComputerName $SecondRDWebGW -ScriptBlock $Command -credential $Cred

# -----------------

# Start Syncing RDWeb-01 and RDWeb-02

write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' Start Sync')


$SyncBatchProcess = Start-Process -passthru -FilePath "$env:comspec" -ArgumentList '/C C:\Cert\Sync.bat'


# -----------------

# Renew Cert with Let's Encrypy

write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' Start WACS')

Start-Process -wait -NoNewWindow -FilePath "$env:comspec" -ArgumentList ('/C ' + $WACSPath + $WACSEXEFileName + ' --renew --baseuri "https://acme-v02.api.letsencrypt.org/"')
write-host
write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' End WACS')

write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' End Sync')

$CheckSyncProcessAlive = Get-Process -ID $SyncBatchProcess.Id -ErrorAction SilentlyContinue
if ($CheckSyncProcessAlive -ne $null) {
Stop-Process -ID $SyncBatchProcess.Id -Force
}

# -----------------

# Block Programs Connecting to Internet

write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' Add Firewall Rule Back')

$AddFirewallRuleBack = New-NetFirewallRule -DisplayName "Block Connection To Internet" -Group "Block Connection To Internet" -Direction Outbound -Action Block -RemoteAddress $BlockIPs

# -----------------

# Block TCP Port 80 Connection From Internet

$AddFirewallRuleBack = New-NetFirewallRule -DisplayName "TCP80BlockFromInternet" -Group "TCP80BlockFromInternet" -Direction Inbound -LocalPort 80 -Protocol TCP -Action Block -RemoteAddress $BlockIPs


# -----------------

# Block TCP Port 80 Connection From Internet for Second RDWebGW Server
$Command = {
param($BlockIPs)
$AddFirewallRuleBack = New-NetFirewallRule -DisplayName "TCP80BlockFromInternet" -Group "TCP80BlockFromInternet" -Direction Inbound -LocalPort 80 -Protocol TCP -Action Block -RemoteAddress $BlockIPs
}
$Cred = New-Object System.Management.Automation.PSCredential -ArgumentList $SecondRDWebGWAdmin,$SecondRDWebGWPassword
Invoke-Command -ComputerName $SecondRDWebGW -ArgumentList $BlockIPs -ScriptBlock $Command -credential $Cred

# -----------------

# Check New Cert and Old Cert

Import-Module RemoteDesktopServices

$WebGWPFXFullPath = "$PFXPath$RDWebGWURL.pfx"
$RDCBPFXFullPath = "$PFXPath$RDCBHAURL.pfx"

if (-not (Test-Path $WebGWPFXFullPath -PathType Leaf)) {

write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + $WebGWPFXFullPath + ' Not Exist.')
Stop-Transcript
exit;
};

if (-not (Test-Path $RDCBPFXFullPath -PathType Leaf)) {

write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + $RDCBPFXFullPath + ' Not Exist.')
Stop-Transcript
exit;
};

write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' Get Actived RDCB Server')

$ActiveRDCBMaster = $(Get-RDConnectionBrokerHighAvailability -ConnectionBroker $DefaultActivedRDCBMaster).ActiveManagementServer
write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + 'Active RDCB Server is ' + $ActiveRDCBMaster)

write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' Get New Cert Properties')

$NewRDWebGWCertProperties = $(Get-PfxData -FilePath $WebGWPFXFullPath -Password $CertPassword).EndEntityCertificates | Select Thumbprint,NotAfter
$NewRDCBCertProperties = $(Get-PfxData -FilePath $RDCBPFXFullPath -Password $CertPassword).EndEntityCertificates | Select Thumbprint,NotAfter

write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' Get Old Cert Properties')

$LastRDWebAccessCertProperties = Get-RDCertificate -ConnectionBroker $ActiveRDCBMaster -Role RDWebAccess | Select Thumbprint,ExpiresOn
$LastRDGatewayCertProperties = Get-RDCertificate -ConnectionBroker $ActiveRDCBMaster -Role RDGateway | Select Thumbprint,ExpiresOn
$LastRDRedirectorCertProperties = Get-RDCertificate -ConnectionBroker $ActiveRDCBMaster -Role RDRedirector | Select Thumbprint,ExpiresOn
$LastRDPublishingCertProperties = Get-RDCertificate -ConnectionBroker $ActiveRDCBMaster -Role RDPublishing | Select Thumbprint,ExpiresOn

$NewRDWebGWCertThumbprint = $NewRDWebGWCertProperties.Thumbprint

$NewRDWebGWCertExpireDateTime = $NewRDWebGWCertProperties.NotAfter

$LastRDWebAccessCertThumbprint = $LastRDWebAccessCertProperties.Thumbprint

$LastRDWebAccessCertExpireDateTime = $LastRDWebAccessCertProperties.ExpiresOn
$LastRDGatewayCertThumbprint = $LastRDGatewayCertProperties.Thumbprint
$LastRDGatewayCertExpireDateTime = $LastRDGatewayCertProperties.ExpiresOn

$NewRDCBCertThumbprint = $NewRDCBCertProperties.Thumbprint

$NewRDCBCertExpireDateTime = $NewRDCBCertProperties.NotAfter

$LastRDRedirectorCertThumbprint = $LastRDRedirectorCertProperties.Thumbprint

$LastRDRedirectorCertExpireDateTime = $LastRDRedirectorCertProperties.ExpiresOn
$LastRDPublishingCertThumbprint = $LastRDPublishingCertProperties.Thumbprint
$LastRDPublishingCertExpireDateTime = $LastRDPublishingCertProperties.ExpiresOn

write-host

write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' New RDWebGW Cert Thumbprint is: ' + $NewRDWebGWCertThumbprint)
write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' New RDWebGW Cert ExpireDateTime is: ' + $NewRDWebGWCertExpireDateTime)
write-host
write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' Last RDWebAccess Cert Thumbprint is: ' + $LastRDWebAccessCertThumbprint)
write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' Last RDWebAccess Cert ExpireDateTime is: ' + $LastRDWebAccessCertExpireDateTime)
write-host
write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' Last RDGateway Cert Thumbprint is: ' + $LastRDGatewayCertThumbprint)
write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' Last RDGateway Cert ExpireDateTime is: ' + $LastRDGatewayCertExpireDateTime)
write-host
write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' New RDCB Cert Thumbprint is: ' + $NewRDCBCertThumbprint)
write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' New RDCB Cert ExpireDateTime is: ' + $NewRDCBCertExpireDateTime)
write-host
write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' Last RDRedirector Cert Thumbprint is: ' + $LastRDRedirectorCertThumbprint)
write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' Last RDRedirector Cert ExpireDateTime is: ' + $LastRDRedirectorCertExpireDateTime)
write-host
write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' Last RDPublishing Cert Thumbprint is: ' + $LastRDPublishingCertThumbprint)
write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' Last RDPublishing Cert ExpireDateTime is: ' + $LastRDPublishingCertExpireDateTime)
write-host

if (($NewRDWebGWCertExpireDateTime -le $LastRDWebAccessCertExpireDateTime) -or ($NewRDWebGWCertExpireDateTime -le $LastRDGatewayCertExpireDateTime)) {

write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' New RDWebGW Certs ExpireDate is Less than or equal to the old one, Please check.')
Stop-Transcript
exit;
}

if (($NewRDWebGWCertThumbprint -eq $LastRDWebAccessCertThumbprint) -or ($NewRDWebGWCertThumbprint -eq $LastRDGatewayCertThumbprint)) {

write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' RDWebGW Cert Doesnt Change, Program Close, Please check.')
Stop-Transcript
exit;
}

if (($NewRDCBCertExpireDateTime -le $LastRDRedirectorCertExpireDateTime) -or ($NewRDCBCertExpireDateTime -le $LastRDPublishingCertExpireDateTime)) {

write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' New RDCB Certs ExpireDate is Less than or equal to the old one, Please check.')
Stop-Transcript
exit;
}

if (($NewRDCBCertThumbprint -eq $LastRDRedirectorCertThumbprint) -or ($NewRDCBCertThumbprint -eq $LastRDPublishingCertThumbprint)) {

write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' RDCB Cert Does not Change, Program Close, Please check.')
Stop-Transcript
exit;
}

# ----------------- # Output Thumbprint to Text File for use of GPO Updating $ThumbprintOutput = $LastRDPublishingCertThumbprint; if ($NewRDCBCertThumbprint -ne $LastRDPublishingCertThumbprint) { $ThumbprintOutput += (',' + $NewRDCBCertThumbprint) }; $ThumbprintFileFullPath = "C:\inetpub\wwwroot\Thumbprint.txt" $ThumbprintOutput | Out-File -Encoding ASCII -FilePath $ThumbprintFileFullPath -Force $ThumbprintFileFullPath = ('\\' + $SecondRDWebGW + '\C$\inetpub\wwwroot\Thumbprint.txt') $ThumbprintOutput | Out-File -Encoding ASCII -FilePath ('\\' + $SecondRDWebGW + '\C$\inetpub\wwwroot\Thumbprint.txt') -Force # ----------------- # Replace Cert if it is Sunday or Saturday and the Cert will be Expired in 15 days if ( (([Int](Get-Date).DayOfWeek) -eq 0) -or (([Int](Get-Date).DayOfWeek) -eq 6) ) { write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' It is ' + (Get-Date).DayOfWeek + '~') if ( ($(New-TimeSpan –Start (Get-Date) –End $LastRDWebAccessCertExpireDateTime).Days -lt 15) -and ($(New-TimeSpan –Start (Get-Date) –End $LastRDRedirectorCertExpireDateTime).Days -lt 15) ) { write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' Updateing RDWebAccess Cert.') Set-RDCertificate -ConnectionBroker $ActiveRDCBMaster -Role RDWebAccess -ImportPath $WebGWPFXFullPath -Password $CertPassword -Force write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' Updateing RDGateway Cert.') Set-RDCertificate -ConnectionBroker $ActiveRDCBMaster -Role RDGateway -ImportPath $WebGWPFXFullPath -Password $CertPassword -Force write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' Updateing RDRedirector Cert.') Set-RDCertificate -ConnectionBroker $ActiveRDCBMaster -Role RDRedirector -ImportPath $RDCBPFXFullPath -Password $CertPassword -Force write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' Updateing RDPublishing Cert.') Set-RDCertificate -ConnectionBroker $ActiveRDCBMaster -Role RDPublishing -ImportPath $RDCBPFXFullPath -Password $CertPassword -Force write-host ($(Get-Date -format ("yyyy.MM.dd HH:mm:ss")) + ' All Set!') }; }; Stop-Transcript exit;

沒有留言:

張貼留言