diff --git a/inc/vCenter-SSL.ps1 b/inc/vCenter-SSL.ps1 index e3a925c0..7d7a9910 100644 --- a/inc/vCenter-SSL.ps1 +++ b/inc/vCenter-SSL.ps1 @@ -2,14 +2,17 @@ # ----------------------------------------------------------------------------------- # vCenter Machine SSL automation for vSphere 8.0 U3 # - Uses Let's Encrypt via Posh-ACME (PowerDNS) -# - Skips issuance if cert >30 days +# - Skips issuance if existing cert has >30 days remaining # - Pushes Posh-ACME cert into vCenter using Set-VIMachineCertificate -# - Works WITHOUT deprecated REST endpoints (no 404s) +# - Adds CA chain with Add-VITrustedCertificate +# - Automatically restarts vpxd via Restart-VIApplianceService +# - Runs completely non-interactive (no prompts) # ----------------------------------------------------------------------------------- . /opt/idssys/nodemgmt/conf/powerwall/settings.ps1 $ErrorActionPreference = 'Stop' +$ConfirmPreference = 'None' # Disable all confirmation prompts # ---------------------------- # Logging @@ -23,23 +26,29 @@ function Write-Log { [Parameter(Mandatory)] [string]$Message ) - $ts = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' - $line = "[{0}] {1}: {2}" -f $ts,$Level,$Message + $ts = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' + $line = "[{0}] {1}: {2}" -f $ts, $Level, $Message + Write-Host $line try { $dir = Split-Path $LogFile -Parent - if (-not (Test-Path $dir)) { + if ($dir -and -not (Test-Path $dir)) { New-Item -Path $dir -ItemType Directory -Force | Out-Null } Add-Content -Path $LogFile -Value $line - } catch {} + } catch { + # Logging failure is non-fatal + } } function Show-Failure { param([System.Management.Automation.ErrorRecord]$ErrorRecord) - Write-Log ERROR $ErrorRecord.Exception.Message + + $msg = $ErrorRecord.Exception.Message + Write-Log ERROR $msg + Write-Host "======================================================" -ForegroundColor Red - Write-Host "ERROR: $($ErrorRecord.Exception.Message)" -ForegroundColor Red + Write-Host "ERROR: $msg" -ForegroundColor Red Write-Host "======================================================" -ForegroundColor Red exit 1 } @@ -47,8 +56,8 @@ function Show-Failure { # ---------------------------- # Safety constants # ---------------------------- -$RenewalWindow = 30 -$DnsSleep = 15 +$RenewalWindow = 30 # Don't re-issue if cert has > 30 days left +$DnsSleep = 15 # Seconds for DNS propagation for ACME DNS-01 # ---------------------------- # Ensure PowerCLI + Posh-ACME @@ -59,7 +68,14 @@ try { Install-Module -Name VMware.PowerCLI -Scope AllUsers -Force -AllowClobber } Import-Module VMware.PowerCLI -ErrorAction Stop - Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false | Out-Null + + # Disable all confirmations and cert warnings from PowerCLI + Set-PowerCLIConfiguration -Scope AllUsers ` + -InvalidCertificateAction Ignore ` + -Confirm:$false ` + -ParticipateInCEIP:$false ` + -DisplayDeprecationWarnings:$false | Out-Null + Write-Log INFO "VMware.PowerCLI loaded." } catch { Show-Failure $_ } @@ -78,13 +94,14 @@ try { try { Write-Log INFO "Connecting to vCenter $VCENTERHOST..." Connect-VIServer -Server $VCENTERHOST -User $VCENTERUSER -Password $VCENTERPASS -Force | Out-Null + Write-Host "========================================" Write-Host "SUCCESS: Connected to vCenter." Write-Host "========================================" } catch { Show-Failure $_ } # ---------------------------- -# Get VM list (sanity) +# Get VM list (sanity check) # ---------------------------- try { $vms = Get-VM @@ -94,9 +111,11 @@ try { } # ---------------------------- -# Locate Posh-ACME cert object +# Locate existing Posh-ACME cert for VCENTERHOST # ---------------------------- -$paCert = $null +$paCert = $null +$needNewCert = $false + try { $allPaCerts = Get-PACertificate -List if ($allPaCerts) { @@ -104,6 +123,7 @@ try { foreach ($c in $allPaCerts) { Write-Log INFO (" MainDomain={0} Exp={1}" -f $c.MainDomain, $c.NotAfter) } + $paCert = $allPaCerts | Where-Object { $_.MainDomain -eq $VCENTERHOST -or ($_.AllSANs -contains $VCENTERHOST) } | Sort-Object NotAfter -Descending | @@ -113,15 +133,18 @@ try { Write-Log WARN "Failed to query Posh-ACME certs: $($_.Exception.Message)" } -$needNewCert = $false - if ($paCert) { $daysLeft = ($paCert.NotAfter - (Get-Date)).TotalDays - Write-Log INFO "Existing cert expires $($paCert.NotAfter) (~$([math]::Round($daysLeft)) days)." - if ($daysLeft -le $RenewalWindow) { $needNewCert = $true } - else { Write-Log INFO "Skipping ACME issuance: certificate valid >30 days." } + Write-Log INFO ("Existing cert expires {0} (~{1:N0} days)." -f $paCert.NotAfter, $daysLeft) + + if ($daysLeft -le $RenewalWindow) { + Write-Log INFO "Existing cert within $RenewalWindow days of expiry. Will request new ACME certificate." + $needNewCert = $true + } else { + Write-Log INFO "Skipping ACME issuance: certificate valid > $RenewalWindow days." + } } else { - Write-Log WARN "No existing Posh-ACME cert found. Will issue new." + Write-Log WARN "No existing Posh-ACME cert found for $VCENTERHOST. Will request new certificate." $needNewCert = $true } @@ -135,10 +158,10 @@ if ($needNewCert) { PowerDNSApiKey = (ConvertTo-SecureString $PDNSAPI -AsPlainText -Force) PowerDNSUseTLS = $true PowerDNSPort = 443 - PowerDNSServerName = "localhost" + PowerDNSServerName = 'localhost' } - Write-Log INFO "Requesting new ACME certificate..." + Write-Log INFO "Requesting new ACME certificate via Posh-ACME..." New-PACertificate ` -MainDomain $VCENTERHOST ` -Plugin PowerDNS ` @@ -146,13 +169,13 @@ if ($needNewCert) { -Contact $ACMEEMAIL ` -AcceptTOS ` -DnsSleep $DnsSleep ` - -Verbose -Force + -Verbose ` + -Force $paCert = Get-PACertificate - Write-Log INFO "New ACME certificate issued." - } - catch { - Write-Log ERROR "ACME issuance failed: $($_.Exception.Message)" + Write-Log INFO ("New ACME certificate issued. NotAfter={0}" -f $paCert.NotAfter) + } catch { + Write-Log ERROR ("ACME issuance failed: {0}" -f $_.Exception.Message) if (-not $paCert) { Write-Host "No fallback certificate exists; aborting." -ForegroundColor Red exit 1 @@ -161,21 +184,37 @@ if ($needNewCert) { } } +# Ensure we have a certificate at this point +if (-not $paCert) { + Write-Log ERROR "No usable Posh-ACME certificate available. Aborting." + exit 1 +} + # ---------------------------- -# Locate certificate files (LEAF ONLY!) +# Locate certificate files (LEAF ONLY for vCenter) # ---------------------------- $certFolder = Split-Path $paCert.CertFile -Parent -$certPath = Join-Path $certFolder "cert.cer" # LEAF ONLY -$keyPath = Join-Path $certFolder "cert.key" # PRIVATE KEY -$chainPath = Join-Path $certFolder "chain.cer" # INTERMEDIATE +if (-not $certFolder) { + Write-Log ERROR "Unable to determine certificate folder from Posh-ACME. Aborting." + exit 1 +} + +# Posh-ACME outputs: +# cert.cer = leaf certificate +# cert.key = private key +# chain.cer = intermediate chain +# fullchain.cer = leaf + chain (NOT used by Set-VIMachineCertificate) +$certPath = Join-Path $certFolder "cert.cer" # leaf only +$keyPath = Join-Path $certFolder "cert.key" # private key +$chainPath = Join-Path $certFolder "chain.cer" # intermediate CA(s) Write-Log INFO "Using cert folder: $certFolder" Write-Log INFO " CERT(leaf) : $certPath" -Write-Log INFO " KEY : $keyPath" -Write-Log INFO " CHAIN : $chainPath" +Write-Log INFO " KEY : $keyPath" +Write-Log INFO " CHAIN : $chainPath" -foreach ($f in @($certPath,$keyPath,$chainPath)) { +foreach ($f in @($certPath, $keyPath, $chainPath)) { if (-not (Test-Path $f)) { Write-Log ERROR "Missing certificate file: $f" exit 1 @@ -183,44 +222,53 @@ foreach ($f in @($certPath,$keyPath,$chainPath)) { } # ---------------------------- -# Add chain to trusted store +# Add CA chain to vCenter trusted store # ---------------------------- try { - Write-Log INFO "Adding/updating CA chain to vCenter trusted store..." + Write-Log INFO "Adding/updating CA chain in vCenter trusted store..." $pemChain = Get-Content $chainPath -Raw if ($pemChain.Trim().Length -gt 0) { Add-VITrustedCertificate -PemCertificateOrChain $pemChain -VCenterOnly -ErrorAction SilentlyContinue | Out-Null + } else { + Write-Log WARN "CHAIN file appears empty; skipping trusted chain update." } } catch { - Write-Log WARN "Failed to add trusted chain: $($_.Exception.Message)" + Write-Log WARN "Failed to add trusted chain (non-fatal): $($_.Exception.Message)" } # ---------------------------- -# Compare current vCenter cert +# Compare current vCenter Machine SSL cert # ---------------------------- -$vcCert = $null -try { $vcCert = Get-VIMachineCertificate -VCenterOnly } -catch { Write-Log WARN "Unable to read current vCenter cert: $($_.Exception.Message)" } - +$vcCert = $null $needPush = $true +try { + $vcCert = Get-VIMachineCertificate -VCenterOnly +} catch { + Write-Log WARN "Unable to read current vCenter certificate: $($_.Exception.Message). Assuming update required." +} + if ($vcCert) { - Write-Log INFO ("Current vCenter cert: Subject={0} NotAfter={1}" -f $vcCert.Subject,$vcCert.NotValidAfter) + Write-Log INFO ("Current vCenter cert: Subject={0} NotAfter={1}" -f $vcCert.Subject, $vcCert.NotValidAfter) + if ($vcCert.Thumbprint -eq $paCert.Thumbprint) { - Write-Log INFO "vCenter already using the Posh-ACME certificate." + Write-Log INFO "vCenter already using the same certificate as Posh-ACME. No update necessary." $needPush = $false } else { - Write-Log INFO "vCenter cert differs; update required." + Write-Log INFO "vCenter certificate differs from Posh-ACME cert. Update required." } } # ---------------------------- -# Apply certificate (PowerCLI) +# Apply certificate via Set-VIMachineCertificate # ---------------------------- if ($needPush) { try { - $leafPem = Get-Content $certPath -Raw # ONE cert block - $keyPem = Get-Content $keyPath -Raw # private key + $leafPem = Get-Content $certPath -Raw + $keyPem = Get-Content $keyPath -Raw + + if (-not $leafPem.Trim()) { throw "Leaf certificate PEM appears empty." } + if (-not $keyPem.Trim()) { throw "Private key PEM appears empty." } Write-Log INFO "Applying certificate via Set-VIMachineCertificate..." Set-VIMachineCertificate -PemCertificate $leafPem -PemKey $keyPem | Out-Null @@ -228,18 +276,30 @@ if ($needPush) { Write-Host "===========================================================" Write-Host "SUCCESS: vCenter Machine SSL certificate updated." -ForegroundColor Green Write-Host "===========================================================" - Write-Log INFO "Certificate applied successfully." + + # ---------------------------- + # Restart vpxd service via appliance APIs + # ---------------------------- + try { + Write-Log INFO "Restarting vpxd service via Restart-VIApplianceService..." + # vpxd service name is typically 'vpxd' + $vpxdSvc = Get-VIApplianceService -Name 'vpxd' -ErrorAction Stop + if ($vpxdSvc) { + $null = $vpxdSvc | Restart-VIApplianceService -Confirm:$false + Write-Log INFO "vpxd restart requested successfully." + } else { + Write-Log WARN "vpxd service not found via Get-VIApplianceService. Please check manually." + } + } catch { + Write-Log WARN "Automatic vpxd restart failed: $($_.Exception.Message). Please restart vCenter services manually if required." + } + } catch { - Write-Log ERROR "Failed updating vCenter cert: $($_.Exception.Message)" - Write-Host "===========================================================" -ForegroundColor Red - Write-Host "ERROR applying certificate." -ForegroundColor Red - Write-Host $($_.Exception.Message) -ForegroundColor Red - Write-Host "===========================================================" -ForegroundColor Red - exit 1 + Show-Failure $_ } } else { - Write-Log INFO "Skipping cert update; already matched." + Write-Log INFO "Skipping vCenter certificate update; thumbprints already match." } # ---------------------------- @@ -247,4 +307,4 @@ if ($needPush) { # ---------------------------- Write-Host "==========================================================" -ForegroundColor Green Write-Host "INFO: Script complete. Log: $LogFile" -ForegroundColor Green -Write-Host "==========================================================" +Write-Host "==========================================================" -ForegroundColor Green