Repair mkcert CA trust on macOS instead of trusting presence alone - #1237
Conversation
CATrusted() only checked whether mkcert's root CA was present in the keychain, via security find-certificate, never whether it actually carried trust settings. A certificate can sit in the keychain with its trust settings cleared independent of the certificate item itself, most commonly after a macOS update, and find-certificate reports that identically to a genuinely trusted CA. lerd would then treat a reinstall as a no-op and skip the sudo announcement, leaving the CA silently untrusted and every site's HTTPS certificate invalid in the browser, even though podman and the vhost itself looked completely healthy. platformTrustCheck now parses security trust-settings-export -d, which security itself keys by each certificate's SHA-1 fingerprint and which only carries a trustSettings array for a certificate actually configured as trusted, the distinction find-certificate can't make. The old presence-only check is kept under platformPresenceCheck for the one thing it's still useful for, telling CAPresentButUntrusted apart from a CA that was never installed at all. mkcert's own "already installed" self-check can't be trusted to notice and repair this on its own: it verifies the self-signed root against itself, which succeeds cryptographically regardless of its actual trust settings, the same false positive this fix exists to avoid. So ensureMkcertCA repairs trust directly with the same security add-trusted-cert command mkcert itself uses, before mkcert gets a chance to wrongly decide there's nothing to do.
|
Hey @retr0ripper some CI issues |
CI failed on Build & Test (Linux) with TempDir cleanup errors in both internal/podman and internal/xdebugops: unlinkat on a containers/storage overlay diff directory, permission denied. Both packages' setupConfigHome tried to block real podman by emptying PATH, but DetectHostGatewayIP's last-resort fallback, parseHostGatewayFromProbe, runs podman run --rm --network lerd docker.io/library/alpine getent hosts ... whenever every cheaper probe comes up empty, which is always true in these tests since lerd-nginx never actually runs. PodmanBin() also tries hardcoded absolute paths (homebrew's install locations) before giving up, and those bypass PATH entirely since a path containing a slash skips exec.Command's LookPath, so whichever of those exists on the machine running the test lets a real podman run and pull the alpine image, leaving overlay layers that a plain os.RemoveAll can't remove afterward. internal/podman's ini_race_test.go is in-package, so its setupConfigHome now stubs the package's own execCommand seam directly, the same one every other test in the package already uses, guaranteeing no real binary is ever reached regardless of what PodmanBin() resolves to. internal/xdebugops can't reach that unexported seam from outside the package, so its setupConfigHome instead pre-creates the shared hosts file with the gateway entry already in place, giving ensureFPMHostsFile its already-done fast path so it returns before ever calling WriteContainerHosts.
|
Hi @geodro , |
The trust check treated an entry in the admin trust-settings export as trusted only when it carried an explicit trustSettings array. Per SecTrustSettings an absent or empty array means trust as a root, which is how macOS records everything security add-trusted-cert installs, so two genuinely trusted roots on the machine this was measured on, a Laravel Valet CA and a Blizzard local cert, read as untrusted while security verify-cert reports both as verifying successfully and security dump-trust-settings counts both among the trusted certs. Only mkcert writes an explicit array, through the trust-settings-import step it runs after add-trusted-cert. That mattered most for the repair itself. RepairSystemTrust writes the entry through add-trusted-cert alone, which leaves no explicit array, so a repaired CA would have kept reading as untrusted and every later lerd install would have announced and re-run the repair, prompting for authorization each time. Presence of the entry is the trust decision now, and only an entry whose explicit results all refuse counts as untrusted. A certificate with no entry at all is still the drifted state the repair exists for, so a macOS update that drops the trust settings while leaving the certificate in the keychain is caught exactly as before. An export that could not be read reports nothing rather than reporting untrusted, so a security that fails cannot drive a privileged repair on its own. A failed repair is named instead of swallowed, since macOS draws the authorization dialog itself and a run with no window server behind it fails there.
platformTrustCheck now parses security trust-settings-export -d, which security itself keys by each certificate's SHA-1 fingerprint and which only carries a trustSettings array for a certificate actually configured as trusted, the distinction find-certificate can't make. The old presence-only check is kept under platformPresenceCheck for the one thing it's still useful for, telling CAPresentButUntrusted apart from a CA that was never installed at all.
Since mkcert's own self-check has the same blind spot, ensureMkcertCA now repairs trust directly with the same security add-trusted-cert command mkcert itself uses, before mkcert gets a chance to wrongly decide there's nothing to do.
Refs #1236