15 Commits

Author SHA1 Message Date
7ccb2ae64f feat: implement multi-listener TLS setup with ConnectInfo propagation
- Add server module that orchestrates the full startup sequence:
  parse config, init dynamic config, init shared state, bind health
  check, bind admin socket, bind all listener ports, load TLS config,
  start TCP listeners, start background tasks, signal readiness
- For each ListenerConfig: bind TCP listener, construct appropriate
  ServerConfig (manual or ACME via TlsMode), create TlsAcceptor
- ConnectInfo<SocketAddr> populated from TcpStream::peer_addr() BEFORE
  TLS wrapping via ConnectInfoService wrapper that inserts ConnectInfo
  into request extensions for each connection
- Per-listener axum::Router instances sharing Arc<ProxyState> via State
- Fail-fast: if any bind or TLS load fails, exit with non-zero code
- All ports bound before any connections accepted
- /health endpoint available on HTTPS listener(s) as fallback
  (proxy_router already includes /health route)
- sd_notify(READY=1) sent after all listeners started
- Use hyper_util for TLS connection serving with TowerToHyperService
  and ConnectInfoService to bridge ConnectInfo from pre-TLS peer_addr
- Add sd-notify dependency for systemd readiness notification
2026-06-11 13:38:39 +00:00
388523d6fe Merge feat/proxy/headers-and-forwarding into main 2026-06-11 13:24:40 +00:00
9abae2093b Merge feat/ops/admin-socket into main 2026-06-11 13:20:23 +00:00
b9126a96f4 Implement proxy header injection, hop-by-hop removal, and request forwarding
- Add ProxyError enum with IntoResponse for error handling (400, 404, 502, 504)
- Implement proxy header injection: X-Real-IP, X-Forwarded-For (replaced, not appended), X-Forwarded-Proto
- Implement hop-by-hop header removal for both request and response headers
- Implement request forwarding via shared hyper::Client with HTTP and HTTPS support
- Add ProxyState with http_client and https_client instances shared via axum State
- Add per-site timeout overrides using tokio::time::timeout
- Add HTTPS upstream support with system native TLS root certificates
- No Server or Via headers added to responses
- Host header preserved as-is
- Add unit tests for header injection, hop-by-hop removal, and URI building
- Add integration tests for proxy forwarding, hop-by-hop removal, and 502 on unreachable upstream
2026-06-11 13:18:56 +00:00
56eda4e47c feat: implement Unix domain socket admin API for config reload and status
Add admin socket module that binds to a configurable Unix domain socket
path (default /run/reverse-proxy/admin.sock) supporting reload and status
commands. Reload re-reads config and swaps DynamicConfig via ArcSwap with
serialized access using the same Mutex as SIGHUP. Status returns uptime
and site count. Unknown commands and invalid input return structured
JSON error responses. Stale socket files are removed at startup; if the
socket is occupied by another process, a warning is logged and the socket
is disabled. Empty admin_socket_path disables the socket entirely.

Also adds FullConfig struct to config module for parsing complete config
files during reload, and adds serde_json dependency for JSON responses.
2026-06-11 13:13:15 +00:00
24a7f9ed86 Merge feat/ops/body-size-limit into main 2026-06-11 13:12:27 +00:00
5fa0fc600e Implement body size limit middleware with dynamic config reload
Add body_limit middleware that reads limit from ArcSwap<DynamicConfig>
on each request, enabling runtime config changes without restart.
Uses Content-Length header check for fast rejection and http_body_util::Limited
for streaming body enforcement. Default limit: 100 MB (104,857,600 bytes).
Returns 413 Payload Too Large when exceeded.
2026-06-11 13:02:59 +00:00
2791070971 Implement token bucket rate limiting with IPv6 /64 normalization
- Add TokenBucket with nodelay semantics (nginx limit_req burst nodelay)
- Per-IP rate limiting: IPv4 /32, IPv6 /64 prefix normalization
- DashMap for concurrent access, ArcSwap for lock-free config reads
- Background eviction task for stale entry cleanup
- 429 response with plain text body, RATE_LIMIT log prefix
- Config reload adopts new rate/burst on next request without clearing state
- Unit tests for bucket algorithm and IPv6 normalization
- Integration tests for 429 responses and per-IP independence
2026-06-11 13:01:25 +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
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
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
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
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