From f3a8d45722344a77cefd96500cfcde054d4c9a6f Mon Sep 17 00:00:00 2001 From: Noah Masur <7386960+nmasur@users.noreply.github.com> Date: Mon, 3 Aug 2026 15:49:03 -0400 Subject: [PATCH] trust corporate MITM root CA on lookingglass for TLS-intercepting proxy --- docs/CHANGELOG.md | 37 ++++++++++++++++ hosts/aarch64-darwin/lookingglass/default.nix | 10 +++++ .../nmasur/presets/security/corporate-ca.nix | 44 +++++++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 platforms/nix-darwin/modules/nmasur/presets/security/corporate-ca.nix diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 3174e152..770e792d 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,6 +2,43 @@ ## 2026-08-03 +- Added `presets/security/corporate-ca.nix` (nix-darwin) and enabled it on the + `lookingglass` host to trust a corporate TLS-intercepting proxy's root CA. + Behind the corp network, Nix fetches failed with `SSL peer certificate ... + self-signed certificate in certificate chain (19)` because Nix's stock Mozilla + CA bundle doesn't contain the interception root. The module appends the cert + to `security.pki.certificateFiles`, which rebuilds + `/etc/ssl/certs/ca-certificates.crt` (read by both the Nix daemon and, via + `NIX_SSL_CERT_FILE`, client-side flake fetches). + + The cert is kept **out of this public repo** and referenced by absolute path. + It is passed as a string (not a Nix path literal) so pure flake evaluation + doesn't read it at eval time, and it lives at a root-owned, world-readable + path because the unprivileged `nixbld` build user cannot traverse `$HOME` + (mode `0750`) to read it at build time. + + One-time setup on a machine behind the proxy: + + ```sh + # 1. Extract the self-signed corporate root from any TLS connection it MITMs + # (the last cert in the chain, subject == issuer). Any HTTPS host works: + echo | openssl s_client -connect example.com:443 -servername example.com \ + -showcerts 2>/dev/null \ + | awk '/BEGIN CERT/{c++} c==2' > /tmp/CorpCA.pem + openssl x509 -in /tmp/CorpCA.pem -noout -subject -issuer # sanity check + + # 2. Install to the root-owned path the config points at: + sudo install -d -m 0755 -o root -g wheel /etc/ssl/corp-ca + sudo install -m 0644 -o root -g wheel /tmp/CorpCA.pem \ + /etc/ssl/corp-ca/CorpCA.pem + + # 3. Bootstrap the first rebuild (which must fetch inputs over the proxy) + # with a combined bundle, then it's permanent: + cat /etc/ssl/certs/ca-certificates.crt /etc/ssl/corp-ca/CorpCA.pem \ + > /tmp/combined-ca.crt + NIX_SSL_CERT_FILE=/tmp/combined-ca.crt nh darwin switch . --configuration lookingglass + ``` + - Fixed the `zellij-session` fish function in `presets/programs/zellij.nix` truncating the session name (derived from the target directory basename) to 20 characters. Zellij names each session's Unix-domain IPC socket diff --git a/hosts/aarch64-darwin/lookingglass/default.nix b/hosts/aarch64-darwin/lookingglass/default.nix index 4c7ea049..79992732 100644 --- a/hosts/aarch64-darwin/lookingglass/default.nix +++ b/hosts/aarch64-darwin/lookingglass/default.nix @@ -17,6 +17,16 @@ rec { # gaming.enable = true; }; + # Corporate network runs a TLS-intercepting proxy. Trust its root CA so Nix + # fetches don't fail with "self-signed certificate in certificate chain". The + # cert lives outside this public repo at a root-owned, world-readable path so + # the unprivileged Nix build user can read it (a copy under $HOME is not + # traversable by nixbld). See docs/CHANGELOG.md to extract and install it. + nmasur.presets.security.corporateCa = { + enable = true; + certFile = "/etc/ssl/corp-ca/CorpCA.pem"; + }; + home-manager.users."Noah.Masur" = { nmasur.settings = { username = nmasur.settings.username; diff --git a/platforms/nix-darwin/modules/nmasur/presets/security/corporate-ca.nix b/platforms/nix-darwin/modules/nmasur/presets/security/corporate-ca.nix new file mode 100644 index 00000000..151e7966 --- /dev/null +++ b/platforms/nix-darwin/modules/nmasur/presets/security/corporate-ca.nix @@ -0,0 +1,44 @@ +{ + config, + lib, + ... +}: + +let + cfg = config.nmasur.presets.security.corporateCa; +in + +{ + + options.nmasur.presets.security.corporateCa = { + + enable = lib.mkEnableOption "trusting a corporate MITM root CA for TLS interception"; + + certFile = lib.mkOption { + type = lib.types.str; + example = "/etc/ssl/corp-ca/CorpCA.pem"; + description = '' + Absolute path to a PEM-encoded corporate root CA certificate to add to + the system trust store. + + Kept out of this (public) repo on purpose, so it points at a file you + drop on the machine by hand. Passed as a string rather than a Nix path + literal so pure flake evaluation does not try to read the out-of-repo + file at eval time; it is read at build time instead (Darwin builds run + without a sandbox). + ''; + }; + + }; + + config = lib.mkIf cfg.enable { + + # Append the corporate root to /etc/ssl/certs/ca-certificates.crt, which + # both the Nix daemon and (via NIX_SSL_CERT_FILE) client-side flake fetches + # read. Without this, fetches behind the corporate TLS-intercepting proxy + # fail with "self-signed certificate in certificate chain". + security.pki.certificateFiles = [ cfg.certFile ]; + + }; + +}