Update vCenter-SSL.ps1

This commit is contained in:
2025-11-15 20:44:30 -06:00
parent 3eec3a66ec
commit d5a42efd89

View File

@@ -50,7 +50,6 @@ function Show-Banner {
Add-Content -Path $LogFile -Value $log
}
# ----------------------------
# Global variables for troubleshooting
# ----------------------------
@@ -82,12 +81,15 @@ function Get-CertFingerprintFromPem {
)
try {
$pem = $PemString -replace "\r","" -split "`n" | Where-Object {
($_ -notlike "-----BEGIN*") -and ($_ -notlike "-----END*") -and ($_ -ne "")
} | Out-String
$pem = $pem.Trim()
# Strip the BEGIN/END lines and blank lines
$pemBody = $PemString -replace "`r","" -split "`n" | Where-Object {
($_ -notlike "-----BEGIN*") -and
($_ -notlike "-----END*") -and
($_ -ne "")
}
$pemBody = ($pemBody -join "") # single base64 string
$bytes = [Convert]::FromBase64String($pem)
$bytes = [Convert]::FromBase64String($pemBody)
$cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($bytes)
$sha256 = [System.Security.Cryptography.SHA256]::Create()
@@ -142,24 +144,26 @@ if (-not (Get-Module -ListAvailable -Name Posh-ACME)) {
Import-Module Posh-ACME -ErrorAction Stop
# ----------------------------
# ACME / PowerDNS certificate request with 30-day + rate-limit handling
# ACME / Posh-ACME certificate logic (v4.30-compatible)
# ----------------------------
$certSuccess = $false
$certPath = $null
$keyPath = $null
$chainPath = $null
$certPath = $null
$keyPath = $null
$chainPath = $null
# Get latest existing cert (Posh-ACME)
$existingPACert = Get-PACertificate -Domain $VCENTERHOST -ErrorAction SilentlyContinue |
# Get most recent Posh-ACME certificate for this domain
$existingPACert = Get-PACertificate |
Where-Object { $_.MainDomain -eq $VCENTERHOST } |
Sort-Object NotAfter -Descending |
Select-Object -First 1
$renewCert = $true
$skipReason = ""
# Rule 1: Skip if cert valid >30 days
if ($existingPACert) {
$daysLeft = ($existingPACert.NotAfter - (Get-Date)).TotalDays
Write-Log -Level "INFO" -Message "Existing Posh-ACME cert expires $($existingPACert.NotAfter) (~$([math]::Round($daysLeft)) days left)." -Color "Gray"
Write-Log -Level "INFO" -Message "Existing cert expires $($existingPACert.NotAfter) (~$([math]::Round($daysLeft)) days left)." -Color "Gray"
if ($daysLeft -gt 30) {
$renewCert = $false
@@ -167,15 +171,28 @@ if ($existingPACert) {
}
}
# Rule 2: LE rate-limit safety (don't request if last issuance < 168h)
if ($renewCert -and $existingPACert) {
$hoursSinceIssued = ((Get-Date) - $existingPACert.Created).TotalHours
if ($hoursSinceIssued -lt 168) {
$renewCert = $false
$skipReason = "LE rate-limit safety: last cert issued $([math]::Round($hoursSinceIssued)) hours ago (must wait 168h)."
}
}
if (-not $renewCert -and $existingPACert) {
Write-Log -Level "INFO" -Message "Skipping ACME request: $skipReason" -Color "Green"
Write-Log -Level "INFO" -Message "Skipping ACME issuance: $skipReason" -Color "Yellow"
$certPath = $existingPACert.CertificatePath
$keyPath = $existingPACert.PrivateKeyPath
$chainPath = $existingPACert.ChainPath
$certSuccess = $true
}
else {
# Need or want to renew
# We either have no existing cert, it's near expiry, or outside LE safety window
Write-Log -Level "INFO" -Message "Requesting new ACME certificate via Posh-ACME..." -Color "Cyan"
if ($PDNSAPI -is [string]) {
$securePDNSAPI = ConvertTo-SecureString $PDNSAPI -AsPlainText -Force
} else {
@@ -191,11 +208,17 @@ else {
}
try {
Write-Log -Level "INFO" -Message "Requesting new certificate via Posh-ACME..." -Color "Cyan"
New-PACertificate -Domain $VCENTERHOST -DnsPlugin PowerDNS -PluginArgs $pArgs `
-Contact $ACMEEMAIL -AcceptTOS -Verbose -Force -DnsSleep 15
New-PACertificate -Domain $VCENTERHOST `
-DnsPlugin PowerDNS `
-PluginArgs $pArgs `
-Contact $ACMEEMAIL `
-AcceptTOS `
-DnsSleep 15 `
-Force `
-Verbose
$newCert = Get-PACertificate -Domain $VCENTERHOST |
$newCert = Get-PACertificate |
Where-Object { $_.MainDomain -eq $VCENTERHOST } |
Sort-Object NotAfter -Descending |
Select-Object -First 1
@@ -206,24 +229,26 @@ else {
$certSuccess = $true
Show-Banner -Text "New ACME certificate successfully created." -Type "SUCCESS"
} else {
Write-Log -Level "ERROR" -Message "New-PACertificate succeeded but Get-PACertificate returned nothing." -Color "Red"
Write-Log -Level "ERROR" -Message "ACME issuance succeeded but no certificate object found from Get-PACertificate." -Color "Red"
$certSuccess = $false
}
}
catch {
$msg = $_.Exception.Message
Write-Log -Level "ERROR" -Message "ACME certificate request failed: $msg" -Color "Red"
$global:helpme = $msg
$errorMessage = $_.Exception.Message
Write-Log -Level "ERROR" -Message "ACME request failed: $errorMessage" -Color "Red"
$global:helpme = $errorMessage
# If we hit LE rate limit, fall back to existing cert (if any)
if ($msg -like "*too many certificates*") {
# If LE rate-limit hit, try to fall back to existing cert
if ($errorMessage -like "*too many certificates*") {
Show-Banner -Text "Lets Encrypt rate-limit reached. Using existing certificate if available." -Type "WARN"
if ($existingPACert) {
$certPath = $existingPACert.CertificatePath
$keyPath = $existingPACert.PrivateKeyPath
$chainPath = $existingPACert.ChainPath
$certSuccess = $true
} else {
Write-Log -Level "ERROR" -Message "No existing certificate available to fall back to." -Color "Red"
Show-Banner -Text "No existing certificate available to fall back to!" -Type "ERROR"
$certSuccess = $false
}
} else {
@@ -232,9 +257,7 @@ else {
}
}
# ----------------------------
# Verify certificate files exist
# ----------------------------
# Verify cert files exist
if ($certSuccess) {
foreach ($f in @($certPath, $keyPath, $chainPath)) {
if (-not (Test-Path $f)) {
@@ -250,19 +273,19 @@ if (-not $certSuccess) {
}
# ----------------------------
# Compare fingerprints with current vCenter cert
# vCenter REST: Compare fingerprints and update if needed
# ----------------------------
$updateNeeded = $true
$sessionHeaders = @{
'vmware-api-session-id' = $vCenterConn.ExtensionData.Content.SessionManager.SessionId
}
$vcenterCertUri = "https://$VCENTERHOST/rest/vcenter/certificate-management/vcenter/tls"
$updateNeeded = $true
try {
Write-Log -Level "INFO" -Message "Querying current vCenter TLS certificate..." -Color "Cyan"
$vcResp = Invoke-RestMethod -Uri $vcenterCertUri -Method Get -Headers $sessionHeaders -SkipCertificateCheck -ErrorAction Stop
# Try to find PEM in common shapes
$currentPem = $null
if ($vcResp.value -and $vcResp.value.cert) {
$currentPem = $vcResp.value.cert
@@ -276,14 +299,14 @@ try {
$newFp = Get-CertFingerprintFromPem -PemString $newPem
if ($currentFp -and $newFp -and ($currentFp -eq $newFp)) {
Write-Log -Level "INFO" -Message "vCenter already has the same certificate (SHA-256 fingerprint match)." -Color "Green"
Write-Log -Level "INFO" -Message "vCenter already has the same certificate (fingerprint match)." -Color "Green"
$updateNeeded = $false
} else {
Write-Log -Level "INFO" -Message "vCenter certificate fingerprint differs. Update is required." -Color "Yellow"
Write-Log -Level "INFO" -Message "vCenter certificate differs from Posh-ACME cert. Update is required." -Color "Yellow"
$updateNeeded = $true
}
} else {
Write-Log -Level "WARN" -Message "Could not find existing vCenter certificate in REST response. Assuming update is required." -Color "Yellow"
Write-Log -Level "WARN" -Message "Could not parse existing vCenter certificate from REST response. Assuming update required." -Color "Yellow"
$updateNeeded = $true
}
}
@@ -293,7 +316,7 @@ catch {
}
# ----------------------------
# Upload and apply certificate via REST (only if needed)
# Upload/apply cert if needed
# ----------------------------
$restartNeeded = $false
@@ -329,7 +352,7 @@ if ($updateNeeded) {
}
# ----------------------------
# Automatic vpxd restart via REST (only if we changed the cert)
# vpxd restart via REST (only if cert changed)
# ----------------------------
if ($restartNeeded) {
$maxRetries = 20
@@ -357,7 +380,7 @@ if ($restartNeeded) {
if (-not $restartSucceeded) {
Show-Banner -Text "Automatic vpxd restart failed after $maxRetries attempts. Please restart manually via SSH." -Type "ERROR"
Write-Log -Level "ERROR" -Message "Manual restart command: ssh root@$VCENTERHOST 'service-control --stop vpxd; service-control --start vpxd'" -Color "Red"
Write-Log -Level "ERROR" -Message "Manual restart: ssh root@$VCENTERHOST 'service-control --stop vpxd; service-control --start vpxd'" -Color "Red"
}
} else {
Write-Log -Level "INFO" -Message "Skipping vpxd restart because no certificate changes were applied." -Color "Green"