Apache-2.0 license with CLA for dual-licensing. Lefthook enforces
Signed-off-by on all commits. AGENTS.md updated with new modules.
Signed-off-by: Sienna Meridian Satterwhite <sienna@r3t.io>
Signed-off-by: Sienna Meridian Satterwhite <sienna@sunbeam.pt>
Add in-memory HTTP response cache using pingora-cache MemCache backend.
Cache runs after the detection pipeline so cache hits bypass upstream
request modifications and body rewriting. Respects Cache-Control
(no-store, private, s-maxage, max-age), skips caching for routes with
body rewrites or auth subrequest headers, and supports configurable
default TTL, stale-while-revalidate, and max file size per route.
Signed-off-by: Sienna Meridian Satterwhite <sienna@sunbeam.pt>
Add static file serving with try_files chain ($uri, $uri.html,
$uri/index.html, fallback), regex-based URL rewrites compiled at
startup, response body find/replace for text/html and JS content,
auth subrequests with header capture for path routes, and custom
response headers per route. Extends RouteConfig with static_root,
fallback, rewrites, body_rewrites, and response_headers fields.
Signed-off-by: Sienna Meridian Satterwhite <sienna@sunbeam.pt>
Generate UUID v4 request IDs per request, create manual tracing spans
(Pingora types don't impl Debug), record Prometheus metrics for
detection decisions and request totals, and forward X-Request-Id to
both upstream requests and downstream responses.
Signed-off-by: Sienna Meridian Satterwhite <sienna@sunbeam.pt>
Add a prometheus metrics module with counters for requests, DDoS/scanner/
rate-limit decisions, active connections gauge, and request duration
histogram. Spawn a lightweight HTTP server on a configurable port
(default 9090) serving /metrics and /health endpoints.
Signed-off-by: Sienna Meridian Satterwhite <sienna@sunbeam.pt>
On Linux with net.ipv6.bindv6only=0 (default), binding [::]:port
claims both IPv4 and IPv6, causing the subsequent 0.0.0.0:port bind
to fail. Set IPV6_V6ONLY=1 so each socket only handles its own
address family, fixing SSH TCP proxy bind failure in containers.
Signed-off-by: Sienna Meridian Satterwhite <sienna@sunbeam.pt>
Wire up all three detection layers in request_filter with pipeline
logging at each stage for unfiltered training data. Add DDoS, scanner,
and rate_limit config sections. Bot allowlist check before scanner
model on the hot path. CLI subcommands for train/replay.
Signed-off-by: Sienna Meridian Satterwhite <sienna@sunbeam.pt>
ArcSwap-based lock-free hot-reload via file mtime polling. Bot
allowlist with CIDR (instant) + reverse/forward DNS (cached with
background worker thread) IP verification to prevent UA spoofing
by known crawlers, LLM agents, and commercial B2B bots.
Signed-off-by: Sienna Meridian Satterwhite <sienna@sunbeam.pt>
12-feature extraction (zero-alloc hot path), 2 interaction terms,
weighted linear scoring model with hard allowlist short-circuits for
configured host+cookies and host+browser UA. Returns ScannerVerdict
with score+reason for pipeline logging.
Signed-off-by: Sienna Meridian Satterwhite <sienna@sunbeam.pt>
14-feature vector extraction, KNN classifier using fnntw, per-IP
sliding window aggregation, and heuristic auto-labeling for training.
Includes replay subcommand for offline evaluation and integration tests.
Signed-off-by: Sienna Meridian Satterwhite <sienna@sunbeam.pt>
This commit implements comprehensive dual-stack support for the proxy,
allowing it to listen on both IPv4 and IPv6 addresses simultaneously.
Key changes:
- Added new dual_stack.rs module with DualStackTcpListener implementation
- Updated SSH module to use dual-stack listener
- Updated configuration documentation to reflect IPv6 support
- Added comprehensive tests for dual-stack functionality
The implementation is inspired by tokio_dual_stack but implemented
natively without external dependencies. It provides fair connection
distribution between IPv4 and IPv6 clients while maintaining full
backward compatibility with existing IPv4-only configurations.
Signed-off-by: Sienna Meridian Satterwhite <sienna@sunbeam.pt>
Add optional [ssh] config block that proxies port 22 → Gitea SSH pod,
running on a dedicated thread/runtime matching the cert-watcher pattern.
Also start HTTP-only on first deploy when the TLS cert file doesn't exist
yet — once ACME challenge completes and the cert watcher writes the file,
a graceful upgrade adds the TLS listener without downtime.
Fix ACME watcher to handle InitApply events (kube-runtime v3+) so
Ingresses that existed before the proxy started are picked up correctly.
Signed-off-by: Sienna Meridian Satterwhite <sienna@sunbeam.pt>
Docker's OCI distribution protocol sends Expect: 100-continue for blob
uploads larger than ~5 MB. Without this fix, Pingora forwarded the header
to Gitea, Gitea responded with 100 Continue, and Pingora could not reliably
proxy the informational response back — causing spurious 400 errors for the
client on large image layer pushes.
Fix: respond with 100 Continue in request_filter before upstream_peer is
called, then strip the Expect header in upstream_request_filter so the
upstream never sends its own 100 Continue.
Also adds a unit test verifying that remove_header("expect") strips the
header from the upstream request without disturbing other headers.
Signed-off-by: Sienna Meridian Satterwhite <sienna@sunbeam.pt>
Root cause: upstream_request_filter was inserting x-forwarded-proto with
a raw headers.insert() call (via DerefMut) which only updates base.headers
but NOT the CaseMap. header_to_h1_wire zips CaseMap with base.headers, so
headers added without a CaseMap entry are silently dropped on the wire.
Fix: use insert_header() which keeps both maps in sync.
Also adds:
- src/lib.rs + [lib] section: exposes SunbeamProxy/RouteConfig/AcmeRoutes
to integration tests without re-declaring modules in main.rs
- tests/e2e.rs: real end-to-end test — starts a SunbeamProxy over plain
HTTP, routes it to a TCP echo backend, and asserts x-forwarded-proto: http
is present in the upstream request headers
- Updated unit tests to verify header_to_h1_wire round-trip (not just that
HeaderMap::insert works in isolation)
Signed-off-by: Sienna Meridian Satterwhite <sienna@sunbeam.pt>
By default every plain-HTTP request is 301-redirected to HTTPS — no upstream
is ever contacted, making it as close to an L4 redirect as HTTP allows.
New RouteConfig field `disable_secure_redirection` (bool, default false):
when set to true on a route, plain-HTTP requests for that host pass through
to the backend unchanged instead of being redirected.
Also fixes the redirect URL to include the original query string, which was
previously dropped (e.g. ?next=/dashboard would be lost after redirect).
Signed-off-by: Sienna Meridian Satterwhite <sienna@sunbeam.pt>