feat: initial sunbeam-proxy implementation

Custom Pingora-based edge proxy for the Sunbeam infrastructure stack.

- HTTPS termination: mkcert file-based (local dev) or rustls-acme ACME (production)
- Host-prefix routing with path-based sub-routing (auth virtual host)
- HTTP→HTTPS redirect, WebSocket passthrough
- cert-manager HTTP-01 challenge routing via Kubernetes Ingress watcher
- TLS cert auto-reload via K8s Secret watcher
- JSON structured audit logging (tracing-subscriber)
- OpenTelemetry OTLP stub (disabled by default)
- Multi-stage Dockerfile: musl static binary on chainguard/static distroless image

Signed-off-by: Sienna Meridian Satterwhite <sienna@sunbeam.pt>
This commit is contained in:
2026-03-10 23:38:19 +00:00
commit 6ec0f78a5b
13 changed files with 5042 additions and 0 deletions

61
src/config.rs Normal file
View File

@@ -0,0 +1,61 @@
use anyhow::{Context, Result};
use serde::Deserialize;
use std::fs;
#[derive(Debug, Deserialize, Clone)]
pub struct Config {
pub listen: ListenConfig,
pub tls: TlsFileConfig,
pub telemetry: TelemetryConfig,
pub routes: Vec<RouteConfig>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct ListenConfig {
pub http: String,
pub https: String,
}
#[derive(Debug, Deserialize, Clone)]
pub struct TlsFileConfig {
pub cert_path: String,
pub key_path: String,
}
#[derive(Debug, Deserialize, Clone)]
pub struct TelemetryConfig {
pub otlp_endpoint: String,
}
/// A path-prefix sub-route within a virtual host.
/// Matched longest-prefix-first when multiple entries share a prefix.
#[derive(Debug, Deserialize, Clone)]
pub struct PathRoute {
pub prefix: String,
pub backend: String,
/// Strip the matched prefix before forwarding to the backend.
#[serde(default)]
pub strip_prefix: bool,
#[serde(default)]
pub websocket: bool,
}
#[derive(Debug, Deserialize, Clone)]
pub struct RouteConfig {
pub host_prefix: String,
pub backend: String,
#[serde(default)]
pub websocket: bool,
/// Optional path-based sub-routes (longest prefix wins).
/// If the request path matches a sub-route, its backend is used instead.
#[serde(default)]
pub paths: Vec<PathRoute>,
}
impl Config {
pub fn load(path: &str) -> Result<Self> {
let raw = fs::read_to_string(path)
.with_context(|| format!("reading config from {path}"))?;
toml::from_str(&raw).with_context(|| "parsing config.toml")
}
}