Commit Graph

7 Commits

Author SHA1 Message Date
Brian Krabach 33ea016f7e feat(tls): add --method ca for persistent local CA-signed leaves
Adds a new TLS certificate method ('ca') that generates a persistent local
Certificate Authority and signs leaf certificates against it. This allows
users to install the CA once on client devices and have browser-trusted
HTTPS for plain LAN names (my-host, 192.168.1.5, my-host.local) without
requiring external services like Tailscale.

Key improvements over existing --method selfsigned:
- Persistent CA: the root cert is stored separately and never rotates,
  so leaf certificate renewal doesn't require re-trusting on clients
- Auto-detected SANs: includes LAN IP, tailnet name (if applicable),
  hostname, and localhost variants
- Per-platform install guide: comprehensive docs/TRUSTING_THE_LOCAL_CA.md
  with Windows PowerShell, macOS/Linux, iOS, and Android install steps

Solves PWA installation issues on Windows machines with corporate IT
policy blocking Tailscale: PWAs now persist in standalone mode when the
local CA is trusted in the Windows user cert store.

Changes:
- muxplex/tls.py: added _default_lan_ip(), _default_tailnet_name(),
  generate_local_ca(), generate_leaf_signed_by_ca()
- muxplex/cli.py: added 'ca' to --method choices, wired setup_tls()
  to generate CA + leaf with auto-detected SAN
- docs/TRUSTING_THE_LOCAL_CA.md: comprehensive per-platform install guide
- README.md: added --method ca documentation and cross-links
- CHANGELOG.md: documented v0.5.0 features

Generated with Amplifier

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
2026-05-06 12:37:34 -07:00
Brian Krabach cb2d425fa7 fix: Tailscale detection reads DNSName from Self + suppress cryptography deprecation
Bug 1: detect_tailscale() looked for DNSName at top level of JSON but
tailscale status --self --json nests it inside Self.DNSName. Fixed to
read from Self first with top-level fallback.

Bug 2: getattr(cert, 'not_valid_after_utc', cert.not_valid_after)
eagerly evaluated the fallback, triggering CryptographyDeprecationWarning.
Replaced with try/except AttributeError pattern in all three occurrences
(generate_self_signed and get_cert_info).
2026-04-04 03:07:13 -07:00
Brian Krabach 708dffe20e feat: add detect_mkcert() and generate_mkcert() for locally-trusted CA certificates
- detect_mkcert() returns True if mkcert is on PATH via shutil.which
- generate_mkcert() runs mkcert -install then generates cert for hostname,
  hostname.local, localhost, 127.0.0.1, ::1, plus any extra_hostnames;
  deduplicates preserving order; sets key permissions to 0o600
- Returns None on install failure, subprocess error, or missing output files
- Added 4 tests: detect true/false, generate success/install-fails
2026-04-03 23:03:56 -07:00
Brian Krabach 33b9ebb392 feat: add generate_tailscale() for Tailscale cert generation
Add generate_tailscale(cert_path, key_path, hostname) to muxplex/tls.py.
The function calls 'tailscale cert' to obtain real Let's Encrypt certificates
via the Tailscale CLI.

- Creates parent directories with mkdir(parents=True, exist_ok=True)
- Runs subprocess with --cert-file and --key-file flags (timeout=30)
- Returns None on non-zero exit, TimeoutExpired, OSError, or missing files
- On success: sets key_path chmod(0o600), reads cert info, returns metadata dict
  with method='tailscale', cert_path, key_path, hostnames, expires

Tests added:
- test_generate_tailscale_calls_tailscale_cert: verifies --cert-file/--key-file flags
- test_generate_tailscale_returns_none_on_failure: verifies None on rc=1
2026-04-03 22:56:45 -07:00
Brian Krabach 1e20d5b131 feat: add detect_tailscale() to tls.py with 4 tests (task-1-tailscale-detection)
- Probe Tailscale CLI via shutil.which('tailscale')
- Run 'tailscale status --self --json' with 10-second timeout
- Return dict(hostname, ips, cert_domains) on success
- Return None if not installed, non-zero exit, timeout/JSON/OS error,
  empty CertDomains, or empty DNSName
- All imports (json, shutil, subprocess) inside function body
- 4 tests: available, not installed, not connected, no cert domains
2026-04-03 22:51:34 -07:00
Brian Krabach 89f39b2366 fix: use compat getattr for not_valid_after/before_utc to support cryptography v41 2026-04-03 22:44:17 -07:00
Brian Krabach 4cca6d1c82 feat: add muxplex.tls module with self-signed cert generation and inspection
- Add muxplex/tls.py with generate_self_signed() and get_cert_info()
- generate_self_signed() creates RSA 2048-bit key + X.509 cert with SAN
  entries (DNS names + 127.0.0.1 + ::1), sets key perms to 0o600,
  creates parent dirs, returns metadata dict
- get_cert_info() inspects PEM certs, returns expires/not_before/
  hostnames/serial, returns None for missing/unreadable files
- Add muxplex/tests/test_tls.py with 9 tests covering all acceptance criteria
- Install cryptography library dependency

Co-authored-by: Amplifier <amplifier@anthropic.com>
2026-04-03 22:44:17 -07:00