diff --git a/inc/vCenter-SSL.ps1 b/inc/vCenter-SSL.ps1 index 281986e9..6866bbce 100644 --- a/inc/vCenter-SSL.ps1 +++ b/inc/vCenter-SSL.ps1 @@ -2,7 +2,7 @@ # ----------------------------------------------------------------------------------- # vCenter + Posh-ACME SSL Automation Script # Fully idempotent, rate-limit safe, fingerprint matching, full logging -# Compatible with: Posh-ACME 4.30.0 +# Compatible with Posh-ACME 4.30.0 # ----------------------------------------------------------------------------------- . /opt/idssys/nodemgmt/conf/powerwall/settings.ps1 @@ -12,9 +12,7 @@ # ---------------------------- $LogFile = "/opt/idssys/nodemgmt/logs/vc-ssl.log" $logDir = Split-Path -Path $LogFile -Parent -if (-not (Test-Path $logDir)) { - New-Item -Path $logDir -ItemType Directory -Force | Out-Null -} +if (-not (Test-Path $logDir)) { New-Item -Path $logDir -ItemType Directory -Force | Out-Null } function Write-Log { param([string]$Level,[string]$Message,[string]$Color="White") @@ -52,22 +50,21 @@ function Show-Failure { } # ---------------------------- -# SHA-256 CERT FINGERPRINT +# CERT FINGERPRINT FUNCTION # ---------------------------- function Get-CertFingerprintFromPem { param([string]$PemString) - try { - $pemBody = $PemString -replace "`r","" -split "`n" | - Where-Object {$_ -notmatch "^-----"} | - Where-Object {$_ -ne ""} - $pemBody = ($pemBody -join "") - $bytes = [Convert]::FromBase64String($pemBody) - $cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($bytes) + $pemBody = $PemString -replace "`r","" -split "`n" | + Where-Object {$_ -notmatch "^-----" -and $_ -ne ""} + + $bytes = [Convert]::FromBase64String(($pemBody -join "")) + $cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($bytes) $sha256 = [System.Security.Cryptography.SHA256]::Create() - $hash = $sha256.ComputeHash($cert.RawData) + $hash = $sha256.ComputeHash($cert.RawData) return ($hash | ForEach-Object { $_.ToString("X2") }) -join "" - } catch { + } + catch { Write-Log -Level "WARN" -Message "Fingerprint calc error: $($_.Exception.Message)" -Color Yellow return $null } @@ -100,25 +97,22 @@ if (-not (Get-Module -ListAvailable Posh-ACME)) { Import-Module Posh-ACME -ErrorAction Stop # ---------------------------- -# ACME CERTIFICATE LOGIC (Posh-ACME 4.30) +# ACME CERTIFICATE LOGIC # ---------------------------- $certSuccess = $false $certPath = $keyPath = $chainPath = $null -# Log all certs first $allPACerts = Get-PACertificate -ErrorAction SilentlyContinue if ($allPACerts) { Write-Log INFO "Found $($allPACerts.Count) Posh-ACME certs." Gray foreach ($c in $allPACerts) { Write-Log INFO (" Cert: MainDomain={0} SANs={1} Exp={2}" -f $c.MainDomain, ($c.AllSANs -join ","), $c.NotAfter) Gray } -} else { - Write-Log INFO "No Posh-ACME certs found." Yellow } -# Find cert matching VCENTERHOST +# Match by MainDomain OR SANs $existingPACert = $allPACerts | - Where-Object {$_.MainDomain -eq $VCENTERHOST -or ($_.AllSANs -contains $VCENTERHOST)} | + Where-Object { $_.MainDomain -eq $VCENTERHOST -or ($_.AllSANs -contains $VCENTERHOST) } | Sort-Object NotAfter -Descending | Select-Object -First 1 @@ -129,31 +123,40 @@ $skipReason = "" if ($existingPACert) { $daysLeft = ($existingPACert.NotAfter - (Get-Date)).TotalDays Write-Log INFO "Existing cert expires $($existingPACert.NotAfter) (~$([math]::Round($daysLeft)) days)." Gray + if ($daysLeft -gt 30) { - $renewCert = $false + $renewCert = $false $skipReason = "Existing certificate still valid >30 days." } } -# RULE 2: LE rate-limit safety (168h) +# RULE 2: Rate-limit safety (168 hours) if ($renewCert -and $existingPACert) { - $hoursSinceIssued = ((Get-Date) - $existingPACert.Created).TotalHours - if ($hoursSinceIssued -lt 168) { + $hoursSince = ((Get-Date) - $existingPACert.Created).TotalHours + if ($hoursSince -lt 168) { $renewCert = $false - $skipReason = "LE rate-limit safety: last cert was $([math]::Round($hoursSinceIssued)) hours ago." + $skipReason = "Last certificate issued $([math]::Round($hoursSince)) hours ago (<168h)." } } -# If skipping ACME issuance +# If skipping issuance if (-not $renewCert -and $existingPACert) { - Write-Log INFO "Skipping new ACME issuance: $skipReason" Yellow - $certPath = $existingPACert.CertificatePath - $keyPath = $existingPACert.PrivateKeyPath - $chainPath = $existingPACert.ChainPath + Write-Log INFO "Skipping ACME issuance: $skipReason" Yellow + + # --- FIX: DERIVE CERT FOLDER FROM PFXPATH --- + Write-Log INFO "Applying fix: deriving certificate folder from PfxPath." Gray + $certFolder = Split-Path $existingPACert.PfxPath -Parent + Write-Log INFO "Cert folder resolved to: $certFolder" Gray + + $certPath = Join-Path $certFolder "cert.pem" + $keyPath = Join-Path $certFolder "privkey.pem" + $chainPath = Join-Path $certFolder "chain.pem" + $certSuccess = $true } -else { - # NEED NEW CERTIFICATE + +# --- OTHERWISE REQUEST NEW CERT --- +if ($renewCert) { Write-Log INFO "Requesting new ACME certificate..." Cyan $securePDNSAPI = if ($PDNSAPI -is [string]) { @@ -173,19 +176,20 @@ else { -Contact $ACMEEMAIL -AcceptTOS -DnsSleep 15 -Force -Verbose $newCert = Get-PACertificate | - Where-Object {$_.MainDomain -eq $VCENTERHOST -or ($_.AllSANs -contains $VCENTERHOST)} | + Where-Object { $_.MainDomain -eq $VCENTERHOST -or ($_.AllSANs -contains $VCENTERHOST) } | Sort-Object NotAfter -Descending | Select-Object -First 1 if ($newCert) { - $certPath = $newCert.CertificatePath - $keyPath = $newCert.PrivateKeyPath - $chainPath = $newCert.ChainPath + $certFolder = Split-Path $newCert.PfxPath -Parent + $certPath = Join-Path $certFolder "cert.pem" + $keyPath = Join-Path $certFolder "privkey.pem" + $chainPath = Join-Path $certFolder "chain.pem" $certSuccess = $true - Show-Banner "New ACME certificate created." SUCCESS + Show-Banner "New ACME cert created." SUCCESS } else { - Write-Log ERROR "ACME succeeded but no certificate found." Red - $certSuccess = $false + Write-Log ERROR "ACME succeeded but no cert found." Red + exit 1 } } catch { @@ -194,20 +198,20 @@ else { $global:helpme = $msg if ($msg -like "*too many certificates*") { - Show-Banner "Rate-limit hit. Trying fallback..." WARN + Show-Banner "Rate-limit hit. Falling back to existing cert." WARN if ($existingPACert) { - $certPath = $existingPACert.CertificatePath - $keyPath = $existingPACert.PrivateKeyPath - $chainPath = $existingPACert.ChainPath + $certFolder = Split-Path $existingPACert.PfxPath -Parent + $certPath = Join-Path $certFolder "cert.pem" + $keyPath = Join-Path $certFolder "privkey.pem" + $chainPath = Join-Path $certFolder "chain.pem" $certSuccess = $true } else { Show-Banner "No fallback cert exists. Aborting." ERROR exit 1 } - } - else { exit 1 } + } else { exit 1 } } } @@ -220,7 +224,7 @@ foreach ($f in @($certPath,$keyPath,$chainPath)) { } # ---------------------------- -# CHECK VCENTER CERT & FINGERPRINT MATCH +# RETRIEVE VCENTER CURRENT CERT + COMPARE FINGERPRINTS # ---------------------------- $sessionHeaders = @{ 'vmware-api-session-id' = $vCenterConn.ExtensionData.Content.SessionManager.SessionId @@ -238,36 +242,37 @@ try { $newFp = Get-CertFingerprintFromPem -PemString $newPem if ($currentFp -and $newFp -and ($currentFp -eq $newFp)) { - Show-Banner "vCenter certificate already up-to-date." SUCCESS + Show-Banner "vCenter SSL certificate already up-to-date." SUCCESS $updateNeeded = $false } else { - Write-Log INFO "Certificate fingerprints differ. Update required." Yellow + Write-Log INFO "Certificate fingerprint mismatch. vCenter update required." Yellow } } catch { - Write-Log WARN "Cannot read vCenter cert, assuming update needed." Yellow - $updateNeeded = $true + Write-Log WARN "Unable to read vCenter cert; assuming update needed." Yellow } # ---------------------------- -# UPLOAD + APPLY NEW CERT +# UPLOAD + APPLY CERT IF NEEDED # ---------------------------- $restartNeeded = $false if ($updateNeeded) { try { + Write-Log INFO "Uploading new TLS certificate..." Cyan + $body = @{ cert = Get-Content -Raw $certPath key = Get-Content -Raw $keyPath chain = Get-Content -Raw $chainPath } - Write-Log INFO "Uploading new TLS cert to vCenter..." Cyan Invoke-RestMethod -Uri $vcenterCertUri -Method Post ` -Headers $sessionHeaders -ContentType "application/json" ` -Body ($body | ConvertTo-Json -Compress) -SkipCertificateCheck - Write-Log INFO "Applying new TLS cert..." Cyan + Write-Log INFO "Applying new TLS certificate..." Cyan + Invoke-RestMethod -Uri "$vcenterCertUri?action=apply" ` -Method Post -Headers $sessionHeaders -SkipCertificateCheck @@ -280,11 +285,11 @@ if ($updateNeeded) { } } else { - Write-Log INFO "Skipping cert upload/apply." Green + Write-Log INFO "Skipping upload/apply (not needed)." Green } # ---------------------------- -# RESTART VPXD IF NEEDED +# RESTART VPXD VIA REST # ---------------------------- if ($restartNeeded) { $maxRetries = 20 @@ -300,7 +305,7 @@ if ($restartNeeded) { break } catch { - Write-Log WARN "vpxd REST not ready, retry $i/$maxRetries..." Yellow + Write-Log WARN "vpxd REST not ready; retry $i/$maxRetries..." Yellow Start-Sleep -Seconds 15 } }