Files
reverse-proxy/AGENTS.md
glm-5.1 c8ab794ef3 Add LICENSE, README, AGENTS.md, and deployment setup guide
Dual MIT/Apache-2.0 license, public-facing README with quick start
and config reference, step-by-step deploy/README.md for Docker and
systemd setups, and AGENTS.md for LLM-assisted development.
2026-06-12 11:42:08 +00:00

156 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AGENTS.md
Guidance for LLM agents (and humans) working on this project.
## Project Overview
A memory-safe reverse proxy built with Rust/axum that replaces vulnerable nginx
installations. Terminates TLS, routes requests by Host header to upstream
services, enforces rate limits, and injects proxy headers. See `README.md` and
`docs/architecture/` for full details.
## Build & Run
```bash
cargo build # debug build
cargo build --release # release build
cargo test # run all tests (unit + integration)
cargo test -- --nocapture # run tests with stdout visible
cargo clippy # lint
reverse-proxy --config config.toml # run (defaults to /etc/reverse-proxy/config.toml)
reverse-proxy --validate --config config.toml # validate config only
```
For a static binary with no libc dependency:
```bash
cargo build --release --target x86_64-unknown-linux-musl
```
## Project Structure
```
src/
├── main.rs # Entry point, server startup, listener binding
├── cli.rs # CLI parsing (clap), config loading, validation
├── lib.rs # Library root, module declarations
├── config/
│ ├── static_config.rs # StaticConfig — immutable, requires restart
│ ├── dynamic_config.rs# DynamicConfig — hot-reloadable via ArcSwap
│ ├── validation.rs # Config validation rules (called at startup and reload)
│ └── test_fixtures.rs # Test config generation helpers
├── proxy/
│ ├── handler.rs # Core reverse proxy handler (forward requests to upstream)
│ ├── headers.rs # Proxy header injection (X-Real-IP, X-Forwarded-For, etc.)
│ ├── body_limit.rs # Request body size limiting middleware
│ ├── error.rs # Error response types (502, 504, 429, etc.)
│ └── mod.rs # Router construction, client creation
├── tls/
│ ├── acceptor.rs # TLS acceptor setup (manual + ACME)
│ ├── acme.rs # ACME certificate provisioning via rustls-acme
│ ├── config.rs # TLS ServerConfig construction, cipher suites
│ └── redirect.rs # HTTP → HTTPS 301 redirect listener
├── rate_limit/
│ ├── mod.rs # Rate limiting middleware, eviction task
│ └── bucket.rs # Token bucket implementation (IPv4 /32, IPv6 /64)
├── admin/
│ ├── socket.rs # Unix domain socket admin API (reload, status)
│ └── mod.rs
├── health.rs # Health check endpoint on localhost:9900
├── logging/
│ ├── mod.rs # Logging init (file + stdout, ANSI disabled)
│ └── format.rs # Structured log format (REQUEST, RATE_LIMIT, etc.)
├── server.rs # HTTPS listener serving with ALPN detection
├── shutdown.rs # Graceful shutdown (SIGTERM, SIGINT) + SIGHUP reload
└── utils.rs # Shared utilities
```
## Key Architecture Concepts
- **StaticConfig vs DynamicConfig**: Static config (bind addresses, TLS,
ports) requires a restart. Dynamic config (sites, rate limits, body limits)
can be reloaded at runtime via SIGHUP or admin socket, using `ArcSwap` for
lock-free reads.
- **Multi-listener**: `[[listeners]]` in TOML — each listener has its own bind
address, TLS config, and site routing. Sites are collected into a global
routing table at runtime.
- **Edge proxy model**: The proxy is the edge — X-Forwarded-For is replaced
(not appended), X-Real-IP is set from the connection's remote address.
- **No `/health` on public listener**: Health checking is localhost:9900 only.
The main listener does not intercept any paths.
- **HTTP/2 client-facing only**: ALPN detects h2 vs http/1.1. Upstream
connections are always HTTP/1.1.
- **IPv6 rate limiting**: IPv6 addresses are normalized to /64 prefixes so
addresses within the same /64 share a token bucket.
## Config Format
TOML. See `docs/architecture/config.md` for full schema. Key validation rules:
- `bind_addr` must be explicit (no `0.0.0.0`) unless `allow_wildcard_bind` is
enabled via config or `--allow-wildcard-bind` CLI flag (OR logic)
- Site `host` values must be unique across all listeners
- `upstream` must be in `host:port` format (e.g., `gitea:3000`, `127.0.0.1:3000`)
- ACME mode requires `acme_domains` (non-empty) and `acme_contact` (valid
`mailto:` URI)
- Manual mode requires `cert_path` and `key_path` pointing to readable files
- `rate_limit.requests_per_second` and `rate_limit.burst` must be > 0
- `body.limit_bytes` must be > 0
- `http_port` must be 0 (disabled) or 165535; `https_port` must be 165535
- `health_check_port` must not conflict with any listener's http_port or
https_port on the same bind address
## Testing
Tests use `rcgen` for self-signed certificate generation and `reqwest` for
HTTP client requests. Integration tests are in `tests/integration_test.rs`
with helpers in `tests/helpers/`.
```bash
cargo test # all tests
cargo test --test integration # integration tests only
cargo test --lib # unit tests only
```
## Code Style
- No comments unless explicitly requested
- Error handling uses `anyhow` for application code and `thiserror` for
library error types
- Structured logging with `tracing` — always `with_ansi(false)`
- Config types implement `serde::Deserialize` for TOML parsing
- All network operations use `tokio` async runtime
## Deployment Files
`deploy/` contains production-ready deployment configs:
- `Dockerfile` — multi-stage build (rust:alpine → alpine)
- `docker-compose.yml` — complete setup with Gitea example
- `reverse-proxy.service` — systemd unit file with security hardening
- `fail2ban/` — filter and jail config for rate limit log parsing
See `deploy/README.md` for step-by-step setup instructions.
## Common Modifications
### Adding a new site
Add a `[[listeners.sites]]` entry to config and reload:
```bash
echo "reload" | socat - UNIX-CONNECT:/run/reverse-proxy/admin.sock
```
### Changing rate limits
Update `[rate_limit]` in config and reload (no restart needed).
### Changing bind address or TLS config
These are in StaticConfig — require a full process restart.
### Adding per-site timeouts
Set `upstream_connect_timeout_secs` and `upstream_request_timeout_secs` on a
site definition. Defaults are 5s connect, 60s request.