Rust-native L4/L7 load balancer with an Axum admin API and a Yew (WASM) control panel.
Author: Eduard Gevorkyan (egevorky@arencloud.com)
License: Apache 2.0
Current version: UI 0.1.3 • API 0.1.3 • build dev
flowchart LR
subgraph ControlPlane["Control Plane (Axum)"]
API["/api/* (CRUD + Auth/RBAC)"]
Supervisor["Listener Supervisor"]
Store["Config Store (file)"]
Static["/assets + UI (ServeDir)"]
end
subgraph AdminUI["Admin UI (Yew/WASM)"]
Form["Create/Edit Listener"]
List["Active Listeners + Health"]
Auth["Login + Users (Admin)"]
end
subgraph DataPlane["Data Plane"]
HTTP["HTTP L7 Balancer\n(Axum proxy)"]
TCP["TCP L4 Balancer\n(Tokio copy_bidirectional)"]
Upstreams["Upstream Pools"]
end
AdminUI -->|XHR /api/listeners, /api/stats| API
API --> Store
API --> Supervisor
Supervisor -->|Spawn| HTTP
Supervisor -->|Spawn| TCP
HTTP --> Upstreams
TCP --> Upstreams
AdminUI --> Static
- Backend (
backend): Axum HTTP server exposing CRUD APIs for listeners and serving the compiled admin UI. A supervisor spawns runtime tasks per listener:- HTTP (L7): Axum fallback proxy with round-robin upstream selection and header sanitization. Host-based routing is first-class: each host binds to an upstream pool and can carry its own TLS/ACME settings (SNI).
- TCP (L4): Tokio
copy_bidirectionalbridge with round-robin upstream selection. TCP listeners now reuse upstream pools (pick a pool; the UI hydrates endpoints automatically).
- Admin UI (
admin): Yew single-page app compiled to WASM via Trunk. Provides listener creation, editing, deletion, and live stats against/api/*. Uses the logo atimages/balor.png. - Workspace: Cargo workspace rooted at repository top; shared dependencies declared in the workspace
[dependencies].
Prerequisites: Rust toolchain, wasm32-unknown-unknown target, and trunk for the UI (cargo install trunk).
# Build the admin UI to admin/dist
cd admin
trunk build --release
# Run the backend (serves API + UI)
cargo run -p backend
# Admin UI available at http://127.0.0.1:8080Environment knobs:
BALOR_HTTP_ADDR(default0.0.0.0:9443) – admin API + UI bind address (port configurable; TLS for console planned).BALOR_ADMIN_DIST(defaultadmin/dist) – path where the Yew assets are served from.BALOR_STATE_FILE(defaultdata/balor_state.json) – persisted listener config storage.BALOR_DEFAULT_ADMIN_PASSWORD(defaultadmin) – bootstrap password for the auto-createdadminuser if no users exist.BALOR_ADMIN_TOKEN(optional) – alternative bearer token that always maps toadminrole (bypasses password).BALOR_BOOTSTRAP_SECRET_FILE(defaultdata/admin-bootstrap.txt) – when no users exist and noBALOR_DEFAULT_ADMIN_PASSWORDis set, a strong password is generated and written here (mode 600) for first login; rotate and delete after use.
make fmt– format workspace.make backend/make admin-check– quick checks for backend/admin.make ci– fmt --check, clippy (all targets/features), backend tests.cargo run -p backend --bin balorctl -- --state data/balor_state.json export backup.json– CLI helper to export/import the persisted state file (see Makefile targetsbalor-export/balor-import).
- Docker:
docker build -t balor:latest .(multi-stage, bundles admin/dist + backend). Run with-e BALOR_ADMIN_DIST=/app/admin/dist -p 9443:9443. - Homebrew (tap example under
packaging/balor.rb): provides a formula to build UI + backend from source; setBALOR_ADMIN_DISTto the installeddistpath (see caveats in the formula). - Backup/restore: use
balorctl(cargo run -p backend --bin balorctl -- --state data/balor_state.json export OUT.json/... import IN.json) or Makefile shortcutsmake balor-export OUT=.../make balor-import IN=...to snapshot/restore the JSON state file. - Container image:
make docker-build IMAGE=balor:0.1.3 REGISTRY=ghcr.io/your-org(tags optional).make docker-pushwill push both the registry tag and local tag whenREGISTRYis set. - Homebrew checksum: v0.1.3 tarball SHA256
d5558cd419c8d46bdc958064cb97f963d1ea793866414c025906ec15033512edis populated inpackaging/balor.rb; update on next release tag. - GitHub Actions release workflow builds/tests, uploads artifacts, pushes GHCR images, and updates a Homebrew tap (expects
${OWNER}/homebrew-taprepo and GITHUB_TOKEN permissions). - Systemd: a sample unit (
packaging/balor.service) and env file (packaging/balor.env.sample) are provided. Usemake systemd-unitto stage copies indist/, edit paths/passwords, then install to/etc/systemd/system/balor.serviceand/etc/balor/balor.env.
GET /api/health– service heartbeat.GET /api/stats– listener/task counts.GET /api/listeners– list configured listeners.POST /api/listeners– create listener. HTTP listeners rely on host-based routes:{ name, listen, protocol: "http", rate_limit?, host_routes: [{ host, pool, rate_limit?, tls?, acme? }], upstreams: [...] }(TCP usesupstreams, typically hydrated from a selected pool in the UI). Pools are referenced by name.rate_limitis{ rps, burst }and enforces 429 +Retry-Afteron overage.GET /api/listeners/{id}– fetch a listener.PUT /api/listeners/{id}– update a listener.POST /api/listeners/{id}/drain– gracefully drain and disable a listener before delete/update (stops runtime, keeps config).DELETE /api/listeners/{id}– remove a listener and stop its runtime.GET/POST/DELETE /api/pools– manage reusable upstream pools (attach to host routes).GET/POST/PUT/DELETE /api/acme/providers– manage DNS-01 provider profiles (Cloudflare, Route53, generic).POST /api/acme/request– schedule an ACME issuance (host-bound or standalone) and persist the job;GET /api/acme/standalone,/acme/standalone/renew,/acme/standalone/deletemanage standalone jobs.GET /api/certificates– list uploaded/issued certs;POSTuploads a PEM bundle.POST /api/login/POST /api/logout– session tokens for the UI.GET/POST/PUT/DELETE /api/users– RBAC user management (admin only).GET/PUT /api/logging– get/set trace sampling (permyriad, min 1–max 10_000).
- HTTP upstream addresses should include scheme (e.g.,
http://127.0.0.1:7000). TCP upstreams use host:port. HTTP host routes pick from pools (pool selection is required per host); TCP listeners select a pool and the UI fills endpoints for you. Weighted upstreams are supported by adding an optional weight after a space, e.g.,api=http://10.0.0.2:8080 5(defaults to 1). - State persists to
data/balor_state.jsonon each change (path override viaBALOR_STATE_FILE). - Background health checks run every ~5 seconds and mark upstreams up/down in the UI automatically; HTTPS probes verify certificates by default, with an opt-out toggle in the UI when you intentionally use self-signed upstreams. Data-plane proxying will still pass traffic to HTTPS upstreams with self-signed/cluster CAs (OpenShift APIs/routers) even if health validation is enabled.
- HTTP listeners can terminate TLS via PEM cert/key paths; files are reloaded when they change. Multiple certificates on one port are supported via per-host SNI selection; a listener-level certificate acts as fallback.
- Sticky sessions supported per HTTP listener (cookie or client IP hash).
- Upstream
Set-Cookiedomains are rewritten to the client-facing host to keep sessions alive across refreshes when proxied. - RBAC roles: Admin (full), Operator (CRUD listeners), Viewer (read-only).
- Prometheus metrics exposed at
/metrics(HTTP counters/latency and TCP connection totals per listener). - WebSocket pass-through supported for HTTP listeners (upgrade + bidirectional frames).
- ACME automation: HTTP-01 responder serves tokens from
BALOR_ACME_CHALLENGE_DIR(defaultdata/acme-challenges); HTTP-01 issuance now runs automatically for host routes that request ACME and stores PEMs underdata/certs. DNS-01 flow uses provider profiles (Cloudflare, Route53, generic); Cloudflare automation shipped, Route53/generic automation still pending. Standalone ACME jobs persist with validity dates and can be renewed from the UI. - Certificates dashboard: upload/download manual PEM bundles (stored under
BALOR_CERT_DIR, defaultdata/certs) from the Admin UI Certificates tab. - ACME provider type auto-sets API base URLs (Cloudflare/Route53) with overrides allowed.
- The UI defaults to a sample listen address (
0.0.0.0:9000) and a single upstream; adjust per environment. - Browser support note: Chrome/Chromium and Firefox are both supported; Users/Metrics views were refactored to avoid the previous Firefox layout crash.
- API server (6443): Create a TCP listener on
0.0.0.0:6443pointing at a pool of control-plane nodes onhttps://<node>:6443. Health probes can be TCP (script) or HTTPS with “skip TLS verify” enabled if using cluster CAs. - Routers (80/443): Create HTTP listeners on
:80and:443, attach host-based routes to router pools, and enable SNI/TLS per host. WebSockets/HTTP2 are passed through; self-signed router certs are accepted automatically at the proxy layer while health checks still verify unless disabled. For API/routers addressed by IP, Balor now preserves the original Host header and forces SNI to the client host while dialing the IP via DNS override to satisfy cluster-issued certs.
- ✅ Persistent config store
- ✅ Health checks (HTTP/TCP)
- ✅ TLS termination (hot reload, per-host SNI)
- ✅ Sticky sessions (cookie/IP hash)
- ✅ RBAC (admin/operator/viewer)
- ✅ Metrics (Prometheus HTTP/TCP)
- ✅ Cookie rewrite to client host
- ✅ Host-based routing & SNI multi-cert
- ✅ Pools dashboard; listeners/pools reuse
- ✅ WebSocket pass-through
- ✅ Certificates dashboard (manual PEM upload/download; ACME-issued certs register)
- ✅ Version banner: UI/API/build version surfaced via
/api/versionand shown in the header - ✅ Metrics tab now includes latency quantiles (p50/p95/p99) derived from Prometheus buckets with quick visual cards
- ✅ Logs tab (admin-only): live buffer view, level filter, JSONL download/preview from the backend log store (retention + rotation), and hardened sampling controls (clamped 1–100% with safe persistence)
- ✅ Admin console port/TLS controls: default bind
0.0.0.0:9443, UI control to change port and pick a console cert (restart required) - ✅ Rate limiting: per-listener and per-host-route token-bucket limits (RPS + burst) with 429 +
Retry-Afteron overage - ✅ ACME automation: HTTP-01 + Cloudflare/Route53/Generic (webhook) DNS-01 with periodic renewal and backoff retries. ACME jobs (host-bound & standalone) persist with “valid until” dates; renew/edit/remove from UI; existing ACME certs are reused on edit to avoid needless re-issuance. Standalone issuance is timeout-guarded to prevent console hangs.
- ✅ Listener bind flexibility: any valid
host:portaccepted in the UI/API for HTTP/TCP listeners; backend enforces socket-addr validity only. - ✅ gRPC pass-through: HTTP listeners preserve
TE: trailers; point upstreams at your gRPC service. - ✅ Health probes: configurable HTTP health path/headers and optional probe scripts with timeouts/env to mark upstream health.
- ✅ Browser support: Chrome/Chromium and Firefox verified after Users/Metrics fixes
