From 90adcebc440c4aee17b7e7e3195b288465690d8b Mon Sep 17 00:00:00 2001 From: Spencer McGuire Date: Sun, 18 Oct 2026 21:41:03 -0600 Subject: [PATCH] scripts: vsql certificate script fixes the six review findings, with a Pester suite on PowerShell 5.1 and 7 - Admin check uses -not (IsAdmin); the old call passed -eq $false as arguments. - Named instances: WQL filter Name='MSSQL$DEV' unescaped, lookup errors surface. - SANs decoded from the raw extension (DER walk) and matched as whole names. - Immutable deploy-, request- and rollback- records; already-bound instances get no snapshot; -Rollback takes the newest snapshot not yet rolled back, -Snapshot picks one. - certreq -new, -submit, -accept and the key grants go through ShouldProcess; a declined or failed prerequisite binds nothing. - Rollback and automatic restore work per instance, read back, report failures. Also found while testing: PowerShell 7 returns RSACng for a CSP key, so the provider check now reads CERT_KEY_PROV_INFO; ForEach-Object member form returned nothing under -WhatIf; stderr capture under Stop threw on 5.1; LocalSystem and NetworkService accounts go to icacls as SIDs; SQL Agent is started again after a restart; signature checked by OID. DEPLOY.md gains the run procedure: preflight, DEV, docker-1 checks, production, rollback. Co-Authored-By: Claude Opus 5.5 --- docs/DEPLOY.md | 58 ++ scripts/README.md | 16 +- scripts/Request-VsqlServerCertificate.ps1 | 868 +++++++++++++----- .../Request-VsqlServerCertificate.Tests.ps1 | 598 ++++++++++++ 4 files changed, 1313 insertions(+), 227 deletions(-) create mode 100644 scripts/tests/Request-VsqlServerCertificate.Tests.ps1 diff --git a/docs/DEPLOY.md b/docs/DEPLOY.md index aa51908..8ca797d 100644 --- a/docs/DEPLOY.md +++ b/docs/DEPLOY.md @@ -180,6 +180,64 @@ Docker's default address pool on docker-1 holds fifteen /16 bridge networks (172 `vsql.pesco.com\DEV` serves TLS (`VISUAL_ENCRYPT=trust` connects), but with `VISUAL_ENCRYPT=yes` OpenSSL in the image refuses it with "EE certificate key too weak": the server certificate's RSA key is under 2048 bits, which is what SQL Server's auto-generated self-signed certificate looks like. IT needs to issue vsql a 2048-bit, SHA-256 certificate from PESCO-PSYLOCKE-CA with `vsql.pesco.com` in the subject alternative names, install it on the DEV and production instances in SQL Server Configuration Manager and restart them. The image then needs the PESCO CA in its OpenSSL trust store for the ODBC driver (the entrypoint can concatenate the system bundle and /etc/ssl/pesco/ca.pem into /tmp and export `SSL_CERT_FILE`), and the stacks switch to `VISUAL_ENCRYPT=yes`. Tracked as Gitea issue 5. +### Installing the vsql certificate + +`scripts/Request-VsqlServerCertificate.ps1` does the request, the checks and the binding. Spencer runs it on the SQL Server host, in an elevated PowerShell, as an account that may enroll from the WebServer template. Every step there is a real change, so preview first. Copy only the script to `C:\ProgramData\PacketStation\` and work from there. + +1. Preflight. Nothing changes. Check the instance names, the service accounts, the INF and the list of steps. + + ```powershell + cd C:\ProgramData\PacketStation + powershell -ExecutionPolicy Bypass -File .\Request-VsqlServerCertificate.ps1 -Instances DEV -WhatIf + ``` + +2. DEV first. This enrolls, validates, grants the DEV service account read on the key, saves a snapshot and binds DEV. Production is untouched. + + ```powershell + powershell -ExecutionPolicy Bypass -File .\Request-VsqlServerCertificate.ps1 -Instances DEV + ``` + + If the CA holds the request as pending, the script stops with the request id. Issue it on the CA (certsrv.msc, Pending Requests), then run the `certreq -retrieve` line it printed and continue with `-CerFile -Instances DEV`. If validation fails, nothing is bound. The usual cause is a template that forces a Key Storage Provider. + +3. Restart DEV and check the log. `Restart-Service -Name 'MSSQL$DEV' -Force`, then start `SQLAgent$DEV` again if it ran. Or rerun with `-Thumbprint -Instances DEV -RestartInstances`, which leaves the binding alone and does both. The instance's ERRORLOG should read `The certificate [Cn = vsql.pesco.com] was successfully loaded for encryption`. If the instance does not start, roll back (step 6). + +4. Check from docker-1. Export the certificate (`Export-Certificate -Cert Cert:\LocalMachine\My\ -FilePath vsql.cer`) and copy it to docker-1. OpenSSL cannot speak TDS, so `openssl s_client` does not work against SQL Server. Verify the file at the image's security level instead: + + ```bash + openssl x509 -inform DER -in vsql.cer -out vsql.pem + openssl x509 -in vsql.pem -noout -subject -dates -ext subjectAltName,keyUsage,extendedKeyUsage + openssl verify -auth_level 2 -purpose sslserver -CAfile ~/packetstation/test/ca/pesco-ca.pem vsql.pem + ``` + + Then prove the live connection with the doctor and `VISUAL_ENCRYPT=yes`. The image does not yet add the PESCO CA to OpenSSL's trust store for the ODBC driver (the issue 5 follow-up), so this one-off check builds a bundle in `/tmp`. It skips the entrypoint, so the M-Files password line fails. Read the VISUAL line. + + ```bash + docker compose -p packetstation-test --env-file /etc/packetstation/test/stack.env \ + -f deploy/compose.yaml -f deploy/compose.proxy.yaml run --rm --no-deps \ + -e VISUAL_ENCRYPT=yes --entrypoint sh app -c \ + 'cat /etc/ssl/certs/ca-certificates.crt /etc/ssl/pesco/ca.pem > /tmp/ca.pem && SSL_CERT_FILE=/tmp/ca.pem exec packetstation doctor' + ``` + + That the ODBC driver honours `SSL_CERT_FILE` is untested. If the VISUAL line fails with an issuer error while `openssl verify` passed, the trust step is the gap, not the server. + +5. Production, off-hours. Production (`vsql`, `PESCOINC`) is the default instance, `MSSQLSERVER`. Bind the same certificate. VISUAL users lose their connection during the restart. + + ```powershell + powershell -ExecutionPolicy Bypass -File .\Request-VsqlServerCertificate.ps1 -Thumbprint -Instances MSSQLSERVER -WhatIf + powershell -ExecutionPolicy Bypass -File .\Request-VsqlServerCertificate.ps1 -Thumbprint -Instances MSSQLSERVER -RestartInstances + ``` + + Check the ERRORLOG line again. The production stack's own doctor run is Spencer's, with its own approval. + +6. Rollback. Each deployment saved `deploy-