Commit Graph

37 Commits

Author SHA1 Message Date
f1cada010f Mark proxy/host-routing as complete 2026-06-11 12:59:48 +00:00
d5f5713deb Implement host-based routing with global routing table
Add routing table (HashMap<String, SiteConfig>) to DynamicConfig for
O(1) host lookup. Implement normalize_host (lowercase + strip port)
per RFC 7230 §2.7.3. Add proxy_handler that routes /health to 200,
missing Host to 400, unknown host to 404, and known host to 200.
Routing table updates atomically via ArcSwap.
2026-06-11 12:57:31 +00:00
994ce0fb66 Mark config/validation as complete 2026-06-11 12:49:46 +00:00
64686b0ccf Merge remote-tracking branch 'origin/feat/config/validation' 2026-06-11 12:49:30 +00:00
07fb4ce411 Mark ops/logging as complete 2026-06-11 12:49:05 +00:00
63e173ebb2 Merge remote-tracking branch 'origin/feat/ops/logging' 2026-06-11 12:48:33 +00:00
f72fe791e1 Implement config validation with all 18 rules
Add comprehensive validation for StaticConfig and DynamicConfig:
- ValidationError enum with thiserror for descriptive error messages
- validate() function that collects ALL errors (doesn't stop at first)
- All 18 validation rules from config.md implemented
- OR logic for allow_wildcard_bind (config OR CLI flag)
- Hostname normalization to lowercase during validation
- File existence check for manual mode cert_path and key_path
- Unit tests covering each validation rule with valid/invalid inputs
- Updated ConfigReloadHandle to use new validate() function
- Added PartialEq derives to config structs for diff_static_config
2026-06-11 12:48:21 +00:00
30d391b353 Mark config/dynamic-config as complete 2026-06-11 12:48:10 +00:00
aca93f3732 Remove accidentally staged worktree dirs 2026-06-11 12:48:03 +00:00
e9a1d25909 Merge feat/config/dynamic-config into main 2026-06-11 12:47:59 +00:00
36319db10e Implement structured logging with tracing, dual output, and fail2ban-compatible format
- Add logging::init() with dual output (file + stdout) via tracing-subscriber Layer composition
- Support configurable log level via LoggingConfig.level and JSON/text format via LoggingConfig.format
- Create log file and parent directories when log_file_path is configured
- Add KvVisitor for custom key=value event field formatting
- Add log_request!, log_rate_limit!, log_upstream_error!, log_config_reload! macros
  with REQUEST, RATE_LIMIT, UPSTREAM_ERROR, CONFIG_RELOAD prefixes
- Add format_event_fields() for extracting structured fields from tracing events
- Add tracing-subscriber env-filter and json features to Cargo.toml
- Add unit tests for KvVisitor formatting, log macros, and init function
- Apply cargo fmt to existing tls/config.rs tests
2026-06-11 12:47:19 +00:00
fbae1c464e Implement DynamicConfig with ArcSwap hot-reload and ConfigReloadHandle
Add ConfigReloadHandle with Arc<ArcSwap<DynamicConfig>> for lock-free reads
on the request hot path and tokio::sync::Mutex-serialized reload. Add static
config change detection via diff_static_config(). Add DynamicConfig validation
(rate_limit, body_limit, site checks). Add PartialEq derives to config types.
Include unit tests for ArcSwap swap visibility, invalid config rejection, and
concurrent reload serialization.
2026-06-11 12:42:16 +00:00
5ca658e8f3 Mark ops/health-check as complete 2026-06-11 12:41:29 +00:00
c423a58778 Implement health check endpoint on separate local port and HTTPS fallback
- Add health.rs module with start_health_check_listener() that binds to
  127.0.0.1:{health_check_port} and serves GET /health returning 200 OK
  with empty body
- Add health_route() in proxy/handler.rs for HTTPS listener fallback
- Add port conflict detection in config validation: health_check_port
  must not conflict with listener ports on 127.0.0.1/localhost/0.0.0.0
- health_check_port = 0 disables the separate listener (handled at call
  site)
- Add unit and integration tests for health check functionality
2026-06-11 12:39:24 +00:00
468adb21de Mark tls/manual-tls as complete 2026-06-11 11:59:12 +00:00
4dbdde328f Remove accidentally staged worktree dirs 2026-06-11 11:59:05 +00:00
c9d6b5b95a Merge remote-tracking branch 'origin/feat/tls/manual-tls'
# Conflicts:
#	Cargo.toml
#	src/tls/config.rs
#	src/tls/mod.rs
2026-06-11 11:58:58 +00:00
dd748b973d Implement manual TLS certificate loading and ServerConfig construction
- Add tls::config module with manual TLS mode support
- Load PEM certificates and private keys via rustls_pemfile
- Build ServerConfig with aws_lc_rs crypto provider
- Restrict cipher suites per ADR-012 (4 TLS 1.2 ECDHE-AES-GCM + all TLS 1.3)
- Configure protocol versions to TLS 1.2 and 1.3 only
- Implement SniCertResolver for multi-domain manual mode
- Unknown SNI hostname fails handshake (no default cert)
- Add tempfile dev dependency for test file operations
- Add 11 unit tests covering config, cipher suites, and SNI resolution
2026-06-11 11:57:24 +00:00
ff5112e4d5 Mark tls/acme-tls as complete 2026-06-11 11:56:31 +00:00
e2ea7122a6 Merge feat/tls/acme-tls into main 2026-06-11 11:55:50 +00:00
b11f15d977 Implement ACME certificate provisioning with rustls-acme
Add ACME TLS module with automatic Let's Encrypt certificate provisioning
and renewal using rustls-acme 0.12. Each listener creates its own AcmeConfig
with domain list, cache directory, and Let's Encrypt directory URL. The ACME
state machine runs as a background tokio task per listener, and
ResolvesServerCertAcme serves the provisioned certificate. Certificate
failure behavior: fail to start without valid cert, continue serving if one
exists. TLS-ALPN-01 is the default challenge type with acme-tls/1 ALPN
registered. Cipher suites restricted to 4 TLS 1.2 + all TLS 1.3 suites.

Also implements manual TLS mode with PEM file loading, SNI-based cert
resolution, and shared CryptoProvider with restricted cipher suites.
2026-06-11 11:55:00 +00:00
33a448505e Mark setup/test-infrastructure as complete 2026-06-11 11:49:37 +00:00
c987136137 Merge remote-tracking branch 'origin/feat/setup/test-infrastructure' 2026-06-11 11:47:16 +00:00
75f7b778df Add test infrastructure with fixtures, helpers, and integration tests
- Add [lib] target to enable integration test imports
- Add rcgen and reqwest dev-dependencies for TLS and HTTP test helpers
- Create src/config/test_fixtures.rs with test_static_config() and test_dynamic_config()
- Create tests/ with integration tests, HTTP test helper (TestUpstream), and TLS test helper (SelfSignedCert)
- Add Clone derives to StaticConfig and related structs for test fixture construction
- All existing tests continue to pass
2026-06-11 11:46:43 +00:00
dc12e75bdf Mark config/static-config as complete 2026-06-11 11:46:38 +00:00
ac30d890e9 Add Clone derive and TOML deserialization tests for static config structs
Add Clone derive to StaticConfig, ListenerConfig, TlsConfig, and
LoggingConfig to support immutable-after-startup pattern. Add unit
tests verifying TOML deserialization for multi-config (dedicated-IP)
and shared-IP (SAN certificate) deployment formats, default value
application, logging config, and site defaults.
2026-06-11 11:44:15 +00:00
9b4cabc4d6 Mark setup/project-init as complete 2026-06-11 11:36:51 +00:00
97178800f9 Initialize Rust project with Cargo, dependencies, and module skeleton
Set up single-binary reverse-proxy project with all core dependencies
(axum, tokio, hyper, tower, rustls, tokio-rustls, rustls-acme, serde,
toml, arc-swap, tracing, tracing-subscriber, rustls-pemfile,
rustls-pki-types, clap, signal-hook, anyhow, thiserror) pinned to exact
versions. Create module skeleton (config, proxy, tls, rate_limit,
logging, admin, health, shutdown) matching architecture spec.
2026-06-11 11:34:53 +00:00
309878c561 Decompose architecture into 23 atomic tasks across 7 parallel generations
Task graph covers all Phase 1 concerns: config system, TLS termination,
proxy handler, operations (rate limiting, logging, health check, admin
socket, signals, shutdown, body size limit), deployment artifacts, and
two review checkpoints.

No circular dependencies. Critical path length of 7. Risk distribution:
3 high-risk (ACME, TLS listener setup, startup orchestration), 7 medium,
11 low, 2 trivial.
2026-06-11 11:21:10 +00:00
ceb59ad9b9 Resolve all architecture review findings (7 critical, 14 warnings, 6 suggestions)
Critical findings resolved:
- C1: Site routing is global (per-listener TOML, global runtime lookup)
- C2: X-Forwarded-For replaces (not appends) — edge proxy model (ADR-021)
- C3: Hop-by-hop header handling rules specified (proxy.md)
- C4: ACME failure behavior defined (tls.md)
- C5: Startup sequence with fail-fast semantics (operations.md)
- C6: Per-listener Router instances with shared global state (overview.md)
- C7: Rate limiter adopts new params on next request, no state clear (operations.md)

Warnings resolved:
- W1: Admin socket wire protocol specified
- W2: Host header port stripped, hostnames only in config
- W3: HTTP redirect URL construction with port handling
- W4: /health on HTTPS matches regardless of Host header
- W5: Static config changes logged as warning during reload
- W6: Reload operations serialized via Mutex
- W7: http_port validation rules added (9 new rules total)
- W8: upstream format validation (host:port required, no scheme)
- W9: TLS error handling table (SNI, version, cipher failures)
- W10: IPv6 rate limited per /64 prefix
- W11: Graceful shutdown sequence specified (6 steps)
- W12: Error response bodies: minimal plain text, no version disclosure
- W13: upstream_scheme HTTPS uses system CA store
- W14: allow_wildcard_bind is OR between config and CLI
- W15: ADR-010 Phase 2 list updated (timeouts moved to Phase 1)
- W17: LoggingConfig static/restart note added

Suggestions applied:
- S2: ConnectInfo propagation note
- S3: Case-insensitive host matching (RFC 7230)
- S5: Response streaming behavior (chunk-by-chunk)
- S6: Token bucket nodelay semantics
- S7: File watching explicitly out of scope
- S8: All paths forwarded without filtering
- S9: shutdown_timeout_secs referenced in shutdown description
- S11: Consolidated defaults table in config.md
2026-06-11 10:56:40 +00:00
bcc58bc7ce Add architecture gap review identifying 7 critical, 17 warning, and 11 suggestion findings
Comprehensive pre-implementation review of all architecture specs (overview,
proxy, tls, config, operations, 20 ADRs, open questions). Findings cover:
- Site routing model contradiction (per-listener vs global)
- X-Forwarded-For security model (edge proxy should replace, not append)
- Missing hop-by-hop header handling rules
- Undefined ACME failure behavior at startup/renewal
- Unspecified startup sequence and partial failure semantics
- Ambiguous per-listener vs shared router architecture
- Rate limiter state behavior on config reload

Plus warnings about admin socket protocol, Host header port handling,
port validation gaps, upstream format validation, TLS error handling,
shutdown draining, error response bodies, reload race conditions, and more.
2026-06-11 10:43:27 +00:00
fecc385d75 Add container deployment model (ADR-020) and fix review issues
- ADR-020: Document defense-in-depth rationale for running in a minimal
  Docker container (memory-safe language + container isolation), flexible
  upstream addressing (Docker DNS, loopback, LAN, tunnel endpoints),
  file-primary logging for fail2ban, and volume mount strategy
- ADR-016: Add allow_wildcard_bind override for container deployments
  where 0.0.0.0 is correct inside the container network namespace
- operations.md: Add container deployment section with Docker Compose
  example, networking table, volume mounts, and health check integration;
  flip logging to file-primary for fail2ban reliability; note systemd as
  alternative to container deployment
- config.md: Restructure logging fields into nested LoggingConfig (matching
  TOML [logging] section), add allow_wildcard_bind, shutdown_timeout_secs,
  and log_file_path fields; clarify upstream addressing supports Docker
  DNS and tunnel endpoints; update validation rule for 0.0.0.0 override
- overview.md: Update architecture diagram for container model with Docker
  networking and volume mounts; add ADR-020 reference
- proxy.md: Clarify X-Forwarded-Proto is determined by listener port, not
  hardcoded 80/443
- ADR-013: Fix health_check_port default contradiction (default is 9900,
  not 0/disabled as previously stated)
2026-06-11 10:10:32 +00:00
346754fb2b Resolve OQ-07: add multi-config listener support (ADR-019)
Introduce [[listeners]] configuration to support both dedicated-IP
(1 IP = 1 cert = 1 domain) and shared-IP (SAN certificate) deployment
models. Each listener is an independent TLS endpoint with its own bind
address, TLS config, and site routing. OQ-07 is now resolved.

Changes:
- Add ADR-019 for multi-config listener support
- Update config format from [server] to [[listeners]] entries
- Update tls.md for per-listener TLS and certificate provisioning
- Update overview.md architecture diagram and scope
- Update proxy.md for per-listener HTTP redirect
- Fix stale references in ADR-010, ADR-011, ADR-016
- Update OQ-05 resolution (per-listener bind_addr supersedes)
- Add unique-host rationale to config validation rules
- Architecture review: fix all 3 critical and 6 warning issues
2026-06-11 09:35:24 +00:00
9a2352e61c Resolve 5 open questions, add 7 ADRs for previously undocumented decisions
Resolve open questions:
- OQ-01: Restrict cipher suites to match nginx scope (4 ECDHE-AES-GCM
  suites for TLS 1.2 + all TLS 1.3 suites) — ADR-012
- OQ-03: Health check on separate local port (default 9900, localhost
  only) — ADR-013
- OQ-04: Add Unix domain socket admin API for config reload alongside
  SIGHUP, with structured success/failure responses — ADR-014
- OQ-06: Per-site upstream timeouts with defaults (5s connect, 60s
  request), overridable in SiteConfig — ADR-015

Document previously undocumented decisions flagged by architecture review:
- ADR-016: Explicit bind address requirement (reject 0.0.0.0)
- ADR-017: Upstream connection defaults (HTTP/1.1, no redirects, pooling)
- ADR-018: 100 MB body size limit (matches nginx, Gitea compatibility)

OQ-07 (per-site TLS overrides) remains open for future consideration.

Spec updates:
- config.md: add health_check_port, admin_socket_path, per-site timeout
  fields, update TOML example and validation rules
- proxy.md: reference ADR-015/017/018 for timeouts, connection defaults,
  and body limit decisions
- tls.md: replace OQ-01 cipher suite section with ADR-012 decision
- operations.md: add local health check port section, admin socket reload
- overview.md: update Phase 1 scope with new features, add ADR references
- open-questions.md: resolve OQ-01/03/04/06, keep OQ-07 open
2026-06-11 09:07:36 +00:00
7efc142406 Expand architecture: multi-site Phase 1, multi-domain TLS, fix review issues
Promote multi-site support from Phase 2 to Phase 1 (ADR-010): the proxy
must support git.alk.dev and alk.dev from initial release. Add multi-domain
TLS configuration (ADR-011): acme_domains array replaces acme_domain string,
single SAN certificate via rustls-acme.

Key changes:
- ADR-010: Multi-site in Phase 1 — avoids config format migration later
- ADR-011: Multi-domain TLS — single SAN cert, acme_domains Vec<String>
- ADR-002: Updated rationale for multi-site (one upstream per domain)
- overview.md: Phase 1 now includes multi-site, alk.dev pass-through,
  dual licensing (MIT OR Apache-2.0), real IP removed
- config.md: acme_domain → acme_domains, TOML example shows both sites,
  validation adds unique host check, real IP replaced with 203.0.113.10
- tls.md: Multi-domain SNI section moved from Future to current, manual
  mode uses ResolvesServerCert for SNI mapping, TOML header fixed
- proxy.md: Updated for multi-site, removed single-domain language
- operations.md: RFC 5737 documentation IPs, clarified rate limit eviction
  semantics (distinct scan interval vs eviction age)
- open-questions.md: OQ-05 resolved (single bind_addr sufficient), new
  OQ-07 (per-site TLS overrides)

Review fixes:
- acme_domains (plural) consistently used across all docs and diagram
- ADR-011 clearly scopes acme_domain as previous design
- Inline decision rationale extracted: tls.md hot-reload → ADR-004 ref,
  config.md static/dynamic → ADR-008 ref
- TOML section headers consistent (server.tls)
2026-06-11 08:50:03 +00:00
8ee6284b62 Add architecture specification for Rust/axum reverse proxy
Phase 1 architecture docs covering proxy handler, TLS termination (ACME +
manual), TOML config with static/dynamic split (ArcSwap), and operations
(rate limiting, logging, health check, systemd, graceful shutdown).

Nine ADRs documenting key decisions: Rust/axum, custom proxy handler,
TOML config, rustls-acme for cert management, tokio-rustls direct,
token bucket rate limiting, custom log format for fail2ban,
static/dynamic config split, and signal handling strategy.

Includes threat landscape research documenting the nginx CVEs motivating
this project.
2026-06-11 07:25:50 +00:00
5c54a28822 init 2026-06-11 06:32:33 +00:00