Commit Graph

5 Commits

Author SHA1 Message Date
Alphaeus Mote a0c937b876 feat(tls): publish the serving leaf to the Windows My store; fix context leak
Clients that resolve a certificate by store lookup rather than by reading our
PEM files had nothing to find: in auto mode the leaf lived only under
<data>/tls, and the My store was opened strictly read-only.

InstallLeafToMyStore imports the exported PKCS#12 into LocalMachine\My with
the private key persisted to the machine keyset (CNG KSP, matching the
ncryptSigner path used when serving *from* the store). Renewal is accounted
for: the new leaf is added with REPLACE_EXISTING, then pruneSupersededLeaves
removes any certificate sharing its subject *and* issuer, so the store holds
exactly one current leaf instead of one per renewal. Only certificates issued
by our own CA to our own subject are ever deleted - anything from another
issuer is left strictly alone. Best-effort: it needs admin rights and TLS
serving does not depend on it.

RemoveLeafFromMyStore runs on service removal, alongside the firewall rule,
so uninstalling leaves no orphaned certificate.

Also fixes a genuine leak found while auditing this code, in response to a
question about whether listing the store could damage it (it cannot - the
listing handle is read-only and stores are not exclusively locked):
ensureWindowsStore returned from inside the enumeration without freeing the
matched CertContext. CertEnumCertificatesInStore frees the previous context
each call and the last on completion, so only the early-return paths leaked -
and because CertCloseStore(store, 0) defers until outstanding contexts are
released, the store handle leaked with it, once per certificate load.

Verified on Windows 11: leaf appears in LocalMachine\My with a usable private
key and full SANs; forcing a re-issue replaces it (one cert, new thumbprint,
old one pruned) and leaves unrelated certificates untouched; HTTPS keeps
serving throughout. Cross-compiles for linux via the no-op stubs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-03 13:23:03 -04:00
Alphaeus Mote 2befe64a0c feat(install): idempotent Windows firewall rule; skip the blank MSI EULA
Firewall:
- On service initialize/install, create an idempotent inbound allow rule
  ("OrchestrAD") for the configured listen port, scoped to RFC 1918 private
  ranges plus CGNAT (10/8, 172.16/12, 192.168/16, 100.64/10). The rule is
  deleted-then-added so it always reflects the current port, and removed on
  service uninstall. Best-effort (needs admin; the MSI custom action and
  service run elevated); no-op off Windows. Verified the netsh rule lands
  with the expected port and remote-address scoping.

MSI:
- Skip the license/EULA page (Welcome now goes straight to the install
  directory), since it was blank. A standard short notice is kept in
  license.rtf only so the stock license control resolves at build time.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 21:46:08 -04:00
Alphaeus Mote 72d6f84bf7 fix(cli): pin service working directory to the executable dir
A service launched by the SCM inherits the manager's working directory
(C:\Windows\System32 on Windows), which put the default ./data there. When
running non-interactively, chdir to the executable's directory so the database
lands beside the installed binary (Program Files\OrchestrAD\data). Interactive
and container runs are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 09:27:35 -04:00
Alphaeus Mote 491b5abe83 feat(cli): idempotent initialize/remove service commands
Add initialize (install + start) and remove (stop + uninstall) that are safe to
re-run: initialize skips reinstall/restart when already up, remove treats a
not-installed service as done. install/uninstall become aliases. Uses the
service status to branch. Verified idempotent on Windows (repeat runs exit 0).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 08:55:33 -04:00
Alphaeus Mote 0debf9ab33 feat(cli): real cross-platform service management
Implement install/uninstall/start/stop via kardianos/service (Windows SCM,
systemd/upstart/sysv, launchd). The run command now goes through service.Run so
foreground/container and service execution share one startup path; install also
starts the service and uninstall stops then removes it. Route CLI and main
command output/errors through the centralized logger. Verified end to end as a
real Windows service (install -> RUNNING -> uninstall).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 08:49:15 -04:00