scripts: Export-PescoCaBundle.ps1 exports the PESCO CA chain from Windows and copies it to docker-1
ci / frontend (push) Successful in 35s
ci / server (push) Successful in 18m3s
ci / image (push) Successful in 13s
ci / e2e (push) Successful in 1m44s

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
2026-10-17 12:04:10 -06:00
co-authored by Claude Fable 5.1
parent c244a684b2
commit 94008ceed0
2 changed files with 74 additions and 0 deletions
+70
View File
@@ -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 = '[email protected]:~/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"
+4
View File
@@ -2,3 +2,7 @@
Backup and restore scripts arrive in phase 9: a per-stack `pg_dump` backup with a 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. 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.