307 lines
11 KiB
PowerShell
307 lines
11 KiB
PowerShell
#!/usr/bin/env pwsh
|
|
# -----------------------------------------------------------------------------------
|
|
# vCenter Machine SSL Automation Script (vSphere 8.0 U3)
|
|
# - Uses Let's Encrypt via Posh-ACME and PowerDNS
|
|
# - Skips issuance if existing cert has >30 days
|
|
# - Pushes cert into vCenter using Set-VIMachineCertificate
|
|
# - Adds CA chain to trusted store
|
|
# - Restarts vpxd (Restart-VIApplianceService)
|
|
# - Performs Veeam Rescan-VBREntity when certificate changes
|
|
# - Fully non-interactive (no prompts)
|
|
# -----------------------------------------------------------------------------------
|
|
|
|
. /opt/idssys/nodemgmt/conf/powerwall/settings.ps1
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
$ConfirmPreference = 'None' # Disable all PowerCLI confirmations
|
|
|
|
# ----------------------------
|
|
# Logging
|
|
# ----------------------------
|
|
$LogFile = "/opt/idssys/nodemgmt/logs/vc-ssl.log"
|
|
|
|
function Write-Log {
|
|
param(
|
|
[ValidateSet('INFO','WARN','ERROR')]
|
|
[string]$Level,
|
|
[string]$Message,
|
|
[string]$ForegroundColor
|
|
)
|
|
|
|
$ts = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
|
|
$line = "[$ts] $Level : $Message"
|
|
|
|
if ($ForegroundColor) {
|
|
Write-Host $line -ForegroundColor $ForegroundColor
|
|
} else {
|
|
Write-Host $line
|
|
}
|
|
try {
|
|
$dir = Split-Path $LogFile
|
|
if (-not (Test-Path $dir)) {
|
|
New-Item -ItemType Directory -Path $dir -Force | Out-Null
|
|
}
|
|
Add-Content -Path $LogFile -Value $line
|
|
} catch {}
|
|
}
|
|
|
|
function Show-Failure {
|
|
param([System.Management.Automation.ErrorRecord]$ErrorRecord)
|
|
$msg = $ErrorRecord.Exception.Message
|
|
|
|
Write-Log ERROR $msg Red
|
|
Write-Host "======================================================" -ForegroundColor Red
|
|
Write-Host "ERROR: $msg" -ForegroundColor Red
|
|
Write-Host "======================================================" -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
|
|
# ----------------------------
|
|
# Constants
|
|
# ----------------------------
|
|
$RenewalWindow = 30
|
|
$DnsSleep = 15
|
|
|
|
# ----------------------------
|
|
# Load PowerCLI + Posh-ACME
|
|
# ----------------------------
|
|
Write-Log INFO "Loading Modules..."
|
|
try {
|
|
if (-not (Get-Module -ListAvailable -Name VCF.PowerCLI)) {
|
|
Install-Module VCF.PowerCLI -Force -Scope AllUsers -AllowClobber
|
|
}
|
|
|
|
Import-Module VCF.PowerCLI -ErrorAction Stop *>$null
|
|
Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false `
|
|
-ParticipateInCEIP:$false -DisplayDeprecationWarnings:$false | Out-Null
|
|
|
|
Write-Log INFO "VCF.PowerCLI loaded."
|
|
} catch { Show-Failure $_ }
|
|
|
|
try {
|
|
if (-not (Get-Module -ListAvailable -Name Posh-ACME)) {
|
|
Install-Module Posh-ACME -Force -Scope AllUsers
|
|
}
|
|
Import-Module Posh-ACME -ErrorAction Stop *>$null
|
|
Write-Log INFO "Posh-ACME loaded."
|
|
} catch { Show-Failure $_ }
|
|
|
|
# ----------------------------
|
|
# Connect to vCenter
|
|
# ----------------------------
|
|
try {
|
|
Write-Log INFO "Connecting to vCenter $VCENTERHOST..."
|
|
Connect-VIServer -Server $VCENTERHOST -User $VCENTERUSER -Password $VCENTERPASS -Force | Out-Null
|
|
|
|
Write-Host "========================================" -ForegroundColor Green
|
|
Write-Host "SUCCESS: Connected to vCenter." -ForegroundColor Green
|
|
Write-Host "========================================" -ForegroundColor Green
|
|
} catch { Show-Failure $_ }
|
|
|
|
# ----------------------------
|
|
# Sanity check: list VMs
|
|
# ----------------------------
|
|
try {
|
|
$vms = Get-VM
|
|
Write-Log INFO "Retrieved $($vms.Count) VMs from vCenter."
|
|
} catch {
|
|
Write-Log WARN "Failed to enumerate VMs: $($_.Exception.Message)" Orange
|
|
}
|
|
|
|
# ----------------------------
|
|
# Detect existing Posh-ACME cert
|
|
# ----------------------------
|
|
$paCert = $null
|
|
$needNewCert = $false
|
|
|
|
try {
|
|
$allCerts = Get-PACertificate -List
|
|
if ($allCerts) {
|
|
Write-Log INFO "Found $($allCerts.Count) Posh-ACME cert(s)."
|
|
$paCert = $allCerts |
|
|
Where-Object { $_.MainDomain -eq $VCENTERHOST -or ($_.AllSANs -contains $VCENTERHOST) } |
|
|
Sort-Object NotAfter -Descending |
|
|
Select-Object -First 1
|
|
}
|
|
} catch {
|
|
Write-Log WARN "Failed to query Posh-ACME certificates: $($_.Exception.Message)" Orange
|
|
}
|
|
|
|
if ($paCert) {
|
|
$daysLeft = ($paCert.NotAfter - (Get-Date)).TotalDays
|
|
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. ACME issuance required."
|
|
$needNewCert = $true
|
|
} else {
|
|
Write-Log INFO "Skipping issuance — certificate valid >$RenewalWindow days."
|
|
$needNewCert = $false
|
|
}
|
|
} else {
|
|
Write-Log WARN "No existing cert found — issuance required." Orange
|
|
$needNewCert = $true
|
|
}
|
|
|
|
# ----------------------------
|
|
# ACME issuance (only if needed)
|
|
# ----------------------------
|
|
if ($needNewCert) {
|
|
try {
|
|
Write-Log INFO "Requesting new ACME certificate via Posh-ACME..."
|
|
|
|
$pluginArgs = @{
|
|
PowerDNSApiHost = $WDNSHOST
|
|
PowerDNSApiKey = (ConvertTo-SecureString $PDNSAPI -AsPlainText -Force)
|
|
PowerDNSUseTLS = $true
|
|
PowerDNSPort = 443
|
|
PowerDNSServerName = 'localhost'
|
|
}
|
|
|
|
# Posh-ACME v4 syntax:
|
|
New-PACertificate `
|
|
-Domain $VCENTERHOST `
|
|
-Plugin PowerDNS `
|
|
-PluginArgs $pluginArgs `
|
|
-Contact $ACMEEMAIL `
|
|
-AcceptTOS `
|
|
-DnsSleep $DnsSleep `
|
|
-Verbose `
|
|
-Force
|
|
|
|
$paCert = Get-PACertificate
|
|
Write-Log INFO ("New certificate issued: NotAfter={0}" -f $paCert.NotAfter)
|
|
} catch {
|
|
Write-Log ERROR ("ACME issuance failed: {0}" -f $_.Exception.Message) Red
|
|
if (-not $paCert) {
|
|
Write-Log ERROR "No fallback certificate exists — aborting." Red
|
|
exit 1
|
|
}
|
|
Write-Log WARN "Using existing Posh-ACME certificate." Orange
|
|
}
|
|
}
|
|
|
|
if (-not $paCert) {
|
|
Write-Log ERROR "No usable certificate available — aborting." Red
|
|
exit 1
|
|
}
|
|
|
|
if ($needNewCert) {
|
|
# # ----------------------------
|
|
# # Certificate file resolution
|
|
# # ----------------------------
|
|
$certFolder = Split-Path $paCert.CertFile -Parent
|
|
|
|
$certPath = Join-Path $certFolder "cert.cer"
|
|
$keyPath = Join-Path $certFolder "cert.key"
|
|
$chainPath = Join-Path $certFolder "chain.cer"
|
|
|
|
Write-Log INFO "Using cert folder: $certFolder"
|
|
Write-Log INFO " CERT : $certPath"
|
|
Write-Log INFO " KEY : $keyPath"
|
|
Write-Log INFO " CHAIN: $chainPath"
|
|
|
|
foreach ($f in @($certPath,$keyPath,$chainPath)) {
|
|
if (-not (Test-Path $f)) {
|
|
Write-Log ERROR "Missing cert file: $f" Red
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
# ----------------------------
|
|
# Add CA chain to trusted store (remove duplicates)
|
|
# ----------------------------
|
|
try {
|
|
Write-Log INFO "Cleaning old CA trust entries..."
|
|
$issuer = ($paCert.Issuer)
|
|
$existingCA = Get-VITrustedCertificate | Where-Object { $_.Subject -eq $issuer }
|
|
foreach ($ca in $existingCA) {
|
|
Remove-VITrustedCertificate -Certificate $ca -Confirm:$false -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
$pemChain = Get-Content $chainPath -Raw
|
|
Write-Log INFO "Adding CA chain to vCenter trust store..."
|
|
Add-VITrustedCertificate -PemCertificateOrChain $pemChain -VCenterOnly -Confirm:$false | Out-Null
|
|
|
|
} catch {
|
|
Write-Log WARN "Failed to manage CA trust entries: $($_.Exception.Message)" -FvoregroundColor Orange
|
|
}
|
|
|
|
# ----------------------------
|
|
# Compare current vCenter cert
|
|
# ----------------------------
|
|
$needPush = $true
|
|
try {
|
|
$vcCert = Get-VIMachineCertificate -VCenterOnly -ErrorAction Stop
|
|
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 this certificate."
|
|
$needPush = $false
|
|
}
|
|
} catch {
|
|
Write-Log WARN "Unable to read vCenter cert, assuming update required." Orange
|
|
}
|
|
|
|
# ----------------------------
|
|
# Apply new certificate
|
|
# ----------------------------
|
|
if ($needPush) {
|
|
|
|
Write-Log INFO "Applying new Machine SSL certificate..."
|
|
|
|
$leafPem = Get-Content $certPath -Raw
|
|
$keyPem = Get-Content $keyPath -Raw
|
|
|
|
try {
|
|
Set-VIMachineCertificate -PemCertificate $leafPem -PemKey $keyPem -Confirm:$false | Out-Null
|
|
|
|
Write-Host "===========================================================" -ForegroundColor Green
|
|
Write-Host "SUCCESS: vCenter Machine SSL certificate updated." ForegroundColor Green
|
|
Write-Host "===========================================================" -ForegroundColor Green
|
|
|
|
Write-Log INFO "Certificate updated successfully."
|
|
|
|
# ----------------------------
|
|
# Restart vpxd service
|
|
# ----------------------------
|
|
try {
|
|
Write-Log INFO "Restarting vpxd via Restart-VIApplianceService..."
|
|
$svc = Get-VIApplianceService -Name 'vpxd' -ErrorAction Stop
|
|
$null = $svc | Restart-VIApplianceService -Confirm:$false
|
|
Write-Log INFO "vpxd restarted successfully."
|
|
} catch {
|
|
Write-Log WARN "vpxd restart failed: $($_.Exception.Message)" Orange
|
|
}
|
|
|
|
# ----------------------------
|
|
# Trigger Veeam rescan
|
|
# ----------------------------
|
|
if ($VEEAMHOSTSSH) {
|
|
try {
|
|
Write-Log INFO "Triggering Veeam host rescan on $VEEAMHOSTSSH..."
|
|
$veeamCmd = "Rescan-VBREntity -AllHosts"
|
|
$sshCmd = "ssh -tq -o ConnectTimeout=3 -o ConnectionAttempts=1 $VEEAMHOSTSSH '$veeamCmd'"
|
|
$result = bash -c $sshCmd
|
|
Write-Log INFO "Veeam rescan result: $result"
|
|
} catch {
|
|
Write-Log WARN "Veeam rescan failed: $($_.Exception.Message)" Orange
|
|
}
|
|
}
|
|
|
|
} catch {
|
|
Show-Failure $_
|
|
}
|
|
|
|
} else {
|
|
Write-Log INFO "No certificate update needed. Skipping vpxd restart + Veeam rescan."
|
|
}
|
|
}
|
|
|
|
# ----------------------------
|
|
# Script Complete
|
|
# ----------------------------
|
|
Write-Host "==========================================================" -ForegroundColor Green
|
|
Write-Host "INFO: Script complete. Log: $LogFile" -ForegroundColor Green
|
|
Write-Host "==========================================================" -ForegroundColor Green
|