Update vCenter-SSL.ps1
This commit is contained in:
@@ -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
|
||||
@@ -24,22 +27,28 @@ function Write-Log {
|
||||
[string]$Message
|
||||
)
|
||||
$ts = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
|
||||
$line = "[{0}] {1}: {2}" -f $ts,$Level,$Message
|
||||
$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
|
||||
$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"
|
||||
|
||||
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)" }
|
||||
|
||||
$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 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
|
||||
Write-Log WARN "Automatic vpxd restart failed: $($_.Exception.Message). Please restart vCenter services manually if required."
|
||||
}
|
||||
|
||||
} catch {
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user