From 94008ceed0335de091d5140f9cc958d0de8abf89 Mon Sep 17 00:00:00 2001 From: Spencer McGuire Date: Sat, 17 Oct 2026 12:04:10 -0600 Subject: [PATCH] scripts: Export-PescoCaBundle.ps1 exports the PESCO CA chain from Windows and copies it to docker-1 Co-Authored-By: Claude Fable 5.1 --- scripts/Export-PescoCaBundle.ps1 | 70 ++++++++++++++++++++++++++++++++ scripts/README.md | 4 ++ 2 files changed, 74 insertions(+) create mode 100644 scripts/Export-PescoCaBundle.ps1 diff --git a/scripts/Export-PescoCaBundle.ps1 b/scripts/Export-PescoCaBundle.ps1 new file mode 100644 index 0000000..b896765 --- /dev/null +++ b/scripts/Export-PescoCaBundle.ps1 @@ -0,0 +1,70 @@ +<# +.SYNOPSIS +Exports the PESCO internal CA certificates from this PC's Windows certificate stores +to a PEM bundle and copies it to docker-1 for the Packet Station stacks. + +.DESCRIPTION +Run on a domain-joined PC. It reads the LocalMachine and CurrentUser Root and CA stores, +keeps every unexpired certificate whose subject matches -SubjectMatch (default PESCO), +writes them as one PEM file with a comment line per certificate, and copies the file to +the remote path with scp. The file holds public certificates only; nothing secret. + +The stacks point LDAP_CA_BUNDLE and SMTP_CA_BUNDLE at this file. MFILES_CA_BUNDLE stays +unset because M-Files uses a public certificate. + +.EXAMPLE +.\scripts\Export-PescoCaBundle.ps1 +Writes the bundle and copies it to spencerm@192.168.101.12:~/packetstation/test/ca/pesco-ca.pem + +.EXAMPLE +.\scripts\Export-PescoCaBundle.ps1 -Remote 'spencerm@192.168.101.12:/etc/packetstation/ca/pesco-ca.pem' + +.EXAMPLE +.\scripts\Export-PescoCaBundle.ps1 -NoCopy -OutFile C:\temp\pesco-ca.pem +#> +[CmdletBinding()] +param( + # Regex matched against each certificate subject. + [string]$SubjectMatch = 'PESCO', + # Local PEM file to write. + [string]$OutFile = (Join-Path $env:TEMP 'pesco-ca.pem'), + # scp destination. Needs an SSH key that works without a prompt. + [string]$Remote = 'spencerm@192.168.101.12:~/packetstation/test/ca/pesco-ca.pem', + # Write the file only; do not copy it. + [switch]$NoCopy +) + +$ErrorActionPreference = 'Stop' +$now = Get-Date + +$stores = 'Cert:\LocalMachine\Root', 'Cert:\LocalMachine\CA', 'Cert:\CurrentUser\Root', 'Cert:\CurrentUser\CA' +$certs = Get-ChildItem $stores | + Where-Object { $_.Subject -match $SubjectMatch -and $_.NotAfter -gt $now } | + Sort-Object Thumbprint -Unique + +if (-not $certs) { + throw "No unexpired certificate with subject matching '$SubjectMatch' in the Root or CA stores." +} + +$sb = New-Object System.Text.StringBuilder +foreach ($c in $certs) { + $sig = $c.SignatureAlgorithm.FriendlyName + [void]$sb.AppendLine("# $($c.Subject) | issued by $($c.Issuer) | expires $($c.NotAfter.ToString('yyyy-MM-dd')) | $sig") + [void]$sb.AppendLine('-----BEGIN CERTIFICATE-----') + [void]$sb.AppendLine([Convert]::ToBase64String($c.RawData, 'InsertLineBreaks')) + [void]$sb.AppendLine('-----END CERTIFICATE-----') + Write-Host (" {0} expires {1} {2}" -f $c.Subject, $c.NotAfter.ToString('yyyy-MM-dd'), $sig) + if ($sig -match 'sha1') { + Write-Warning "$($c.Subject) is SHA-1 signed. OpenSSL 3 refuses SHA-1 chains at its default security level; see docs/DEPLOY.md." + } +} + +# LF line endings so the file is clean on Linux. +[IO.File]::WriteAllText($OutFile, $sb.ToString().Replace("`r`n", "`n")) +Write-Host "Wrote $OutFile ($($certs.Count) certificates)" + +if ($NoCopy) { return } + +& scp -o BatchMode=yes $OutFile $Remote +if ($LASTEXITCODE -ne 0) { throw "scp to $Remote failed (exit $LASTEXITCODE)" } +Write-Host "Copied to $Remote" diff --git a/scripts/README.md b/scripts/README.md index 4c96fa5..60912b6 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -2,3 +2,7 @@ Backup and restore scripts arrive in phase 9: a per-stack `pg_dump` backup with a systemd timer on docker-1, and a restore script with a rehearsal recorded in docs/DEPLOY.md. + +## Export-PescoCaBundle.ps1 + +Run on a domain-joined PC. Exports every unexpired PESCO CA certificate from the Windows stores to one PEM bundle and copies it to docker-1 with scp (default target `~/packetstation/test/ca/pesco-ca.pem`). Use `-Remote` for another path and `-NoCopy` to only write the file. It warns on SHA-1 signed certificates; see docs/DEPLOY.md.