Razvan Dimescu 039254280b fix: cross-platform CA trust (Arch/Fedora + Windows) (#41)
* fix: cross-platform CA trust (Arch/Fedora + Windows)

Closes #35.

trust_ca_linux now detects which trust store the distro ships and
runs the matching refresh command, instead of hardcoding Debian's
update-ca-certificates. Detection walks a const table in priority
order, picking the first whose anchor dir exists:

  - debian: /usr/local/share/ca-certificates  (update-ca-certificates)
  - pki:    /etc/pki/ca-trust/source/anchors  (update-ca-trust extract)
  - p11kit: /etc/ca-certificates/trust-source/anchors (trust extract-compat)

Falls back with a clear error listing every backend tried.

Adds Windows support via certutil -addstore Root / -delstore Root,
removing the silent CA-trust gap on numa install (previously the
service installed but the trust step quietly errored, leaving every
HTTPS .numa request throwing browser warnings).

Refactor: trust_ca and untrust_ca are now thin dispatchers calling
per-platform helpers. CA_COMMON_NAME and CA_FILE_NAME are centralized
in tls.rs and reused from system_dns.rs and api.rs. untrust_ca_linux
no longer pre-checks file existence (TOCTOU) and skips the refresh
when no file was actually removed.

Test: tests/docker/install-trust.sh runs the install/uninstall
contract against debian:stable, fedora:latest, and archlinux:latest
in containers, asserting the cert lands in (and is removed from)
the system bundle. All three pass locally.

README notes the Firefox/NSS limitation (separate trust store).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style: rustfmt fixes for trust_ca_linux helpers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: macOS CA trust contract test (manual)

Adds tests/manual/install-trust-macos.sh — a sudo bash script that
mirrors trust_ca_macos / untrust_ca_macos against a fixture cert with
a unique CN. Designed to coexist with a running production numa:

- Refuses to run if a real "Numa Local CA" is already in System.keychain
  (fail-closed protection for dogfood installs)
- Uses a unique CN ("Numa Local CA Test <pid-timestamp>") so the test
  cert can never collide with production
- Mirrors the by-hash deletion loop from untrust_ca_macos
- Trap-cleanup on success or interrupt

Lives under tests/manual/ to signal "host-mutating, dev-only" — distinct
from tests/docker/install-trust.sh which is hermetic.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: relax bail-out in macOS trust test (safe alongside production)

The bail-out was overly defensive. The test cert uses a unique CN
("Numa Local CA Test <pid-ts>") that is strictly longer than the
production CN, so `security find-certificate -c $TEST_CN` cannot
substring-match the production cert. All deletes are by-hash, which
can only target the test cert's specific hash. Coexistence is
provably safe; document the reasoning in the header comment block
and replace the refusal with an informational notice.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 15:18:01 +03:00
2026-04-08 02:53:43 +03:00
2026-04-08 02:53:43 +03:00
2026-04-06 22:28:30 +03:00
2026-04-06 22:28:30 +03:00

Numa

CI crates.io License: MIT

DNS you own. Everywhere you go.numa.rs

A portable DNS resolver in a single binary. Block ads on any network, name your local services (frontend.numa), and override any hostname with auto-revert — all from your laptop, no cloud account or Raspberry Pi required.

Built from scratch in Rust. Zero DNS libraries. RFC 1035 wire protocol parsed by hand. Caching, ad blocking, and local service domains out of the box. Optional recursive resolution from root nameservers with full DNSSEC chain-of-trust validation, plus a DNS-over-TLS listener for encrypted client connections (iOS Private DNS, systemd-resolved, etc.). One ~8MB binary, everything embedded.

Numa dashboard

Quick Start

# macOS
brew install razvandimescu/tap/numa

# Linux
curl -fsSL https://raw.githubusercontent.com/razvandimescu/numa/main/install.sh | sh

# Windows — download from GitHub Releases
# All platforms
cargo install numa
sudo numa                              # run in foreground (port 53 requires root/admin)

Open the dashboard: http://numa.numa (or http://localhost:5380)

Set as system DNS:

Platform Install Uninstall
macOS sudo numa install sudo numa uninstall
Linux sudo numa install sudo numa uninstall
Windows numa install (admin) + reboot numa uninstall (admin) + reboot

On macOS and Linux, numa runs as a system service (launchd/systemd). On Windows, numa auto-starts on login via registry.

Local Services

Name your dev services instead of remembering port numbers:

curl -X POST localhost:5380/services \
  -d '{"name":"frontend","target_port":5173}'

Now https://frontend.numa works in your browser — green lock, valid cert, WebSocket passthrough for HMR. No mkcert, no nginx, no /etc/hosts.

Add path-based routing (app.numa/api → :5001), share services across machines via LAN discovery, or configure everything in numa.toml.

Ad Blocking & Privacy

385K+ domains blocked via Hagezi Pro. Works on any network — coffee shops, hotels, airports. Travels with your laptop.

Three resolution modes:

  • forward (default) — transparent proxy to your existing system DNS. Everything works as before, just with caching and ad blocking on top. Captive portals, VPNs, corporate DNS — all respected.
  • recursive — resolve directly from root nameservers. No upstream dependency, no single entity sees your full query pattern. Add [dnssec] enabled = true for full chain-of-trust validation.
  • auto — probe root servers on startup, recursive if reachable, encrypted DoH fallback if blocked.

DNSSEC validates the full chain of trust: RRSIG signatures, DNSKEY verification, DS delegation, NSEC/NSEC3 denial proofs. Read how it works →

DNS-over-TLS listener (RFC 7858) — accept encrypted queries on port 853 from strict clients like iOS Private DNS, systemd-resolved, or stubby. Two modes:

  • Self-signed (default) — numa generates a local CA automatically. numa install adds it to the system trust store on macOS, Linux (Debian/Ubuntu, Fedora/RHEL/SUSE, Arch), and Windows. On iOS, install the .mobileconfig from numa setup-phone. Firefox keeps its own NSS store and ignores the system one — trust the CA there manually if you need HTTPS for .numa services in Firefox.
  • Bring-your-own cert — point [dot] cert_path / key_path at a publicly-trusted cert (e.g., Let's Encrypt via DNS-01 challenge on a domain pointing at your numa instance). Clients connect without any trust-store setup — same UX as AdGuard Home or Cloudflare 1.1.1.1.

ALPN "dot" is advertised and enforced in both modes; a handshake with mismatched ALPN is rejected as a cross-protocol confusion defense.

LAN Discovery

Run Numa on multiple machines. They find each other automatically via mDNS:

Machine A (192.168.1.5)              Machine B (192.168.1.20)
┌──────────────────────┐             ┌──────────────────────┐
│ Numa                 │    mDNS     │ Numa                 │
│  - api (port 8000)   │◄───────────►│  - grafana (3000)    │
│  - frontend (5173)   │  discovery  │                      │
└──────────────────────┘             └──────────────────────┘

From Machine B: curl http://api.numa → proxied to Machine A's port 8000. Enable with numa lan on.

Hub mode: run one instance with bind_addr = "0.0.0.0:53" and point other devices' DNS to it — they get ad blocking + .numa resolution without installing anything.

How It Compares

Pi-hole AdGuard Home Unbound Numa
Local service proxy + auto TLS .numa domains, HTTPS, WebSocket
LAN service discovery mDNS, zero config
Developer overrides (REST API) Auto-revert, scriptable
Recursive resolver Yes Yes, with SRTT selection
DNSSEC validation Yes Yes (RSA, ECDSA, Ed25519)
Ad blocking Yes Yes 385K+ domains
Web admin UI Full Full Dashboard
Encrypted upstream (DoH) Needs cloudflared Yes Native
Encrypted clients (DoT listener) Needs stunnel sidecar Yes Yes Native (RFC 7858)
Portable (laptop) No (appliance) No (appliance) Server Single binary, macOS/Linux/Windows
Community maturity 56K stars, 10 years 33K stars 20 years New

Performance

691ns cached round-trip. ~2.0M qps throughput. Zero heap allocations in the hot path. Recursive queries average 237ms after SRTT warmup (12x improvement over round-robin). ECDSA P-256 DNSSEC verification: 174ns. Benchmarks →

Learn More

Roadmap

  • DNS forwarding, caching, ad blocking, developer overrides
  • .numa local domains — auto TLS, path routing, WebSocket proxy
  • LAN service discovery — mDNS, cross-machine DNS + proxy
  • DNS-over-HTTPS — encrypted upstream
  • DNS-over-TLS listener — encrypted client connections (RFC 7858, ALPN strict)
  • Recursive resolution + DNSSEC — chain-of-trust, NSEC/NSEC3
  • SRTT-based nameserver selection
  • pkarr integration — self-sovereign DNS via Mainline DHT
  • Global .numa names — DHT-backed, no registrar

License

MIT

Description
Portable DNS resolver in Rust — .numa local domains, ad blocking, developer overrides
Readme MIT 4.1 MiB
v0.14.2 Latest
2026-04-23 04:57:37 +08:00
Languages
Rust 76%
HTML 12.1%
Shell 11.4%
Python 0.2%
Makefile 0.1%