Update vCenter-SSL.ps1
This commit is contained in:
@@ -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 "Let’s 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"
|
||||
|
||||
Reference in New Issue
Block a user