Compare commits

...
92 Commits
Author SHA1 Message Date
Mateusz Gruszczyński fe945f51a5 0.36.9 2026-11-09 12:28:08 +01:00
Mateusz Gruszczyński b9d716d260 v0.36.9 2026-11-09 11:42:46 +01:00
Mateusz Gruszczyński 992a1795af v0.36.8 2026-11-09 11:22:09 +01:00
Mateusz Gruszczyński 0cbe0566f5 v0.36.8 2026-11-08 23:42:50 +01:00
Mateusz Gruszczyński 0119b11005 v0.36.8 2026-11-08 23:23:21 +01:00
Mateusz Gruszczyński 1ac94213e0 v0.36.7 2026-11-08 21:26:30 +01:00
Mateusz Gruszczyński 213332d0b5 v0.36.6 2026-11-08 21:11:33 +01:00
Mateusz Gruszczyński e7143151bd v0.36.6 2026-11-08 12:32:50 +01:00
Mateusz Gruszczyński 993062520f v0.36.6 2026-11-08 12:00:38 +01:00
Mateusz Gruszczyński 3d3e0ea5b8 v0.36.6 2026-11-08 11:47:01 +01:00
Mateusz Gruszczyński 0cde2c8a18 v0.36.6 2026-11-08 11:16:41 +01:00
Mateusz Gruszczyński d7fb869783 v0.36.5 2026-11-08 09:55:44 +01:00
Mateusz Gruszczyński 6ef01212ec v0.36.5 2026-11-08 09:46:32 +01:00
Mateusz Gruszczyński 34490fd5fc 0.36.4 2026-11-08 07:11:46 +01:00
Mateusz Gruszczyński 17e314e159 v0.36.3 2026-11-08 06:20:03 +01:00
Mateusz Gruszczyński 5cb63f542c v0.36.2 2026-11-07 15:26:22 +01:00
Mateusz Gruszczyński 064772765b v0.36.2 2026-11-07 15:26:06 +01:00
Mateusz Gruszczyński 6d7f152a0b 0.36.1_hf2 2026-11-07 14:59:21 +01:00
Mateusz Gruszczyński da1fe0df55 v0.36.1_hf2 2026-11-07 14:21:13 +01:00
Mateusz Gruszczyński 4793af758f v0.36.1_hf1 2026-11-07 14:16:25 +01:00
Mateusz Gruszczyński 6f52ac3d00 v0.36.1 2026-11-07 13:56:11 +01:00
Mateusz Gruszczyński 672c84bc13 v0.36.0 2026-11-07 13:13:25 +01:00
Mateusz Gruszczyński 3a384baa6b 0.35.5_changelog 2026-11-07 11:05:30 +01:00
Mateusz Gruszczyński e7b4991316 0.35.5 2026-11-07 10:23:28 +01:00
Mateusz Gruszczyński ff42a13dbf 0.35.4 2026-11-07 08:45:14 +01:00
Mateusz Gruszczyński cd3c28d390 0.35.4 2026-11-07 08:16:11 +01:00
Mateusz Gruszczyński c038ad44b3 v0.35.3_gitignore 2026-11-06 23:40:49 +01:00
Mateusz Gruszczyński 51f22a72cc v0.35.3 2026-11-06 23:38:00 +01:00
Mateusz Gruszczyński f4606cce18 v0.35.2 2026-11-06 23:16:36 +01:00
gru 09414dfb09 Merge pull request 'Release/0.35.1' (#3) from release/0.35.1 into master
Reviewed-on: #3
2026-11-05 21:54:24 +01:00
gru eba9f89283 v0.35.1: performance, AGPL licensing and documentation overhaul
- Web Server reads static files inline; proxy middleware chain flattened
- About window with license summary and project links; AGPL-3.0-or-later
- Rewritten documentation: feature list, screenshots, performance guide
- Fix false spikes in per-Vhost request-rate charts
2026-11-05 18:29:56 +01:00
Mateusz Gruszczyński d3b492874b v0.35.0 2026-10-31 23:42:58 +01:00
Mateusz Gruszczyński 2c03fcf248 v0.34.16 2026-10-27 13:49:21 +01:00
Mateusz Gruszczyński f27fbf6a30 v0.34.16 2026-10-27 13:22:43 +01:00
Mateusz Gruszczyński e364392659 v0.34.15 2026-10-27 12:18:20 +01:00
Mateusz Gruszczyński 62b7574553 v0.34.14 2026-10-27 09:27:00 +01:00
Mateusz Gruszczyński 5c2e50ec06 0.34.13_cleaup_code 2026-10-26 14:14:41 +01:00
Mateusz Gruszczyński ec46a96a29 v0.34.13 2026-10-25 17:47:01 +01:00
Mateusz Gruszczyński 24406b9580 v0.24.13 2026-10-25 17:46:10 +01:00
Mateusz Gruszczyński ed7e3377c1 v0.24.13 2026-10-25 17:45:57 +01:00
Mateusz Gruszczyński bd077754a9 v0.34.12 2026-10-25 10:19:42 +01:00
Mateusz Gruszczyński a4042d6439 v0.34.12 2026-10-25 10:15:26 +01:00
Mateusz Gruszczyński b3dfd570b5 v0.34.12 2026-10-25 10:06:38 +01:00
Mateusz Gruszczyński 5ac3617947 v0.34.12 2026-10-25 09:58:39 +01:00
Mateusz Gruszczyński 37c197c095 0.34.11 2026-10-24 00:05:09 +02:00
Mateusz Gruszczyński 63cdecea6c 0.34.10 2026-10-23 23:29:52 +02:00
Mateusz Gruszczyński ee54aa6875 v0.34.10 2026-10-23 13:07:11 +02:00
Mateusz Gruszczyński 66ff9dfaa2 v0.34.9_css_refactor 2026-10-23 11:15:35 +02:00
Mateusz Gruszczyński 2b7c67d755 0.34.9 2026-10-22 23:43:19 +02:00
Mateusz Gruszczyński 70388578cb v0.34.8 2026-10-22 23:00:07 +02:00
Mateusz Gruszczyński d8bdc6e780 v0.34.8 2026-10-22 22:50:03 +02:00
Mateusz Gruszczyński 5645787795 v0.34.8 2026-10-22 19:26:38 +02:00
Mateusz Gruszczyński 5a0a063031 v0.34.8 2026-10-22 19:20:57 +02:00
Mateusz Gruszczyński 58c7d63194 v0.34.7 2026-10-22 15:34:17 +02:00
Mateusz Gruszczyński 1cd6b7ac99 v0.34.7 2026-10-22 15:31:47 +02:00
Mateusz Gruszczyński 3ea00045bf v0.34.7 2026-10-22 14:20:03 +02:00
Mateusz Gruszczyński 133d2f0f91 0.34.6_repo_cleanup 2026-10-22 09:31:15 +02:00
Mateusz Gruszczyński ce1a90397c v0.34.6 2026-10-21 16:04:54 +02:00
Mateusz Gruszczyński 5cb3b37627 v0.34.6 2026-10-21 14:39:51 +02:00
Mateusz Gruszczyński e037190c14 v.34.5_fix_systemd 2026-10-21 10:52:12 +02:00
Mateusz Gruszczyński 7cfc3a6da1 v0.34.5 2026-10-21 09:38:21 +02:00
Mateusz Gruszczyński 3776edf4fc v0.34.4 2026-10-20 23:13:39 +02:00
Mateusz Gruszczyński 52e7ce7b46 v0.34.4 2026-10-20 22:47:13 +02:00
Mateusz Gruszczyński ee174bc283 v0.34.4 2026-10-20 22:09:50 +02:00
Mateusz Gruszczyński 1fd81eaf5f v0.34.3_ht2 2026-10-20 12:36:58 +02:00
Mateusz Gruszczyński 29d9a7dd6c v0.34.3_hf1 2026-10-20 11:48:59 +02:00
Mateusz Gruszczyński 8f41b39c26 v0.34.3 2026-10-20 11:19:55 +02:00
Mateusz Gruszczyński 8c0de520d7 0.34.2_agent 2026-10-19 22:38:17 +02:00
Mateusz Gruszczyński b2b5b45d8a v0.34.2 2026-10-19 15:40:37 +02:00
Mateusz Gruszczyński 34fb534e02 v0.34.2 2026-10-19 14:31:07 +02:00
Mateusz Gruszczyński b57beb83c7 v0.3 2026-10-19 14:05:13 +02:00
Mateusz Gruszczyński 28e39f7ce4 v0.34.1 2026-10-19 12:24:11 +02:00
Mateusz Gruszczyński 2ab44c4c62 v.34.1 2026-10-19 11:09:48 +02:00
gru 4afa88a65e Merge pull request '0.34' (#2) from 0.34 into master
Reviewed-on: #2
2026-10-18 12:01:50 +02:00
Mateusz Gruszczyński c207e8a03f v0.34.0_fina; 2026-10-18 12:00:59 +02:00
Mateusz Gruszczyński 1ca019d849 v0.34.0_13 2026-10-18 10:43:47 +02:00
Mateusz Gruszczyński 929854898f v0.34.0_12 2026-10-18 10:38:06 +02:00
Mateusz Gruszczyński 9f20d5195e v0.34.0_12 2026-10-18 10:11:16 +02:00
Mateusz Gruszczyński 943d51b528 v0.34.0_11 2026-10-18 09:39:49 +02:00
Mateusz Gruszczyński dd2502dd11 v0.34.0_10 2026-10-18 09:24:03 +02:00
Mateusz Gruszczyński 8c4cbc583f v0.34.0_9 2026-10-18 08:41:39 +02:00
Mateusz Gruszczyński 480c3ec769 v0.34.0_8 2026-10-18 08:26:35 +02:00
Mateusz Gruszczyński b6a3e12225 v0.34.0_7 2026-10-18 07:48:59 +02:00
Mateusz Gruszczyński 47ce1f18c2 v0.34.0_6 2026-10-18 07:23:17 +02:00
Mateusz Gruszczyński 5910e6e901 v0.34.0_4 2026-10-18 07:00:34 +02:00
Mateusz Gruszczyński f20c8da718 v0.34.0_1 2026-10-18 06:41:20 +02:00
Mateusz Gruszczyński 9cfc4588cc v0.34.0_2 2026-10-18 06:13:42 +02:00
Mateusz Gruszczyński 434e789144 v0.34.0_2 2026-10-17 23:37:43 +02:00
Mateusz Gruszczyński 0a8bc6f040 v0.34.0_p1 2026-10-17 22:33:00 +02:00
Mateusz Gruszczyński 00f29f92c8 v0.34.0_1 2026-10-17 20:34:13 +02:00
Mateusz Gruszczyński 9b3d6731cc v0.33.8 2026-10-17 11:57:30 +02:00
Mateusz Gruszczyński 87c8393d86 v0.33.8 2026-10-17 11:57:16 +02:00
538 changed files with 53088 additions and 36658 deletions

No files matched your search

+2
View File
@@ -9,3 +9,5 @@ __pycache__/
problems.txt
todo/
scripts/routeros-oci-build/.env
dist
/*.*
+26 -73
View File
@@ -1,83 +1,36 @@
# RustProxy implementation rules
# RustProxy source and documentation maintenance
Read README.md, ROADMAP.md and docs/TEST_REPORT.md before making changes.
The project name is RustProxy; the Cargo package/binary remains `rust-proxy` for
compatibility. Current source version: 0.33.7.
Current source version: **0.36.9**. The Cargo package and binary are named `rust-proxy`.
## Delivery
Before editing, read [README.md](README.md), the relevant [documentation guide](docs/README.md) and the actual code and tests. Historical release experiments are not current specifications. Use the bundled `src/admin/openapi.json` for API routes and payloads; the source implementation is authoritative for runtime behavior.
Maintain English documentation and Markdown files. Keep translations in discovered langs/*.json packs, with en_US as fallback. Never hardcode a two-language switch. Preserve the existing logo.
CHANGELOG.md uses version headings with no release dates. Patch increments may be
bundled; substantial optional integrations raise the minor version. 0.7.0 adds managed renewal, server deployment helpers, Basic Auth and visual locations. The reported compression threshold type mismatch is removed without narrowing. Cloudflare Tunnel and Docker discovery are deferred.
## Keep documentation in sync
Deliver a complete archive with dev.sh, tests and an updated verification report.
Do not include databases, credentials, real certificates/keys, target directories,
node_modules or fabricated Cargo.lock content. Preserve an actually resolved
lockfile when a compiler-enabled environment can produce it.
- Write product documentation in English. Prefer focused instructions: purpose, configuration, defaults, restrictions, verification and common failures. Keep change history technical but user/operator-facing, not a series of profiling notes.
- Update the guide, [feature list](docs/FEATURES.md), [docs index](docs/README.md), [changelog](CHANGELOG.md), embedded Reader registry (`src/admin/documentation/documents.rs`) and API schema/tests together when functionality changes.
- Reader Markdown is compiled into the binary using `include_str!`. Renaming/deleting a guide without updating the registry breaks compilation. Avoid broken relative links.
- Keep per-component optional defaults accurate (for example COLD off, Prometheus off, backend `tls_verify` off unless explicitly set, and optional Web Server `sendfile` off). Mark source-only or untested changes accordingly.
- Maintain existing `langs/*.json` packs; `build.rs` discovers available locales. Do not add credentials, databases, real certificates, environment secrets or build artifacts to source archives.
## Engineering
## Authentication provider extension contract
Keep proxy traffic independent from control-plane database queries and providers.
Use async I/O, streaming bodies, bounded DNS work, bounded queues and explicit
cancellation. Hold request and connection permits through full body/tunnel lifetimes.
Run ACLs before opening OR reusing an upstream connection. Resolve only configured
backend origins; validate every DNS answer and use the validated addresses directly.
Backend TLS certificate verification defaults OFF when `tls_verify` is omitted for vhost
backends. Administrators may explicitly enable chain/identity verification per HTTPS backend.
This compatibility default is limited to application backends: never widen it to Tinyauth or
other verifier clients. Disabling verification must not weaken backend address/DNS/loop/control-
plane protections.
- Keep provider implementations isolated under `src/<provider>/mod.rs` and `tests.rs`; the request pipeline only sees the object-safe `auth_provider::Provider` trait and the single `CompiledFrontend.auth_provider` slot. Preserve existing Vhost JSON fields on upgrades.
- Prefer the shared `forward_auth::Adapter` transport for compatible HTTP verifiers, and reserve protected-host callback routes through `reserved_path` / `serve_reserved` only when required.
- Return fail-closed provider errors using `auth_provider::gateway_failure` and one `src/admin/web/error_auth_provider.html` template (with escaped provider names); never create a separate HTML file per vendor or expose backend diagnostics to clients. `AuthenticationResponse` prevents custom Vhost error pages from overriding auth errors/redirects, and `Identity` / `CookieSink` keep identities and cookies request-scoped.
- Keep the `authentication` provider/method selector in `auth_provider/choice.rs` separate from the shared OIDC integration transport. Extend the allowed provider/method combinations without duplicating OIDC protocol code. Accept legacy Vhost settings without a selector; when present, reject selectors inconsistent with enabled settings. Update the panel, mutual-exclusion validation, config compiler, OpenAPI, docs and tests together. Prove access/denial, redirects, header stripping, timeouts, and reserved routes with real verifiers before deployment.
Preserve the explicit auth module path and upgrade regression coverage. Replace
managed source directories instead of leaving obsolete modules during upgrades.
Keep administrator intent, provider observations and live connection state separate.
Hot changes must publish validated, immutable snapshots after a successful revisioned
SQLite write. Invalid updates leave the old snapshot active.
## Engineering safeguards
## Verification
- Keep proxy traffic independent of control-plane database/provider calls. Prefer asynchronous streaming, bounded queues, resource limits and explicit cancellation. Hold backend permits for the entire body or tunnel lifetime.
- Run access rules before connecting to or reusing an upstream. Resolve only configured origin hosts; validate resolved addresses before use. Preserve controls against loopback/private/control-plane destinations unless explicitly authorized.
- Backend TLS verification's compatibility default must not weaken certificate verification for external authentication/integration clients. Preserve validation of Host, listener, authority, forwarded identity, IP/ACL and control-plane access.
- Vhosts bind to named frontends via listener IDs; never derive listener identity from spoofable forwarding headers. Listener and transport changes can require a restart. Health status is observation, not desired configuration.
- Keep HOT/COLD cache privacy, freshness, validators, scoped invalidation and persistent format compatibility. Do not expose cached private content across hosts, credentials, cookies, or `Vary` dimensions.
- Preserve administrator session, CSRF, Host/Origin, certificate-scoped credentials and the authenticated offline documentation/OpenAPI assets. Do not expose private API routes without updating access tests.
- Preserve safe deployment/upgrade behavior: stop or reject incompatible legacy layouts, validate changes before activation, respect data/secrets outside the source tree, and keep full encrypted recovery usable.
Keep production Rust source files within 500 lines. Rust unit and integration
**test files are exempt** from this limit: do not create numbered `part_XX.rs`
fragments to satisfy source-size checks. Consolidate coherent test suites in
`tests.rs` and preserve the existing test cases.
Run `./dev.sh check` and `./dev.sh test` when Rust is available. Run the SQL and upgrade
scripts even when it is not. UI tests use a simulated API; the Python protocol-peer
self-test is not a test of RustProxy. Record these distinctions accurately.
Do not mark a stage accepted or production-ready on the basis of static inspection.
Review real protocol behavior, restored data and resource bounds before expanding
scope. Do not expand provider work ahead of the core real-runtime acceptance gate. Keep that gate explicitly open until Rust runs.
## Verification and delivery
## Vhost invariants
Use the accepted socket's actual local address and TLS state for frontend selection;
never infer a private binding or sticky identity from forwarding headers. Vhosts receive
frontend sockets only through `frontend.listener_ids` referencing `listeners.endpoints`.
Keep hostname ownership unambiguous. Socket/ALPN changes require restart, with actual
and desired plans visible.
Keep health observations separate from desired configuration. Preserve observations
only for compatible members; re-enabled/changed origins require new health checks.
Keep backend leases through request/response/tunnel completion. Do not replay failed
writes to another backend. Cookies are routing affinity, never authentication. Health
probes must use the same origin/DNS policy, bounded concurrency and cancellation.
Retain HTTP/1.1 in TLS Auto negotiation; require H2 in explicit HTTPS H2 mode,
and do not implement fallback by replaying traffic.
## Vhost configuration invariants
The pre-production route/inline-listener compatibility importer has been removed.
Persist only the current vhost document and named listener references; old documents
must fail validation instead of being silently converted. Keep path-pinned origins
distinct from balanced pools. Do not silently widen listener bindings.
ACME secrets belong in private credentials files, not SQLite, stdout, argv or API.
HTTP-01 only serves bounded tokens for domains in the active job. No CA call on boot.
The exporter checks actual peer CIDRs and is disabled by default; secret configuration
stores a variable name, never a token. Keep metric labels bounded by configuration.
## Administration access regression gate (0.8.1)
No new public admin routes without updating the exact method/path allowlist and
access tests. Operational assets and the OpenAPI schema are private. Keep the
public login document/bundle separate from the dashboard. Documentation uses
the existing cookie session/CSRF, not a second auth mechanism. Preserve the pinned
Swagger build configuration and keep external validators/CDNs disabled.
- Run `python3 scripts/preflight.py` and documentation/link checks after restructuring documentation. When Rust is available, run `./dev.sh check` and `./dev.sh test`; run platform/protocol and restore tests before claiming production readiness.
- Python mock origins or static source inspection do not constitute testing of the real RustProxy binary. Record verification limits; do not claim measured performance gains from source changes.
- Ship complete reproducible source archives with `make_zip.py`, `SHA256SUMS`, the application scripts, current tests and docs. Never ship live state, private keys or generated build directories. Keep the technical deep-history backup outside the authenticated documentation Reader.
+644 -1253
View File
File diff suppressed because it is too large. Load diff
Generated
+67 -1
View File
@@ -202,6 +202,29 @@ version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "aws-lc-rs"
version = "1.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b281d307588d634de920874890732659e2e7672f72b5e10e81badc1a8a83621e"
dependencies = [
"aws-lc-sys",
"zeroize",
]
[[package]]
name = "aws-lc-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bff6c3b54fad79a2e60b8102caf565819711497c1f5f092f49508e2f5c31b27"
dependencies = [
"cc",
"cmake",
"dunce",
"fs_extra",
"pkg-config",
]
[[package]]
name = "axum"
version = "0.8.9"
@@ -488,6 +511,15 @@ version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486"
[[package]]
name = "cmake"
version = "0.1.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
dependencies = [
"cc",
]
[[package]]
name = "cmov"
version = "0.5.4"
@@ -669,6 +701,12 @@ version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
[[package]]
name = "dunce"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
[[package]]
name = "either"
version = "1.18.0"
@@ -769,6 +807,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "fs_extra"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
[[package]]
name = "futures-channel"
version = "0.3.34"
@@ -1286,6 +1330,15 @@ dependencies = [
"windows-link",
]
[[package]]
name = "libmimalloc-sys"
version = "0.1.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a45a52f43e1c16f667ccfe4dd8c85b7f7c204fd5e3bf46c5b0db9a5c3c0b8e9"
dependencies = [
"cc",
]
[[package]]
name = "libproc"
version = "0.14.11"
@@ -1383,6 +1436,15 @@ dependencies = [
"windows",
]
[[package]]
name = "mimalloc"
version = "0.1.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d4139bb28d14ad1facf21d5eb8825051b326e172d216b39f6d31df53cc97862"
dependencies = [
"libmimalloc-sys",
]
[[package]]
name = "mime"
version = "0.3.17"
@@ -1881,7 +1943,7 @@ dependencies = [
[[package]]
name = "rust-proxy"
version = "0.33.7"
version = "0.36.9"
dependencies = [
"anyhow",
"arc-swap",
@@ -1905,6 +1967,7 @@ dependencies = [
"ipnet",
"libc",
"metrics-process",
"mimalloc",
"quinn",
"rand 0.10.3",
"rcgen",
@@ -1917,6 +1980,7 @@ dependencies = [
"serde_json",
"sha1",
"sha2",
"socket2",
"sqlx",
"subtle",
"sysinfo",
@@ -1969,6 +2033,7 @@ version = "0.23.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634"
dependencies = [
"aws-lc-rs",
"once_cell",
"ring",
"rustls-pki-types",
@@ -2002,6 +2067,7 @@ version = "0.103.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2"
dependencies = [
"aws-lc-rs",
"ring",
"rustls-pki-types",
"untrusted",
+13 -3
View File
@@ -1,9 +1,13 @@
[package]
name = "rust-proxy"
version = "0.33.7"
version = "0.36.9"
edition = "2021"
rust-version = "1.99"
rust-version = "1.99.1"
description = "RustProxy - independent reverse proxy with local administration"
authors = ["Mateusz Gruszczyński"]
license = "AGPL-3.0-or-later"
homepage = "https://rustproxy.linuxiarz.pl/"
repository = "https://git.linuxiarz.pl/gru/RustProxy"
publish = false
[dependencies]
@@ -13,6 +17,7 @@ ipnet = "2.11"
rpassword = "7.4"
zeroize = "1.8"
anyhow = "1.0"
mimalloc = { version = "0.1", default-features = false, optional = true }
libc = "0.2"
arc-swap = "1.7"
axum = { version = "0.8.9", default-features = false, features = ["http1", "http2", "json", "query", "tokio", "ws"] }
@@ -40,7 +45,7 @@ url = "2.5"
base64 = "0.22"
sha1 = "0.10"
rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] }
rustls = { version = "0.23", default-features = false, features = ["ring", "aws_lc_rs", "std", "tls12"] }
ring = "0.17"
tokio-rustls = { version = "0.26", default-features = false, features = ["ring", "tls12"] }
hyper-rustls = { version = "0.27", default-features = false, features = ["ring", "http1", "http2", "tls12"] }
@@ -56,6 +61,7 @@ zstd = "0.14"
h3 = { package = "hpx-h3", version = "=2.5.20", features = ["quinn"] }
quinn = { version = "0.11.12", default-features = false, features = ["runtime-tokio", "rustls-ring"] }
socket2 = "0.6.5"
metrics-process = { version = "=2.4.3", default-features = false, features = ["dummy"] }
sysinfo = { version = "0.39", default-features = false, features = ["network", "system"] }
@@ -66,6 +72,10 @@ systemstat = "=0.2.7"
# Swagger assets are prepared by build.rs and embedded behind the admin guard.
# No adapter build script, implicit version fallback or Cargo-config dependency.
[features]
default = []
mimalloc = ["dep:mimalloc"]
[build-dependencies]
serde_json = "1.0"
+661
View File
@@ -0,0 +1,661 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.
+54 -997
View File
File diff suppressed because it is too large. Load diff
-667
View File
@@ -1,667 +0,0 @@
# RustProxy roadmap
## 0.33.7 - named dashboard presets, focused HTML extraction and chart typography
- [x] Apply uniform 12px sentence-case labels to all Overview widgets,
including Process, RAM cache and backend tiles. Normalize secondary
captions and small values to monospace without changing other panels.
- [x] Space the two Cache table cards by 10px without altering other sections.
- [x] Reuse the backend quick-view popover for clickable Vhost upstream H1/H2
protocol details; keep it stable during repeated live status refreshes.
- [x] Keep the clickable Backend and Health fields square, visually matching
adjacent Vhost tiles under every interface corner style.
- [x] Standardize the full legend text (including every value) at 9px monospace,
matching Requests per second across desktop, mobile and density modes.
- [x] Add a compact, nonmodal Vhost backend quick-view through both backend
count and health metric; use existing configuration and status fields, with
accurate unknown/last HTTP status handling and accessible dismissal.
- [x] Introduce account-scoped, bounded SQLite storage for 16 named Overview
layout snapshots without changing proxy configuration or existing preferences.
- [x] Replace Arrange Overview modal with save/load/overwrite/rename/delete,
and factory reset; keep Unlock layout editing intact.
- [x] Extract metrics and charts only into compile-time embedded HTML partials
without changing existing card DOM order, IDs or polling.
- [x] Standardize chart headings on 12px proportional type (Overview in
sentence case, all other panels uppercase); use mono for full legends,
captions and chart data across all chart families.
- [x] Validate SQL migration and run available simulated-browser/JS tests.
- [ ] Build/test Rust and run the new endpoint/UI against a real server with
representative persisted data. The production acceptance gate remains open.
## 0.33.6 - Stable dashboard chart ranges and plot heights
- [x] Fix all 14 Overview/WWW full-width SVG heights to 125px and align their
grid lines with the actual drawing range.
- [x] Apply zero-based, unit-specific scale floors to transform/compression,
rate, connection, latency, memory and WWW series; retain meaningful high
readings with stepped-up ceilings and no scale collapse on low samples.
- [x] Keep CPU/other percentage sparklines on 0-100% without changing legends.
- [x] Fix full Cache/Transform live charts at 180px and stabilize their rate
axes, including scope-change resets.
- [x] Test Chromium layout/scaling across 320-1920px and run available UI,
schema and source tests. Rust runtime acceptance remains pending.
## 0.33.5 - Fixed Overview CPU chart scale
- [x] Set System and process CPU to an absolute 0–100% axis, independent of
history, while keeping real percentage labels and both plotted series.
- [x] Fix the CPU plot height at 125px regardless of viewport width or
measured values; leave other chart scales and sizes unchanged.
- [x] Check stable CPU chart geometry, percent mapping, clipping and labels
in a simulated browser across viewport widths and Dashboard densities.
- [x] Replace the Overview Requests per second LIVE badge with its live maximum
rate, remove the LIVE TRAFFIC eyebrow and the duplicate peak footer entry.
- [x] Remove Connection Activity's LIVE badge, preserving its metrics.
- [x] Browser-test the two card headings, dynamic peak update and responsive
placement across viewport widths, dashboard densities and card spans.
## 0.33.4 - Web Server/Vhost live-metric visual parity
- [x] Show per-Web-Service live metrics without expanding a section.
- [x] Reuse Vhost metric tiles and the full-width traffic chart with the same
desktop/mobile layout, chart styling and theme-aware appearance.
- [x] Preserve counter sampling, PHP fallback, existing translations and the
existing status feed without new API or persistence state.
- [x] Add simulated Chromium layout, rendering and sampled-status regressions.
## 0.33.3 - Adjustable Overview card widths and cross-type ordering
- [x] Apply Appearance **theme** highlighting and corner radius to sidebar
navigation, including mobile layout and accessible contrast on light themes;
keep menu colors independent of the chart palette.
- [x] Test navigation colors in Chromium across 4 themes and 8 chart palettes
(invariance within each theme), 5 corner settings, hover, mobile and RTL.
- [x] Keep the visual layout editor readable at 320-1920px with stable
three/two/one-column previews, wrapping controls and no horizontal overflow;
leave actual dashboard widths and free ordering unchanged.
- [x] Stretch plotted SVG history across the actual card width when resizing,
including dynamic network-interface plots, without stretching donut charts.
- [x] Allow widths of 2, 3, 4, 6 and 12/12 columns in inline and visual editors;
keep the smallest text/sparkline cards expandable and add a 4-chart desktop row.
- [x] Allow free card placement across former size groups, including at the
top of Overview, and reduce unused row gaps with CSS dense packing.
- [x] Preserve legacy grouped layouts until edited, retain saved hidden/order
preferences, and support cancel, save, modal preview and reset.
- [x] Add browser coverage for real move/size clicks and responsive layouts.
## 0.33.2 - Shared progressbar visual settings
- [x] Apply Appearance corner rounding to native Cache progressbars and
custom Overview, WWW and traffic bars with matching track/fill shapes.
- [x] Apply the chosen chart color palette to Cache progressbars and other
progress fills; retain theme-aware tracks, without new API/status work.
- [x] Unify the System Load RAM meter with other Overview progressbars,
retaining the 5px height and accessible percentage updates.
- [x] Verify live CSS changes with Chromium across corners, chart palettes,
light/dark themes and mobile/desktop viewport widths.
## 0.33.1 - Factory dashboard preset and inline live preview
- [x] Ship the operator-supplied default ordering, hidden cards and compact density
in one shared Rust/UI JSON preset; use 600 as the first-use history sample default.
- [x] Keep all existing explicitly saved preferences and restore the factory preset
only for new accounts or an explicit dashboard reset.
- [x] Honor unsaved inline hide/show changes while backend and Stream status refresh;
preserve the draft across unrelated preference updates.
## 0.33.0 - Web Server observability (implementation complete; live acceptance pending)
- [x] Add one Overview 60/120/300/600 history selector on the connection-status header row, with temporary changes and explicit browser-persistent choice. Use the same source stream and leave the Cache and Transform selectors independent.
- [x] Match the Web Server metric method visibility to `ResponseKind` (no `private_interfaces` warning).
- [x] Align donut totals beside the ring, preserving the legend and compact tile heights.
- [x] Add opt-in Web Server Overview cards, with all new metrics hidden by default and per-service drilldown.
- [x] Extend Web Server/PHP diagnostics and bounded Prometheus metric families without untrusted labels or additional request-path database calls.
- [x] Extend the packaged Grafana operations dashboard with Web Server/PHP panels and per-service filters.
## 0.32.8 - Overview action alignment (runtime acceptance pending)
- [x] Convert bounded cache RAM/queue/disk, file descriptor, HTTP protocol share and backend capacity cards into meter/progress-bar visuals, with clear unknown-limit behavior.
- [x] Replace cache hit ratio sparklines with compact HIT/MISS donut visualizations and legends.
- [x] Add large transformation/compression four-series live charts and Polish/English labels, preserving existing status sampling and layout/editor preferences.
- [x] Add deterministic UI metric tests and Chromium responsive-layout regression checks.
- [x] Replace mobile three-column Overview actions with natural wrapping: keep the heading at left, actions to the right, without shrinking buttons.
- [x] Remove redundant button-only classes and outdated small-screen overrides while preserving existing common button variants, layout controls and editor handlers.
## 0.32.7 development follow-up: Appearance and H2-close diagnostics
- [x] Use common button sizing for Overview quick actions and Appearance visual editor/show-all; preserve the normal and compact global button styles.
- [x] Strengthen the distinction between Dashboard density levels for tiles, charts and non-table panels without changing grid gutters or preferences.
- [x] Lower only correlated graceful H2 close terminal diagnostics to TRACE; keep 502/access details and uncorrelated cancellations as errors.
- [x] Verify the UI sizes with a simulated Chromium browser at desktop and phone widths.
## 0.32.7 - Mobile Overview and upstream protocol visibility (runtime acceptance pending)
- [x] Share counter-rate pause/resume handling between Cache, Transform,
Overview, backend, Stream and Vhost plots. Retain historical points after
backgrounding, skip suspended intervals and restart normal samples after
one fresh counter baseline, with no new polling.
- [x] Development follow-up: preserve Network diagnostic chart DOM through
periodic refresh and fix descendant CSS selector minification.
- [x] Feed Process CPU/RSS and per-interface network charts from the cached
background OS sampler through live status (default 3 s). Leave slow Process
details on `system_info_seconds` and retain valid 2-s saved preferences; do
not add per-request probes. Group upstream badges with other Vhost badges
and match their corner radius while preserving observed protocol colors.
- [x] On phone-size Overview, remove the nested Network interface chart scroll
region, while retaining chart SVG/text nodes on ordinary live updates.
## 0.32.6
- [x] Reinstate two small Overview cards per row across phone-size widths.
- [x] Apply global corner styles to additional vhost/entity panel surfaces.
- [x] Avoid reparenting already-ordered Overview nodes; retain natural touch
scroll and stable values/row boundaries during live updates.
- [x] Include last HTTP protocol observed from real proxy upstream responses
in the authenticated status and each Vhost's per-backend badge. Show unknown
when no fresh observation exists instead of guessing Auto's ALPN outcome.
- [x] Add simulated mobile Chromium and mocked status UI regressions plus a
Rust unit regression for protocol-version observation.
## 0.32.5 - Dynamic DNS client ACLs (runtime acceptance pending)
- [x] Preserve CIDR/IP rules and introduce allow-only FQDNs for client ACLs.
- [x] Add bounded A/AAAA periodic resolver, immutable cache, expiration and
immediate refresh on published configuration changes.
- [x] Add DNS/DDNS editor selector, PL/EN guidance and source/regression tests.
## 0.32.4 - Upstream Host/authority parity and Overview gutters (runtime acceptance pending)
- [x] Remove `preserve_host` H1-only validation and the Auto/H2 UI forced-uncheck.
- [x] Align outgoing H2 authority with selected Host while keeping the origin DNS,
ACL, TCP destination, TLS SNI and certificate verification independent.
- [x] Update HTTPS-by-IP guidance; preserve opt-in diagnostic H1 preset.
- [x] Make Overview small-to-large row gutter consistent across density modes.
- [x] Add protocol/authority and UI/source contracts; preserve WebSocket isolation.
## 0.32.3 - Backward-compatible upstream modes and Overview Cards registry (runtime acceptance pending)
- [x] Replace ragged custom Overview masonry with symmetric CSS Grid rows grouped by widget footprint. Equal-height peers, traditional gaps, responsive columns and live-content growth are verified in Chromium. Block dragging remains supported inside its size group; group boundaries prevent visually inconsistent mixing. Registry (62), preferences and version stay unchanged.
- [x] Snap manually rearranged Overview columns to common pixel-aligned coordinates and align the bottoms of a tall panel and its stacked short neighbors where bounded distribution allows. Keep factory rows, saved widget IDs/visibility, spacing controls and narrow-screen layout unchanged. Verify with a Chromium fixture, including dynamic height, density and zoom.
- [x] Remove numeric/chart type badges from the Settings → Appearance Overview list, retaining them in the actual visual editor; keep right-aligned controls.
- [x] Open Appearance first unless Settings has a remembered last tab; honor explicit URL tabs and legacy configuration/logging links.
- [x] Decouple roundness from the color palette through a backward-compatible, account-scoped five-preset global `corner_style`. Keep original corners as the default for all four themes; restore coral/green logo marks in Ultra Dark.
- [x] Preserve original symmetric and density-aware Overview rows for the default configuration. When blocks are manually reordered on desktop, keep the same column widths/gutters while allowing multiple natural-height charts to stack next to tall diagnostics; use CSS-layout-pixel row spans for zoom safety and fall back to classic rows on narrow canvases.
- [x] Exclude the RustProxy logo from global corner style overrides. Keep unlocked Overview × Hide and arrow controls consistent under all five presets, without changing color themes or account data.
- [x] Add 6 small and 7 large opt-in time-series views (62 registered blocks total), reusing existing status updates, bounded samples and version 0.32.3. Place block metadata before right-anchored editing controls.
- [x] Preserve legacy `http2`: HTTPS ALPN H2/H1 fallback; HTTP H2C. Add a separate explicit `h2` mode requiring H2 over TLS / H2C over plaintext. Retain H1/Auto and isolate WebSocket clients.
- [x] Retain one safe Auto ALPN negotiation before the first request, without H1 retry after a potentially executed operation.
- [x] Establish a shared JSON registry for Overview IDs/metadata, server validation, ordered visibility settings and compatibility with unknown saved widget IDs.
- [x] Add fourteen opt-in HTTP origin monitoring cards, including request-rate, capacity, 5xx, timeout, network errors, retries, queue pressure and client-pool recycling; reuse existing status counters without extra polling. Retain the original 16 and existing account preferences.
- [x] Add an authenticated Overview modal for draggable visual card ordering, keyboard move, hide/show catalog, draft cancel and a single preferences PUT for Save. Keep existing Appearance controls and defaults.
- [x] Add opt-in AMOLED **Ultra dark** and muted **Warm cream** palettes with translated selectors, login-cache support, server-side preference validation and high-contrast palette checks. Preserve current dark/light themes, default and schema version.
- [x] Let administrators hide/remove visible Overview blocks directly while unlocked, including panels and tables; recover them from a compact hidden-block shelf in the same draft and persist visibility only after Save.
- [x] Add a direct Overview **Unlock layout** mode: drag actual blocks by dedicated grips, move with arrows, cancel without persisting, and save+lock in one request. Classify and label all 49 blocks by display type, including mixed number+chart cards, with a hidden-block type filter.
- [x] Expand to a shared 49-block Overview grid (30 prior widgets, 12 new opt-in charts and seven original full-size sections). Add visual default-layout preview, confirmed reset and account preference compatibility without new metrics probes or API routes.
- [x] Verify ALPN is already negotiated on connection establishment, then reused by Hyper per-backend pooling. Retain age/request-count generation rotation and new-connection negotiation rather than introduce a global H2/H1 cache or unsafe protocol-retry probe.
- [x] Present all four modes with clear HTTPS ALPN versus h2c semantics, keeping persisted `http2` records valid with unchanged meaning.
- [x] Add source/JS checks and Rust tests for strict ALPN, old TLS H1 fallback, H2C, WebSocket isolation and widget preferences.
- [x] Resolve the 0.32.2 HTTPS `http2` compatibility regression with a new serialized strict `h2` mode rather than a silent migration.
## 0.32.2 - Web Server conditional cache revalidation (runtime acceptance pending)
- [x] Reuse local Web Server `ETag` / `Last-Modified` revalidation candidates for forced browser refresh and stale/freshness-constrained requests, keeping hit counts and cache bodies on `304`.
- [x] Preserve client conditional semantics, HEAD, range, response policy and transformed representations; invalidate superseded objects.
- [x] Add Rust regression cases and source-only checks.
## 0.32.1 - Dashboard Overview typography and tile height (runtime acceptance pending)
- Restore content-driven heights for desktop Overview tiles (six columns) seen in 0.31.9, removing 0.32.0's fixed-height whitespace at 90% scale.
- Increase compact Dashboard headings, values and secondary descriptions, including the fixed-size Payload legend. Align tile gaps with the same `--ui-gap` as other dashboard blocks for all dashboard density settings.
- Keep opt-in Streams, all responsive breakpoints and chart legends visible inline. Add layout-regression checks. No proxy or persistence changes. Rust/Cargo and authenticated-UI runtime acceptance are still outstanding.
## 0.32.0 - Stream backend pools and opt-in Dashboard cards (runtime acceptance pending)
- [x] Extend legacy TCP/UDP upstream with an optional validated backend pool, Round Robin/Least Sessions, session pinning, and bounded concurrency.
- [x] Add bounded TCP Connect / verified UDP Probe health monitoring with rise/fall hysteresis.
- [x] Add opt-in, bounded backend metrics and reuse the existing Prometheus stream gate and API status.
- [x] Add four translated opt-in Dashboard cards through existing Appearance preferences and common status refresh.
## 0.31.9 - Certificate API credential management (runtime acceptance pending)
- [x] Inspect individual saved Certificate API credential metadata without reading its bearer secret.
- [x] Edit comment, explicit certificate allowlist and deploy grant independently of token rotation.
- [x] Regenerate only a selected credential's token while keeping Key ID, comment and grants, invalidating the old secret and showing the replacement only once.
- [x] Preserve existing scopes, administrator-only access, audit trail, schema 9 and backwards-compatible creation/revocation endpoints.
- [ ] Compile and execute Rust regression tests with Rust 1.99+; verify end-to-end panel flows on a real server before production acceptance.
## 0.31.8 - Stored Logs sorting, built-in pages and allocation reduction (runtime acceptance pending)
- [x] Consolidate 28 numbered Rust test fragments into seven large `tests.rs` files; exempt test sources from the 500-line production limit and remove obsolete fragment paths.
- [x] Sort Stored Logs across the bounded server-side scan before pagination, including dynamic columns and durable account preferences.
- [x] Complete the built-in status response pages in the existing visual style, including 200 OK.
- [x] Pack GeoIP IPv6 records and remove unused CSV Vec capacity without changing lookup results.
- [x] Add bounded zero-copy transformed-cache fill where exclusive ownership and exact allocation capacity are available.
- [x] Correct Docker VHosts post-restart pending/failed status, first-read synchronization and TLS pin indicator, without changing poll/TLS/SQLite protocols.
## 0.31.7 - UA-specific throttling and managed update housekeeping (runtime acceptance pending)
- [x] Group global UA blocking, headers and response controls in a tabbed Settings page.
- [x] Add per-vhost UA rate-limit overrides with separate bounded counters.
- [x] Show leaf-level revision diffs, with optional full JSON comparison dialog.
- [x] Space apart listener protocol buttons and collapse ACL attachments by default.
- [x] Fix release retention path deletion; preserve two releases; limit Cargo cleanup to expired incremental state.
- [x] Verify update marker and forced HOT→COLD shutdown code path, and add completion logging.
- [ ] Compile and test with Rust/Cargo; run real update/rollback and browser/UI tests on a controlled host.
## 0.31.6 - H3 iOS navigation hardening (runtime acceptance pending)
- Finish live Safari/Chrome iPhone logo-navigation and repeated-refresh acceptance over H3 after Rust compilation.
- Collect matching `http3_*` diagnostic records and navigation/asset response records; verify the new top-level GET is not interrupted when old SSE/API streams cancel.
## 0.31.5 - User-Agent blocking and consolidated log analysis
- [x] Add bounded global and per-vhost User-Agent blocklists and enforce them before upstream contact.
- [x] Add one-click copy for Traffic values used during investigation.
- [x] Add repeated-value aggregation for arbitrary parsed Stored Logs fields.
- [x] Consolidate active and rotated gzip per-vhost logs into one logical source on demand.
- [x] Update OpenAPI, translations, documentation and Rust-independent regression checks.
## 0.31.4 - Administration-only public HTTPS Origin with one-field configuration
- [x] Add explicit HTTPS `administration.public_origin` and restrict its use to trusted reverse-proxy peers for panel requests.
- [x] Derive the pinned DNS hostname for the effective Host allowlist without duplicating it in saved `allowed_hosts`.
- [x] Keep additional allowed DNS hostnames in an advanced panel field and remove redundant legacy duplicates on edit/save.
- [x] Preserve strict Origin, CSRF, trusted proxy, forwarded Host and HTTPS-first header checks and the opt-in self-admin session-cookie boundary.
- [x] Add panel settings UI, translations, API schema, Rust source regressions and Node simulations without changing public proxy forwarding.
## 0.31.3 - Mobile session recovery and Origin diagnostics
- [x] Reproduce the overlapping resume-session checks, reload-loop marker reset and early SSE restart using the 0.31.2 source in a deterministic JavaScript simulation.
- [x] Coalesce page visibility/BFCache session verification, defer background requests until revalidated and offer a visible retry on failure without weakening Origin enforcement.
- [x] Display a safe 403 HTML message for invalid-Origin dashboard navigation and provide credential-redacted rejection diagnostics.
## 0.31.2 - Dependency updates
- [x] Prepare stable dependency-family and code API migrations, preserving existing wire/configuration/SQLite formats.
## 0.31.1 - Mobile panel loading stability
- [x] Load dashboard modules as one ordered JavaScript resource and wait for document/module readiness before session initialization.
- [x] Version dashboard JS/CSS requests; show a recoverable startup error on missing modules.
## 0.31.0 - Panel refresh orchestration
- [x] Centralize authenticated polling with source IDs, view lifecycle, hidden-tab pause, abort, in-flight protection and cleanup.
- [x] Separate compact live telemetry from infrequent cached system diagnostics; allow per-client SSE pacing and reconnect-only fallback.
- [x] Add per-account refresh preferences, reset/manual actions, language packs, endpoint contracts and regression tests.
## 0.30.1 - Protocol legend and system information
- [x] Color the H1/H2/H3 legend dots consistently with their themed chart strokes.
- [x] Remove ACME/Python runtime details from About and provide a read-only System information settings tab.
- [x] Read available ACME Python/pip/Certbot version metadata and OS/hardware/process details with existing libraries, without subprocesses for information gathering or new configuration fields.
## 0.30.0 - Per-backend Health editor and logging resource review
- [x] Provide an intuitive Health-tab backend selector restricted to monitored members, with independently editable TCP/HTTP policies.
- [x] Deduplicate file/Logstash JSONL serialization; cap Stored Logs directory scan retention during enumeration; review long-lived queues and other memory-heavy facilities.
- [x] Preserve vhost wire schema, SQLite schema 9 and Cold Cache format 3.
## 0.29.7 - Backend health scoping, PHP-FPM isolation and HIT-only cache diagnostics
- [x] Support per-backend active/passive health monitoring disablement and independent health-check settings.
- [x] Expose optional global and vhost-level cache HIT-only diagnostic headers.
- [x] Identify unsafe PHP-FPM shared-socket directive differences and document dedicated pool migration for Roundcube/WordPress.
- [x] Preserve existing settings and schema 9/Cold format 3.
## 0.29.6 - FastCGI compression EOF and diagnostic routing
- [x] Fuse FastCGI response streams so compressors may safely probe the end of a PHP-FPM response multiple times.
- [x] Route WARN/ERROR diagnostics and panic-hook events to the configured error file, or stderr when the file is disabled/unavailable.
- [x] Add source tests for repeated EOF polling, gzip output, and live error-log settings changes.
- [x] Retain schema 9, Cold Cache format 3 and 0.29.x saved-configuration compatibility.
## 0.29.5 - Roundcube PHP PATH_INFO routing
- Resolve PHP `PATH_INFO` on an existing script before `try_files` falls back to the front controller, while preserving normal URL and script security checks.
- Verify FastCGI parameters from an actual mock PHP-FPM exchange in Rust integration tests when Cargo is available.
- Retain schema 9, Cold Cache format 3 and all previous PHP-FPM profile UI and vhost-cache behavior.
## 0.29.4 - Shared PHP-FPM profiles and local Web Server processing
Current direction: 0.29.3 -> 0.29.4
- [x] Add reusable PHP-FPM profiles attachable to individual Web Services, keeping their local settings on detach.
- [x] Apply RustProxy cache and response transformations for Web Server files and PHP-FPM responses.
- [x] Supply FastCGI `HTTP_HOST` from request authority for HTTP/2 and HTTP/3 WordPress requests.
- [x] Add source/UI regression coverage for new configuration and response behavior.
- [x] Keep SQLite schema 9 and Cold Cache format 3 unchanged.
## 0.29.3 - HTTP/2 and HTTP/3 internal fallback stabilization
Current direction: 0.29.2 -> 0.29.3
- [x] Allow empty GET/HEAD bodies with unknown length hints to enter the internal error fallback path.
- [x] Preserve the final internal fallback response, including the fallback backend's HTML on HTTP 404.
- [x] Add Rust regression tests for fallback dispatch, request-body safety and error-page precedence.
- [x] Preserve SQLite schema 9 and Cold Cache format 3 without configuration migrations.
## 0.29.2 - PHP-FPM operator controls and observability
- [x] Add bounded, operator-only PHP_ADMIN_VALUE INI overrides per Web Service.
- [x] Show admin overrides in the Web Service UI/API, reusing existing location-to-service routing.
- [x] Track PHP responses by status class, response-header latency and free concurrency slots in Prometheus.
## 0.29.1 - Internal error routing, multipart ranges and PHP PATH_INFO
Current direction: 0.29.0 -> 0.29.1
- [x] Add configurable internal error fallbacks for the main vhost source and Custom locations.
- [x] Preserve original method, URI, query string and request context during internal fallback dispatch.
- [x] Reject fallback cycles at validation time and cap runtime fallback depth.
- [x] Intercept configured errors before final response/cache handling and run the target through normal route processing.
- [x] Add bounded Prometheus counters for triggered, successful and failed error fallbacks.
- [x] Support fixed fallback status codes 403/404/500/502/503/504.
- [x] Add streaming multipart byte-range responses with a bounded range count.
- [x] Add opt-in secure PHP PATH_INFO splitting for PHP-FPM.
- [x] Keep SQLite schema 9 and Cold Cache format 3 unchanged.
## 0.29.0 - Redirect, health-host, forced-cache and rotated-log controls
Current direction: 0.28.2 -> 0.29.0
- [x] Add one-checkbox canonical www/non-www redirects per vhost.
- [x] Add first-class Custom location redirects with capture-based rewrite targets and query preservation.
- [x] Default HTTP health-check Host to the first exact vhost hostname while keeping the manual field as an override.
- [x] Add explicit per-vhost forced-cache targets with response privacy protections retained.
- [x] Read bounded logrotate gzip rotations in Stored Logs.
- [x] Verify local/static HTML pages can intentionally return statuses such as 503.
- [x] Preserve valid nested URL query parameters while retaining ambiguous-backslash rejection.
- [x] Keep SQLite schema 9 and Cold Cache format 3 unchanged.
## 0.28.2 - External certificate hardening and least-privilege reload API
Current direction: 0.28.1 -> 0.28.2
- [x] Accept POSIX ACL-based read access for external certificate keys without requiring one exact chmod value.
- [x] Reject unsafe world-readable private keys, group/world-writable external PEMs and untrusted/writable external-path parent directories.
- [x] Validate both configured Certbot `live/` paths and their resolved `archive/` targets while keeping hot reload behavior.
- [x] Scope Certificate API credentials to explicit certificate IDs, default new tokens to reload-only, block global reload for tokens and make deploy an explicit permission.
- [x] Migrate existing Certificate API credentials to reload-only compatibility grants and move SQLite to schema 9.
- [x] Document read-only systemd access plus POSIX ACL examples for `/etc/letsencrypt` and `/etc/ssl` in English and Polish.
- [x] Keep the saved proxy configuration format and COLD format 3 unchanged.
## 0.28.1 - External certificate paths and certificate-source clarity
Current direction: 0.28.0 -> 0.28.1
- [x] Allow manual certificate entries to reference an absolute external certificate path and absolute external private-key path.
- [x] Resolve Certbot-style `live/` symlinks on every reload while preserving bounded PEM parsing, regular-file checks and private-key permission checks.
- [x] Keep targeted certificate reload revision-stable so renewal wrappers can call `POST /api/certificates/{certificate_id}/reload` after renewing external files.
- [x] Preserve managed `<data-dir>/certs` filenames and reject mixed managed/external pairs.
- [x] Exclude external certificate material from RustProxy backups while retaining the configured absolute paths.
- [x] Clarify certificate-source, storage and path expectations in English/Polish UI text and operator documentation.
- [x] Keep SQLite schema 8 and COLD format 3 unchanged.
## 0.28.0 - GeoIP control, listener clarity and cache policy refinement
Current direction: 0.27.3 -> 0.28.0
- [x] Add on-demand GeoIP refresh through the authenticated administration UI and reuse the automatic updater pipeline.
- [x] Hot-swap validated GeoIP databases so subsequent country ACL/rate-limit/logging lookups use the new data without a process restart.
- [x] Use direct GeoIP file URLs with optional per-file SHA-256 verification and support either separate IPv4/IPv6 CSV ranges or one native MaxMind MMDB database.
- [x] Present active listeners as readable HTTP/HTTPS endpoint cards with explicit H3/UDP state.
- [x] Add configurable `Via: RustProxy` cache response metadata while preserving upstream Via values.
- [x] Preserve cache objects across bypass and response-only diagnostic/Via setting changes.
- [x] Separate vhost Security header-rule Delete controls from the Operation selector.
- [x] Keep SQLite schema 8 and COLD format 3 unchanged.
## 0.27.3 - HTTP/3 cache parity and bounded memory retention
Current direction: 0.27.2 -> 0.27.3
- [x] Make body-less `HEAD` cache eligibility consistent between HTTP/2 and HTTP/3.
- [x] Reduce persistent COLD-index RAM while preserving the existing on-disk cache format and cache behavior.
- [x] Reclaim expired rate-limit client buckets and avoid duplicating the vhost name for every tracked IP.
- [x] Confirm downstream compression remains streaming and does not retain complete response bodies in memory.
- [x] Exclude rebuildable managed Certbot Python environments from backups.
- [x] Document DEB/RPM and OPNsense repositories as the official installation paths; keep Debian source builds optional.
- [x] Separate application-coupled scripts from QA/dev/audit tooling and external platform integrations while preserving all entry points.
- [x] Keep SQLite schema 8 and saved configuration compatibility with 0.27.2.
## 0.27.2 - Managed ACME renewal repair
Current direction: 0.27.1 -> 0.27.2
- Make the managed Certbot runtime independent of pip console-script shebang relocation.
- Reuse existing 0.27.1 `acme-python` environments without operator cleanup.
- Preserve the system Certbot fallback and schema/config compatibility.
## 0.27.1 - Portable certificate runtime and list sorting
Current direction: 0.27.0 -> 0.27.1
- [x] Show complete default and standard-service setup-token commands during first-administrator onboarding.
- [x] Initialize the Certbot Python environment under the RustProxy data directory with app-owned `venv`/`pip` state and a local `virtualenv` bootstrap when stdlib `venv` is unavailable.
- [x] Fall back to distribution Certbot binaries when usable and report missing Python/pip or setup failures in the Certificates panel.
- [x] Add account-scoped column + direction sorting for vhosts, Docker vhosts, redirects, 404 hosts and streams; keep Redirects/404 toolbar placement identical to Vhosts.
- [x] Use the installed production binary in the Certbot hook example and expose official source/package repository links plus Python/pip/Certbot runtime state in application information.
- [x] Keep SQLite schema 8 and additive/defaulted administrator-preference compatibility.
## 0.27.0 - Compression policy and persistent Docker publications
Current direction: 0.26.7 -> 0.27.0
- [x] Enable zstd/Brotli/gzip downstream compression by default, keep deflate opt-in, honor client q-values and use deterministic `zstd -> br -> gzip -> deflate` tie priority.
- [x] Allow authenticated/cookie/private/no-store responses to compress while retaining explicit no-transform/range/SSE/upgrade/existing-encoding exclusions.
- [x] Add inherited dynamic-compression overrides and per-scope codec filters for ordinary vhosts and Web Server services under the global codec ceiling; keep Docker publications intentionally simple with one Compression on/off checkbox using the global codec set.
- [x] Default fresh installations to `Server: RustProxy` without rewriting existing saved policies.
- [x] Remove Debian package-build metadata from this source tree; packaging is maintained separately at `rustproxy.linuxiarz.pl`.
- [x] Keep published Docker hostnames materialized while a service is missing, return 503 until it returns, and reuse last-known inventory during temporary agent communication failures.
- [x] Render cache occupancy progress fill consistently across Chromium/WebKit and Firefox.
- [x] Keep SQLite schema 8 and additive/defaulted saved configuration compatibility with 0.26.x.
## 0.26.7 - Cache occupancy and backend rotation continuity
Current direction: 0.26.6 -> 0.26.7
- [x] Show HOT/COLD cache occupancy progress against configured entry and byte limits on `/cache`.
- [x] Preserve compatible upstream client generations across unrelated runtime snapshot rebuilds so rotation age/counters remain continuous.
- [x] Keep request-count and age-based backend client recycling covered by tests and visible diagnostics.
- [x] Refresh container documentation and include all supported Certbot DNS plugins in the shipped OCI image.
- [x] Review current installation/cache/backend lifecycle documentation for stale behavior statements.
- [x] Keep existing 0.26.6 custom `Age` rules loadable while isolating response-side Age overrides from cache freshness/storage semantics.
- [x] Keep SQLite schema 8 and saved proxy configuration compatibility with 0.26.6.
## 0.26.6 - Access-policy composition and administration fixes
Current direction: 0.26.5 -> 0.26.6
- [x] Keep backend table visible columns, sort key and sort direction synchronized with account-scoped user preferences.
- [x] Clear the certificate-issuance form after the ACME issuance job succeeds while preserving non-secret values after failed jobs.
- [x] Document the sibling Python modules required by `scripts/migration/migrate_npm.py` and fix the container helper example.
- [x] Treat explicit IP allows and allow-only countries as alternative allow sources inside one ACL policy scope, with explicit denies taking precedence and separate scopes remaining cumulative.
- [x] Keep SQLite schema 8 and saved proxy configuration compatibility with 0.26.5.
## 0.26.5 - Scaled controls and listener navigation
Current direction: 0.26.4 -> 0.26.5
- [x] Keep compact toolbar selects and inputs on the same interface-scaled control height.
- [x] Reuse the interface-scale field variables for ordinary form controls so inputs/selects resize consistently with the UI scale setting.
- [x] Route the Streams **Add TCP/UDP listener** shortcut to Settings -> Listeners instead of opening a Stream editor.
- [x] Keep SQLite schema 8 and saved proxy configuration compatibility with 0.26.4.
## 0.26.4 - Administration consistency and backend rotation reliability
Current direction: 0.26.3 -> 0.26.4
- [x] Add 404-host forms to the session-only draft workflow.
- [x] Complete Polish translations for the Button style appearance setting.
- [x] Align toolbar search/filter input height with the active button style.
- [x] Reuse the vhost Enabled control styling in 404 hosts, Redirects and Streams.
- [x] Replace the Overview-card checkbox list with compact visibility/reorder tiles.
- [x] Run age-based backend client recycling independently of request arrival.
- [x] Keep SQLite schema 8 and saved proxy configuration compatibility with 0.26.3.
## 0.26.3 - Administration appearance and draft workflow polish
Current direction: 0.26.2 -> 0.26.3
- [x] Make interface scaling global across navigation, workspace, dialogs, overlays and the draft dock; extend the range to 70-125% and make form-control sizing respond more visibly across that range.
- [x] Make Overview telemetry cards denser without changing responsive breakpoints.
- [x] Expand chart themes to eight full-palette presets affecting colors, surfaces, grid and line treatment.
- [x] Restyle the draft dock as a square-corner RustProxy panel without rounded/glass styling.
- [x] Add a collapse-to-bar state and per-draft age indicator to the session-only draft dock.
- [x] Add Settings search without rebuilding forms, preserving unsaved values while categories are filtered.
- [x] Add chart-style palette previews beside the eight chart presets.
- [x] Add independent Normal/Compact/Ultra table-log density and Overview dashboard density preferences.
- [x] Add per-account Overview card visibility and ordering controls.
- [x] Add per-section Appearance resets plus a full Appearance reset while keeping unrelated account preferences intact.
- [x] Add an account-scoped Normal/Compact button-style selector, with Compact matching the smaller source-style control dimensions independently from global scale, including generated vhost actions and modal tabs.
- [x] Retain sticky Settings category navigation and unsaved-category indicators.
- [x] Keep SQLite schema 8 and saved proxy configuration compatibility with 0.26.2.
## 0.26.2 - Unified logs search and dynamic fields
Completed direction: 0.26.1 -> 0.26.2
- [x] Add independent in-session editor drafts for Web Server, Vhosts, Redirects and Streams, with minimize/restore/discard actions in a bottom-right dock.
- [x] Consolidate theme/language/display controls under **Settings -> Appearance**, replace density modes with an interface-scale slider and persist appearance choices in administrator preferences.
- [x] Add 12/24-hour timestamp formatting, chart presentation presets and a denser expanded Stored Logs record view.
- [x] Merge Stored Logs and Live Logs into one top-bar Logs menu.
- [x] Replace fixed Stored Logs filters with one bounded field-query syntax supporting AND, negation, quotes, wildcards and numeric comparisons.
- [x] Discover selectable Stored Logs columns from parsed record fields and keep arbitrary safe field choices in account preferences.
- [x] Parse the built-in RAW error-log line format into basic structured fields while retaining the raw record.
- [x] Remove the configured-file inventory/source selector from the Logs workspace and keep bounded all-source scanning by default.
- [x] Move Stored Logs column configuration to the table toolbar and use arrow symbols for backend sort direction.
## 0.26.1 - Vhost-aware protocol diagnostics and Logs Explorer UX
Current direction: 0.26.0 -> 0.26.1
- Attribute persistent protocol errors through one shared vhost resolver using routed vhost, authority/Host and SNI in that order.
- Keep unresolved traffic in `_unmatched.jsonl` and keep low-level protocol errors out of Live Logs.
- Add bounded protocol diagnostic identity fields for Stored Logs filtering and inspection.
- Add selectable Stored Logs columns, sticky headers, highlighted selection and inline details.
- Hide configured-file inventory by default and repair mapped client-IP/search behavior.
- Preserve schema 8 and the existing saved proxy configuration format.
## 0.26.0 - Persistent protocol diagnostics and stored-log explorer
Completed direction: 0.25.7 -> 0.26.0
- [x] Persist malformed HTTP, TLS/QUIC handshake failures, HTTP/2/HTTP/3 protocol failures, invalid h2c attempts and connection-limit rejects through the existing bounded logging sink.
- [x] Keep low-level protocol failures out of the Live Logs memory ring so hostile scans cannot displace ordinary live request history.
- [x] Add a local read-only **Logs** explorer with bounded server-side filtering and pagination over configured access/error files.
- [x] Prevent arbitrary-path browsing and symlink following; enumerate only paths derived from active RustProxy logging configuration.
- [x] Add `/logs` dashboard navigation plus a Logging-settings launcher while retaining the existing Live Logs dialog unchanged.
- [x] Keep schema 8 and configuration compatibility with 0.25.7.
## 0.25.7 - Vhost operations and backend observability
Current direction: 0.25.6 -> 0.25.7
- [x] Add a fast vhost enable/disable action to the vhost cards using the existing revisioned save path.
- [x] Add backend-table search, sorting and account-scoped persisted column visibility preferences to the main dashboard.
- [x] Style scrollbars for dark and light dashboard themes.
- [x] Export retry and upstream-client lifecycle metrics for ordinary and Docker backends.
- [x] Add Grafana panels for backend client generation, age, retry activity and rotations.
- [x] Make the vhost editor Enabled control visually distinct from ordinary settings.
## 0.25.6 - Backend memory diagnostics visibility
Current direction: 0.25.5 -> 0.25.6
- [x] Show upstream client generation, current-generation request count, generation age and total rotations in the main Backends diagnostics table.
- [x] Reuse the existing `/api/events` status snapshot refresh path; do not add a second diagnostics polling loop.
- [x] Keep the 0.25.5 client-generation recycling defaults and configuration compatibility unchanged.
## 0.25.5 - Traffic country consistency and Revision History styling
Current direction: 0.25.4 -> 0.25.5
- [x] Use trusted `CF-IPCountry` when verified Cloudflare client-IP mode is active, with local GeoIP fallback for missing/invalid country headers.
- [x] Keep country analytics, retained country logging and country policy evaluation on the same verified country source.
- [x] Align the Revision History dialog with the shared square-corner modal styling and remove duplicated history-only dialog CSS.
- [x] Add Cloudflare-country trust regression coverage and release/source-contract checks.
- [x] Extend Prometheus with a live Traffic analytics collector, fixed-cardinality User-Agent/method/source families, country/top-route views and built-in Web Server inventory/source metrics.
- [x] Extend the bundled Grafana dashboard with a Traffic vhost filter, Web Server request rate, countries, User-Agent families, serving sources, methods, status classes, cache outcomes, capture-state gauges and configured routes.
- [x] Bound backend Hyper client lifetime with configurable request/age recycling and an upstream HTTP/1 buffer ceiling so sustained traffic can release pool/TLS high-water allocations without a vhost save.
## 0.25.4 - Configuration history restore
Current direction: 0.25.3 -> 0.25.4
- [x] Restore an older retained configuration through the normal validation and hot-publish path.
- [x] Save restore as a new revision without deleting or rewinding existing history.
- [x] Require current-revision CAS plus administrator session/CSRF and audit restore source/new revision.
- [x] Add restore action and confirmation to the existing revision-history modal.
## 0.25.3 - Request policy, correlation and configuration history
Current direction: 0.25.2 -> 0.25.3
- [x] Add per-vhost HTTP method allow/block policy.
- [x] Add per-location HTTP method policy, reusable ACL profile, inline IP ACL and country allow/block policy.
- [x] Enforce host and location policies before upstream selection/connection.
- [x] Generate one SHA-256 request ID per exchange and forward it unchanged to the backend as `X-Request-ID`.
- [x] Persist bounded manual-configuration revision history in SQLite schema 8.
- [x] Add a calendar/day revision-history modal with change preview in the existing dashboard style.
## 0.25.2 - Live backup and resumed-session hardening
Current direction: 0.25.1 -> 0.25.2
- [x] Allow encrypted full-backup creation while RustProxy owns the data-directory process lock.
- [x] Snapshot SQLite consistently and stage durable files before archive encryption.
- [x] Preserve only validated relative symlinks that resolve inside the staged data tree, including Certbot `live` -> `archive` links.
- [x] Keep restore offline, validated, staged and atomically activated.
- [x] Recover transient invalid-origin state in restored/resumed admin tabs with a one-shot same-origin reload, without weakening server Host/Origin/CSRF checks.
## 0.25.1 - Traffic UX and source modularization
Current direction: 0.25.0 -> 0.25.1
- [x] Split Traffic into Overview, Clients, Requests and Delivery sub-tabs.
- [x] Chart every bounded Traffic dimension with donut or horizontal bar visualizations suited to the data shape.
- [x] Keep every Rust source file at or below 500 lines and keep unit tests outside implementation files.
- [x] Split oversized dashboard source bundles and the Docker Agent into bounded source modules while preserving behavior and URLs.
- [x] Guard the Web Server split against dangling attributes and missing `htaccess`, `server_protocol` and `anyhow::Context` imports.
## 0.25.0 - Web Server foundation
Current direction: 0.24.10 -> 0.25.0
- [x] Add independent reusable Web Service configuration with name, document root, index list and directory-listing switch.
- [x] Add Web Service CRUD through the dashboard/API and persist it through the existing revisioned configuration store.
- [x] Add `Reverse Proxy` / `Web Server` source selection to vhost fallback and custom locations without storing Web Server filesystem settings in vhosts.
- [x] Allow one Web Service to be referenced by multiple vhosts and one vhost to mix Web Server and Reverse Proxy locations.
- [x] Add `GET`, `HEAD`, MIME, `Content-Length`, streaming, index lookup, directory slash redirect and optional directory listing.
- [x] Add repeatable per-request directory listing, per-directory index-before-listing behavior, Web Service MIME overrides/default MIME and a default static `Cache-Control` policy.
- [x] Organize Web Service editing into General, Routing, MIME, Cache & headers, Delivery, PHP-FPM and Security tabs.
- [x] Add Web Service `try_files`, internal/redirect regex rewrites, ordered per-extension/MIME Cache-Control rules, fixed response headers, precompressed `.br`/`.gz` delivery and forced-download extensions.
- [x] Add bounded FastCGI/PHP-FPM execution over Unix sockets or loopback TCP with `index.php`/front-controller routing and no source fallback on upstream failure; expose advanced bounded limits, Authorization forwarding, custom FastCGI parameters and controlled `PHP_VALUE` overrides with in-panel syntax documentation.
- [x] Add explicit symlink policy: deny by default, with Linux-only inside-root following for static files; PHP-FPM remains restricted to deny.
- [x] Add a bounded `.htaccess` compatibility layer for common Apache migration rules, including rewrite conditions/rules, directory index/listing, MIME, headers, Expires and PHP AddHandler integration, with a dashboard documentation modal and fail-closed security-sensitive directives.
- [x] Add one-shot Web Service presets for Static, SPA, PHP generic, WordPress, Drupal, Laravel, Symfony, Joomla, PrestaShop and Downloads, with optional deterministic hardening for common exploit probes/sensitive paths and fingerprint-header stripping.
- [x] Expose vhost-wide request/active/error/EWMA/traffic metrics for Web Server-only vhosts and fix custom logging mappings for route/cache/transform effective fields.
- [x] Add bounded per-vhost Traffic analytics, User-Agent logging and opt-in country/GeoIP logging with top-dimension views, effective-client-IP rankings and donut charts for the main dimensions.
- [x] Keep Web Service dashboard rendering stable when configuration loads before the deferred Web Server UI module and use the existing generic/custom 403 error-page pipeline when directory listing is disabled.
- [x] Add automatic allocator release and idle HOT-cache waterline trimming so sustained traffic does not require a vhost save/reload to return reclaimable RSS.
- [x] Add metadata-based `ETag`/`Last-Modified`, conditional `304`, single `Range`/`If-Range`, `206` and `416`.
- [x] Enforce decoded-path validation and document-root confinement; Unix traversal uses `openat` + `O_NOFOLLOW` and does not serve symlinks.
- [x] Keep Web Server inside the existing routing, ACL/auth, custom-header/error-page/security and H1/H2/H3 response pipeline; do not create separate routing/error/cache subsystems.
- [x] Add unit/source-contract coverage, OpenAPI/UI language coverage and Web Server documentation.
- [x] Clean README/Docs/CHANGELOG/ROADMAP material, add a canonical docs index, authenticated Markdown Reader, 0.25 specification audit and open-work TODO.
- [ ] Run compiler-enabled `./dev.sh fmt`, `./dev.sh check`, `./dev.sh test` plus live H1/H2/H3/static-file acceptance on the release host.
## Web Server follow-up
Future Web Server work must retain the 0.25 split between reusable **Web Service** configuration and vhost/location source selection.
- Multipart byte ranges if operational demand justifies them.
- Named locations without introducing a second URL router.
- Opt-in PHP `PATH_INFO` script/path splitting with an unambiguous confinement rule.
- Race-resistant filesystem confinement for non-Unix targets before enabling local file serving there.
- Real PHP-FPM release-host acceptance over Unix socket and loopback TCP.
See [`docs/TODO.md`](docs/TODO.md) for unresolved work and release acceptance, and [`docs/ROADMAP_HISTORY.md`](docs/ROADMAP_HISTORY.md) for completed historical milestones.
+465 -424
View File
@@ -1,104 +1,100 @@
61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore
c793a8be7f4b76350e04ab36dbeec3792bb2dcf33c87a86f76fa2f330e26dd9f AGENTS.md
91edfded868ff72e97fbb34d08e1be2df5d5b8fa6582b534a8e4d109cd312acd CHANGELOG.md
d5dc2b778af98f52ad52a7515e2d1b4ba8c2e01a04cc6e02564377075b01c501 Cargo.lock
7fd5dd1ccae8fac77bbbea4bbf960f5cf762f712ff17a2e629a5dd8629eb7ff9 Cargo.toml
e76e688874828e3743d03693b1d2872b9633b4db09909673786caf30f7cfe489 .gitignore
9648830349e8ff28509f8a62a4f62c3cca1721498118cf9fd71e2cb7d95fb4ac AGENTS.md
11b4e849d5933ca383fc4e3b61a3f28e19deef7ee6d768e5108008e58b8dbc3e CHANGELOG.md
b7b5cd49c635e709301850d07d24014876e3d4e0942dff320bc1cd621a2aeb55 Cargo.lock
7a9f5c1cb40414c8cb1e5a3af9c1f1322c69769b76c7c8701d4ee81338168a7c Cargo.toml
25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile
30708454b96c0dd57c9c621050ace58b92dbdbd85591c7b8199ebb54937dc372 README.md
e796ab6e379b3d71cf5d77ca3eb30bc6a320080af0c9e35f07c82737e09cbc2e ROADMAP.md
0d96a4ff68ad6d4b6f1f30f713b18d5184912ba8dd389f86aa7710db079abcb0 LICENSE
82d1dee2fc68158f0ea73259151461e64c8b18d807cd1fecfb1ed99dc30cf5f4 README.md
da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml
f645f218dd062bf6cb54a8ee634290562cad364a4cccf09f16e3c8303582af8c build.rs
ea8d091843f86f4c66a5c76b20268b2feb397070851b6e05c5bc7d7a8d9157eb deploy/README.md
4834c278430ddf3bf842995735195368f1ac2c3b5c1d749df4f9238caf1e2b25 deploy/rustproxy.service
c0df6b46bf795d49d00fcd9e2a9247aed0d3a99b6cae1dc5ab6614e8b3d30009 build.rs
95ececaba550a4bcfd6754073877ba0633373b1e1451521b9ac905a51e8ca6eb deploy/README.md
13d514de5042a230cacf8c10abe1ef183c94ac799a89b8e2c111152ac7c69b48 deploy/rustproxy.service
c111f5b20484707b80efb7dd9176acbaa27b63ac78db497261ac234821e1fade dev.sh
5f51efe621f45512a466fa7f2656867c6502a135ca040f4bfeec06a6919b39b6 docker-agent/README.md
7fee7d51a7deb67e73e53a0bdff3aa4630aea9bab9b23a0f9b8001b2169892b1 docker-agent/certificate.go
d213e774a9cd07bff0c79324722bd05c0aca18cc033d50b2c0558cdb72972bca docker-agent/README.md
e9f18b358a740f1fe16e369ed16ee37581b8ce4c94ddeb34a1a37b89a49b5a06 docker-agent/certificate.go
600d5aebc8557d6987158969d494bade1ba46ccf9d1cd573d357d0106db26b2b docker-agent/cli_test.go
067793eee01fd284da66f79c6f8b6f19f8773e64f84af03168c8f751ff044685 docker-agent/credentials.go
5e4f565c3c9de4dba897af2e801ea646a83395e72eae4717a6d502dff2fcec10 docker-agent/docker-agent.env.example
bf687a851374d8f1fa59d050f3f76532f7c12f16a6daa7d3c36102c2d9a8ec9c docker-agent/docker.go
ae660c2a90f193167b166977277abfc86a25e043ab4def4c273b2a13d5a51b8b docker-agent/docker.go
9e9f3f17f38f9b0730f598f65988ce8ef562d2182ef6778fc7292b990e9cf22f docker-agent/go.mod
9d77c90767fa929ee8338734d72ce83284d3d965673270f62cb2b1613d587a33 docker-agent/install.sh
b68234472bec5cedd67c7cfe1de7d8806a965dfd4101f944aa6320132f18c14e docker-agent/main.go
3d4051a1f805d145df3877476e4b87773e74be8e123045065955a5133afdc68c docker-agent/main_test.go
68ad007eac216f115809695d3c2c6651e2850ff3d7334685ae6e49bc21b01087 docker-agent/model.go
7b70bd43a7f39bae8968decf4cd76af80fe0d08bf1bc53b894d89ead80581d56 docker-agent/install.sh
b7d0466f70b541a705d234fc8a88b99b6197fdd5e682d3a288a78c76764358cf docker-agent/main.go
d660f46f7ef8c539dd3f5486a31d5a1254b9737be8a278c7f9352c254558f8ac docker-agent/main_test.go
351bcd0321c823694e12d578f96dc68ceb929979a70146d12af5394f639a486f docker-agent/model.go
e52b31d0abf65ae3034f6cb367cca113633014ce8d1d799ad4dd5624d2c7b8bb docker-agent/network.go
634790f35ff4627df2a78cec2ed371f6e9ec9df00afb386075595fc7f3ed72eb docker-agent/rustproxy-docker-agent.service
5220d669171ec3ad65f50e0bc8de5178dadaa4904def8e1a4f61002aa9e9a55b docker-agent/server.go
875faa3969df9409d79f050a84b4183496f557edda3d34aaec19fe556cf8c471 docs/ACME_PROVIDERS.md
ec15163f5f51c9212003b7edd1fc380321311c2496fbaa9b45c838281b9af475 docs/ACME_RFC2136.md
e21a5c3cea78386fcb3ebd6516a66eed2ecd1966481ae0d3b2f57fa01010542e docs/API.md
f27053f8a93d7883025a04922d015e0b697cad653e714be632b757e167297de1 docs/API_ACCESS.md
d30551a52ffd34c5963094d2826ba39795210a4e6a4e4d7333066dae7456fc50 docs/ARCHITECTURE.md
fb99fac0a490af262a0ecdad593ffe5164004c2166fe7ddd607476bcc56a1623 docs/CACHE.md
9113f3abbfe81bafc4855d8680df8aa2a381ecc358cc3e1a0f78b6b756421117 docs/CACHE_DIAGNOSTICS.md
3822d72e12f82c37a9e70ab40c7e89e89145ad37f29914e4702db4bf024a5501 docs/CHANGELOG_TECHNICAL_ARCHIVE.md
c8e69e492179ce39958cc63615cf46c4ac08cfdb7f3483aaab66868a3abcd687 docs/CONTAINER.md
833ddb12f23dcac796223521388b19c6ec3ea04c5550cf1c5f4ea4eb7c58f3a0 docs/CUSTOM_LOCATIONS.md
3cbcf562114fae47f01ed4614d49844b88e7a4d8aee7eb724400d5681be90936 docs/DASHBOARD_PRESET.md
4d70797bda8d227787fcff897951e702e2458fc6c6f5352aa0b5577595bd834b docs/DOCKER_DISCOVERY.md
46811a09a03d83f4af686c33e30e3f94d0508afe2c75147a26885c7247c49cbd docs/ERROR_LOGGING.md
f749f7bd2eb4ae9c484f77a3cd8cd5ff21445319a3938e0384a3414134fe713a docs/FILE_LOGS.md
36599e0b59164e662442f3e8203c16b3123d09b7684d8ecdbffce42d22cc221b docs/HTTP3.md
a00ec3c1d3b28ba10677435128e031e5f18fe8c5cef2ac90eb0f6779cefeb434 docs/INSTALL.md
3b5c31c805e5aee0dc2100b81246a9b631b5abb996faa598ae94f87ba39305f3 docs/INTEGRATIONS_040.md
4bb56add544f15d54695cf797cd16a51c09024360cdeef803a88988f4a54b3a5 docs/LIVE_LOGS.md
7c35c4cdbe7bbb81d94f3bf6aeb22b8a866af3e7f0d62bbedc299e4d6f264e0d docs/MANAGEMENT.md
d8829cc68d982c933d04207cf7bc78cc918044c72e024e7ecad5220953bb3a79 docs/MEMORY_STRESS.md
e36247774051aa9c83c1a2fda91c1f7857991dcaa4fa90320ca5b492e31dbcf8 docs/MIGRATION.md
5f090a190000c6badab2a74e5d4968e70c34de43b59c1452f5b05503b5c5c18a docs/MOCK_AUTH.md
1f85fac0e06e432ef6f744f0b5f9c1b7a27a1e9e1ad30dc1a218eab96b1caffe docs/MOCK_SERVER.md
e7516646dfb107d6240370f4e7460b1189f2eb21f398740381d5d102847bbfe6 docs/NOT_FOUND_HOSTS.md
89a1af6c9fe10c572b211f9632ce256166e67990b5dacacf2ae07265d99a5172 docs/NPM_MIGRATION.md
e68d5a5cf0991de9308f78afb0264e02979f7463a5f395eb7a6e9ec2bd3a3d54 docs/PHP_FPM.md
bc068a176056f5ea4d3b637071e644ed58a2140494c233ef22aa66e9700e3299 docs/PHP_FPM_ISOLATION.md
ad6e3f003b820a4f45e30e39577d120a69d167c70910bda4891971759458c3dd docs/PROMETHEUS.md
bb15713876184682cf1907d8c5edd18e657735b6de6cc5f8ca976dbdec4dd936 docs/README.md
6e46a137b9c88dac8cb9e3fdca877430092392de9913d746278dbff8652460c2 docs/REDIRECTS.md
0dfd0d431c5aedc67abac1ce5de040ffa790ec9578fd7d6f91a987c333d73358 docs/REFERENCES.md
d85c5567fc6a7714b96ad7cfe4ee9ff2085f84c4f31534a6484340d03a73e0f7 docs/RENEWAL.md
cd4ed3af26eb51c9ff6ef3dda9d52ff5120bcfc78c842bce34a639cf85b6aea9 docs/ROADMAP_HISTORY.md
759bf2987f2b602488829d1856fa33d57c2a3b23bc681ef5a931b9633af3a533 docs/SECRET_STORE_BACKUP.md
ec7e32fd19e6ba6ed1732f1f3dd92ac0860609e8c0bbd3c1998840cdfb24bf54 docs/SECURITY.md
620b69e566d27af04f0604f4ca3f88cf95cdfea7fbc6ae9dd92446d2b2b5d9be docs/SECURITY_AUDIT.md
007537ee971463072b2482dfa4fdcf1955f837ced0b711d4eef3b6267d16d4f6 docs/SECURITY_AUDIT_0.31.0.md
4dcf3243a13d9ae1827b5cefdbb9e27bd6310321c50d4d5934527d44437df6ef docs/SETTINGS.md
8fc112cb45a2f35ef5b0dd4371aa6f43aca4df85db62c6ae02e56d74a3b702ae docs/STREAMS.md
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 docs/TEST_REPORT.md
cfb452d8496839e832b5fbacdd4e52525b0ecc3112fbb62340c1e580bd5c1717 docs/TODO.md
751c9b13b47d0959a8575d6e82bdecba988b950a374ba0b084fbba08b4b65ebe docs/TRANSFORM.md
fe3ec6ca16ff933239c12dacf39dd7b45717c0551a8b9f6ce601bf78136c9e37 docs/TRANSPORT.md
711c69c9e05761ba5cb96dadc3c368eb2961e2d2287ca039eb49c7fbca0dd63c docs/VHOSTS.md
09b60fb7b40db4d0139fa2a9e69a2aa9f28d4e61b8643521cfc7e936d7df8774 docs/VHOST_ACCESS.md
4257729a4c1b1f3949538410af78a12067bd3417c70525a15bd01411155d25fd docs/WEB_SERVER.md
c336f998027330c58d5afbbbb3a3a682fd44ae3997643882fe342633822f8bb3 docs/screenshots/README.md
60d108fd168064e4d6980ed7499f3219017f9878c743697e77f2efc592e4d629 docs/ACCESS.md
5a8821f28a1f529263db462a0ed1f7c082ad91b5936491806913e5cb5b274fd9 docs/API.md
728b15b9825f547f34c4121219784798dd046fbbdd72ece8906d0cd77e8efc64 docs/BACKUP.md
4fb55a549535f37467f14536cbf0db7be027ca8a019c2919a384af4f23655300 docs/CACHE.md
5e7b6668be6bfec77755ae45b9d369aa14135fce7632de392c6b29a7b584245a docs/CERTIFICATES.md
02bb383b5c3bce22616c3cc3db30a9469806dc16decb509973e84c60aa30fc5b docs/CONFIGURATION.md
fec29ce846d6705eb45819d85d382f28e28a146b265feab2e4c7cce2a35e5883 docs/CUSTOM_LOCATIONS.md
9c54b8da03adb1bc1a3db96a72fb99924dca25eed5ac662989aba18ca226d066 docs/DASHBOARD.md
11a08bf2110bbeabc924eb049a30177a216af5607ccdaf8be9178e751f78a2c4 docs/DOCKER_DISCOVERY.md
1a15342f58600dc1579b3387a898f95d2c5ac0348245bada75ea8e453e4755db docs/FEATURES.md
2c5f97b12be53db32c7e2888d23c0ef1d82d1742df44f7e60ea818dc801a719a docs/INSTALL.md
6e37859bf0dced99d4a7db93790cb38fcd90191932827946d5223eaa4c784fce docs/LOGS.md
0e9ae9509a78d00951d2c7c3f13eaf518d1f3448a29b048f5933e1f5f8440275 docs/MIGRATION.md
0972248b7062b37e61edcc34dab5fe73ee9bb50195a0aa475c72efa0dd311aa9 docs/MONITORING.md
364c1faf9e21849de0f7c4ae8c1e55c1fa4d3ae3a7167a33cb58651174b4bfda docs/PERFORMANCE.md
d5894c07c68ec4230987767198434abee13b0e525842fea21bcbe752fa82742b docs/PHP_FPM.md
954b1602c865e157c867f1c81dca615f2beaa33c34de0b0fa8a6069db2aae7d0 docs/README.md
490a6dbe870f2b1fd4ec7816768dde6c86fa7957fc9de2d2f075e123c9222222 docs/RESPONSE_DELIVERY.md
70e8c8f3352629f5600537058b1997739d9e465747cf3051da8a2cc779c3b728 docs/SCREENSHOTS.md
3a48184b36d225757fc5e2d22c629f89bfe5d4007ff087f0be0ce02a5fb04c28 docs/SECURITY.md
bbef60ff1771da9f48af812987766e1e479408873ae2f079b13f1d5c62ecc6ff docs/STREAMS.md
8cfcda59060106151f13e93ca25c1280b4390433b3fbe2921a6eb3c13bf9766d docs/TRANSPORT.md
be6e8af62a7bf958b94ec15e9e3263263e511d1d33abae4e45d114833fa52b07 docs/TROUBLESHOOTING.md
b4abb86942f665230904fad570c1487bc173aa84633801e2999f2bfa197de4a7 docs/VHOSTS.md
e67928a9d044e71b681c3e52f9dd8b151d6b72f965d16d5da8b0eadde98b70e9 docs/WEB_SERVER.md
3769f97c74b1a4a2f14d82a6f9c30fa3ee48892f423bf82dbd2ec1bd9280e45a docs/images/about.png
a85a7b9d8cac74fb1b231471d313e6587ccfc2e8abd2885a79067704176c724b docs/images/api.png
1ec8e47b6c10a905d8ce63a060be3f51efa3bf2c12529456a8c3d3a0653db95f docs/images/cache.png
02b4ec5bc7e39aeb8f8928a015779b431ee12c4c16733954631de0e6699388f6 docs/images/certificates.png
40d0a4d28c3938612c93425a308c895dea0659f50c661849ec5bde606a141a97 docs/images/documentation.png
6e7f634d6c0cfe8f9fbeba129d745fff00cb8bc6f388de10c4c158ae4a854a17 docs/images/login.png
1f921d98aa16da3a43bdcaa46ade68eb921b1d88d95a8fa87f0b51b6f2a8db9c docs/images/logs.png
74c23173c2299f8ee150fd8fe160ffec0cbfb5f63b89d8debb1789fa4526fb19 docs/images/overview.png
585f7f450bb7dbbf32ce146edbee6555f849903d1abbcf665a75b5fb12ef36b8 docs/images/settings.png
fcfc9f1b932e243e2133061aa996d8ca6f8fede830190865b771482f4ab01ca5 docs/images/traffic.png
d50eff63d8c11af616a794d24967ca16a2ad827d9c3ec47299d578419b3c449f docs/images/vhost-editor.png
bb977580c5d93bd7f2eb931c795ae7f4b8676f316d0fbd1552f78e2757dd79b1 docs/images/vhosts.png
28549d423d6976181d9b4ff9e13626ccee3b4c44db36d442eb6b3e20e9f90d6a docs/images/web-server.png
5502e8ce44276213803b66c58e3a4d804a1354f5ed5d83f47ee805e8c44ab088 examples/acl-headers.json
2431dd88a2d9ad2cf3cd4b8fcdfa602563ab1013c612d8917fddbe7febfb2dc3 examples/cache-dashboard.json
e31ac05a99615e4ea6c4c9a8b2e090968583fe3959f838a600b5740cb170f8c5 examples/config.json
39cad993003930febf98460311d185a278e7a9508b6b591299d9339d0f0c51cf examples/empty.json
695cc7621aa403148fcd0868f351b6e9ccc076fe35610e779af4a72f94d7d5f4 examples/http3-vhost.json
b26833cb4251f9390a44192ecc827ff7f052fd19016a5ccf7498c94503ab4ce3 examples/mock-vhost.json
a6b835dc07defc12b8f0e48491d5f413843c8be2ba4501d7c28140a2822b034f examples/oidc-vhost.json
134d4a59727b27accb3141d4cee26a4a74157d236de4996bfd682e639462b2b5 examples/tinyauth-vhost.json
5a8f3a8a054abc89c6e78896b9fe2f903212199a24dc3d2d2bcbd0696a31bcde examples/transport.json
d242de9614fbb52933b4accd32f4715ca94107abe4a3b1d7e088362b07a6ba60 examples/vhosts-balanced.json
af2bdb1343a8141069e94ccd2c7f92574ad848437e2f9fdb5c7576a9a18ef4a5 examples/vhosts-private.json
1b8974af79a5026e0648ceab58a32f233e1760e21e7465a0d46e9c41fb09964f grafana/README.md
ea8aaf0257a5ab75153ee2fd383a4d5c2ac725c47042f9770949451636d442f1 grafana/rustproxy-overview.json
a05cbbf101b288bf9cf83fedfc5017925a8b360a78f313d31c1a70f15dc1dd22 install-debian13.sh
35e4f3acc652ac23a9442600c4c76173ae34966592031b3e942b62c249fc869e grafana/README.md
0d389faa4f64f095e9ec62cbae329658682b96f5e4d02ead79c7d2892ab89fa8 grafana/rustproxy-overview.json
1a041c505669625a7f0f8011484cf5b6d2ec69a7de859801312e4fcf4c8d3555 install-debian13.sh
909e9ab94617ace74a425f15084bf85f3086cc52ba5b28f2b441ff800abc6ef9 install.sh
42f9f1d18c021fc7734b5bd020844bf678725a0e539e2fcbe05ec676f6239eae integrations/README.md
2d4c859aae5ba357a6d30024c8487ae8f59f01b192711f13f9c2f8fee0f48b50 integrations/opnsense-acme/README.md
c73b814a42bb4ff703441dbd177ee625055eeb115f9b5bb0c2150aede573a159 integrations/opnsense-acme/opnsense-rustproxy-cert-deploy.sh
ba753f69557bd9fa990e56beabebccf39f93b5f6fb7e66a2e241c8c461dfd763 integrations/opnsense-acme/README.md
3a1f8c326618a4613bb35f9e83428f6d1cf668b9e37b4731e5d38c3081fc94d8 integrations/opnsense-acme/actions_rustproxy_cert.conf
de6550ab8f48d556d752c252212c38ba65491fd224f972c10fbe552f91ed7520 integrations/opnsense-acme/opnsense-rustproxy-cert-deploy.sh
73784ac652708b04ca6c91aeed4524f51f50d63715bbeefe7f5590b353e78943 integrations/opnsense-acme/rustproxy-acme.conf.example
45be151cd85fc2ef2f1fdc3003ace63381af262e73af8a685ab48cb697cc0de1 integrations/routeros-oci-build/.env.routeros.example
11aa102de1089ecc375f20c4bd5420fcfd3b2ef23fe0c8bdf04953fcfaf40ef0 integrations/routeros-oci-build/Dockerfile.routeros
7c7a3e51f43b43e1c962a4b0bb591f99c1f06977bf77f23719f7f867dfe7eb8e integrations/routeros-oci-build/Dockerfile.routeros.dockerignore
cb01fdef286f38b442dd9252c23c4f8e44eb72e9ecdd99adb926e8e9f6318bb1 integrations/routeros-oci-build/README.md
96563a83e87cfb33e238c2a970ce36b1a1b51e91318d09b123c3755002ba2646 integrations/routeros-oci-build/build-routeros-oci.sh
6f7a659cd2f13dc0a091049c15800967481fcd6c44984332f6f37bcc758cd5a1 integrations/routeros-oci-build/README.md
3f092a9ab80221eddd67be4d1a776acbc6e04c1eb6011b588280c28e83b2582f integrations/routeros-oci-build/build-routeros-oci.sh
3f267cafa16006e5456ade78ee2d5fae02cc7ce747839523068ffd8d7c98798a integrations/routeros-oci-build/docker-entrypoint.sh
13189fa546c3488dddd8efc80c47f6da5241e5fc28b79cbfe795414ea05c7a4f langs/README.md
1a69ed38b730c3b379b8b14b5707d8ae923320a0c66d084c7ee478e6a01eb53c langs/en_US.json
6513435292eb947935f50eeadf9ef2ee0995f4b57e989699dfc9cda686f21685 langs/pl_pl.json
a99ad040be10fa028351e290b9363236b3513a1cadbdc9fb9e0260d517b9b0ae make_zip.py
ab85a75c125279859f70aa00f5eec44914c8e7a571593c09dd15a400eaddad80 langs/en_US.json
e63c7fa00f90410e3fa0d087dcfb09d639310680011fe3221ac682db8eb84c56 langs/pl_pl.json
8ed80f14633970899d56f4ea72684a8d4b1f2843c1c78bfacc4cbb5f7ce9ef4b make_zip.py
e830d37dd1f81a4b0aa7d535f5bba69e34e433b0fd99cc8ee0b296786c32379a migrations/0002_admin.sql
20112cc82192098b8d602f1c4f7ca08712e9e1366c845fabb92753187c370d35 migrations/0003_certificate_renewals.sql
5c62da30fa9db594a4d656c77ae676d3c388072b6a85f91170ec7d911d7cde43 migrations/0004_admin_preferences.sql
@@ -107,7 +103,9 @@ faa7e50922bf180c363f7627fbcfe8079831e36282220d17481cf69bc64b57f1 migrations/000
f093aef9639a959a2522946995a02d2e67c464e794c3ef665261a9a5b348c968 migrations/0007_secret_store.sql
90f6dce865496e018c047133d3bc1721fc2a19b2f4ced22e2a3625d9e9ff18ac migrations/0008_config_history.sql
fc7585fdcbe99fe3496f0e62f4c4e2161f4a0173222ba4950331aec6c44e50f8 migrations/0009_certificate_api_grants.sql
a8b1e5ec3236a0c2681dfa1746c526eeef77e8bcb5ea8634d954ac398b4642bb migrations/0010_dashboard_presets.sql
d58a464870895cbefcaa2b4adfd6fcd0d5182d19f2528eef6509e190046cc9fe migrations/0010_dashboard_presets.sql
e56ac3718ea7d73fd9afb1c3b44ca95c9bea424c32166e37ce913bee0e67a277 migrations/0011_tokens.sql
273ce62a518657853428ca44dfe4dd2a56abbbbba684e905a2d0147d08359888 migrations/0012_admin_preferences_size.sql
478220552ffeea3de00e2866d427d9f87f4cfc4ae5232e5c3a774e62425b1e0c nagios/README.md
91233910aa4dab07ae917fef93d018a7a0beaab697f9a0bb889ac613e595cca4 nagios/check_rustproxy.py
a42e01b341725c9da0928edf40c8c3b9c814ef90592f208c52b1f2c56a2ad383 nagios/check_rustproxy_api
@@ -115,123 +113,123 @@ ee50f38c037765fab742e888d4b9fc4d874c31c785c6064d4eca3d2c4d24d944 nagios/check_r
1465396cf1358067e9fe48b3d61f28fd4ac65c577b67c502950ce6b42ecbe3b3 nagios/check_rustproxy_latency
6e036f736a2b40feb78eec17bf34871771524190f283591d1c89e48e6d2271e6 nagios/check_rustproxy_queues
7458b2d74495e6fb1c1edeea4d5a793705476431dadb60627187111f91441e13 rust-toolchain.toml
bd313cacfc1b450a90368b30ae39ab48b877c8bed3c2db8bbd4e4a1f40865c9e scripts/README.md
31953ddf3fda1c9b47fb5de7f85b91a23782b4ccc9555b8423a965659a0a8786 scripts/build/swagger_assets.py
fd1c34599f725ad5f7c3c0cc89e08b11917baef02c83af054ac8528b2e01dd8e scripts/README.md
1d63e9cea61a744f1bf2085cbb7c3d8755b3eda1569c8532574dd4fb6a840678 scripts/__init__.py
292c65ad2a7f0ca13da4a00a836df17c3192728dc02034b75a6a70eb6b5eb25c scripts/audit_css_usage.py
5fc5c7f274aa5a6c07970f7b2e548acabae3dc2a24d598308fd768a8d9de2510 scripts/build/__init__.py
c3066f40f152a2655f88314fd9cf784eddbfa6c3238a494da13d972ef9ad930c scripts/build/panel_css.py
2bf1851e3cf7ac576e38fe4e02532898df2ec11ff8e802266c1db203f29f6a91 scripts/build/swagger_assets.py
e4d15aa6aa864d04b9b30aca33826b833632af02ca643f8b23e219a5ea1298dd scripts/deploy/deploy.py
cc268d9d3dd125c9cbff2522fe3137a561fb4fcbd5ee461d70ef2e3a8241cdb1 scripts/deploy/deploy_core.py
8c7da13e95bcddd2bd1b4f1b22ade1c5ac1ecf54448601daa8416905d4239751 scripts/deploy/deploy_runtime.py
a641bd35400c56f3b68b0a92e97b602d06090f03972ca8f01fa744e90dd97403 scripts/deploy/upgrade.py
f0edbdca3af49cfa8f9c118d3eb0977c99569264dc5d74a85c4610234ea1fe88 scripts/deploy/deploy_core.py
f26297284e0ad9fb4e4317deac24b25de819c10d04fed551134ddb6debc41837 scripts/deploy/deploy_runtime.py
a59587ce8ad41b1bbb431356b58398f9c8ff7cb96a613a91752e907a70e10c2e scripts/deploy/upgrade.py
2cc658faaa610b21b48942c59b955525539ad4503fd60f2a49c7e626a22faaff scripts/migration/migrate_npm.py
a459d95bbb0076f33ade8b273b813308167f9d8d45242b309a2d18320ca0093c scripts/migration/migrate_npm_api.py
31ccad3ec3c880119f3b7002f0c6652f8b43634edad8ab3763cb0e4497d0bb1f scripts/migration/migrate_npm_core.py
b5b3135449f6be4e97088b76ef7cf2428e2de3c7944c9cbbcb1edd2016c0fd4a scripts/preflight.py
4c905a387f210c30fbf12a7738676fd39819bc96bc4a272da1db5f18a2d3013e scripts/migration/migrate_npm_core.py
960fdf97ec899107a567405e9b9c2b70ee16951d5ea26c37d885a7920f7b3aa1 scripts/preflight.py
3d6a0503afc25beab79ddc86864bcbb15248ded7368d33aa64321b8cdff59933 scripts/resolve_dependencies_0312.sh
20124173452b4514bad8850c1637569a9a191882478451e549bd16b432605fc1 scripts/runtime/acme_provider.py
d759315babd9971b354d6f702d4c861eb1cd5c41b1e2e0f1a94ad4d2c2db49a3 scripts/runtime/acme_provider.py
e69813fb1d3a4cdcfb7243964cc323fc21a603087bf2e84188fa42b9d3cee4b5 scripts/runtime/acme_rfc2136.py
174c8059625e3315a3d9fefe74c17f78ed850ff25ee15bfdabc148107dd344eb scripts/start_stream_backends.sh
91c883cfdce94c8c22c98749f611317f538a112d29be4f3fb1fa460b8f29ceaa scripts/start_stream_backends.sh
046b4282550f5cb4e53906665c91c7442be70fc68fd0743466fd832582f7c979 scripts/test_stream_tcp.py
f0b48ec0cc4411755cc0b4128eebce710375afa99344cb76198cf31074f8eb63 scripts/test_stream_udp.py
efb7a1fde2c74f30cd4a1abb88ae07c1cac7378bb33685dd09d52bb30849daac scripts/tests/dashboard_chart_headers_0335_browser_test.py
eb2c9e5c968938b6fbe4675b547ba1f537519878b9095e1fea133f4d83f96fc8 scripts/tests/dashboard_chart_typography_0337_browser_test.py
6a908fd434d2a8a80adfd06a66d4b83c4174e448d453a8c10b1812475d036677 scripts/tests/dashboard_chart_width_0333_browser_test.py
252b401c29e2db50e105d7fb044bcae14752748261a67da1a4025d71bfa2608c scripts/tests/dashboard_cpu_autoscale_0335_browser_test.py
41a2b3765006c138324ae1312f5c1edfed6ce48051230f85779e8d2aad34d2a3 scripts/tests/dashboard_cpu_fixed_scale_0335_browser_test.py
de583adde771e0d6f64ea0d5aed45b1c942a7c78b7193598eeeb77fff00c2803 scripts/tests/dashboard_layout_0331_browser_test.py
1c813c78dfe96ba4d2c9470c93dd24e81be06e6485ebdc39cbfa30f8d32aa0fe scripts/tests/dashboard_metric_visuals_ui_test.mjs
f14626c5ec006477062b496bdc0025caa3366c7037ee598e15cd55e29041d58f scripts/tests/dashboard_modal_0333_browser_test.py
f934f5ce66105a1394b93761a6081a51133837ac038f5faf67a236f676b15c1b scripts/tests/dashboard_overview_labels_0337_browser_test.py
df87420f16c418aa17a8ba8808e506095681446d32a7d76ae0244222ed37928a scripts/tests/dashboard_presets_sql_test.py
df1457797241aab114a5671ce3f0229815dd29d06ab6f6254c0a1048576af6e1 scripts/tests/dashboard_resizing_0333_browser_test.py
149910e6e8d569443a724fd21701e2fd8f6b5e360991d4f710f5d21364402bce scripts/tests/dashboard_resume_rate_charts_ui_test.mjs
3868dc7b241f1cf15bc297104e29449ec3e35f75cb66078439bc4b5c4ee11040 scripts/tests/dashboard_stable_scales_0336_browser_test.py
fa42ec0475e4398d647b3c094309b4332cd2b5649db223cae0b8e821d6b44a28 scripts/tests/dashboard_title_color_0337_browser_test.py
3c1b2bda6410d3348b0f5a05c70494cca9636339bed8217fc4abc0a30981faae scripts/tests/overview_samples_0330_ui_test.mjs
72185f59f31bf04a4e88d53fe2fca52509f3663cb5f40b1d3bc319a5f945ef23 scripts/tests/panel_html.mjs
b0136544af717b75df2cb107900ea65c3f945fdd0040369dbb93919008fb954c scripts/tests/panel_html.py
8d1903e2b17b3d59cd0a5bec3e298e468b3d88ed2dda8b724239cc45d8353c59 scripts/tests/progressbar_0332_browser_test.py
1bb99b148d0297235b707f16865ca077329246fc19ecbb20a143bdeb56a23f37 scripts/tests/sidebar_nav_0333_browser_test.py
12f2b18b0ed2171c49eb8cde2c2b88fc627054c2f574c04bd9847821b00cd4c1 scripts/tests/system_ram_meter_0332_browser_test.py
046b4282550f5cb4e53906665c91c7442be70fc68fd0743466fd832582f7c979 scripts/tests/test_stream_tcp.py
f0b48ec0cc4411755cc0b4128eebce710375afa99344cb76198cf31074f8eb63 scripts/tests/test_stream_udp.py
f0a2e3daa0a49aac54723cb5a281360e49a97c076281372c5cca2a85e096c02c scripts/tests/vhost_backend_popup_0337_browser_test.py
23cb1f790cd5cee1b76ecd85f6a2a44ef2e16b0840bf06beaa2ae13048063b2f scripts/tests/web_observability_0330_browser_test.py
3babdd0fd6e2998755ae88615112f6ea056f5c808245abb6de60fbda555594d1 scripts/tests/web_observability_0330_source_test.py
f55efa99245bbdbc98fe3202e52952d224ac3200612561580d52f6e251810049 scripts/tests/web_server_0330_ui_test.py
0dd1cef06a1c3e1546e2fb9b085bf369dfbed02107b9ab01b4fb4f3b894099c0 scripts/tests/web_server_0334_browser_test.py
22e880e98c7b9d700c7a4c02b9b1d543e5dbd1f4ecdca42813a1083165b32a19 scripts/version.py
55485a513e8c4da2a58daa7abcb15b32ed408a5fc062cf23599fb83d16b90cfe src/acl.rs
63efab1e95d41134fc30cc57320c32810bbae9f81a640bdb24b1be2059e63d8a src/acl/dynamic.rs
4803e44de39b872a5b3b55f9ca9e81c066ba52cb8f6adb879fd57c5f94b93e89 src/acl/dynamic/tests.rs
a22e5ea6381a1eacafe650be0f5cd6e653773e31f6491a562fe0459a309fb7cc src/acl/tests.rs
6f5d3137bf24451da05c1db9f0f9e2a451305db17b6bc2590815ad54a6511829 src/acme.rs
587097620b2f25ce7c6f59f907b47660184b095e3040c7615e74509a82e7cdd8 src/acme/jobs.rs
3f158f46e80b403b658b4e9b1e3710e38c70e47b2d476b8223d9c394ffeeba95 src/acme/model.rs
c5334de6c732d76b2bea7bfc17b3ac305a250bbaad31a5420d6a7d7abb200449 src/acme/providers.rs
b9e57db4bd80ef37564dfc533a96897d8a494671d53acea9fb4bf28d23ba370d src/acme/providers/tests.rs
29ff0647417b0f1d50a4cc075ab51afecff9ddf47db3eb7948d51db5a38f33f3 src/acme/runtime_bootstrap.rs
87f64198770ae026c03a242fa542db2590ca734b02e756f1e5c28d66236f753a src/acme/runtime_env.rs
f8075a5ef9ae5651d72a33ac0f0fd5b44ad0ae30f6e2d2318412177e0ed837f5 src/acme/runtime_versions.rs
e38331a1b186b49fe9d04051de52b603e1fa028def37e4e2f52e386b4d7b29ac src/acme/tests.rs
b6f38f3fd75d6685bbc882ed1077a38bf495cfcf01c397f69ed9b98be68b446f src/admin/access.rs
b4a2a068acfa1143b0032b9cd072c19cf3fdda2580f0b932d11d6368cd2ab3ad src/admin/certificate_api.rs
019f4aa33e3596bc9c11ef8cf5703bcfffe46c15288c0eba83c8dd9e38055fb2 src/admin/certificates.rs
b5f6d49f1add583cfc8cd4aa36ffe4db56dac4de3b151fe8239fe42f507019cc src/admin/dashboard_presets.rs
5e150d4ef0413a2f92db5b6631669933b84d4b4796633d01476c4b39188cd05c src/admin/documentation.rs
958546c43c05bf4bed8121b24db3b3096f9243f3c56a71e1762592b755684013 src/admin/documentation/documents.rs
67b901485d5858d5b5bf78a6260035db32241531d99481754c3533fdf404facb src/admin/documentation/tests.rs
78efae6c43732c0f5527d467bf07d1f890da361925416c89b58bc449d852fc2b src/acl.rs
1d55ecb2e104be1d3b19623a1380584724c106ed234d2e79fd2a2fef1d891ee3 src/acl/dynamic/mod.rs
1eee2ea2c8dd6068dd0bbb058a06d26436f14767d8efbc2010bbdb4e3e523672 src/acl/dynamic/tests.rs
50d9a0009cdcee6dd34a6215755d98f38ea18fa39b9faf070d1a7649eeac6aae src/acl/tests.rs
6189029ba493982acc38c9dc1f47fa1f0c933dec5ecfb95b60f8be83d33b5aa3 src/acme.rs
b9eff08fddf9b8787aa04e72288ea2ca0033965e4563d060ca51f267e4963918 src/acme/jobs.rs
f7f737aa28d2d209f4f20a221885e3bb3596dfc8e3c8c9a22ba53bb975c52df8 src/acme/model.rs
b77026ad33be84706112881dde840e0fe4694b150dd3087d8e106737d07dd464 src/acme/providers/mod.rs
d1dcb7bdfb7cfdc9c3b88cfb6908201c58532bc888744a13f45853079f214659 src/acme/providers/tests.rs
c51fd51177a476be6462ca980e9bc13f0d0b41538f520118b80a685130f0dbf0 src/acme/runtime_bootstrap.rs
5a684eff5f3539c7bb8f66c3b83e7fb62527c52c61da727079d3897e0943f952 src/acme/runtime_env.rs
2f02b08d88ad3c1fc9827e5bd3ef463effba633834d9c05e9c0d6d2a0e8869c5 src/acme/runtime_versions.rs
cfc13a293c126bb511745859669719a14c72dd7ff6be197bc9e0d0e560deebbd src/acme/tests.rs
b28a08ea996befe10949676f40fab5f1036a274c7d0ab1175cf5c43c59849dc3 src/admin/access.rs
3afefdc6640d35bd55f20981e842647573f1ba80248680f99ba79eec9a7c4ce5 src/admin/api_token_access.rs
5f5b150bb8f51f57462e519650093354ae7b40fcb80c4380fd3391a33f4b30f2 src/admin/api_tokens.rs
34e851f725e8605e3c5d9946e56ac035996f3bd9d608169cba3408060d229c9b src/admin/certificate_api.rs
4a8b6406a44508b8ba102fef0d8309e871dbe0cbdbee1e6c8721baaca0c1aee1 src/admin/certificate_inspection_tests.rs
aefbef9839f9914ba05ea62bceff1463da3042b52ee851181ee39d4fbf7b91c3 src/admin/certificates.rs
521ac794501570cfa2e8e50c3a5cec2ca08ee4dbce8ac9c0e0310c09612f1062 src/admin/config_reload.rs
b7b0d3f0df7683b031136c05b314e206f7864e70514fe25165e41fd7a9382854 src/admin/config_sections.rs
c51f725075b5c4fc3fb7fb8b8b5f73987bff698a400e8950e968068b97a77631 src/admin/dashboard_presets.rs
aae1ca0e70d987830bd63948d431f54302083148177c1b551565be12df7fe771 src/admin/documentation.rs
928ba7aa54ea742877cf267ee7ede798f95fb75839f21492063ba53e5e989882 src/admin/documentation/documents.rs
b9c2952e3fcfca540d0ad64f2d3acc5c1f5240e707f4f23ef4d452f46fcb520f src/admin/documentation/tests.rs
1ce779b84aa8f47bdb3c34f83593c1f2f06d2235f0319847096cfcd035434168 src/admin/file_log_search.rs
ab18acf6fb4ac86fb22635b9219a12dac76d24952c8f15538486267a9b4b7855 src/admin/file_logs.rs
c6b552cd5acd9e743c28b1a5564420c214c619fe891ab0561b7d4499aae8d6c0 src/admin/file_logs_io.rs
c720a7cad6a15c57b1f2ae7b3e5fbf30a97c64afb9137915f37b23f4e576bdc5 src/admin/file_logs_query.rs
74636c6560618ee95757335452baf8db03394e1ad18acd54252f54b15746b8d5 src/admin/logging_api.rs
e552c4b565e3198cc2224d1efa3bef21db02a9872cb80cd96f446181035f19fb src/admin/mod.rs
28dd33ef19d7eab08cfa243b253785012d6c731bc4c56b4f69f28edd3d9e07a7 src/admin/openapi.json
d9120f7dc61d27621c5ac27ea49815dc7945c44d5b99d208da503818c198f649 src/admin/operations.rs
30895e05c5d5d0a26be29f667e0f6d72f16a96a5c8a98e09cd11d8ef2d7b4763 src/admin/panel_assets.rs
75bbedd54fdabb2b489642a642ee7c6533f69cdacb85e251763a56ba11174d5c src/admin/panel_content.rs
92b3e05f04711d8001b56816405859631d9e917868e15be965c7e76ef4ddef99 src/admin/preferences.rs
4a5cb4c3a3aa8b9a519020854506ce4be5e688a6c4298260322ae641dded7fe9 src/admin/preferences_columns.rs
9a3f13e2477769ae680c03d63336880c3b7b7d5aba4a91e44390a976608b9b1d src/admin/preferences_tests.rs
4a7a2017ba57c9e7a8b29e73764d439f6d03653403909ef198ded8c48d152784 src/admin/public.rs
059218501907355b22bc84e7e0c41ec72565af283f5e97170ce7707ea5874009 src/admin/file_logs.rs
d58a2ed83319176de4e66790ed292447c9497ae031464ec55cfe27174acb639a src/admin/file_logs_io.rs
77b8c05645969834cc8a58e249a7b60a57d6c47ad1c5cd92ffa5e4a9b5a8e73c src/admin/file_logs_query.rs
743bb7436f77e04015328a0e7e7d425e43ef8aa8d61467bde157e462c1dcdc2e src/admin/logging_api.rs
243f4a9c3fa99781e19c3952354d2632c0ce65e1dafac729567cbe3c177f94cb src/admin/mod.rs
5d7d041c7eaaf4badb9e9d8d48d0876742142a670137e66ae1b9b9b252481fc2 src/admin/openapi.json
f7a356109786635dd58cba590c71c796dbf36b47f25845190c75ee5c6a9fcb86 src/admin/operations.rs
257d93a29d0bda96462bcf695aba8d0ec0f9412c379e066ec46ae8ee034e3a53 src/admin/panel_assets.rs
f5127855c2994d052678865df36d703b74f4a0afc633daf7718b10da18d26e37 src/admin/panel_content.rs
80d7b07d2f0e0494e2a8c53f530b9d1ebc9ba68a98e95059f4553249ccaed65e src/admin/preferences.rs
cc3b08a80f44bc141107eef2ec41802b7f1f7b24ddc220f6375f495736bc0cfa src/admin/preferences_columns.rs
125fa7ea16d126a60f213abf4e5a7adf62c1a830ef96882fa4e0dfc2059f82a0 src/admin/preferences_tests.rs
62ecbae5dee00aeeca3eb806e2605372471dfab971cde8f66f11e711df8fcea1 src/admin/public/mod.rs
7a00e4ab943f74d2b1bdcbfab7f9cd3f584e9f59536b4aca6c4c31f5f5508d12 src/admin/public/tests.rs
9926827131254677a848c178c80850b3ca1993c05b4bf913687dc0c7992ad048 src/admin/refresh_preferences.rs
daef5e663c1bc99a22b3b8fa4481ae5e4916b91db132c104bc050f51b1fdf77f src/admin/router.rs
a40adfbaa908da3ea726d7aa3d45394a70a6a55b6fc0063270ec4d2a2f2026a3 src/admin/session.rs
77fe317c15bf2e03301b22e0552518fe2d7e1ad8d80afaba4e16cd2bf8ddf10c src/admin/status.rs
4b8a0483ede651d08489a4d6e5bb616f06423094bbbefcee70b8755d65c04398 src/admin/status_live.rs
c97881b17b7a42511868b0e48e2f25aab6fb5b613d538f5e1f1967508454e486 src/admin/status_system.rs
6930b4f4b45257772b321b20a9becdf9bc4610274852948ba38c2d0cdf02dfaf src/admin/refresh_preferences.rs
d151a5a8008349fda5af06c46692ea0c8d1e64059cfe81df412ba8a781d81b94 src/admin/router.rs
e135431cb078ddb483165581123c64af8f00ea67acb3a395ec3e53c85d0d0d45 src/admin/session.rs
c4a3f1ab3d74f153f8bb94a21105feba6e04663e5be6354537f5897fc60cb92c src/admin/status.rs
6a405b189f40db88ca635ad054fd43801aad8bbd3e7f00f1b0e99bb57d4cb941 src/admin/status_live.rs
8315c856cd50f1072593ecb30c274d42d82f078b5fb93e7c68a8857cd81b0841 src/admin/status_system.rs
cadd7dca29b9aad8c77c7bc23dd249ea37a15adfa4d636a8ef00273a91ce35dc src/admin/tail.rs
3d9dcf96d8f05f1f03ad41b6f53d2b743ce6f9fadd608745d04fced0efabb9af src/admin/tests.rs
95ce9f0025a8c6ca578d12231259d5baea38b493969c5160a2742ba31161a309 src/admin/tests.rs
c18c369124368f58524652565102817ed69fd25b222b0c5e737ef7632b45dcfb src/admin/web/acl-picker.js
48fcd818c522fe80f2c7b2e218f87de6a7772ffd0a24e93502780b03ec78e335 src/admin/web/acme.js
c0d08c5b8fc13dec9bf53fd842d224b9df3874fc3bdf31413e3ab182a8cec2aa src/admin/web/app/backend-dashboard.js
a085ee336e6669229f3ad2f74536f19bd088412ca79bcaff30dff293f1e1baf9 src/admin/web/app/chart-scales.js
d6c5448db12de34a7bb4924411b44d09c7144bc3e659bd3c8a7c0ebf1938da1c src/admin/web/app/config-history.js
8aa205bc88530e4c4678f84ea324c6ecbc823b4ed681624102ff177f3867bc98 src/admin/web/app/core.js
afe4e0707fdcdfbce9134f3998e21ef05066da7cb29fe22136a67d288d5a92d6 src/admin/web/app/dashboard-inline-layout.js
851a4337127f332db91f6954f689363ee227fcddde2dda82e55f645940d0ce3b src/admin/web/app/dashboard-layout-editor.js
a184c50e1ca6bb5a14954e31b15f09ff9978fb939064ca724851bbb4e661dc56 src/admin/web/app/dashboard-presets.js
bfb3fbac62f9ff762a298d9c06808ae81ae55de7a41e0bb35ce5c167f333ffe2 src/admin/web/app/overview-extra-metrics.js
86abf50f39862d821cd2e43ca425671aa85289272915132c5a9a5dd1fc4182e2 src/admin/web/api-tokens.js
faa9735c2a02746b2954bc4955eb609f94afebb685cd5521d2629640cea2777a src/admin/web/app/backend-dashboard.js
aed3aea4eef0011c6b51d39736782e7d1ec52662e422f40e0c7b9ddfc19d0378 src/admin/web/app/certificate-inspection.js
f6b0533ffd6d0e6471519120c440619a7fb4b255854ba3dd23d81cb35cd21afb src/admin/web/app/chart-scales.js
e5b39b5592ba81292571033f26934ac0cd42bcdca7395e91094973cecb3eeaf8 src/admin/web/app/config-history.js
2bc0b8f29b76abe2ea3d8666b8dd4d7e58f3873ab2503ec056b2c307c7508a77 src/admin/web/app/core.js
1db5fd8f7e1695dd97bbc81a6da276c5a1f171454edf11c5b91c1ea5bf837d42 src/admin/web/app/dashboard-inline-layout.js
e87de2bc141cdb7d3ec62cbbe4debebb047e8d5adf6015743a97dfb0cfd268ba src/admin/web/app/dashboard-layout-editor.js
716d33249a52548f10153d8e2f5bea962a5f0427525272ddd3a1db636647672c src/admin/web/app/dashboard-presets.js
0be1024d9ff657044e04ed71f275cff3b432406d5fdcdeac726daaee54444abd src/admin/web/app/icons.js
e5a92fa1cb7e049082f0764b245560c41a9a34bcd8a9536fd872d9169fa5465d src/admin/web/app/logo-animation.js
4ea37d56919772d239817b7afa36d6d6386452f9d7db6a889940fe44c29d7508 src/admin/web/app/memory-breakdown.js
32aa11f041eafdf16e66adef6f5847a67413785224da2c19ecbf825996622aff src/admin/web/app/overview-extra-metrics.js
5e62cccf6a9507fe208e9f41bbd08453c3d8c6a6b83c56d93a2bb22550ea6e9a src/admin/web/app/overview-masonry.js
b52456b41451f156edf5f58492119c22726f2cf40cee938527748997e8ee7412 src/admin/web/app/overview-samples.js
d997f62e8bd7ac9d4f16bb302800c98ad943afab663bb0b212ea26c35b58de11 src/admin/web/app/overview-wide-metrics.js
5bde5f0881320464eb133cd44de9eff5211e6816e79f7f7e9574b16784fab126 src/admin/web/app/preferences.js
8e75b0915edfbadefc4388701431541f4d35fd5d717363297b63740b3afd34d2 src/admin/web/app/rate-sampling.js
43f4dddd1c3ea7fa42eeb9d7b655a8db48b5ddcc6c68213e08dfc2fcfbe8b17d src/admin/web/app/refresh.js
daa20d634f921191cb4ac37889504f203ab39ad7e8fd8f33e1b182fc4206c24d src/admin/web/app/runtime.js
959a8f49a251629a501163b4b02841c7ec821dc5a0cca57c8f1ef45464266f17 src/admin/web/app/overview-rows.js
4d1d4760cdff44771c9d880a7c0da81d7233ef6e3a4868111a522703831a7a54 src/admin/web/app/overview-samples.js
db9012546cae8c58fc2bcbee71c9a95956a13a6db9e6d126c6a3770d45a4e4d2 src/admin/web/app/overview-wide-metrics.js
bfd9fafb3112197e8f875dbf87a9a66c81aef0fe83687601a84a66f5e13f69bc src/admin/web/app/path-permissions.js
ea6c65fd2584665a7b6fa48c2c024f7d214d7b5f354193f29a9da8bc0dc4b2e0 src/admin/web/app/preferences.js
9e2b12d34ab1fb29716645720577ad49b16c03b46c482a03695e8f645d170e79 src/admin/web/app/rate-sampling.js
3b8e566d81a5b94ef21c73e7805ae128454f085554de9358040de266bcbdb0e0 src/admin/web/app/refresh.js
bebfe4f835d69df1937dd34ebb2bc6da3b701a5e1825a56b89e7199fcc524534 src/admin/web/app/runtime.js
31201586925e6f4792071d7d03fb05dcc4a69a03c02a4e85eb0746f45af68074 src/admin/web/app/session-resume.js
86ac42bd4a6a84bb137d5a3c3dc3d1f4e404a543447845e6ea6e6e8210e268ce src/admin/web/app/settings.js
8386710827e5ccc9432fb46413368f94afc1016425404a6092b460903636837e src/admin/web/app/stream-dashboard.js
7f89bfae66e2a08a05a112733864e908f036bb1ee3d8b5b9ff23783b870acd96 src/admin/web/app/web-overview.js
bb94697a1cbeccaf80069dd47ab84efe14b052d81c472f8309478bbeb4e05f06 src/admin/web/cache-panel.js
02a9f5a61379e68e1723e64ef4c589500e9a993adcb02fe352f1cf9456c90c52 src/admin/web/certificate-api.js
aec5755244242424e5091ff57c0258d2618588618fe1d52d36f081d03879f686 src/admin/web/dashboard-defaults.json
797e2935a34b974cb0e07a88056c31e6948cc11385bf2a076d902a332882d0a0 src/admin/web/dashboard-widgets.json
c7a18238ac426de4d1cf84a395ee4dc66410d60a57a3822695dc10adc668d956 src/admin/web/app/settings.js
b7ead49224a4ec67181d3f24f9d9634bd6d76756f826a4d5d993300637d1d8f3 src/admin/web/app/stream-dashboard.js
7de070cc8cdde45d7e0932259299e341e25b32c9ffbd87e4fcce934d0f090de4 src/admin/web/app/web-overview.js
f87af546830f01411dcf93749b4f75ca0f0bb3745a3390eff73ee14fc768cef0 src/admin/web/cache-panel.js
9f323507649910561518b63cf6632e81b3ecc4b09501ae53b635705f34ea6587 src/admin/web/certificate-api.js
4810137aa4217a86770617d442cf5dc6bec0d9f19e20bb2155e932a9a111cedf src/admin/web/css/00-tokens.css
84584a08df60190f39c3d3d39c4c30fcd53eba6980ea1f237e28f546585bf588 src/admin/web/css/10-base.css
8a1996d37f138790c12a704deedde9de74f733e7dcbb6be816fd68c281e8e0a9 src/admin/web/css/20-shell.css
f6f58db86036ddbe569f6c20f7867519046771b5991bff5cedcd34a6ce7c9b5e src/admin/web/css/30-overview.css
ca93e41d1c6001d82f4891cacdd712e896a0355d6db9f76cf62dd2f2225a8674 src/admin/web/css/40-editors.css
87df062d7478176641364c07de396ca988a37b1a8bcb892b53cc578fe8471850 src/admin/web/css/50-settings.css
74419f367c4128824ec2f0901a99f54f68439d5d949d41d9cf3d6bd57568343e src/admin/web/css/60-monitoring.css
0ec5a4b64609bb31373d95fda8011bc42c0748c75a29d28de1841813f0e04f78 src/admin/web/css/70-responsive.css
8009afdb04d5170687816730cc930a22fead1fe0248835ca7e8de81d6907395d src/admin/web/css/README.md
8241bd9489dfed082863948829a44a49bf0a8e9cd63c55809655109e393e0918 src/admin/web/css/manifest.json
b369bd8927ee84d05f546c267dd48f849a0857ca4355847302d6ac2381100440 src/admin/web/dashboard-defaults.json
6f2631ffbd130db1f69f153749c018ed488116061a3462e0eeb38e5496fb1457 src/admin/web/dashboard-widgets.json
50d1ebcbaf4d0b2968da23bb943fc3f67131c5d32df3f61d23ff37aad9da5b81 src/admin/web/demo.html
d01a2e48f344fe074c1e63ee0a9bbf7151dfe26140690418535197e13a2a16d7 src/admin/web/docker.js
d4aef376cb44b2a13174dca1cb9383f64c04b0b93279f4f0b4805e76002548a1 src/admin/web/docs.css
bd170341634d7fe98172e0714b3cb8ee299a31abdbc9ab1309f5e61816dbcf0d src/admin/web/docker.js
fec062a5959450b1711948adf3189ceb37edca6fbead2e5bfc03b7ce397c70b1 src/admin/web/docs.css
aeffd403fa1823df7d9fa4c48280b4033b2d15e4cb9f66608caff89290de0a52 src/admin/web/docs.html
6adb291fcc98d516fd9745d9673db16e408854a32cbf41335ae956bda11d46de src/admin/web/docs.js
c52c430b9a6e584012e6014709e1e16ae7e775b2a5aedff8eb767fa867c1cbdd src/admin/web/drafts.js
fd8506a91f769b57b62d9d8e389fb67764599354bf702958a913be79f779707b src/admin/web/docs.js
8fbc2a837d6f20a7f96c856a91fcba967b40a33494fe27ec876ee1b3b685178f src/admin/web/drafts.js
007c24189ad7b497d4da024fc52e6c51393f91ee135f525a356e1f14882b7632 src/admin/web/error_400.html
232b766f278ae4424705af351910677fcd7ac9a20bb8320d9a12b763c91219a2 src/admin/web/error_401.html
16293363c7848471d866092fec5e83023df087aa824989e87450cee7b7ca1263 src/admin/web/error_403.html
@@ -244,297 +242,340 @@ f2cb26eec86b6c03151072f847153df8aea612d840ca8b76367f41b9605bce64 src/admin/web/
ff72fc4c6a11907a16234c4b3c7d7f7bcf82eb72868dc3fbf654b04aff806fc8 src/admin/web/error_502.html
386b9dc3aecebe7727f7cf7bbd2a02ab036a3abf01315975111a51559128df73 src/admin/web/error_503.html
e6c4d91d1542d0a5b47bb8736f9539d43a6713df5a473b635519d5b7eac2eb9c src/admin/web/error_504.html
003d382cc8dd69ad7237679632c04a9389366bb71dd7dca8ee64ee06614aa9a5 src/admin/web/error_auth_provider.html
0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg
7cbdef9192a9dee1f8eac42b85f52b3190f7f6efae50004aa7ad116a910e4036 src/admin/web/file-logs.js
b14e7b3578322f309627f6fe965cc16abcf85da3437c9070be6caecae1908743 src/admin/web/host404.js
6684e7de2540499f9129b78c790f9a820b0e6c5de33e3b60c318d9a1afc931a5 src/admin/web/index.html
03ad9572bb14c34059636d612cf6b876d6361ce601817cff674c57b715dc65dd src/admin/web/integrations.js
882e99059bb2ff110c452eadc0b5779a16af1e34cb1b2536c80cf2056f8c3e8a src/admin/web/host404.js
ba4c7aa74400f35e1bee32c3ff86fa8793ae69685a20d81776c762ea681d5c5a src/admin/web/index.html
5746049029caa135646ebcefb3d419b07eda17b560a9c015c59d27f815df64d8 src/admin/web/integrations.js
7a12f020fc587205694978ab41b660e9a80487d413fbf6e34fc525eabc58308f src/admin/web/login-keys.json
cbd41678eadd994dc5252628ee8ef9551fa9f7d8694ca5abd206240d9a96ad30 src/admin/web/login.html
ac9f667273918e18406df06cb2613c923f50a88897693a03a18f500e22dc6ec2 src/admin/web/login.html
c2e5f554810b8e34fb47361fb90a01073a2e332ee26d82e030956fad3ec1f83e src/admin/web/login.js
097fdb41aa1cd526c4d9bc65e502b05a62e664ef9fff8e69f706b93b0b124dc2 src/admin/web/management.js
4481a592fcce9a2aa1db5e4a25619e19fd459c39039b86c0549dfc427eec1b45 src/admin/web/partials/overview-charts.html
c1b831617bf3cd03dfa416b43791777c8e71f0711c78f9d467fd0f1930ca2a1e src/admin/web/partials/overview-metrics.html
90d9a0836662135b79fb6f183c4f3eff60f8a64392d6c5795ccf1d8ef6862263 src/admin/web/redirects.js
c28f4254ddc1f720d62646ee0f0b590b60a89c0bf447e8ec74ff3dde0c7635ba src/admin/web/settings.js
44b44790341067304e15974afcbbc7cc99686e88c2e43f62979d285eca6f726e src/admin/web/management.js
1f3d10e389c9c20e6eb019509fa6e82b50c6b43bbf988c1210a4d42bb73985d0 src/admin/web/partials/overview-charts.html
f619250d8da61517eb2151f17b640b06057e091640280091e08afd483d67e5ff src/admin/web/partials/overview-metrics.html
3928afce2ed6d5f2d3e29475dd29e4c3bd3c6df180ad5119beaff0c7a58e3b3b src/admin/web/prometheus-token.js
6f33915d33d3a8bbfaeec424651a63eda3d23b0f296ae028a59ea46463ba9fdf src/admin/web/redirects.js
76a01f590d49649dcb1f554c221290919b2a8438e6001737f3c096fa6daec74d src/admin/web/settings.js
8eadbada619b15087783dcfa0e889a81f88e78034d084c5ab616f7912b835940 src/admin/web/status_200.html
c13cffe18a616fe5faa5ee945ab0d94d8d392ae8f32474c62fe41010e063ddb1 src/admin/web/streams.js
0109870251482b02f5adc63a8359a790bd793e21c827951e9b39a4fcd5fc9451 src/admin/web/style.css
d290c048450655b3eeae7c6ec54a82d645d1965513694ab29fdf7eef796a1abd src/admin/web/traffic-panel.js
dfb4ded2fefb90fc1e2d33370cdef10b040a85cb43235ea9dbc1d73bef823a79 src/admin/web/transform-panel.js
d816f2f70eef398c18091479320c36d07f9afe07278a250b840ebcdd7bc715f2 src/admin/web/vhosts/core.js
b31782c61da3623389b57fdbba5bd41d2287d45fc172e9c14360a9dd7accf061 src/admin/web/vhosts/editor.js
8338ce13b5e3e3f421437631124cb691aeb5b9e17409d256b4b9bcac457b3c59 src/admin/web/vhosts/health.js
a5191e1272939014a21b8d16588704952e9a705b02bd87fd8f8baca0a64ca919 src/admin/web/streams.js
ae0259cf32f53d69a22a640b1c34562018d4ffb3864494012de3a31c0bb4ac1e src/admin/web/traffic-panel.js
675ea169d4cc55fda5e291af536338b882bede1eaa6c9d5cc7dfde0779c7c4d1 src/admin/web/transform-panel.js
1657db2c683292411c53e4bc0f67784529b7bc367552901d013e2c88793d613f src/admin/web/vhosts/core.js
3f5fc9649801d091708e495634192fecd76482254eb671551427d3ee580eac65 src/admin/web/vhosts/editor.js
3776f72d7051af86ac5fbdd4581040dd88a424c0c6cde789301b032b159094d8 src/admin/web/vhosts/health.js
b3239e67901f1796d7a974ade0796f2e3590ff8064628a5629b78c7f6ce2f32e src/admin/web/vhosts/protocol.js
2170cfddf5c611672de34b0525d36ade8fcbe459cc21269281d6b615d8601b93 src/admin/web/vhosts/rates.js
bb93e2017f40e3a45ff7f4ca92e4188bb464f432704b6d2c39da9d9621b7984c src/admin/web/vhosts/render.js
8c3da49973c62333559b0e3588c02beb0b425c025eac5cfa82a1bb968f70d9dd src/admin/web/vhosts/rates.js
eb9d692cb0437860bed0da512e321e458a3843216773fd486dd1c1f57287cacb src/admin/web/vhosts/render.js
4dfdeb7c73c2775eacaca65fd8ef1dc80b275421b15522d319d338f5b96288cc src/admin/web/web-server-profiles.js
0049e5c2a35e3ec25f3c1590879684a7b15165584e96eb1b022fb60d3c548f2c src/admin/web/web-server.js
53237ae8b08d261ce21e0aa6fd225bf4f3bb17a37a80529e2a4039d45b13f7c5 src/auth/files.rs
9e8e99e3cdfc32097d0c63dabb19d1e439b3be2264baa67678b46715b3a5bb4e src/admin/web/web-server.js
2d40c2cb32d7365df0c9cd6c8cb6f0483c7a799786c648f4836a0d9740c6c5ef src/auth/files/mod.rs
b2f948bcc3dbd24952c7b1b02d41d06757f49f0c38a78caf51f8f8a687d8480d src/auth/files/tests.rs
775178ee5781e64574bdd7c1fb7d64e7e283977fbd9c98c99896532a7f1df612 src/auth/mod.rs
c96a87e82ff0c1c9ce3d0cd5135903bb53af78d1c4fb6ebfe5fea503bf5644ea src/auth/store.rs
50ef648351271d78310e00caa6d246e0009171ed036a3e4e1144737df8c064c2 src/auth/mod.rs
cc4ddd57a264d12bffab131bf04b5cd22df75d38b61e21ce669f5a8a97b109ac src/auth/store.rs
745226cabb007819370fc6aa680995a03a8a13fa057dacfab0fcc21d0c43bbe4 src/auth/tests.rs
09c3a622a7d2169e49e3369ea5f0f816eab2f717eaf1675c625d518c8f34d367 src/backup.rs
76aa2afe9e7302be39b3512f30d0448d9134300f8ae930d64ada40cf3ad4a8a3 src/backup/archive.rs
732cd4f9ed38ae69f8bbae61b1db8baaa6f86b07ca21c41f24a206a30f0a96fc src/backup/ownership.rs
076350506215faec3cfe7d9de4e60baf29672cc22a41eabe601dcc510c257799 src/backup/snapshot.rs
a00571b409d305af86ab2a57a25434b5ab58706772df440d017fc1fec097f7b4 src/backup/tests.rs
f5afae218f3a6f91bc2f1f5a90094630ce5b7ca574de61807b5a317b13937ec2 src/backup/validation.rs
426a946b2c9e244f09f6cbc5f9664b8ad0737529a9bb5a3d549d27196a1f8043 src/balance.rs
d3257fa5222dd4f70a82400f5060d3cbd33c13862a9320931361fe0e1c5bd452 src/balance/health.rs
59d90e709bfc2be6cd48576f1c1d222891523853a46f2561f0562d74d01f60a5 src/balance/lease_runtime.rs
aed9ed896a122d7adcbedf0897cfd5acf62be3c5b63a71a4a425c8fe8474c0eb src/balance/model.rs
41c3c020a60c372dbf965bd277c330dd9cf2ab079f6608b472adf72a656f4cb3 src/balance/pool_core.rs
a4c14df927c22a7fa46023f25cfe48d3aa3c245bec97fce834999d9bcc8d75e1 src/auth_provider/choice.rs
514e99e0d3da21abd633d53872234bc4d7d0178583ab291e53cf4b1802fd5337 src/auth_provider/errors.rs
d3d9b756f8dd0444ce6937376f27fa2eae7eb4b9304a2ff039d272c929592bdb src/auth_provider/mod.rs
72a6e245cef078585bc39932033c05cd866ee518e9cb4e7b89bca87ce941e971 src/auth_provider/testing.rs
59e1af728770d28265198844b3dd5740328bcb1df96d170aae575d3d97ac42d8 src/authelia/mod.rs
36f87348cf1fbe557f6e94296d30e5b36ae4817b6a18071767d96ea21e43f498 src/authelia/tests.rs
0137e66fb7127f1ce023b1c6cfe3fac3fb1e8be17f1de722f8ef4e66d2594a13 src/authentik/mod.rs
d4a388f0897e50ad26e1d8ea527eccdf656557abb4b88fe714cc2b1969d8715e src/authentik/tests.rs
32a25e53ac9542424debeaf1b637155bd4e60abd8692df082729d81232eb90b3 src/backup.rs
5e8b1d7cf8280bec54521cec9e4a8cb9cdbed5deaca07e3a79c43d2336f22a80 src/backup/archive.rs
15213c87d4393739328ebf0eacfcb45f864ac12d7bd7509f750a241032f6c7e9 src/backup/ownership.rs
6edb5aabd210ff2d121e435815b5501c9eccbd7a8ba230c843c6335a3e24f94b src/backup/snapshot.rs
859cec0bcd604f205d7d8e14d1c810e33d97fd7cf00ddcc30877bcea7c5fc2b5 src/backup/tests.rs
5293e1b06f82654929805b53b9092a82b7503a54b34da3e882a999c5f14ee579 src/backup/validation.rs
eec564a7ef886d18ab75289025a47d62bb702f07d63f92d2f83b7afeecebbb15 src/balance.rs
9158a2bbb303c53bd3d2c779d9eb6e4470d028c4f28f8e329788e9c8c830f843 src/balance/health.rs
458733ad4ec56d58112de6ffa3539ca7e9fa8311048a4e120da96b5f899d5dae src/balance/lease_runtime.rs
bd693ea1d15d8da2abc08c6b5b50d42b693d9d4e9344205e9ad7ae779c19387e src/balance/model.rs
ced69832cb32e7dbd0203d74f5a9c60c2cdc682729f4c5e2c01e7c082e7adc24 src/balance/pool_core.rs
477132dd381e23f1be197c32ff58a0925f41d3e6aa63fe77c10973cefb2b76f6 src/balance/pool_selection.rs
8abb6048303ee8c022546a528d5693167766e7450efd8e20f37ec6fd0fa9ef5e src/balance/pool_view.rs
f1a92d188628ab3fb587375e80956e1552301647f2732cafb55e1471c7f743e7 src/balance/tests.rs
5049d2bd01a9c866f3bad2288d8d594df96bba6045dfe15cd22b4173562b7388 src/basic_auth.rs
d789bf40cf9433e2fd16409d112210fe93154588b1ac9bddc9c7f78e1db14426 src/balance/pool_view.rs
a361c341932ffd186c0b21356c91ab1fe8043f5020d40798266d15ae896a114e src/balance/tests.rs
ae06d238b1451328c751a511476ef39c012f3a397393f939ca02f96ecd0119a6 src/basic_auth/mod.rs
9a8858b7ff461a4d961ba701ca3ed6eaec15c3f7f05eef8e0e30edc63617605a src/basic_auth/tests.rs
90f26bcd36720076f874cd0a21230316b7303f68e23839e987cf51e13c23352b src/cache/cache_regression_tests.rs
7def03ace07dc38a4806277d75b6ce673d33ddaaf1c4855fa0a3372be1cf9489 src/cache/cold.rs
a9eeb93519aae2de7a1282296ced91d2eb8307cc966f8fbf6085af0fd21b0ad2 src/cache/cold/cleanup.rs
e022003be144598100389cf38d76f509b5dbcc035d5bae556bb9b94cc8bd30ba src/cache/cold/demotion.rs
9fc18368e2ce278ef0ad2b73ff016aa24936c2186be371ca4010a1f6f63bb0ed src/cache/cold/startup.rs
ea319b6e200b0c6ca93da5e2af7a3f86e3667cef918716f54f82b8012111b9bd src/cache/cold/worker.rs
d51fa4f320be56c572f767617428c3f3838543cb0eeb7d1f8ae791e1a1ce9a71 src/cache/collapse.rs
27a35689fef639f1aea00d9f5a86adc981c0c304dc3e28e6da40494dffbb6bfa src/cache/dashboard.rs
780c2654c67dbe176d8310678e6deec8022bcebfd85b95df7262069ed41f57b1 src/cache/fill.rs
b75ff512a1c6344483e00d2c67578ceea6b5472c279da0246f3622daa68d7e16 src/cache/cold/cleanup.rs
5fde9e9f77277d557372e4614264512804192bb51f3d2eae785988c17b2e7253 src/cache/cold/cold_tier_tests.rs
3c3897980c31a69d96f9295689d1ae19f2190da758c69ee7a0541f4171288bb8 src/cache/cold/demotion.rs
17b2a157b03f43f49ed05f1f3391aef025a453b748056c67229cb3ceaa53bb7a src/cache/cold/startup.rs
83095bc93132f306c537b31ba929669fedac99af3c4f133d0533b5ed7ac4e6ed src/cache/cold/worker.rs
c6c54984cf3460e6227d40d8d6917d7ca5d809ac364c80effe354c64133c765d src/cache/collapse.rs
346fdb40d85627b583a01fa17c65e45c822dc7216198cdeadbb3d07904286fa3 src/cache/dashboard.rs
be3d80cd340edb47755190a668e2f2a2776940665f95ae7110893b706eb94faa src/cache/dashboard_tests.rs
847c660e6cb7d752f9dac2807c5045a000172c25b0e437cbade06ad7b101dcba src/cache/fill.rs
675d18afb53f25c56cba49bbb431fa39ecd9d7bdd192c7b39b0698225de7a2a2 src/cache/invalidation.rs
c1357a13658e1ed5158084d3a41ba9ea906512774a5863ce9c56cb508d3fa6c8 src/cache/invalidation/ban.rs
0cc31d8e70fb3774c4ce3713b94ed79c184a8d8f0d38ce66a97724410733e17e src/cache/invalidation/invalidate.rs
b74945c7460b76ed7afcb02a45ede09d76f08e0703c47920b9b9d794d4b74980 src/cache/lifecycle.rs
bbd9f372648d89b5e14d5b58029582d37f66a89752caf2c14eb1cbf8f24bb7a0 src/cache/invalidation/http_invalidation_tests.rs
6223eaea3f061b111330cc26b28d85b9b239bef30328025f2ef337291e22a445 src/cache/invalidation/invalidate.rs
c1ee3d13b373672c53b5c9ebfe5c4b66b89f2f2913c328defd5cf6502f96a654 src/cache/lifecycle.rs
bd831807ca4720b3a41a6ea1d9a6c0ed2d935225e2dacbd9313be064f2e4a20e src/cache/lookup.rs
88f6210c03d7a8e7a35be1f123c9c15675af6e6a599c50db927d99866851b634 src/cache/lookup/hot.rs
1779abcee77600c13050a2882f896a5b35625b1ae53737c2554179492ce0e40d src/cache/lookup/insert.rs
55a39fbcb8c034a98eaad44d9efc092902cd591ef167b8d8e14a56fd1a9f1794 src/cache/lookup/tiered.rs
58c03fde09e5eab40968679793b364f4f1cf5fd832bd79ebcdc92bb23871ad82 src/cache/mod.rs
c2e258da25fbf6df7da3de5a80a04d74e85c03ab22c175d7c9a6340b8ea1f1b5 src/cache/lookup/hot.rs
4148106b9baf70e41eeba24233b1edbe8dd6f927c11f2c7647e89fbbfb60555e src/cache/lookup/hot_hit_034_tests.rs
e9c6ab0c2be9f96573f826e6a1b9e4c2b8b526f779e72e95a6463ae07e45ab55 src/cache/lookup/insert.rs
7f02dc8eeb6123cb22307b76985b606afda2d87b7f845894685cfe4a9aee8b2a src/cache/lookup/tiered.rs
639d5be6258699ec7e9f260ec33bd2c366203b6655bf0e98f30517c2e12ceeeb src/cache/mod.rs
0c8b0d550e94d4474dcdf991aa0340228dc5f2f904f39350e6e666a16f16fe87 src/cache/model.rs
ba94226addfd6cdab67d666dc15b8412c4d117111e44639e503e89a95175d0a2 src/cache/model/cache.rs
b96e30ef2b12ad7647915457f087a4d11b6141847bc5e971a75c2739c88c3d8c src/cache/model/cache.rs
0ec4f6d0adea1ed80a3ca17c68c9c85502491da2904d9d690b4169ab9b025b21 src/cache/model/cold_metadata.rs
29bfce96b2ffd102d9c848c7b717a45fedb9aa44511f62f7a1f28a53dafd063a src/cache/model/entry.rs
88d74fc1b7b9ca15a54a8f4893ad3993105952148bcfee099e9bc9727b622aa3 src/cache/model/http_invalidation.rs
09a4417f82984eca8c040ea28eb82b3f129c347d951d4b4fab704bb194d9b985 src/cache/model/maintenance.rs
5441fa3a384add28b6fdd15300cf2648f8237dbfc211cad9117754203be76911 src/cache/model/settings.rs
55dcc0ad84d6b73689ba43e759d5685c406419c7733e23e78d5b072891ab17d7 src/cache/model/views.rs
f52123b588205ae96c7cded93569e99a1b4db4b361e1f7a91fc703afa6b6f6e5 src/cache/model/entry.rs
ff1c3a1a3bc0d26218dcc943ef951c2a212c180bbf6e4bf1c542ffbf12a9f150 src/cache/model/http_invalidation.rs
ee9508498d420a726abee1d12ca21fd5117c1908ff53c2ce29f01de4380f26ac src/cache/model/maintenance.rs
723c6f0580db70b1ef40269b28a4aadc89f024b25be00afe73a971950663d252 src/cache/model/settings.rs
14f3f8ae5252a2dc13fcf9556523ffb945b77ee92a5fb4939cd27e113fc61660 src/cache/model/views.rs
e26dce02df9c043329ca164fed215d271781bce8fb03240f528331bf3df6b9d7 src/cache/policy.rs
989df11e246123f760ef31689370f3500e0913fcc6b70d7ac0c2c95e2f412fb0 src/cache/policy/request.rs
d426d341a7678f8ec70406d86052c97d0f49804bbcd8402951103d61a735b9da src/cache/policy/response.rs
7e2bcfbb2b57732cdc704d9df7a9298fb80e735e37984431bc936630e41e7f8a src/cache/range.rs
7ad96b3baae36ec49c90d03b380e749944fb3d54abb045c90db3a35f6b89b1c1 src/cache/tests.rs
981c33d8321c0d5482a00dc3cf0911e95fa69a29c68a2f5b6cd4cff27cec1cbd src/cache/tests/cold_tier_tests.rs
22cb9bf20a0d25bee30c1e2213dfe8278ad7d0d5d294275d47d4a6ff9ac646ab src/cache/tests/dashboard_tests.rs
d7a02eb7bad37e6a5524600aabb5e60ccf50e26c7a487e2806891c9d8acd48f3 src/cache/tests/http_invalidation_tests.rs
1db70ea5f3cc1f1543504a68498ba23cafce5f9bb48c4616a4bf70d9a536790e src/cache/tests/scope_tests.rs
9aa6bfdb88b5ea4ef0ecd72e8e3c6e82b5ab9b37051ad300b6a5dc12f8579ddd src/cache/tests/tests.rs
444d9a364339248bdb86f2285de6d7e598b880502e25a70be0ee877ff3e52ea7 src/cache/tests/v022_cache_tests.rs
a8a0ce446eb53ffdb8f8d97efa3e1344410dc3798b25f62b8817e8e160d28940 src/cache/tickets.rs
28f904cf4827294f2f759e6d3624608ed90059b64e42864e55dc9213824eb655 src/compression.rs
e2f58279499b7c3b47c65d3a7814bb5c7cd6506544d939875fbb0f91fa9aa59d src/compression/tests.rs
7bc8285cb2012506fd1a38a71dffa561f885f4ab65a9b2a7c8344319e3cd1cf6 src/config.rs
34be288c858a037c7174f6c4dee23178b01b565aa9d65f0951f351e1685847df src/config/backend.rs
beb831a208c4e5f7ebe8327686b1e0edc66608c7032f895cb4fc3366b189aae0 src/config/compile.rs
490d05d84b2fe349ddde54eae44880993fab70811652484dd9ba38b5069d603b src/config/internal_routing.rs
a19fc675720c7c2dbf4bba706e1b1c30c83c6df1da3f29e3cbb3b82442e7246a src/config/model.rs
f66c2388f31b19142c12469df23d43451e0f7c48b158d4996920282c06d32076 src/config/routing.rs
818545ad36af3ae8d56976f318349dd1f5264ef5665fbf94681a81b79003f8cb src/config/snapshot.rs
76cbb4f1339de9f13d441561a3c4ee6bc5ff06e90ff0f019b7f0239c74f9542d src/config/tests.rs
23541660151d9005555a07db6d491455d61b185f0cc70c3ec3a0bee2aef08dc3 src/db.rs
30f0b7269224a64e3f1ec9e6fbe9e27ddb1d308dc84cc9882e50545cf612695d src/db/certificate_api.rs
20ba52d3755c359d670968efa53b0604ff7cbed63a416f9f765b50a1f45074ac src/db/dashboard_presets.rs
70c255e15f2b8c030988a2263b0e07b5c16e7e26770c72becbcec6da4550fcd9 src/cache/policy/request.rs
6ae7a79886dbee537d15830c4647f59f158a2b31425d1c0f06218590c261c968 src/cache/policy/response.rs
12a6d5d9d6711e956391b4b952e8149658b8bf64cfcc0a4c59c281f2630a6aac src/cache/policy/scope_tests.rs
c3a4300f7cdb6b2b7c16fbc489d49ba2451331bb4a55a2d9f618c684fd61eecb src/cache/range.rs
e1d3f0512d6290b0ac4998f47398a0a7c404db249c5bbdbc4ad4767071013a72 src/cache/stats.rs
8a300f5dad44815401eb23f4f5a157893761852ba4f3005041e0eb3f16892ce3 src/cache/tests.rs
98af70ada3b14f3ecc94c9c76b028c1b0de1ffd43d9836a0749de763f64d5402 src/cache/tickets.rs
a1132b980833b1b4ce2bba9aa681ff255a0e2d61faaa65a316860da503ea1e27 src/cache/v022_cache_tests.rs
4ba0a2e1c21a79cf68bf34cca1f2c2a3d7b20aef38f3b9f510743e60bc1d425f src/cache/varnish_compatible_tests.rs
1a2eb6684d9ea4a7e90970d936c75f7c8bbec46a4181fdf273be9bdac8eec847 src/compression/mod.rs
8de82d9188c9c337690c6ec4afdad9fff20651c358e34602a93d8bb8c3d2489a src/compression/tests.rs
e695079333ebdc2a6e7b3337cfabef171b3a0a01da0597b77feff70d417f833e src/config.rs
2c4312dd7410dc231ed204d7d201d716eedb41045fd95df7cbb51d5561540d99 src/config/backend.rs
6ad644fc4ae821d2c30329ace36652a7ac12d674e71e7dbdcdda3554c983f6f9 src/config/compile.rs
fbea01fea1213d98aea812c1b78d5db289e09200b98ae514d742235cd3fa70e1 src/config/internal_routing.rs
cc75698be2828000e88f2c482bd26f2fa4050e3432bec1b5a581dca17fef4a37 src/config/model.rs
75839458ddbc7480ec4492a95eeb67b617ba7d62f7b67e7a767a3453b74dbcbc src/config/route_policy_tests.rs
e442c6561723e8dbf73d25912227cebf7aa84ac1523e8bda8e6b4bdad1b674df src/config/routing.rs
5bc35ebca158aa399a1d2273c21130e1cfaf5c89cb401689ef863d7deca6b801 src/config/snapshot.rs
477cddaa1e1c0b9b605a2f550971a04cf30287519cba70cfbb2e1860e3a93ff2 src/config/tests.rs
f35de0ee1516ece6bbfd2b2a7db7d1e0f5048b0ec47cf2a3765dffb799abfd93 src/config/transport_tests.rs
a09f361d70510d6226f4dd6785e649c3dc01ccafc70a4c8b0dcd098e224129b7 src/db.rs
c290d0cb02dfa5d2c60ecabac1cfb20c8884e3d14cca1944906c6d8e677f1784 src/db/api_tokens.rs
892ec0e5985700cca399c861197f6c9d2f1c823d8d95b88abac9d27ae15a069a src/db/certificate_api.rs
5491dd7bb9b3fceac97c4dea63aba6d5c7abcb0fbd06e36ae986e00c79bc07d0 src/db/dashboard_presets.rs
2ff744223934685439374572b83fd639bb3eabdbac168bea4a6501a6e9ce0ee0 src/db/history.rs
c05a02618d476e4670b4f9930555f199cac776cdf09edb8fcc1a675695b8f99f src/db/tests.rs
19ab6c3ae37009c544291cb0b65c1bb9c721e168f95c1ed3fa88155aca7463ad src/demo.rs
e69031875ffec32c9f548c43bed5f1a5c4c626e9568c9173e3b093f9a8b73036 src/diagnostics.rs
b45d6040810c93ae2acb04ef6b0a59c189751edf9008cd81a4521c9e04e7f07d src/docker.rs
93a8a3f6ba12761ddcbd19f22f32fb2c0e6948b660fb774ddd7ebdba9b6562a5 src/docker/client.rs
4c2c988191a97f81568a8df64af772463d010844f1abf327871901cdb32d4336 src/docker/model.rs
5f4610c59b2d4ff6a205a2d9ae11bc84be689b2a68302459f546b3f43e53593c src/docker/reconcile.rs
a12c47df4b90b6fc36c978a42477e6ddffbbbf69aed7252101c2fd829709bd6e src/docker/tests.rs
2a02ea2e010cde1b1fe99b2e6baa80ea344ea6b332dbb3c4be795b71541f64a7 src/forwarding.rs
057e652f216a1478638cbb53b1c64ac88c7f413a65825caacbf893c5af004a63 src/forwarding/tests.rs
af7a4e91c32ffbe4701c8c06e48ba3f8bbbe55e8b9e877aecb30ccdf858e9e8f src/geoip.rs
80850f7acd1ed4c0048b065e9e81740869a8efbb0d9b5d2e83d8ec7fbc494703 src/geoip/mmdb.rs
06aa056d789742cc11c8826c338d985e6a6fb22da6c3530937eec38e35478034 src/geoip/mmdb_tests.rs
d3dc58628683472ef4d205fa5c42f0b7ef83cf90846146d96941cd3b36a40766 src/geoip/tests.rs
70091bdf6f38f03b6df8c8c4407c300677e57f293130122503f936500963d37c src/geoip/update.rs
c1b49bef28d87712e4c46138e70b52c4bcfb1a00196579031fac043d0b4aaa7b src/geoip/update_http.rs
9a912dad0b8d1e10410fd95d0da3684656092ef85cf8b23eda4ffe7fb92921d8 src/geoip/update_tests.rs
13269be4ad8d3e4c91893753f654c3022e6f86d171c5d1ff21eda2d9185ec61f src/headers.rs
fbb6859780309f8cca1aeaba43fa67d13a1edf6f5a60c5c982d2067b71fb0846 src/headers/tests.rs
9e21edc67a14ef8a2ce3dda0ed18784d0fd0be305973e5d216058b7ac802c6cb src/http3.rs
eb57117938a447d9a6c2ec1806ebd9fecc640a68c88950a8b35fa9bad14d0dc6 src/http3/body.rs
ed25da264a2d25b40c4fcddc97c626a3a43d4f3a9be6c6d31af58b9e4925c198 src/http3/diagnostics.rs
37f54961d8f52b79a75fb2be31e81f4fdb1e66b06350345c4483d6197b8a0bbc src/http3/identity.rs
f2a8c2a98f4ccfb708f9d75a48ec7b351232be9b8a4364b7e63ba14d28eead7c src/http3/settings.rs
a31db7eac12b578b1eb5a74fae98bbe36312da95b7d7173135dbc41fc454da21 src/http3/tests.rs
68fb5ab6a889995a66ad12cb6acf88c5d04f0aca572e2ae15f1a9cd171a5f24b src/lib.rs
2551189a3e5701371026c9abd2c5d44d37495f28fb67e220180421bfd315e3dd src/logging.rs
3849a09a7d7402dc4d867b646cda153c574327ddd3c707578ddb4dc6f95b1563 src/logging/error_runtime.rs
a16613114273c54b6d2a16a94fa8748550e91468585469642622bd2c86087baa src/logging/format.rs
f5815d778cc1785723faed4abb15f9880f22b6524b5289fe1e7158905aac0986 src/logging/model.rs
42fef80af50735cd566c6801dad86ba81be98cf4a8ec7d45c2ea26f53f556a22 src/logging/runtime.rs
03c50bd14fecd421b0901a5f0af891dc277e35420e32068781e3a6d78c88c24b src/logging/tests.rs
3836ed14b6710e183f048ee4d7807a35366c27a27338057fe4a2811129b63302 src/main.rs
fc8e99eac83c083d65101f6402aacbcc8392d66508c31566a50de3ebcb12e7da src/main/admin_runtime.rs
42a9197475823f67c1ca762951ede5d7a2f10124bd6ed4d1a3365f4297be894c src/main/bootstrap.rs
dada314e1709e960eb2e225b4d10c3cd12606c62c4bc2c0220aec1ba133e4ade src/demo.rs
9c6b538245b2bcdbef851800ef87d11a6c370de5451488a08d9fab6d67fbbac6 src/diagnostics.rs
045593a42a6e4f632e5052c655788f0843faa313fc929265b89509f6dfff91ed src/docker.rs
9a68e0d5bd98541314f1ab5a4c2e344e9837f315bd39ea3988ffab6a0f0425ab src/docker/client.rs
8feba6d9eb7fde798cbe8a9e339d6a5346cfef039980fce46727386717635eec src/docker/model.rs
f095e5e3c564df409874019c647f7cec2156bf5a845b5b84864879fd07cf80ce src/docker/reconcile.rs
2e8d4841665cc1d29e6262a1789bb64ced1cecd0036681cc1ef7b5d5bfe22065 src/docker/tests.rs
0b6831d03841a1dd2d6ea73d8268d0b3fda678c6d9332447e269f1e45c60c0d3 src/forward_auth/mod.rs
6c25951cf69e972c47cdf815f22a5062916b2b768e39d55a26a5fc9e625a2344 src/forward_auth/tests.rs
5d32cf9177a33d6fb50452541805b10ae1b0833e709d833f5bd1fc6907193ffb src/forwarding.rs
b266c0a464bb0c6b581e423d08304a0121f126e8e42ef3a8469cadad42810fdf src/forwarding/peer_formatting.rs
b821e2efd2939cb13d01e402532f33feba6682acb6a7a3223be086c1cd171820 src/forwarding/tests.rs
0541ad50b7bde1fd958fca59d2a4f9a0fde197bcaef68e6bf892ffdeaac46be2 src/geoip.rs
e3646c044604bbc169aeba549d973d37b8ac52f88bbab4a0ef74c6ea69681eaa src/geoip/mmdb.rs
1e8777b295e6a2de15b55548e8c4c3a4166b1c5af18d4d33bf067d8bece1b590 src/geoip/mmdb_tests.rs
cfc75486e69c310fa6e7bf0448ed39a20cdad9dd8afe75e01592fc7c76a380db src/geoip/tests.rs
3376ea3d018faa5cb8a69da2f1d5b7cdae4bb4b8f28f373f8a0a2996410e1bb7 src/geoip/update.rs
86a16add6757a377264ae6afe071039851d5996e80baca9d4882b71889d7c2d2 src/geoip/update_http.rs
8c72a10f07f82e2c0864551a3c95e261129c23cf6072f902d063564b6ea6770f src/geoip/update_tests.rs
61a2b01db9010f9debfb38b43a26bf588438a5e470dff938e4e54f9d7ba46732 src/headers/mod.rs
3e9a19c221fa7e63d097c185470b17313da7c200a73ce50f6c069b4dddd37a96 src/headers/tests.rs
027d414f51e910ff20fbf3a924ab6b136178858f27819a046a922b1d8f349cd0 src/http3.rs
2554fc93a555e1f39d12d8c172097a4a4571d79db2b6b871a5a1bad0e8dd2778 src/http3/body.rs
741972222414c280104acbcd8f8071bed9cbfcfa2f503c487c11c56f553f53cd src/http3/diagnostics.rs
49ce4452ef7973cafa2e4071e2e14be0e16000e931a6501e4dc613652e49e230 src/http3/identity.rs
0b41072f6bcf28725c80f471584cd55d4900424ce5d55ac7f6909813788c63fd src/http3/settings.rs
5c550075c7b0fe687ad02095c1cad57b46b579fe21dc5371525b6c77db459886 src/http3/tests.rs
ad0fc77b4065e4e59e97db60d77970c7336386e43b779198abcc727245f8c791 src/lib.rs
e8399753a6ff49b7de19edb0a268bf871c9979a518354160cf07b4669051fcbb src/logging.rs
e4be3ad758c3d93ce52dd3b1ab1a87810e5226339400ed997607a5b746354751 src/logging/error_runtime.rs
e9795eaa3e743c09f5ab0e18e6d359bc97a3aee55510c74923532d64df90c873 src/logging/format.rs
66c4596e644d9dd539d468575e2de6459bac2624fb1e1064cdae61a21c2e0240 src/logging/model.rs
bf898b318feb9bd3dd2c335b6b1bad172da63fd5cefbff3e267421ce37c076fb src/logging/runtime.rs
3d6ea5508bc9609c6ce99a3a1d3fec2fafb95a2a8de579a80262b73c6eed3a63 src/logging/tests.rs
cdcb3806669207c17279da959bbfcfacf18cc192f24e862c0afa0c389a41631a src/main.rs
87e69b46c548b861e9d90d9d2e42dfc2630891f4583800b1942f9065d25e29cd src/main/admin_runtime.rs
224da3127b5f846a8932fb7f4b6e23d0dd590dc0170506267381d66042c64666 src/main/bootstrap.rs
5a98d0e249338e73aa57ae2e352dc16db2f79c2d042c1544ce1c2022da76a31c src/main/proxy_runtime.rs
4dd7c1084a6214c0f2513ebf86fe8ef144470695557772bfe25979edd64491ab src/main/shutdown.rs
b240033715323e7cb7b9bcaf92edd481f7089b4da6bab25005ca6733da8756dc src/main/shutdown/mod.rs
0a6578a0f4023b9f8d73043bf942dc5e096ef9afb8c0d2f131c14f65d08ac01d src/main/shutdown/tests.rs
e5c2e9189777725f4afa1c3c052e4f75047ec3f2f486630d990f5d3c194bcc96 src/main/tasks.rs
49d7e104585c8791ac9a203dddd95802784b68c0ba65d6f90f3d57154b4c7871 src/metrics.rs
201ab702722d6d21585fe8bb49939150d61bfac7ed8dc198b98c2cc6098ffb5f src/metrics/global.rs
49b11df65b4fb0b5b727f9f867436584680b45e3f4fbd53f74bc0cab8b9a88ec src/metrics/vhost.rs
a54b5b34ea65f29723b89b81bf7968a238e8247b9c9760f3430d3504cb1017c9 src/metrics/vhost/tests.rs
a4702c95c7e5077c2ccff86e2e3216e1da6ff9ee4383ae199a0aff2f78db325f src/not_found.rs
668840e7bd70590e2e13f59b6d5b9e43ca7c877f3d07c71907baea91b5bba6cb src/not_found/tests.rs
d66936cc15fd5d56566d40fa8cab93f80d94b05f10ac44478a30f63572a95e32 src/pages.rs
da40e8ece87245efc49a0c4e750526b1d19fd97565856bd5caf641fcc1dfe18d src/pages/tests.rs
0dba7b6a33866cc7bbe05509b01961e545892645d1dad4eb88ac8a4bec65e3bf src/prometheus.rs
cc6fb14351b009a16d07ed0f412c2bcc68cd7e551421aea03d8d97ceec9bee40 src/prometheus/core.rs
c46882ed26742d93806a976257674326e105b8ca1fc23f5ca81e317b268057aa src/prometheus/docker_misc.rs
d3c6b08088df0003144544936a55e2a3661aac85292821b594f00adfd19a2951 src/prometheus/model.rs
064b2be7fed2f6acf526b1cce070c1bb1b943d36f08fa50bbfcbdf3cf1c9318c src/prometheus/tests.rs
ff491043180085800b786b77e6bd3c400b60051012482918440cd5dc6493d1e7 src/main/tasks.rs
a719c51aebcba0f16f8810c37618201954e79d6806da085ee6c5e4697a05b521 src/metrics.rs
9c7ed78345abc33d260f8d5cd52339bf8de748c08b53eaa2bdea8a32a0ea2a20 src/metrics/global.rs
31154052757c2bb975b8ce8cad67f3fad7b5132b615a73d5b0d7bf1b5311722b src/metrics/rate_sampling.rs
f27f1f8bed769883d1a7ae6a48f89f7712394dfab65d05702b0f944f0a45bd8e src/metrics/vhost/mod.rs
ee401f943bd8b2706c33419ed69102269c08dcb4ceba6f4be2ac0861115874a7 src/metrics/vhost/tests.rs
f2a632f9d3d7f9985ff796545263ebe03fbf5e9a5c2e88288e89a700d3af81d3 src/not_found/mod.rs
fa2fc6c6987a8356a9da92cade10db62426d8863e9cef3c0d4324c45780c5b33 src/not_found/tests.rs
c19998732c2d65b464f43d17a58644db5bcf7b4a6dd4c31a65a091c57692a5bb src/oidc/mod.rs
c4dbe0f8ded237d45b6a3977bdc7544296e4408996815cae9e28af028419d0b7 src/oidc/tests.rs
992f49379e44361d98088d8f8ca1013d62846a54a5a35c089a5e0e1a9488e08d src/oidc/token.rs
eb95c68c8639ce3ae658eea3334795cdb5e129fb2ba10955f7dffe06d8909b57 src/pages/mod.rs
86008e469c84a7242b7d4947e2116694ababa2cb26120f246d00923a7c019720 src/pages/tests.rs
5650a3f70162abd97021e0a51a92cac2c4dd39004426ef99dff36cbb0741aeac src/prometheus.rs
6c97eb9c593a7802422100e8130921e63b6b9b1b0387b6860af9f49581cd50b3 src/prometheus/core.rs
2321ee667569803dfe6c92c0e98adba0ac8fcdc6a3eacd5857233f1acae93daf src/prometheus/docker_misc.rs
cc909edf740a091f9057912f555dd888b644fa1cb49647fe0e2e1f5dba8ff5af src/prometheus/json.rs
f9aab3297fcf647295849f77cbf99197f4dd27efd6817e54c03451e04972430c src/prometheus/model.rs
f8a9541b568ade946dfc0b3353548a773a0126f705b537815ce0523439b07ef4 src/prometheus/tests.rs
028e1dd9f2ec2afaf699a6b21c2d4b0d96e76f11f5e4ed009c233dadf0f3be51 src/prometheus/traffic.rs
e03f0d6b3f35dae6b98c394837aa0d17e4d73a8306767b4c607868bc00c30286 src/prometheus/vhosts_backends.rs
fb2d8d45cd2e947f783eb981b37fa418c39fcb327a3af17a648fadea7f37f888 src/prometheus/web_php.rs
b5856fd38a957b2ca9c7e3606d84d13d5cfcf3a45b487b15ae1d2dc248cbda57 src/proxy/body.rs
16b9d4db3b20b2ab14c47ac14fcd18d3d8ca77c3ad9a1f37866ebfa60f379862 src/proxy/body/tests.rs
3d2c8462fd259c299ed26493cb2316d0b7349bd32fedc9b0c11e77b21d6faefc src/proxy/cache_helpers.rs
aa49c9928bb91e3d5bae0d340d88396a0417b9edec6192d41e1403533319a897 src/proxy/cache_http_invalidation.rs
771cc1a38927f96e593b144dbab66cd64a5dc0ed42d54b1615c359845b2b7223 src/proxy/error_fallback.rs
bf2e6bbd64fb45fceed54af0bff1c1e00da22b79c64dd8f5c23d2af0dbab0b7d src/proxy/forward/cache_lookup.rs
cfa941b60b5d167ec9d8e2566ea3342b588d1a9078a6ca711ad6062ddf00fec9 src/proxy/forward/response.rs
3b220cfa474325bfe7853d62daf2c71059d0dc582ac749fe61e2864094545d63 src/proxy/forward/route_cache.rs
cea9e47ecb31b0e4c0bbe0eb83e0e53b5e7bdcb9930107b67a8c34eef01a3f49 src/proxy/forward/success_finish.rs
902cbf8dc3c1ec8d25411bc7fe0400d301f3a7cc885afada594e4eaee3bedd08 src/proxy/forward/success_prepare.rs
ce782dce2f7fe65ef93d08f88eb8277162ad9b975c1391da9c45869511196621 src/proxy/forward/upstream.rs
b05c4f5c009c5e410f472ceaa999fa3a9c7fb60364d5b62de219f0924c9d524e src/proxy/forward/web_source.rs
a9c011061585c067f04f7885215d53e6c40474bdced15d2be5a5ba65190910a0 src/proxy/forward_entry.rs
cdbfa0e7f49200dc70ea6df8f0594399ee3e0c5fc3d279cc336e2d83d0f8a535 src/prometheus/vhosts_backends.rs
f1b0254f53b1a4336bd8170ad441ba072b49a114e497b985cb20699b4e375044 src/prometheus/web_php.rs
315cdf2ec1479433aabee00f616e8c0a9ebe4697bd4928885433b80e52a706cd src/proxy/body/mod.rs
5beeac12ca7f041bdb9b74b2dbc9781f5d5524fcf51294b8b6ad88284754ef20 src/proxy/body/tests.rs
1ecfebeff9a6b933368095b84762e2816fae8a99339e0b854e67e9e95a937e3f src/proxy/cache_helpers.rs
2d39552aa01ce3d048560944975243d0a58ae762d838e547f5fa65007d871dc4 src/proxy/cache_http_invalidation.rs
a86b10ce0707b51e925e11ea14f7f40fa108a196166f62cf40007a97f9e17203 src/proxy/cache_http_invalidation_tests.rs
df239d37b008691d54e257626009684310e6e3712e777094b8450579d56f732f src/proxy/cache_plan/mod.rs
811a00b55b24638b4bf8a6bea9ed7c0ffe2a758f76bfe63a95e1d31bce1c2c9c src/proxy/cache_plan/tests.rs
d6bd4aafab7b1371c31b3d22ddf949a08ea942489649ca12c7615b3b2d5a109a src/proxy/error_fallback.rs
d0d64fd4dd89efd4a9fe089ff4551815cbeaf5b883ddcff803f1b13b5e918dc2 src/proxy/forward/cache_fast_path_tests.rs
7611390a02341d5f9fd223626e57e0f2f129ab45e03fb1bc105be68d46b86a9a src/proxy/forward/cache_lookup.rs
287012c81144d8f15af4a148ce3ca911d431bc59adff44e697567561aa372bb3 src/proxy/forward/h3_navigation_tests.rs
728576f7a42292386eca9cf450779364da87f94dc0031ba46c6f4d5302755949 src/proxy/forward/response.rs
0ba6b62e8560d6f7b99ca7e387f058412fd44456f5874a8258c1c24595c435f4 src/proxy/forward/route_cache.rs
28aecd57891ef67a618a0f8e4df0aee95d0cb6090facf37e531cd190331d06e7 src/proxy/forward/success_finish.rs
ccc54869ffbcf1edae9ddb96f6dc2191739f8804ee0318b7a6844def6c4693e5 src/proxy/forward/success_prepare.rs
c965dba1d2639b338f3f3cfc17f760786a30abab708dfb7920ea3bc94d4d734b src/proxy/forward/upstream.rs
5d72200f3b5308215b7a3d7b1f910932eaa4712f9e2954afdfbe2f252e2a2d3c src/proxy/forward/web_source.rs
602879a0f8f475fe63b5e27238f4b371277a6780aa970c6b140c8d7fd3074e37 src/proxy/forward_entry.rs
9b25f5e3ea4443a63ee6f45d12e1957dd316fe8e85325fc00ea416faaab1025a src/proxy/h2_graceful.rs
c438066616527a3108f2ad53e3698ae9b2a80961714394cdd0a450c92f99d992 src/proxy/headers.rs
5cb7522ceb9a9f083a15596ac4898559295e38d1138c80099577211de8a868ec src/proxy/headers/tests.rs
8d17b97d90fd6a749ba8d37a8d28c94e6d7302b6c86905f073e2dc595506961a src/proxy/mod.rs
55133ceace2e5262d9c54fa41bea099a27b817d1e09c1cd0e2493fd9cee4da38 src/proxy/redirect_helpers.rs
ad701033f005c9c8c4b160f9b9b97bcabc17e936d8ade273a111937c483a125a src/proxy/tests/authority_tests.rs
0f2eacadf1447db8aa1c1a78005aa262e0f747096bfe7e0a0e41a2cd31a0d17f src/proxy/tests/h3_navigation_tests.rs
d134b0eb617f82f9971b3f3461379ea37c57bd0079bf54748d0c434b2ef1b475 src/proxy/tests/http_invalidation_tests.rs
591b47fc40cbcb36d7c71998933dc873340dfcf2657e16c561ab64361c4dbab6 src/proxy/types.rs
1b58f39045e82b1a290435502f5bf8a52c208c283bfb0d13618e0a7d150bb6b1 src/proxy/upstream_helpers.rs
2d9142e3ae95895b5271b4009ab48236a3a2074ce51179c021193b35777560e4 src/proxy/web_cache_revalidation.rs
d7b4b809b12ee89293df45e9a64b1c9981fb3f89deaab3762f4a3ad3992dd7b1 src/proxy/web_cache_revalidation/tests.rs
69f775f6746b4c6d729dc10961dbef54168846f4ce9cf7fe453ab5a2b879c9b8 src/proxy/websocket.rs
d866e7e72dee6ff54c2418c34e9899cbcd7e4e027d9cbe2b625044dc9062e4ee src/proxy/websocket/handshake.rs
30057732c35c369740a176f7da285e35a4157571a04328f6c6f1494d4ec8550b src/proxy/headers/authority_tests.rs
e87437ccf80e50e0159d204dfdd1f8dc0739b6e375be192fce0af749810258cf src/proxy/headers/mod.rs
af9b044761a56e8a6dc57f9d760b289ff4a6bf37db11dbfcd3ec4da5b8f426b6 src/proxy/headers/tests.rs
2bc63579975387c92a9459a9677b4a49e76df382b1ee19de12c8341a3ead57e8 src/proxy/identity.rs
a08ee061e146cb7a08311106f73a04468c89f77b6ad05a2c623621a2ae9faf2a src/proxy/mod.rs
92806ce1802da9b6771ece474d91a3458eba3f6ed19b0817b897e828a02e7e32 src/proxy/redirect_helpers.rs
de1763b89d98607e20f0f6e3c9b546483927cbe5450efc2e5634fe691fd52ad6 src/proxy/request_id/mod.rs
78bbc1263bcd600527bf71acdf5af92bf186267a9084b076d1f75ad6492df659 src/proxy/request_id/tests.rs
9d7cc19e78f50c9b96e8ada076e12ba96d7a19a51975a13e554c4463e624ee92 src/proxy/types.rs
e44430c54038a7dc26f327b5d7085e965241da2ffef42c1526ebfc5168981f72 src/proxy/upstream_helpers.rs
c3887e344249ce492862c365896731127b7b6fc51bbfda274d7e7aad3a70be0c src/proxy/web_cache_revalidation/mod.rs
5522456cc85948228250feac92308d53b130c4a7af98d24f669aca4dfe6999b6 src/proxy/web_cache_revalidation/tests.rs
3d591201595a3b1e928ded20a8736477a47e5acc3bcbde158f7c17547ce155ba src/proxy/websocket.rs
aa58bacc35458ac615680e774a235191f80abf0db978b68095571fce94c2b502 src/proxy/websocket/handshake.rs
c02d42b5c7eeedbc54c58e98691f7d8e39f8e9be63a48f6d6b4ad6b8ea431b7e src/proxy/websocket/http2_tests.rs
faedf6cc2bd4c42f342a0aa188b634d6b8d280ca0e121032977eadaecb88e9d7 src/proxy/websocket/http3_tests.rs
7e9c4d4c207badabf90630223964bb2ad641f06d4149843cab751f192b8b4adf src/proxy/websocket/tests.rs
a44421714aab37f7ceda19e3ec2512c645d2a3ce5dcb3da5bc7a0d87a442d6a8 src/proxy/websocket/tunnel.rs
15fabd38576b61f650c039acdbf7ac5a5bd24330062475b94315285b2dc258b6 src/proxy/websocket/validation.rs
545247eb00b0116efbd4cc913cc251195a55feded1530e0d8d8945abbae78039 src/rate_limit.rs
e7e3a898e2c769b1ef11e58a13c1053104ada93dd694f8a03ea4b647c44c7d69 src/rate_limit/tests.rs
d40ad63939097d2b91339127d5835a74b618d83b7ba31db0c512f05a82107d6e src/redirect.rs
999cbf85cb6e917db529e30ba4a5eed1cc9e95a054da7b3039c730bb80061449 src/proxy/websocket/validation.rs
80d0f744f7ad9f0d1edeb0d5c23b13cb40431f6b6f4d10d534ed34d4198e3dfd src/rate_limit/mod.rs
c85c9f05dac65cde6878bef3ef7258eb9de79ca14c26ca2492e0e13839c40fc3 src/rate_limit/tests.rs
db254128c8013adaad7280f40a0edd8b196083afef299cd9e193e6db1d698b82 src/redirect/mod.rs
811c83ae9fd0f5a13d35b5443cd37843f82b9d600bee4bb136ba9b87732a5347 src/redirect/tests.rs
d0939e1b0829c25356a0b67ecb3a178c74936dd07baa15fee24eb32544db6cad src/runtime.rs
8d0d7b01871c8c588c40a54330a1053c85024655fbe38516ca6c94c3c19a8956 src/runtime/certificates.rs
8d2443df8dab1ce6f11878e991b4928046b7362c417c5703f6d1f3a977646cbe src/runtime/constructors.rs
92b4b284f1b69dca58e4e3f76199f0aecc397eed26d10123b427af1bb8a4a57e src/runtime/docker.rs
6b2b57888863a396461fff202bfdaa6d8e869b2628cc171c0823f31f388e5178 src/runtime/tests.rs
4036b48925f5b25de70e21dfbb278488c20b39248dcda4cad4d19d5d7aed2b8d src/runtime/types.rs
e1d172acd2b230b8f4dd671cae8862a76f28416cbf0e43f86214a796edd90fab src/runtime/updates.rs
208a378b14ca80e1412ffa19ef1b140594182971e2e797ee0bb95bcdb8404da7 src/secret_store.rs
6dca8da072a3bd610704a391bb6e25c9042971f4ebc60edb47665ce1d74cbc58 src/secret_store/tests.rs
6fa1d8c352bce7a16ff0208bc4cdbe80404715df0aff4ed26910a5df57cbbcd4 src/security.rs
244d72b3dab36ec505c96d26a967420714fd3807268820f680fb3dcea0e88c0f src/runtime.rs
3c71d8eef9ccb49ad93cf6014c5c27756ce0eaeddfca116048f24c483fda68e2 src/runtime/certificates.rs
2d610dc36f746215ad3bc1d860ee624a9233886d73a019fd243e51901503fc53 src/runtime/constructors.rs
eb2b0eeeb617cad704732d87acb667530144836e26edb0189929b1f25f35c6b5 src/runtime/docker.rs
18f7460e3738386e070f66932cd77240001255447a769d6e19a40573254a1473 src/runtime/external_config.rs
5999d7e91ee4231da88c5e9c6d8a12ea9f92d20d6dcdb24319f9f90891d7253b src/runtime/tests.rs
bab3044f4ad8957b65476a0c307bb8a364825b91d2100233b7bd6efa52e771be src/runtime/types.rs
11e99eb425887ad12b73d949f4b497fabc6897fee8fc7647fcb3766269c2fdc0 src/runtime/updates.rs
e3dbd60953db5a976985dd796e4268b41a0f3df59764c4664015bd41e2578058 src/secret_store/mod.rs
b0c4a46b40b09794fd8a037afca380ee3b95941bf75a0e1102a4e520a4bd8be3 src/secret_store/tests.rs
53bc7b1fcc1eab8860ebafccd0c9ed2aadf75363323891a4a4a22114c323b22a src/security/mod.rs
0e5fc2aff856bf6fd959cba12b5ddd1e3b3cbeb0fbaadde906c811cdfafdaae7 src/security/tests.rs
53a4bed4d1233aa434712b946857ea2050acaaf5de011c6dadca5b5c22155a83 src/server.rs
f2f6196632b2fdb2444189624f961edaf5bdfff57f700b0207b57e2491c1daa5 src/server/http.rs
8c6ca5eaffb9acab57199d0349722703b903cfff3752baad2cf2596bd51188de src/server/listeners.rs
71899a9b1897fa22ab9e8eb6a670054a6d382bad78714006b9b218d07ba99902 src/server/serve.rs
e9e478cf45b88994eaffd115f51764b6b07852ee3c941cafdabb20584d3dabc0 src/server/serve/tests.rs
048f2c7a8ef698a172ba7ed844558f4118b367b2cf340748c5c28da7723d8e85 src/server/tls_runtime.rs
73ec453ac2850f6963f5bc33401cb8c92aa26d5123f15d68e6633860d91f265e src/settings.rs
240ecdd70e8662ff3b84c21830dc0cbbbacadd755d56146e54214bc2887f068c src/settings/public_origin.rs
1f1121c3562c8f8f3fdee2113be2afffb8849be2d303e4c4409f87478b6e3307 src/settings/tests.rs
9dbf814870b8636770f579c3d36d16f5b6afe8bc66adfad356c26d7ae7dbce45 src/stream.rs
254840126be2310426f6332f5efbe98147d9d8de1999731f40a3d13325c1dc6c src/stream/health.rs
bdc56be7dae85a97a4d5b8d46ef31ec4177292b1cadd507fe423b6d4ee1403bd src/stream/model.rs
de138a4ac68bcddba3a4f17ceba008bab643359d76a68726a755069f19334bed src/stream/pool.rs
0e8589f56e3229dec556b427b87c2cc5a5a9ffaa9ea84fc2522dc72d3239027d src/server.rs
4cb5afb53c824f5c87abaa65ba5dc85e937255c4a3dd14af16df9af7e01afb10 src/server/http.rs
533342fb3bf8b52743d3a7102ffe345be9f86d553b16a1ec322e468b44b0a7eb src/server/http1_connection.rs
22fca56396333ee7a5031da35b15c3258e93aea4ead6af733fcbb1716e21654e src/server/listeners.rs
1c4b84a4aa9f120ed3b7669a145ef51e90d825c559d40088a2b4074bd84a4912 src/server/sendfile.rs
5f6a59c6085c1250a93a70020661d1e8174d3b600b494d16f8eaf7941ee3bb99 src/server/serve/mod.rs
048a05a46108f50bef7da50b0c03b752d76e586b411de8ca3f55313bbdb08aee src/server/serve/tests.rs
669ea1041c103cb952f7a516363c8fe7baf51622f721aabdd57015e0e0279530 src/server/tls_runtime.rs
38abd50db3c5742ee6514654bcfa0b854fdf6e2a29e034ac2e5d36c4b95b00ae src/settings.rs
b2e2924047019b1ada59c58a4060476151c99be12c0b4092f2cfd201c0756255 src/settings/public_origin.rs
c599d1cdca84ac18da7e7cdf6e2d1556d4df770481a0607edb96f6068cc051e2 src/settings/tests.rs
2ad7888b263e1dd9f0bd588dc35590c9eb232f8fbacd10ea7554cb55b4dbd25b src/stream.rs
c7771539fbb94544947e958e410c46b73f9647011e56b1b229ce7035fe21851f src/stream/health.rs
4b9cb1135c53f4a159bf2fdc1b0c7900653d7651612568f38c12f06e70b8ca72 src/stream/model.rs
42aee5900c3c8697c80220c77a6af0435358e3b1fdaf1a246784aca4f62edaaa src/stream/pool.rs
6ec42b8647c95a0c2025462e0be29c71481ce1c3b1f4d4193926471f1cf06be0 src/stream/runtime.rs
9346cbd71e006e41a7188f28eb86f9b00bac822e4cb31c76c8d6d2ec5d4c4711 src/stream/tests.rs
3070d78a7b88c2e47ed59960fa35e7d220a4cd7d78e48f6c6d2bcc5ff77f9620 src/system_stats/linux.rs
63924d279f5fdfde24d2e0663518a123dd673cc6ed9ce52233c4b6851bfb1efd src/system_stats/linux/mod.rs
d9123c623e2a97d09e41edaa29993b4b2c8cba39a3abdf2b48a205aa9a9c2939 src/system_stats/linux/tests.rs
751d59582fb92c84e0c0cc7c0855c9d0a545805c9ba3be61826e50fb96c297ed src/system_stats/mod.rs
e0676801259fb09a73825d959b89630bfa0b6f1fd1efd3006af29f60537a2fdf src/system_stats/mod.rs
050aa831b1012c4aaade63863f433f01bfacf5d3f95787e57975572116a30ab2 src/system_stats/tests.rs
7467d83066bb6986d8c9103597f7a7617ac677579ed46d573c573ff6ca57bbd9 src/tinyauth.rs
0e1d87554db55e724f30c41ec4a5c80bdb8fc9c8cba8700ac509864d4abf8871 src/tinyauth/tests.rs
1084cccd9de28acd6cb0f0813de7c0953910f51726f73f4acdb2e3ada4593976 src/tls.rs
4972a436f8878dec5f9eb8ff17eb82670b0bfd9e94e821c516575479f5fc1919 src/tls/certificates.rs
436575144aa352498a4696f12c2258bef3a898f3f50e6fbc6d67c3b93afa099f src/tls/external_paths.rs
3e6947dde167f82a5efc407ccf9bd4c9f5b26cc8633d1418a10284bc1a1503d4 src/tls/server_config.rs
31c0f51a3a0bab433f438c3ba1b1cdaf3c63fae6086e7d99b3464a537f75a5e1 src/tls/tests.rs
2af2164ecdf2b7fd9eef7c6e358dec053dfac9aedaa501dbb75df742d18638b2 src/tls/tests/acme.rs
93b359e9defd62506729bc90e56c96783e2f14dd477dd67f337ca83149da7168 src/tls/tests/external_paths.rs
10c4a187e3aeb0c72a8c8968ea0cc8bfae4e3cb2f3d834ebd3da1056c4171ec5 src/tls/types.rs
c2f802d95da4332fb8480e83f0c7502334457b3408141c1c4516c9ba320a0463 src/traffic.rs
36301bcbd20b557dd3d58e77a9175f3581947f577c0bb0b972899cec6786b03f src/traffic/tests.rs
d88c92f5a2a7773bc3e715d24039db35da4df53881f1a0859eec819b96f06d3b src/transform/apply.rs
87f8af5261d79072f8215f1fb8a90715a58e7626544fd8176ce840e3111bdb92 src/transform/compile.rs
b2c5236589093454ec60984b785ad6a4af6149d47f6db37d8e08c17fec7a2c1b src/transform/css.rs
5478670cef87777b14a2dab308f5a9182df56d7ea2f5625bdc94aa7e3d661503 src/transform/html.rs
6b955b13ef848e90dae1ec2906176336a775e9511f3688779203d922ff7d23a8 src/tinyauth/mod.rs
3d40febfbaf38ca2725630eed6b33106e353086081ea811107e4ec9d24f7de11 src/tinyauth/tests.rs
df98becda799b6bf4ff5e2fec7d0e0a486e6b7eca6f9f5ad49ffdafd49a4f498 src/tls.rs
2af2164ecdf2b7fd9eef7c6e358dec053dfac9aedaa501dbb75df742d18638b2 src/tls/acme_tests.rs
93e0576ac9077e645a72d164bad34cd75fe6032dd0516284d08bddd2fbda4b7c src/tls/certificates.rs
68cc35b3cae43b0321cf667a613310a4b9ec5d8994461d6f3543b1852771bf08 src/tls/external_paths.rs
937b95bb8621ab98d7cad3d223e4dee305350fbd0149b7527396d3bdcc28a68b src/tls/external_paths_tests.rs
1ff95e95d38eedeefcf8a931569d628763d5a328aa5573c73c928f06795037ba src/tls/server_config.rs
94b70c52fdc7bd6e32fba9eed9124d030c692246f6990d61e01b891765275add src/tls/tests.rs
ce51a3280331d67c8406d2c5bc156ac0b492afa58e1f328bcda76db0720a6a9d src/tls/types.rs
a897c48e3528565fef8d4e32f0c3c078adfb68655c80e1af011f44a8bcfac6f6 src/traffic/mod.rs
bd62b4ecdcf831d183c7720766a11703c8612d614e3abf38d2bff28df34710fd src/traffic/tests.rs
bfbbaa3b949ae946fa929bfbae3fd9a25f2ce792abbd0ed370ccf5683250bc4d src/transform/apply.rs
ae30a984fa39d5c45a676effbc880c923078ba8120adff1bff10e108350b31b5 src/transform/compile.rs
ffc0740462e5566a96912ab3089f18fc28f1c093ca65eec860fc6ea801fd907d src/transform/css.rs
2b9d5c11d4c02b4a3ad3b9bb1a18c63693f245b854084f5eba16595df47b47f8 src/transform/html/mod.rs
aa99b0d3029ef92c78371cb90e11f2026cce890d1be8e10ebd7fa9bc5cb13287 src/transform/html/tests.rs
a1d2fec07617c8a8dc425bfaeaf77c844152a0a174568a88a5baf4c317cc6c5c src/transform/js.rs
047acd7209c1db19709801abbe3ee1076c1fc33f0fe80cc5d9b60669d5f1bae4 src/transform/js/mod.rs
cf5195ab6bacccae0082da7b3f1bd0382b73fdeb3607f4ae569ca6daab9d10d0 src/transform/js/tests.rs
226ae561dc7b0243c04e55e28cdfd2b76acd48cf614d1919b81c196660b03cfb src/transform/json.rs
b0d20cf6784bf31457ea5a853e2fa5d85f6452395a78a538392703d97e687a11 src/transform/json/mod.rs
216e24e29ea87be4a124f95f898e155f127fd959d7d5d9b30f8cd0b2b5018f30 src/transform/json/tests.rs
6a8c28be93fb590942493c075b29ace1527981542bd183f232aa82b62e951eca src/transform/mod.rs
fde2fa3aa94b30d8536afec1c11348cfbd922d0b5ff89e91d34266e8e3863e26 src/transform/model.rs
a4e1d2aa2913587c250f251356431097b76995aee6e8bf9e35c4978a95491de1 src/transform/svg.rs
d13fa462d6df516a4cfaee9f4e184044c5b6345e311aeb3d9058efafe54e4c5e src/transform/mod.rs
1a56c6af6d48d52e2a73036b9bfa3cf16b5fa5b057593821e1229269b7ea4c08 src/transform/model.rs
4b52257e1a3643910b5e7043852d7218230c0021c31215d90d0f8e384f2ae233 src/transform/svg/mod.rs
0f53c64f442390ee20a59087a10f8f9ffb58053263e9aa848ddabfae1a6f3f51 src/transform/svg/tests.rs
8ed4baeaf79edfa2a84912cc00f1d13c564e1b997efe6888d2e4b5a16aaa8905 src/transform/tests.rs
6146c59f36cf419623747d497b3de685da6c84964df43317bebdcb21deb9ee09 src/update_check.rs
753047a1a731596f4c87351873c48f37b39f54e730795ff7d6895c3e2f0d56fb src/transform/tests.rs
8d38f2f7d4ca4dd453cd0e2337614ec704fbd0a36d3e2374cda43f05dfcad663 src/update_check/mod.rs
c42d000d1d576721af3ffe930589388693001b8113554a7658f6dcbc665b5d1b src/update_check/tests.rs
92efcdaafb9f5a8514daf63d7fcde29be977d7bdcd90162eda0ce00d6967ffbd src/upstream.rs
ca695e246bee5389b3757fe8588e48bff5d0ad473bc8588ad4ba7b52c99d7dfa src/upstream/client.rs
ad29f3fc15c9b627b5d752af5adc9f99ee1364fec2df63f1e4509cb223988efb src/upstream/client_tests.rs
75fef0a488d3f3dabfa03c2eabc5995442dfb3c9a1fde8eb6653a124dda25d43 src/upstream/protocol_tests.rs
8a112154b9631f62a54584302a3ff6a64e4f177ce79aba678b4cb23e466c6ea3 src/upstream/tests.rs
a6b4905c245a22eafb1aa535d303d0fa24e4353db94e1d67fd42b825bf8c5adb src/user_agent.rs
6d7cb73de7d8c6f3780736480f5f81ef08813f4626fd5ed494aca0552cf214a1 src/user_agent/tests.rs
3aa1b07cd26d4a5b9e5e94d1d6a712ef1397d827e7f1e8c01f7195b98d2c5f67 src/vhost.rs
6678192de56bc10c5cf82bdff8da14eece7685f409592450c07478333aa3b99c src/vhost/error_fallback.rs
63e9fc7f7be78dc3e16a89f015a56a2da6ba3aff0197b70ff48321dfb17529bb src/vhost/model.rs
35a0aca3eee8d47d1bab27c83d4532cd572fc3e17b5f0d4a8d4515a8f85b3d96 src/vhost/redirect.rs
9b16b5307e140db97363f56c9fac8319e56df846bb1c919d3bc06091ab6ce69c src/vhost/tests.rs
b7d0f7f5d0bbd74add3fda9b1308350c4f70f8b6debf979fb977a4f2f659c32f src/vhost/validation.rs
70e6cdf339da86183b4cd07567a4bb99135dccea5b0fb4a6c37c4098ec4762cd src/upstream.rs
fe9d4deb34cea2408ac5a1030315989f29a6a7d078a46ac1b59d12aa07f67878 src/upstream/client.rs
80e9952b6a4b2241aff8ee259d286f0381b3c974032adc2c276764d14878fc67 src/upstream/client_tests.rs
56b1e4242f8153391cd9a01fbb3017a2dbda9d02e83da6bbfb39ed44854534b5 src/upstream/protocol_tests.rs
ae880b40197454ea3fbf4d2113984dc372098b2e54b97e87c83cc319de253377 src/upstream/tests.rs
5c42f1befec267293b6f03eada118a11c90adfb4b32e0134d7f0033c7453c7cc src/user_agent/mod.rs
696a2783a24f7f3d69fb820dc3a772ec49ea46ee017087952a8253363a9b7374 src/user_agent/tests.rs
d61c427c1e9cf457f1232b822d73e846e9f329cfbe61215e7ca061d53762155d src/vhost.rs
846707452ccf10962f1763be66b8f56c050b0398b48968015fb799dac289c0d6 src/vhost/error_fallback.rs
a8c5ec5095178a0656a001b0d7100e240d1f32b052ea60df37f8681269638ab3 src/vhost/model.rs
fc8af7342a3b147921e9040116af5dfe680b3df910db2416ada18198bbeea7df src/vhost/redirect.rs
2651d7e3e3fadf3de74a75eafe2bbad8d02939551ccc0504c1a092a0b33b0db5 src/vhost/tests.rs
c32ff8fae0c291bf072404aa118cb81016752b2929c89c7ec845d97006e677ce src/vhost/validation.rs
8d1eff4b24f01d137414ef5419f75dd281879228adb70409645b11b3c78c20e9 src/web_server/config.rs
aa38e89e84f928bd1952d3e2cb67e48ef2e92c5f444c095b6288034adc5e3d3d src/web_server/config/compile.rs
7853f6ec127a91163a4418ca45b93a92f424d5c618f27b29f9d526598a26f7ef src/web_server/config/model.rs
d9178aa934ee1a7236cb04d864fa842faf64156e9103786e3103c92bc4e18fde src/web_server/config/validation.rs
9744e8c29ae72a4d9fbc9a76166a97f8292c4c312295ea40d1cb699e03d80a9d src/web_server/config/compile.rs
5ed79fa5b9c53f95ca200d89a9ba927dda816b61fbdf3b852bd3c9670e3d2d4f src/web_server/config/model.rs
a863ad1337fbc353c0f42caea4ac8b3644792e2aa08c37f992b7882b4b8dc6d5 src/web_server/config/validation.rs
86c744216d7132d8294a6e90ba9be5fd871646006313d1c62506d40c81db9b12 src/web_server/fastcgi.rs
200cba8cb3482e49db73d2c60cb60786175177c7d4be2a566cd281fec7ba7fca src/web_server/fastcgi/protocol.rs
cef44ccdf882d84892a64bdbfdc1ba01380fce5e5f86d2829b0f9613f738b964 src/web_server/fastcgi/response.rs
8186363315bd998919c6c783e6f7501528f257a5c0da77c5d60f423e8670db5e src/web_server/filesystem.rs
0aafd7f37c4fe784b047646e4ce5ddcb5fe821231a41ed0a9f519529385288a8 src/web_server/htaccess.rs
013830a79a78ce803ce776a9b333de406367751e0cea7e33c3f6a98e6600c243 src/web_server/htaccess/model.rs
76de1d11df75f4a64556b2821fd0a1774d26c9f0ca0e5f2b5fcfad0722d43d47 src/web_server/htaccess/parse.rs
66260d159986d41eab0dfde2cb9f0713fc1cc209c2fb47e9023bd24fd37919e8 src/web_server/htaccess/rewrite.rs
3341264f6e8041ac5a5d3c4b1601a69787386116019b5febc1a5e22bdb1186f9 src/web_server/metrics.rs
7b945065e636ca74147048aa53cc5b34028cced72a780fbaf0aa2499b00718fa src/web_server/mod.rs
67ab8bef567452f123c4b9b46a942b39c93f1f765c128edca7ee808ff1c5e304 src/web_server/request.rs
c5cb561b0f276e8082f904b9446fa5885f6b3c0608f25ea2ef425061e9f7f6b4 src/web_server/response.rs
cb461fb68c2740270edb9030a63e9d8c70c3ab241ba20d92f1a6e5b83915996e src/web_server/response/file.rs
20509f9302b79d3a7be76dda6edaf5711826d3ddf47d19ebe07cb289427ca328 src/web_server/response/headers.rs
9b58d6bb3ff1520060fc5aa86a3594f99192b1a8f8183255be1fb3ff35b4bb35 src/web_server/routing.rs
400e1c8aa2fe857d96036a64499fbc3de44c1e3a53811c5b25101fb843359792 src/web_server/security.rs
a082e1d32aeff7b64976c943251b0c6487409f191883bc822ac4a60bb4d33e7d src/web_server/serve.rs
6b3ade894f02fb68f3507e1ecc578f173050d97100d41261467faeb9fd10c31b src/web_server/tests.rs
54aa2dc94df97faabd4859a9dafe6a7cb1ba3337bfef5fa51a4432097a985d88 src/web_server/fastcgi/protocol.rs
fbaf972f9dd2842ec7df2878f0105c9ef4a877817f037f2332c4d5baabf2ead9 src/web_server/fastcgi/response.rs
92d2ab4d7ecb766bbc705f8eba94c1ca33347ffb84b36e0ad0eccfed5915473f src/web_server/filesystem.rs
d4fec3682532b0f2d11a0aefcd024f69fb623a39922ddb6c528a2d2de265347d src/web_server/htaccess.rs
b4646f705e532f682d6152b6827cc7f4f915766ebcb4af89eba9950965673772 src/web_server/htaccess/model.rs
a484113f2c1fea4e98a437acd8a28be6490d7e612f160a8728ceeaadd363f8ee src/web_server/htaccess/parse.rs
6e58e2674940bb0284035856fe9299a0555fee4c566de75e174deb373c3b164f src/web_server/htaccess/rewrite.rs
8ca74d9cc4470045301f70eb4a9fbabd0dbfe74434429148d2230df9f996c06c src/web_server/htaccess/tests.rs
7fe32af33b4d8e079ad724665aa1cfb62f6e98b9406b77168feaac55dac36137 src/web_server/metrics.rs
0f74d1fad9bc20a5f2ea1753381459c072cd8068cb13e7dfbc74e33eb3ace593 src/web_server/mod.rs
e9ba6e09ba2c8f8a33410c219d5ea29214cc53e279a43b17c110d24f17a5a71d src/web_server/request.rs
d98c80663847d6d83073df074ebff65952755415574708abcc153bd8d3adfaa9 src/web_server/response.rs
b38a1c9e648e3606a534ebdf74f6315863a326c6de1ca8edc433abe9c1fed7b4 src/web_server/response/file.rs
4733bb1824dd0a12b8804fb8d00791c3f9fda4c09b41503d5d8079d9607df058 src/web_server/response/file_body/mod.rs
e5f5fbe702f66bfcbcbc1d1f3cd3d89bdbb929bb86aed94868a2a06a4b9e59e3 src/web_server/response/file_body/tests.rs
7da903c1a69be0ae6b85772b3208c518679efa8ed9d3e98adb694483518156b6 src/web_server/response/headers.rs
2665d4eac9a6430d68224874784cb65d9b9d424353af77735caf044401d08cb4 src/web_server/routing.rs
9e584eb4455a18641c2c89f9e9725745c93f32877abf0f0f2fbea9e3d5ef38c3 src/web_server/security.rs
b791762b4ab7a43c3be19df84e523faf3dfb4df055f66d02178f93edb60d2204 src/web_server/serve.rs
714418c86ab241020a561fcf662a8fe324a6a70cc2fd3a313c56d7939c14b9e8 src/web_server/tests.rs
f00743af2cf4a5952caef7a1dcf68e7817b5599a252a9973e1c22045517c0b0e tests/fixtures/argon2id.json
1725c10331745dc04a07675784d385260da7b14567b8dba3cbae5e891eb5dadf tests/http2.rs
347a4ff9e028a1e999ad129590c355b5734cf928dd81e5099aea0c8aca8d5908 tests/http3.rs
eecf680f001f2110dde4eceaccf2f922859a16c946a644f8854e8de4d3771f7c tests/policies.rs
728999110a6d8cacb62bc0399bc333fa23a66526ee23f563a3c93bf00e5fad19 tests/transport_config.rs
102b83171a6429b6375d8001d6c5294702385cd58ee55727cb657b1f3566e877 tests/panel_icons_browser.py
52e61c389a60d147184ee76925cac76e454f8f2e3cb91039bb06e17787ab900f tests/panel_icons_contract.js
01af3008be27fabb486c141c67099af104f2725cc56f4c656aea9741a3c150cd tests/panel_interface_contract.py
626db3f4a7d83c0925a911db5ee6ce4b77048b7c2a9b91cd2d5d7dfa548cddd5 tests/panel_refresh_contract.js
e24a0f84f1bfc46964df865ac387d651f7b384d781cbc8f3a9a5b0b60f3d69ca tests/vhost_auth_selection.js
cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30 third_party/swagger-ui-5.33.1/LICENSE
0d20d1adef18aee3f40dd258172155521ce702ac445cb5f7b7d60ed32dad2fb2 third_party/swagger-ui-5.33.1/NOTICE
2c9e148b9acd740850aa33ae52878f1ecf19cdc67f6041e2c0a7aab1e95782f2 third_party/swagger-ui-5.33.1/README.md
f51b881c441fe8cab388c744911671ff925694b650e5ae7a5072cfdceb9ddaa4 third_party/swagger-ui-5.33.1/SHA256SUMS
050bc415ee7048dcd881682678f720264e7da5e373f7461d7c58c755305255f7 third_party/swagger-ui-5.33.1/swagger-ui-bundle.js
63818894e4b04cd0e3180d9cb20761e227a939121e7484f8e1d528227c756f89 third_party/swagger-ui-5.33.1/swagger-ui-bundle.js.LICENSE.txt
1ac324f7dcd27e4b9386b4bd6421271ec147e922a22c05ba24b11515e9aa6321 third_party/swagger-ui-5.33.1/swagger-ui.css
eca68a07c2b1e45fdb8f512294f5a432db3ae5a6aec8fb200b47f155cd9a31d6 tools/README.md
358c1e94dd2b4a756430e5335747f0b437345b1713670a9972c5bcf78b837ccb tools/audit/admin_audit.py
7c30c280d0b0a8bf855eeffab583ca34dc161255f7ef6124148aa979c17c26d3 tools/dev/memory_load.py
d1418f8e574a7390fd45a2aa879bdf5720b773162d65e295ab0350506aa5220a tools/dev/mock_auth.py
2d3ff92300b09ce9cecbbaaa9309b4d33160fa4b2291c9952b784e8cfb599ab5 tools/dev/mock_server.py
538b5f6cfa738355a729940ee3d92e7ab3dc25ca69ae330ddaa08f831775b8ba tools/dev/test_compression.py
c711154a9df425a56607bd88f03815ffb408213a31a2b66ed1c1c9a6301a2fc6 tools/legacy/backup.py
89d2b4e60a2f4cb93e8943b74b1b5ca84374b01fa74ea07bec07bdff315ac0e9 update.sh
5421082e24f3794356331157e30b428f3221a0d74c0b5d060bd4d3989e0bcf3a update.sh
56c702111022e1ca2c8f6b72c635c749b05936ffe6387bdb0a202691614cbc96 upgrade.sh
+68
View File
@@ -0,0 +1,68 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
cd "$ROOT"
DIST="$ROOT/dist"
CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-$ROOT/target}"
export CARGO_TARGET_DIR
fail() {
printf 'error: %s\n' "$*" >&2
exit 1
}
need() {
command -v "$1" >/dev/null 2>&1 || fail "$1 is required"
}
need cargo
need rustc
need python3
need sha256sum
python3 -c 'import sys; sys.exit(0 if sys.version_info >= (3,10) else 1)' \
|| fail 'Python 3.10+ is required'
[[ -f Cargo.toml ]] || fail 'run this script from the RustProxy project root'
[[ -f Cargo.lock ]] || fail 'Cargo.lock is required for a reproducible release'
[[ -f scripts/preflight.py ]] || fail 'scripts/preflight.py is missing'
VERSION="$(python3 scripts/version.py)"
HOST="$(rustc -vV | sed -n 's/^host: //p')"
[[ -n "$VERSION" ]] || fail 'could not determine project version'
[[ -n "$HOST" ]] || fail 'could not determine Rust host target'
printf 'RustProxy %s release build for %s\n' "$VERSION" "$HOST"
python3 scripts/preflight.py
cargo fmt --all -- --check
cargo test --locked --all-targets
cargo clippy --locked --all-targets -- -D warnings
rm -rf "$CARGO_TARGET_DIR/release"
cargo build --locked --release
BIN="$CARGO_TARGET_DIR/release/rust-proxy"
[[ -x "$BIN" ]] || fail "release binary was not created: $BIN"
ACTUAL_VERSION="$($BIN --version)"
printf 'Built: %s\n' "$ACTUAL_VERSION"
rm -rf "$DIST"
mkdir -p "$DIST"
ARTIFACT="rust-proxy-${VERSION}-${HOST}"
cp "$BIN" "$DIST/$ARTIFACT"
chmod 0755 "$DIST/$ARTIFACT"
sha256sum "$DIST/$ARTIFACT" > "$DIST/$ARTIFACT.sha256"
SOURCE_ZIP="$DIST/RustProxy-${VERSION}-source.zip"
python3 make_zip.py "$SOURCE_ZIP"
sha256sum "$SOURCE_ZIP" > "$SOURCE_ZIP.sha256"
printf '\nRelease ready:\n'
printf ' binary: %s\n' "$DIST/$ARTIFACT"
printf ' checksum: %s\n' "$DIST/$ARTIFACT.sha256"
printf ' source zip: %s\n' "$SOURCE_ZIP"
printf ' checksum: %s\n' "$SOURCE_ZIP.sha256"
+61 -5
View File
@@ -1,9 +1,56 @@
//! Build extensible language packs and pinned, authenticated Swagger UI assets.
use serde_json::{json, Value};
use std::{collections::HashSet, env, fs, path::PathBuf, process::Command};
// Prepare the actual embedded bytes here, not in a dependency's earlier build.
// Cargo config discovery is deliberately irrelevant to the pinned default.
fn panel_styles(
root: &std::path::Path,
out: &std::path::Path,
) -> Result<(), Box<dyn std::error::Error>> {
println!("cargo:rerun-if-env-changed=RUST_PROXY_PYTHON");
let helper = root.join("scripts/build/panel_css.py");
println!("cargo:rerun-if-changed={}", helper.display());
println!(
"cargo:rerun-if-changed={}",
root.join("src/admin/web/css").display()
);
let interpreters = match env::var_os("RUST_PROXY_PYTHON") {
Some(value) if !value.is_empty() => vec![value],
_ => vec!["python3".into(), "python".into()],
};
for interpreter in interpreters {
match Command::new(&interpreter)
.arg(&helper)
.arg("--root")
.arg(root)
.arg("--out-dir")
.arg(out)
.arg("--cargo")
.status()
{
Ok(status) if status.success() => {
let generated = out.join("panel-style.css");
let metadata = fs::metadata(&generated).map_err(|error| {
format!(
"Panel CSS helper succeeded but {} is unavailable: {error}",
generated.display()
)
})?;
if !metadata.is_file() || metadata.len() == 0 {
return Err(format!(
"Panel CSS helper did not create a non-empty regular file: {}",
generated.display()
)
.into());
}
return Ok(());
}
Ok(_) => return Err("Panel CSS preparation failed; see the diagnostic above and src/admin/web/css/README.md".into()),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => continue,
Err(error) => return Err(error.into()),
}
}
Err("Python 3.10+ is required for build assets; install python3 or set RUST_PROXY_PYTHON to its executable".into())
}
fn swagger_assets(
root: &std::path::Path,
out: &std::path::Path,
@@ -23,9 +70,17 @@ fn swagger_assets(
_ => vec!["python3".into(), "python".into()],
};
for interpreter in interpreters {
match Command::new(&interpreter).arg(&helper).arg("--out-dir").arg(out).status() {
match Command::new(&interpreter)
.arg(&helper)
.arg("--out-dir")
.arg(out)
.status()
{
Ok(status) if status.success() => return Ok(()),
Ok(_) => return Err("Swagger UI asset preparation failed; see the diagnostic above and docs/API_ACCESS.md".into()),
Ok(_) => return Err(
"Swagger UI asset preparation failed; see the diagnostic above and docs/INSTALL.md"
.into(),
),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => continue,
Err(error) => return Err(error.into()),
}
@@ -38,6 +93,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
env::var_os("CARGO_MANIFEST_DIR").ok_or("Cargo did not supply CARGO_MANIFEST_DIR")?,
);
let out = PathBuf::from(env::var_os("OUT_DIR").ok_or("Cargo did not supply OUT_DIR")?);
panel_styles(&root, &out)?;
swagger_assets(&root, &out)?;
let langs = root.join("langs");
println!("cargo:rerun-if-changed={}", langs.display());
+43 -7
View File
@@ -1,7 +1,7 @@
# Example Linux service
`rustproxy.service` is an operator-reviewed deployment example, not an installed or
tested service. It runs the compiled executable as a dedicated `rustproxy` account,
tested service. The example includes `ExecReload` so `systemctl reload rustproxy` uses the authenticated CLI soft reload. If using `RUST_PROXY_CONFIG`, configure it in the service EnvironmentFile so both `ExecStart` and `ExecReload` share the same file source. It runs the compiled executable as a dedicated `rustproxy` account,
uses the public administration profile shown below and grants only the capability needed
for low-port binding. It does not run the proxy as root or grant Docker socket access.
@@ -23,10 +23,13 @@ addresses, limits or a service-account convention. Never point it at a user-owne
source directory containing writable executables or real public web content.
After starting through your service manager, run account/certificate CLI operations
as the same service account with `--data-dir /var/lib/rustproxy`. The example
uses `ProtectHome=true`, so certificate files intended for runtime use must be in
the data directory, not in a home directory. Use an SSH tunnel to access the local
admin port remotely. This reference unit keeps administration on `127.0.0.1:9090`; public HTTP/HTTPS
as the same service account with `--data-dir /var/lib/rustproxy`. The reference
uses `ProtectHome=read-only`, so site roots in `/home` and trusted external
certificates can be read according to Unix permissions, groups and ACLs,
without overriding the systemd sandbox. The reference binds administration
to `0.0.0.0:9090`; restrict this to loopback before running on an untrusted
network, or use administration HTTPS and strict network controls. Use an SSH
tunnel to access a loopback-only admin port remotely. Public HTTP/HTTPS
listeners remain explicit. For direct remote administration, enable Administration HTTPS and persist
a non-loopback bind from the panel rather than exposing the plain-HTTP bootstrap listener. For a
systemd drop-in, reset `ExecStart` before replacing it. Example preserving the loopback-safe admin:
@@ -36,7 +39,6 @@ systemd drop-in, reset `ExecStart` before replacing it. Example preserving the l
ExecStart=
ExecStart=/opt/rustproxy/current/rust-proxy --data-dir /var/lib/rustproxy --admin-listen 127.0.0.1:9090 serve --proxy-listen 0.0.0.0:80 --tls-listen 0.0.0.0:443
ReadWritePaths=/var/log/rustproxy
ReadOnlyPaths=-/etc/letsencrypt -/etc/ssl
```
Saving HTTP/TLS or TCP/UDP stream listener changes in the UI does not restart this service. Restart explicitly
@@ -47,7 +49,37 @@ bind can prevent startup; preserve a pre-change database backup and consult
production deployment. This packaging environment did not run systemd or bind
privileged ports through this unit.
External certificate files under `/etc/letsencrypt` or `/etc/ssl` remain read-only under the systemd sandbox. `ReadOnlyPaths=` does not grant Unix permissions; use narrow POSIX ACLs for the `rustproxy` user. For traversal of a protected root, for example:
## Optional JSON backing file (`--config`)
Use `sudo systemctl edit rustproxy` to override the startup command, **not** to edit the packaged unit directly. For the `/usr/bin` installation requested below (switch to `/opt/rustproxy/current/rust-proxy` on the managed deployment), configure:
```ini
[Service]
ExecStart=
ExecStart=/usr/bin/rust-proxy --data-dir /var/lib/rustproxy --admin-listen 0.0.0.0:9090 serve --proxy-listen 0.0.0.0:80 --tls-listen 0.0.0.0:443 --allow-remote-admin --config /etc/rustproxy/config.json
ReadWritePaths=/etc/rustproxy
```
Only activate after exporting a complete, valid JSON config. The directory and initial file may remain `root:root`, restricted to `0700` and `0600`; use:
```bash
sudo chown root:root /etc/rustproxy /etc/rustproxy/config.json
sudo chmod 0700 /etc/rustproxy
sudo chmod 0600 /etc/rustproxy/config.json
sudo setfacl -m u:rustproxy:rwx /etc/rustproxy
sudo setfacl -m u:rustproxy:rw- /etc/rustproxy/config.json
sudo getfacl /etc/rustproxy /etc/rustproxy/config.json
```
**Atomic panel saves replace the file**: the service needs directory traversal/create/rename permission (`rwx`), not just file write. New config files are written `0600`, normally owned by `rustproxy`; do not assume named file ACLs survive replacement. `ProtectSystem=strict` additionally requires `ReadWritePaths=/etc/rustproxy` in the unit; ACLs do not override the systemd sandbox. Keep the original private data directory and SQLite state. Check the active source in **Settings → Manual config**, inspect the unit with `systemctl cat rustproxy`, then `systemctl restart rustproxy` and verify a save. An external JSON edit needs `systemctl reload rustproxy`; there is no automatic file watching.
**Security:** the shown `0.0.0.0:9090` requires administration HTTPS and network access controls before remote exposure; use loopback administration otherwise. The complete export, privilege commands, validation and rollback guidance live in [Installation](../docs/INSTALL.md#optional-systemd-json-configuration).
External certificate files under `/etc/letsencrypt`, `/etc/ssl` or another
trusted root/service-owned directory remain read-only under the systemd
sandbox. No path-specific `ReadOnlyPaths=` allowlist is necessary;
`ReadOnlyPaths=` does not grant Unix permissions anyway. Use narrow POSIX ACLs
for the `rustproxy` user. For traversal of a protected root, for example:
```bash
setfacl -m u:rustproxy:--x /etc/ssl/
@@ -55,3 +87,7 @@ setfacl -m u:rustproxy:--x /etc/ssl/
Grant `r--` only on the certificate/key files RustProxy actually needs; never grant write access to the service identity.
If Certbot or another renewer recreates a key as `0600`, reapply the named file ACL to the newly resolved target in the renewal hook before calling the RustProxy reload API.
External certificate parent directories and PEM files must be owned by root
or the service account and meet the application's safe-file checks; arbitrary
user-owned `/home` trees work as Web Server roots, but not as PEM ancestry.
See [Installation: external paths](../docs/INSTALL.md#linux-debrpm-read-only-access-to-external-paths).
+6 -4
View File
@@ -14,7 +14,8 @@ Environment=RUST_LOG=rust_proxy=info,warn
Environment=MALLOC_ARENA_MAX=2
Environment=PATH=/usr/local/bin:/usr/bin:/bin
EnvironmentFile=-/etc/rustproxy/environment
ExecStart=/opt/rustproxy/current/rust-proxy --data-dir /var/lib/rustproxy --admin-listen 127.0.0.1:9090 serve --proxy-listen 0.0.0.0:80 --tls-listen 0.0.0.0:443 --allow-remote-admin
ExecStart=/opt/rustproxy/current/rust-proxy --data-dir /var/lib/rustproxy --admin-listen 0.0.0.0:9090 serve --proxy-listen 0.0.0.0:80 --tls-listen 0.0.0.0:443 --allow-remote-admin
ExecReload=/opt/rustproxy/current/rust-proxy --data-dir /var/lib/rustproxy --admin-listen 0.0.0.0:9090 reload
Restart=on-failure
RestartSec=3
KillSignal=SIGINT
@@ -28,9 +29,10 @@ PrivateDevices=true
ProtectSystem=strict
LogsDirectory=rustproxy
LogsDirectoryMode=0770
ReadWritePaths=/var/lib/rustproxy /var/log/rustproxy
ReadOnlyPaths=-/etc/letsencrypt -/etc/ssl
ProtectHome=true
ReadWritePaths=/var/lib/rustproxy /var/log/rustproxy -/etc/rustproxy
# Read-only document roots/certificates may live in /home, /var/www or /srv;
# service-account Unix group/ACL permissions still apply.
ProtectHome=read-only
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
+85 -23
View File
@@ -1,46 +1,108 @@
# RustProxy Docker Agent
# RustProxy Docker Agent: Linux DEB/RPM or manual installation
Small read-only discovery service for Docker hosts. It talks to the local Docker Engine Unix socket, reports running containers with non-loopback published TCP ports, preserves every host binding for each service, and exposes the inventory to RustProxy through a Bearer-token protected HTTPS endpoint.
The optional `rustproxy-docker-agent` runs **on a Docker host**, not on the RustProxy server. It exposes a read-only, Bearer-protected Docker discovery API over HTTPS. It reports containers with published TCP ports, host bindings, and usable host addresses. RustProxy itself never needs access to the Docker socket.
The inventory also reports multiple usable host addresses: active host interface addresses, the primary routed address, an optional public-IP lookup result, and the optional advertised/preferred address. Loopback/link-local and obvious Docker-generated bridge/veth interfaces are omitted from the host-address list.
## Requirements and security
## TLS by default
- Linux amd64/x86_64 with **systemd and Docker Engine** (including a usable `/var/run/docker.sock`). No FreeBSD `pkg` is built for this agent.
- Restrict inbound port `9876` to the RustProxy server through a firewall. The example binds `0.0.0.0:9876`; this is reachable from other machines when the firewall permits it.
- Access to `docker.sock` grants substantial **host-level privilege**, regardless of the agent's read-only HTTP API. The service uses a dedicated unprivileged identity granted access to the `docker` group.
- Self-signed ECDSA P-256 HTTPS is enabled by default; the certificate and private key persist under `/var/lib/rustproxy-docker-agent/` and are **not** regenerated on upgrades/restarts. The key is `0600`.
- The Bearer token is generated using a cryptographically secure random source, saved to `/etc/rustproxy/docker-agent.env` with mode `0600`, and preserved on upgrade. Never include it in tickets, unattended logs, or screenshots.
HTTPS is enabled by default. On first start the agent creates a persistent ECDSA P-256 self-signed certificate under `/var/lib/rustproxy-docker-agent/` and reuses it across restarts. The private key is written with mode `0600`.
## Recommended: install from DEB/RPM
The installer prints the certificate SHA-256 fingerprint. Configure that fingerprint in **RustProxy -> Settings -> Docker agents**. RustProxy then pins the exact agent certificate while still verifying the TLS handshake signatures. A changed/replaced certificate is rejected until the configured fingerprint is updated.
The Linux package repository publishes **two independent packages** built from the same source commit: `rustproxy` (main server) and `rustproxy-docker-agent` (Docker host). Installing the latter does **not** install the main RustProxy server and does **not** require Go on the destination host.
Show the current fingerprint again with:
On a Debian/Ubuntu Docker host, after configuring the RustProxy signed APT repository:
```bash
sudo rustproxy-docker-agent --print-fingerprint
sudo apt update
sudo apt install rustproxy-docker-agent
# Or install a downloaded artifact directly:
sudo apt install ./rustproxy-docker-agent_<version>-<release>_amd64.deb
```
If you replace the generated certificate with one signed by a normal public CA, the fingerprint field in RustProxy may be left empty so standard CA/hostname validation is used. Plain HTTP is available only when `RUSTPROXY_DOCKER_AGENT_TLS=false`; it is not the default.
## Quick install on systemd
On a RHEL/Rocky/Alma/Fedora Docker host, after configuring the signed RPM repository:
```bash
sudo ./install.sh
sudo dnf install rustproxy-docker-agent
# Or install a downloaded artifact directly:
sudo dnf install ./rustproxy-docker-agent-<version>-<release>.x86_64.rpm
```
The installer runs tests, builds the binary, creates the service account, writes a random token on first install, installs the hardened unit, starts it and prints the TLS fingerprint.
Find APT/DNF repository configuration in the Package Builder README or on the repository's index page. The package post-install script creates the system account/state directory, generates missing token and TLS identity, enables the service and starts it when Docker is available. It **does not print the secret token** into package manager logs. If Docker isn't installed/running, the package can be installed first, but the service cannot run until Docker/socket/group are available.
## Build manually
Show all connection details **locally, as root**:
```bash
sudo rustproxy-docker-agent info
sudo rustproxy-docker-agent info --show-token
sudo rustproxy-docker-agent info --json # machine-readable, redacted by default
sudo rustproxy-docker-agent print-fingerprint
sudo systemctl status rustproxy-docker-agent
sudo journalctl -u rustproxy-docker-agent -n 100 --no-pager
```
Configure **RustProxy → Settings → Docker agents** with the host's `https://host:9876` URL, the full Bearer token, and the printed certificate SHA-256 fingerprint. The RustProxy server pins the certificate when a fingerprint is configured; a replaced certificate requires updating the fingerprint in the panel.
## CLI commands
| Command | Behavior |
|---|---|
| `rustproxy-docker-agent serve` (or no arguments) | Run the discovery API, auto-create a missing TLS certificate with the service account, log endpoint/cert details and **redacted** token status |
| `sudo rustproxy-docker-agent init` | Create missing token and self-signed TLS identity; keep existing valid values and certificate |
| `sudo rustproxy-docker-agent info` | Show listening endpoint, hostname, Docker socket, token status/length, TLS certificate paths, SHA-256, issuer, subject, SAN, serial, algorithms and validity |
| `sudo rustproxy-docker-agent info --show-token` | Explicitly reveal the full Bearer token in addition to metadata |
| `sudo rustproxy-docker-agent show-token` | Print **only** the full secret token (protect stdout) |
| `sudo rustproxy-docker-agent rotate-token` | Create a new token without replacing the certificate; **restart the service and update RustProxy** before continuing |
| `sudo rustproxy-docker-agent cert-init` | Create a missing certificate/key and print SHA-256; never overwrites existing valid certificate/key |
| `sudo rustproxy-docker-agent print-fingerprint` | Print current SHA-256 certificate fingerprint |
| `rustproxy-docker-agent version` | Print agent version |
All subcommands accept `--env-file /path/to/file` and relevant connection/TLS overrides such as `--cert-file`, `--key-file`, `--hostname` and `--listen`. Older `--init-cert` and `--print-fingerprint` flags still work. Use `--help` for options; it never includes the configured token in default values.
To rotate the token:
```bash
sudo rustproxy-docker-agent rotate-token
sudo systemctl restart rustproxy-docker-agent
sudo rustproxy-docker-agent info --show-token
```
**Important:** The previous token becomes invalid after restart. Update the token in RustProxy Settings. Do not paste the full token into public logs. At normal startup only nonsecret certificate details and token presence/length are written to the journal.
## Optional: compile and install manually
From `docker-agent/` in this source tree (Go 1.22+ required):
```bash
go test ./...
go build -trimpath -ldflags='-s -w' -o rustproxy-docker-agent .
sudo ./install.sh
```
## systemd
The installer builds Go, installs `/usr/local/bin/rustproxy-docker-agent`, creates the same service account/config/state layout, calls the Go CLI `init`, enables/starts systemd and prints the certificate and token for interactive setup. Subsequent manual installations preserve existing credentials.
1. Create a dedicated `rustproxy-docker-agent` system user and grant it read access to the Docker socket (commonly by membership in the `docker` group). Docker socket access is privileged; isolate this account and the host firewall.
2. Copy the binary to `/usr/local/bin/rustproxy-docker-agent`.
3. Copy `docker-agent.env.example` to `/etc/rustproxy/docker-agent.env`, generate a long random token and bind only to an address reachable by RustProxy.
4. Ensure `/var/lib/rustproxy-docker-agent` is writable only by the service identity. The shipped systemd unit creates it through `StateDirectory=`.
5. Copy `rustproxy-docker-agent.service` to `/etc/systemd/system/`, then run `systemctl daemon-reload && systemctl enable --now rustproxy-docker-agent`.
For completely manual setup, create a system account with access to the `docker` group; install the binary; copy `docker-agent.env.example` into `/etc/rustproxy/docker-agent.env`; make the env file root-only; and place `rustproxy-docker-agent.service` under `/etc/systemd/system`. Create a writable `/var/lib/rustproxy-docker-agent` owned by the service account, then run `sudo rustproxy-docker-agent init`, `sudo systemctl daemon-reload` and `sudo systemctl enable --now rustproxy-docker-agent`.
The inventory endpoint is `GET /v1/inventory` with `Authorization: Bearer <token>`. `/healthz` does not expose Docker inventory. Both are served over HTTPS by default.
### Switching from manual to package-managed
`RUSTPROXY_DOCKER_ADVERTISE_IP` marks a preferred address but does not suppress automatic discovery of the host's other addresses. `RUSTPROXY_DOCKER_PUBLIC_IP_URL` adds the externally observed address when enabled. RustProxy can choose a concrete reported address independently for every Docker-backed vhost.
Both installation types use the **same** config and certificate paths, but different binary paths (`/usr/local/bin` versus `/usr/bin`). A manual unit in `/etc/systemd/system/` takes precedence over the package unit in `/usr/lib/systemd/system/`. To migrate, first back up `/etc/rustproxy/docker-agent.env` and `/var/lib/rustproxy-docker-agent/`, stop the service, and move the manual service unit outside systemd's unit directories. Run `systemctl daemon-reload`, install the DEB/RPM, and inspect `systemctl cat rustproxy-docker-agent`. Remove the obsolete `/usr/local/bin` binary only after verifying the packaged service. Do **not** delete or regenerate the existing token/key.
### Removing or upgrading the package
Upgrades do not reset credentials or private state. The environment file is installed with `config|noreplace`; local edits remain in place according to package-manager conffile behavior. Removal does not delete `/etc/rustproxy/docker-agent.env` or `/var/lib/rustproxy-docker-agent` (back these up separately).
## API and troubleshooting
The API is `GET /v1/inventory` with `Authorization: Bearer <token>`; `GET /healthz` has no inventory and requires no token. Both use HTTPS by default. Plain HTTP is possible with `RUSTPROXY_DOCKER_AGENT_TLS=false` but exposes credentials in transit; use it only in a secured network and never across the public Internet.
- `no valid token`: execute `sudo rustproxy-docker-agent init`; check env file permissions and `EnvironmentFile`.
- `Docker socket unavailable`: install/start Docker Engine, check `/var/run/docker.sock`, the `docker` group and membership of `rustproxy-docker-agent`.
- `TLS certificate/key`: inspect ownership of `/var/lib/rustproxy-docker-agent/`, mode `0600` on the private key, and both certificate/key paths. Refuse partially missing pairs rather than overwriting them.
- `x509` or fingerprint mismatch in RustProxy: compare `sudo rustproxy-docker-agent print-fingerprint` with the pin configured in Settings.
- Cannot reach port `9876`: check `RUSTPROXY_DOCKER_AGENT_LISTEN`, listening socket, firewall, and that HTTPS is used.
`RUSTPROXY_DOCKER_ADVERTISE_IP` adds a preferred host address without suppressing automatic address discovery. `RUSTPROXY_DOCKER_PUBLIC_IP_URL` controls the optional externally observed public IP. The agent's Docker socket access remains privileged; isolate the Docker host and network accordingly.
Default `technical_name` uses the sanitized Docker container name (including Docker Compose container names), not the image or a port suffix. Each published TCP port still has an independent stable service key.
+12 -4
View File
@@ -24,18 +24,26 @@ func certificateFingerprintDER(der []byte) string {
return hex.EncodeToString(sum[:])
}
func certificateFingerprint(certFile string) (string, error) {
func readCertificate(certFile string) (*x509.Certificate, error) {
raw, err := os.ReadFile(certFile)
if err != nil {
return "", err
return nil, err
}
block, _ := pem.Decode(raw)
if block == nil || block.Type != "CERTIFICATE" {
return "", fmt.Errorf("%s does not contain a PEM certificate", certFile)
return nil, fmt.Errorf("%s does not contain a PEM certificate", certFile)
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return "", fmt.Errorf("parse certificate: %w", err)
return nil, fmt.Errorf("parse certificate: %w", err)
}
return cert, nil
}
func certificateFingerprint(certFile string) (string, error) {
cert, err := readCertificate(certFile)
if err != nil {
return "", err
}
return certificateFingerprintDER(cert.Raw), nil
}
+181
View File
@@ -0,0 +1,181 @@
package main
import (
"bytes"
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
)
func TestCLIProvisionShowRedactAndRotate(t *testing.T) {
dir := t.TempDir()
env := filepath.Join(dir, "docker-agent.env")
cert := filepath.Join(dir, "state", "agent.crt")
key := filepath.Join(dir, "state", "agent.key")
initial := "# keep comment\nRUSTPROXY_DOCKER_AGENT_LISTEN=0.0.0.0:19876\n" +
"RUSTPROXY_DOCKER_AGENT_TOKEN=replace-with-at-least-24-random-characters\n" +
"RUSTPROXY_DOCKER_AGENT_HOSTNAME=docker.lab\n"
if err := os.WriteFile(env, []byte(initial), 0600); err != nil {
t.Fatal(err)
}
call := func(command string, flags ...string) string {
t.Helper()
var stdout, stderr bytes.Buffer
args := append([]string{command, "--env-file", env, "--cert-file", cert, "--key-file", key}, flags...)
if err := run(args, &stdout, &stderr); err != nil {
t.Fatalf("run(%s): %v (stderr: %s)", command, err, stderr.String())
}
return stdout.String()
}
output := call("init")
if !strings.Contains(output, "Certificate SHA-256:") {
t.Fatalf("init output %q", output)
}
token := strings.TrimSpace(call("show-token"))
if len(token) != 64 {
t.Fatalf("unexpected token length %d", len(token))
}
if strings.TrimSpace(call("show-token")) != token {
t.Fatal("token changed on read")
}
if !strings.Contains(call("info"), "Token redacted") || strings.Contains(call("info"), token) {
t.Fatal("info leaked the bearer token")
}
if !strings.Contains(call("info", "--show-token"), token) {
t.Fatal("explicit info did not show token")
}
if !strings.Contains(call("init"), "Provisioning complete") {
t.Fatal("second init failed")
}
if strings.TrimSpace(call("show-token")) != token {
t.Fatal("token changed on repeat init")
}
oldFingerprint, err := certificateFingerprint(cert)
if err != nil {
t.Fatal(err)
}
if oldFingerprint != strings.TrimSpace(call("print-fingerprint")) {
t.Fatal("fingerprint changed")
}
if mode, err := os.Stat(key); err != nil || mode.Mode().Perm() != 0600 {
t.Fatalf("key mode: %v, %v", mode, err)
}
if mode, err := os.Stat(env); err != nil || mode.Mode().Perm() != 0600 {
t.Fatalf("env mode: %v, %v", mode, err)
}
if !strings.Contains(call("rotate-token"), "Restart") {
t.Fatal("missing rotation warning")
}
rotated := strings.TrimSpace(call("show-token"))
if len(rotated) != 64 || rotated == token {
t.Fatal("rotation did not change token")
}
raw, err := os.ReadFile(env)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(raw), "# keep comment") || strings.Count(string(raw), "RUSTPROXY_DOCKER_AGENT_TOKEN=") != 1 {
t.Fatalf("rotation changed unrelated config or duplicated tokens: %s", raw)
}
if fp, err := certificateFingerprint(cert); err != nil || fp != oldFingerprint {
t.Fatal("rotation changed certificate")
}
var info agentDetails
if err := json.Unmarshal([]byte(call("info", "--json")), &info); err != nil {
t.Fatal(err)
}
if info.Token != "" || !info.TokenConfigured || info.Certificate == nil || info.Certificate.SHA256 != oldFingerprint || info.Listen != "0.0.0.0:19876" {
t.Fatalf("incorrect json info: %+v", info)
}
if info.Certificate.Subject == "" || info.Certificate.Issuer == "" || info.Certificate.NotAfter == "" || !info.Certificate.ValidNow {
t.Fatalf("missing certificate details: %+v", info.Certificate)
}
}
func TestCLIHelpNeverLeaksToken(t *testing.T) {
dir := t.TempDir()
env := filepath.Join(dir, "agent.env")
secret := strings.Repeat("very-secret-", 4)
if err := os.WriteFile(env, []byte("RUSTPROXY_DOCKER_AGENT_TOKEN="+secret+"\n"), 0600); err != nil {
t.Fatal(err)
}
var out, errout bytes.Buffer
if err := run([]string{"--env-file", env, "--help"}, &out, &errout); err != nil {
t.Fatal(err)
}
if strings.Contains(out.String()+errout.String(), secret) {
t.Fatal("--help leaked token")
}
}
func TestCLIRejectsShortTokenWithoutSilentReplacement(t *testing.T) {
dir := t.TempDir()
env := filepath.Join(dir, "agent.env")
if err := os.WriteFile(env, []byte("RUSTPROXY_DOCKER_AGENT_TOKEN=weak\n"), 0600); err != nil {
t.Fatal(err)
}
var out, errout bytes.Buffer
if err := run([]string{"init", "--env-file", env}, &out, &errout); err == nil {
t.Fatal("short custom token must not be silently replaced")
}
got, err := os.ReadFile(env)
if err != nil || !strings.Contains(string(got), "TOKEN=weak") {
t.Fatalf("unexpected file modification: %s %v", got, err)
}
}
func TestCLICertWithoutToken(t *testing.T) {
dir := t.TempDir()
cert := filepath.Join(dir, "cert.crt")
key := filepath.Join(dir, "cert.key")
var out, errout bytes.Buffer
args := []string{"cert-init", "--env-file", filepath.Join(dir, "missing.env"), "--cert-file", cert, "--key-file", key}
if err := run(args, &out, &errout); err != nil {
t.Fatal(err)
}
if len(strings.TrimSpace(out.String())) != 64 {
t.Fatal("invalid SHA fingerprint")
}
}
func TestCLIInfoCanReportMissingCertificate(t *testing.T) {
dir := t.TempDir()
var out, errout bytes.Buffer
err := run([]string{"info", "--env-file", filepath.Join(dir, "missing.env"), "--cert-file", filepath.Join(dir, "no.crt")}, &out, &errout)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(out.String(), "TLS certificate: missing") {
t.Fatalf("info: %s", out.String())
}
}
func TestCLIDoesNotFollowSymlinkToProvisionToken(t *testing.T) {
dir := t.TempDir()
original := filepath.Join(dir, "original")
if err := os.WriteFile(original, []byte("untouched\n"), 0600); err != nil {
t.Fatal(err)
}
link := filepath.Join(dir, "agent.env")
if err := os.Symlink(original, link); err != nil {
t.Fatal(err)
}
if _, _, err := provisionToken(link, true); err == nil {
t.Fatal("symlink must be rejected")
}
got, _ := os.ReadFile(original)
if string(got) != "untouched\n" {
t.Fatal("target file was modified")
}
}
func TestCLIRejectsExamplePlaceholderAsServerToken(t *testing.T) {
if validToken("replace-with-at-least-24-random-characters") {
t.Fatal("example placeholder accepted as a token")
}
if !validToken(strings.Repeat("t", 24)) {
t.Fatal("valid random-like token incorrectly rejected")
}
}
+278
View File
@@ -0,0 +1,278 @@
package main
import (
"bufio"
"crypto/rand"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"os/user"
"path/filepath"
"strconv"
"strings"
"time"
)
const defaultEnvFile = "/etc/rustproxy/docker-agent.env"
// requestedEnvFile is handled before other flags so CLI calls can load the same
// configuration as systemd. Both --env-file=X and --env-file X are supported.
func requestedEnvFile(args []string) (string, error) {
path := defaultEnvFile
for i := 0; i < len(args); i++ {
if args[i] == "--env-file" || args[i] == "-env-file" {
if i+1 >= len(args) {
return "", errors.New("--env-file requires a path")
}
path = args[i+1]
i++
} else if strings.HasPrefix(args[i], "--env-file=") || strings.HasPrefix(args[i], "-env-file=") {
_, path, _ = strings.Cut(args[i], "=")
}
}
if path == "" {
return "", errors.New("--env-file cannot be empty")
}
return path, nil
}
// Read a restricted, plain KEY=VALUE EnvironmentFile. Never execute it as shell.
func readEnvironmentFile(path string) (map[string]string, error) {
result := make(map[string]string)
f, err := os.Open(path)
if errors.Is(err, os.ErrNotExist) {
return result, nil
}
if err != nil {
return nil, fmt.Errorf("read %s: %w", path, err)
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
line = strings.TrimPrefix(line, "export ")
key, value, ok := strings.Cut(line, "=")
if !ok {
continue
}
key = strings.TrimSpace(key)
if strings.HasPrefix(key, "RUSTPROXY_DOCKER_") {
value = strings.TrimSpace(value)
if len(value) >= 2 && value[0] == '"' && value[len(value)-1] == '"' {
decoded, err := strconv.Unquote(value)
if err != nil {
return nil, fmt.Errorf("invalid quoted environment value for %s: %w", key, err)
}
value = decoded
} else if len(value) >= 2 && value[0] == '\'' && value[len(value)-1] == '\'' {
value = value[1 : len(value)-1]
}
result[key] = value
}
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("read %s: %w", path, err)
}
return result, nil
}
func makeToken() (string, error) {
data := make([]byte, 32)
if _, err := rand.Read(data); err != nil {
return "", err
}
return hex.EncodeToString(data), nil
}
func provisionToken(path string, rotate bool) (string, bool, error) {
// Reject symlinks, devices and directories before opening anything.
stat, err := os.Lstat(path)
var raw []byte
if err == nil {
if !stat.Mode().IsRegular() {
return "", false, fmt.Errorf("refusing non-regular environment file %s", path)
}
if stat.Size() > 1<<20 {
return "", false, fmt.Errorf("environment file too large: %s", path)
}
raw, err = os.ReadFile(path)
if err != nil {
return "", false, err
}
} else if !errors.Is(err, os.ErrNotExist) {
return "", false, err
}
values, err := readEnvironmentFile(path)
if err != nil {
return "", false, err
}
token := values["RUSTPROXY_DOCKER_AGENT_TOKEN"]
needsToken := token == "" || strings.HasPrefix(token, "replace-with-")
if !rotate && !needsToken {
if len(token) < 24 {
return "", false, errors.New("configured Bearer token is shorter than 24 characters; use rotate-token")
}
return token, false, nil
}
token, err = makeToken()
if err != nil {
return "", false, err
}
if len(raw) == 0 {
raw = []byte("# RustProxy Docker Agent: edit the listen address and firewall as needed.\n" +
"RUSTPROXY_DOCKER_AGENT_LISTEN=0.0.0.0:9876\n" +
"RUSTPROXY_DOCKER_AGENT_TLS=true\n")
}
// Preserve non-secret settings/comments, remove duplicate token assignments.
lines := strings.Split(strings.TrimRight(string(raw), "\n"), "\n")
filtered := make([]string, 0, len(lines)+1)
for _, line := range lines {
normalized := strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(line), "export "))
key, _, ok := strings.Cut(normalized, "=")
if ok && strings.TrimSpace(key) == "RUSTPROXY_DOCKER_AGENT_TOKEN" {
continue
}
filtered = append(filtered, line)
}
filtered = append(filtered, "RUSTPROXY_DOCKER_AGENT_TOKEN="+token)
if err := atomicWrite(path, []byte(strings.Join(filtered, "\n")+"\n"), 0600); err != nil {
return "", false, err
}
return token, true, nil
}
// Generated keys must be readable by the unprivileged systemd service even
// when 'init' is invoked via sudo during DEB/RPM or manual installation.
func setServiceCertificateOwnership(certFile, keyFile string) error {
if os.Geteuid() != 0 {
return nil
}
serviceUser, err := user.Lookup("rustproxy-docker-agent")
if err != nil {
return nil
} // Standalone CLI; warn operators to run init as service user.
uid, err := strconv.Atoi(serviceUser.Uid)
if err != nil {
return err
}
gid, err := strconv.Atoi(serviceUser.Gid)
if err != nil {
return err
}
if err := os.Chown(keyFile, uid, gid); err != nil {
return err
}
if err := os.Chown(certFile, uid, gid); err != nil {
return err
}
// Parent generated via atomicWrite must also be traversable by the service.
dir := filepath.Dir(keyFile)
if dir == filepath.Dir(certFile) {
if err := os.Chown(dir, uid, gid); err != nil {
return err
}
}
return nil
}
type certDetails struct {
Path string `json:"path"`
KeyPath string `json:"key_path"`
SHA256 string `json:"sha256"`
Subject string `json:"subject"`
Issuer string `json:"issuer"`
Serial string `json:"serial"`
NotBefore string `json:"not_before"`
NotAfter string `json:"not_after"`
DNSNames []string `json:"dns_names"`
IPAddresses []string `json:"ip_addresses"`
SignatureAlgorithm string `json:"signature_algorithm"`
PublicKeyAlgorithm string `json:"public_key_algorithm"`
ValidNow bool `json:"valid_now"`
}
func describeCertificate(path string) (certDetails, error) {
cert, err := readCertificate(path)
if err != nil {
return certDetails{}, err
}
now := time.Now()
result := certDetails{
Path: path, SHA256: certificateFingerprintDER(cert.Raw), Subject: cert.Subject.String(),
Issuer: cert.Issuer.String(), Serial: cert.SerialNumber.Text(16),
NotBefore: cert.NotBefore.UTC().Format(time.RFC3339), NotAfter: cert.NotAfter.UTC().Format(time.RFC3339),
DNSNames: cert.DNSNames, SignatureAlgorithm: cert.SignatureAlgorithm.String(),
PublicKeyAlgorithm: cert.PublicKeyAlgorithm.String(),
ValidNow: !now.Before(cert.NotBefore) && !now.After(cert.NotAfter),
}
for _, ip := range cert.IPAddresses {
result.IPAddresses = append(result.IPAddresses, ip.String())
}
return result, nil
}
type agentDetails struct {
Version string `json:"version"`
Listen string `json:"listen"`
TLS bool `json:"tls"`
Hostname string `json:"hostname"`
DockerSocket string `json:"docker_socket"`
EnvironmentFile string `json:"environment_file"`
TokenConfigured bool `json:"token_configured"`
TokenLength int `json:"token_length"`
Token string `json:"token,omitempty"`
Certificate *certDetails `json:"certificate,omitempty"`
CertificateStatus string `json:"certificate_status"`
}
func printAgentInfo(w io.Writer, cfg config, envFile string, showToken, asJSON bool) error {
info := agentDetails{Version: version, Listen: cfg.Listen, TLS: cfg.TLS, Hostname: cfg.Hostname,
DockerSocket: cfg.DockerSock, EnvironmentFile: envFile,
TokenConfigured: validToken(cfg.Token), TokenLength: len(cfg.Token), CertificateStatus: "disabled"}
if showToken {
info.Token = cfg.Token
}
if cfg.TLS {
info.CertificateStatus = "missing"
if _, err := os.Stat(cfg.CertFile); err == nil {
certificate, err := describeCertificate(cfg.CertFile)
if err != nil {
return err
}
certificate.KeyPath = cfg.KeyFile
info.Certificate = &certificate
info.CertificateStatus = "present"
} else if !errors.Is(err, os.ErrNotExist) {
return err
}
}
if asJSON {
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
return enc.Encode(info)
}
fmt.Fprintf(w, "RustProxy Docker Agent %s\nListen: %s://%s\nHostname: %s\nDocker socket: %s\nConfiguration: %s\n", info.Version, map[bool]string{true: "https", false: "http"}[cfg.TLS], cfg.Listen, cfg.Hostname, cfg.DockerSock, envFile)
fmt.Fprintf(w, "Bearer token: configured=%t length=%d", info.TokenConfigured, info.TokenLength)
if showToken {
fmt.Fprintf(w, " value=%s", cfg.Token)
}
fmt.Fprintln(w)
fmt.Fprintf(w, "TLS certificate: %s\n", info.CertificateStatus)
if c := info.Certificate; c != nil {
fmt.Fprintf(w, " Certificate: %s\n Private key: %s (contents never displayed)\n SHA-256: %s\n Subject: %s\n Issuer: %s\n Serial: %s\n Valid from: %s\n Valid until: %s\n Valid now: %t\n DNS SAN: %s\n IP SAN: %s\n Signature algorithm: %s\n Public key algorithm: %s\n",
c.Path, c.KeyPath, c.SHA256, c.Subject, c.Issuer, c.Serial, c.NotBefore, c.NotAfter, c.ValidNow, strings.Join(c.DNSNames, ", "), strings.Join(c.IPAddresses, ", "), c.SignatureAlgorithm, c.PublicKeyAlgorithm)
}
if !showToken {
fmt.Fprintln(w, "Token redacted. Use 'sudo rustproxy-docker-agent info --show-token' or 'show-token' to reveal it.")
}
return nil
}
// Compile-time reference: readCertificate returns an x509 parsed certificate.
+6 -17
View File
@@ -54,13 +54,9 @@ func dockerContainers(ctx context.Context, sock string) ([]dockerContainer, erro
func cleanName(names []string) string {
if len(names) == 0 {
return "container"
return ""
}
name := strings.TrimPrefix(names[0], "/")
if name == "" {
return "container"
}
return name
return strings.TrimPrefix(names[0], "/")
}
func dnsLabel(value string) string {
@@ -78,9 +74,6 @@ func dnsLabel(value string) string {
}
}
out := strings.Trim(b.String(), "-")
if out == "" {
out = "app"
}
if len(out) > 48 {
out = strings.Trim(out[:48], "-")
}
@@ -92,22 +85,15 @@ func serviceIdentity(c dockerContainer, p dockerPort) (key, technical string) {
project := c.Labels["com.docker.compose.project"]
composeService := c.Labels["com.docker.compose.service"]
replica := c.Labels["com.docker.compose.container-number"]
suffix := strconv.Itoa(int(p.PrivatePort))
if project != "" && composeService != "" {
if replica == "" {
replica = name
}
key = fmt.Sprintf("compose:%s/%s/%s:%d/%s", project, composeService, replica, p.PrivatePort, p.Type)
technicalBase := project + "-" + composeService
if replica != "1" {
technicalBase += "-" + replica
}
technical = dnsLabel(technicalBase + "-" + suffix)
} else {
key = fmt.Sprintf("container:%s:%d/%s", name, p.PrivatePort, p.Type)
technical = dnsLabel(name + "-" + suffix)
}
return key, technical
return key, dnsLabel(name)
}
func servicesFrom(containers []dockerContainer) []service {
@@ -115,6 +101,9 @@ func servicesFrom(containers []dockerContainer) []service {
bindingSeen := make(map[string]map[string]struct{})
for _, c := range containers {
name := cleanName(c.Names)
if dnsLabel(name) == "" {
continue
}
for _, p := range c.Ports {
if p.PublicPort == 0 || p.Type != "tcp" {
continue
+6 -24
View File
@@ -26,33 +26,15 @@ install -m 0755 "$TMP/rustproxy-docker-agent" /usr/local/bin/rustproxy-docker-ag
install -d -m 0750 /etc/rustproxy
install -d -o rustproxy-docker-agent -g docker -m 0750 /var/lib/rustproxy-docker-agent
if [[ ! -f /etc/rustproxy/docker-agent.env ]]; then
install -m 0640 docker-agent.env.example /etc/rustproxy/docker-agent.env
TOKEN=$(od -An -N32 -tx1 /dev/urandom | tr -d ' \n')
sed -i "s/replace-with-at-least-24-random-characters/$TOKEN/" /etc/rustproxy/docker-agent.env
echo "Created /etc/rustproxy/docker-agent.env with a random token."
install -m 0600 docker-agent.env.example /etc/rustproxy/docker-agent.env
fi
# Provisioning is delegated to the Go CLI, which keeps existing credentials.
/usr/local/bin/rustproxy-docker-agent init
install -m 0644 rustproxy-docker-agent.service /etc/systemd/system/rustproxy-docker-agent.service
systemctl daemon-reload
systemctl enable --now rustproxy-docker-agent.service
systemctl --no-pager --full status rustproxy-docker-agent.service || true
CERT_FILE=/var/lib/rustproxy-docker-agent/agent.crt
for _ in $(seq 1 30); do
[[ -s "$CERT_FILE" ]] && break
sleep 0.1
done
if [[ -s "$CERT_FILE" ]]; then
FINGERPRINT=$(/usr/local/bin/rustproxy-docker-agent --cert-file "$CERT_FILE" --print-fingerprint)
printf '\nAgent certificate SHA-256:\n%s\n' "$FINGERPRINT"
else
echo "Agent certificate was not created; inspect journalctl -u rustproxy-docker-agent." >&2
fi
TOKEN=$(sed -n 's/^RUSTPROXY_DOCKER_AGENT_TOKEN=//p' /etc/rustproxy/docker-agent.env | head -n1)
if [[ -n "$TOKEN" ]]; then
printf '\nBearer token:\n%s\n' "$TOKEN"
else
echo "Bearer token is missing from /etc/rustproxy/docker-agent.env." >&2
fi
echo "Installed. Configure RustProxy -> Settings -> Docker agents with the https:// endpoint, bearer token, and certificate fingerprint printed above."
printf '\nAgent endpoint, TLS certificate details and Bearer token (sensitive):\n'
/usr/local/bin/rustproxy-docker-agent info --show-token
printf '\nInstalled. Configure RustProxy -> Settings -> Docker agents with the HTTPS endpoint, token and SHA-256 fingerprint above.\n'
+180 -47
View File
@@ -5,6 +5,7 @@ import (
"errors"
"flag"
"fmt"
"io"
"log"
"net"
"net/http"
@@ -16,80 +17,214 @@ import (
"time"
)
func envOr(name, fallback string) string {
if value := strings.TrimSpace(os.Getenv(name)); value != "" {
return value
}
func envBoolValue(raw string, fallback bool) bool {
if strings.TrimSpace(raw) == "" {
return fallback
}
func envBool(name string, fallback bool) bool {
raw := strings.TrimSpace(os.Getenv(name))
if raw == "" {
return fallback
}
value, err := strconv.ParseBool(raw)
value, err := strconv.ParseBool(strings.TrimSpace(raw))
if err != nil {
return fallback
}
return value
}
func validToken(token string) bool {
return len(token) >= 24 && !strings.HasPrefix(token, "replace-with-")
}
func main() {
if err := run(os.Args[1:], os.Stdout, os.Stderr); err != nil {
log.Fatal(err)
}
}
func run(args []string, stdout, stderr io.Writer) error {
command := "serve" // Preserve the legacy invocation used by existing systemd units.
if len(args) > 0 && !strings.HasPrefix(args[0], "-") {
command, args = args[0], args[1:]
}
if command == "help" {
printUsage(stdout)
return nil
}
if command == "version" {
fmt.Fprintln(stdout, version)
return nil
}
envFile, err := requestedEnvFile(args)
if err != nil {
return err
}
fileValues, err := readEnvironmentFile(envFile)
if err != nil {
// PID 1 already loaded EnvironmentFile for the unprivileged daemon;
// access to the on-disk secret is deliberately restricted to root.
if !errors.Is(err, os.ErrPermission) || os.Getenv("RUSTPROXY_DOCKER_AGENT_TOKEN") == "" {
return err
}
fileValues = map[string]string{}
}
env := func(name, fallback string) string {
if v, ok := os.LookupEnv(name); ok {
return strings.TrimSpace(v)
}
if v, ok := fileValues[name]; ok {
return strings.TrimSpace(v)
}
return fallback
}
hostname, _ := os.Hostname()
cfg := config{}
flag.StringVar(&cfg.Listen, "listen", envOr("RUSTPROXY_DOCKER_AGENT_LISTEN", "127.0.0.1:9876"), "listen address")
flag.StringVar(&cfg.Token, "token", envOr("RUSTPROXY_DOCKER_AGENT_TOKEN", ""), "Bearer token (prefer environment variable)")
flag.StringVar(&cfg.DockerSock, "docker-socket", envOr("RUSTPROXY_DOCKER_SOCKET", "/var/run/docker.sock"), "Docker Engine unix socket")
flag.StringVar(&cfg.AdvertiseIP, "advertise-ip", envOr("RUSTPROXY_DOCKER_ADVERTISE_IP", ""), "optional preferred host IP; all usable host addresses are still reported")
flag.StringVar(&cfg.PublicIPURL, "public-ip-url", envOr("RUSTPROXY_DOCKER_PUBLIC_IP_URL", "https://api.ipify.org"), "public IP lookup URL; empty disables")
flag.StringVar(&cfg.Hostname, "hostname", envOr("RUSTPROXY_DOCKER_HOSTNAME", hostname), "reported host name")
flag.BoolVar(&cfg.TLS, "tls", envBool("RUSTPROXY_DOCKER_AGENT_TLS", true), "serve HTTPS; enabled by default")
flag.StringVar(&cfg.CertFile, "cert-file", envOr("RUSTPROXY_DOCKER_AGENT_CERT_FILE", "/var/lib/rustproxy-docker-agent/agent.crt"), "TLS certificate file")
flag.StringVar(&cfg.KeyFile, "key-file", envOr("RUSTPROXY_DOCKER_AGENT_KEY_FILE", "/var/lib/rustproxy-docker-agent/agent.key"), "TLS private key file")
initCert := flag.Bool("init-cert", false, "create the self-signed TLS certificate if missing and exit")
printFingerprint := flag.Bool("print-fingerprint", false, "print SHA-256 certificate fingerprint and exit")
flag.Parse()
fs := flag.NewFlagSet("rustproxy-docker-agent "+command, flag.ContinueOnError)
fs.SetOutput(stderr)
fs.StringVar(&envFile, "env-file", envFile, "systemd EnvironmentFile path")
fs.StringVar(&cfg.Listen, "listen", env("RUSTPROXY_DOCKER_AGENT_LISTEN", "127.0.0.1:9876"), "listen address")
// NEVER supply a token as a flag default: `--help` would leak it into output.
tokenOverride := fs.String("token", "", "Bearer token override (prefer the protected environment file)")
fs.StringVar(&cfg.DockerSock, "docker-socket", env("RUSTPROXY_DOCKER_SOCKET", "/var/run/docker.sock"), "Docker Engine unix socket")
fs.StringVar(&cfg.AdvertiseIP, "advertise-ip", env("RUSTPROXY_DOCKER_ADVERTISE_IP", ""), "preferred host IP")
fs.StringVar(&cfg.PublicIPURL, "public-ip-url", env("RUSTPROXY_DOCKER_PUBLIC_IP_URL", "https://api.ipify.org"), "public IP lookup URL")
fs.StringVar(&cfg.Hostname, "hostname", env("RUSTPROXY_DOCKER_HOSTNAME", hostname), "reported host name")
fs.BoolVar(&cfg.TLS, "tls", envBoolValue(env("RUSTPROXY_DOCKER_AGENT_TLS", "true"), true), "serve HTTPS")
fs.StringVar(&cfg.CertFile, "cert-file", env("RUSTPROXY_DOCKER_AGENT_CERT_FILE", "/var/lib/rustproxy-docker-agent/agent.crt"), "TLS certificate file")
fs.StringVar(&cfg.KeyFile, "key-file", env("RUSTPROXY_DOCKER_AGENT_KEY_FILE", "/var/lib/rustproxy-docker-agent/agent.key"), "TLS private key file")
showToken := fs.Bool("show-token", false, "include the SECRET Bearer token in 'info' output")
asJSON := fs.Bool("json", false, "print 'info' as JSON")
initCert := fs.Bool("init-cert", false, "legacy alias for 'cert-init'")
printFingerprint := fs.Bool("print-fingerprint", false, "legacy alias for 'print-fingerprint'")
showVersion := fs.Bool("version", false, "print version")
fs.Usage = func() { printUsage(stderr); fs.PrintDefaults() }
if err := fs.Parse(args); err != nil {
if errors.Is(err, flag.ErrHelp) {
return nil
}
return err
}
if fs.NArg() != 0 {
return fmt.Errorf("unexpected arguments: %s", strings.Join(fs.Args(), " "))
}
if *showVersion {
fmt.Fprintln(stdout, version)
return nil
}
if *initCert {
fingerprint, created, err := ensureSelfSignedCertificate(cfg.CertFile, cfg.KeyFile, cfg.Hostname)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", fingerprint)
if created {
log.Printf("created self-signed certificate %s", cfg.CertFile)
}
return
command = "cert-init"
}
if *printFingerprint {
command = "print-fingerprint"
}
cfg.Token = env("RUSTPROXY_DOCKER_AGENT_TOKEN", "")
if *tokenOverride != "" {
cfg.Token = *tokenOverride
}
switch command {
case "init":
_, created, err := provisionToken(envFile, false)
if err != nil {
return err
}
if created {
fmt.Fprintf(stdout, "Created a random Bearer token in %s\n", envFile)
}
// Reload because a new token was written after the initial environment read.
fresh, err := readEnvironmentFile(envFile)
if err != nil {
return err
}
cfg.Token = fresh["RUSTPROXY_DOCKER_AGENT_TOKEN"]
if cfg.TLS {
fingerprint, created, err := ensureSelfSignedCertificate(cfg.CertFile, cfg.KeyFile, cfg.Hostname)
if err != nil {
return err
}
if created {
if err := setServiceCertificateOwnership(cfg.CertFile, cfg.KeyFile); err != nil {
return err
}
}
fmt.Fprintf(stdout, "Certificate SHA-256: %s\n", fingerprint)
}
fmt.Fprintf(stdout, "Provisioning complete. Run 'sudo rustproxy-docker-agent info --show-token' to configure RustProxy.\n")
return nil
case "rotate-token":
if _, _, err := provisionToken(envFile, true); err != nil {
return err
}
fmt.Fprintf(stdout, "Rotated token in %s. Restart rustproxy-docker-agent.service to activate it and update RustProxy's agent configuration.\n", envFile)
return nil
case "show-token":
if !validToken(cfg.Token) {
return errors.New("no valid token configured; run 'sudo rustproxy-docker-agent init'")
}
fmt.Fprintln(stdout, cfg.Token) // Deliberate: an explicit administrative command.
return nil
case "info":
return printAgentInfo(stdout, cfg, envFile, *showToken, *asJSON)
case "cert-init":
fingerprint, created, err := ensureSelfSignedCertificate(cfg.CertFile, cfg.KeyFile, cfg.Hostname)
if err != nil {
return err
}
if created {
if err := setServiceCertificateOwnership(cfg.CertFile, cfg.KeyFile); err != nil {
return err
}
}
fmt.Fprintln(stdout, fingerprint)
return nil
case "print-fingerprint":
fingerprint, err := certificateFingerprint(cfg.CertFile)
if err != nil {
log.Fatal(err)
return err
}
fmt.Printf("%s\n", fingerprint)
return
fmt.Fprintln(stdout, fingerprint)
return nil
case "serve":
return serve(cfg)
default:
return fmt.Errorf("unknown command %q (use 'help')", command)
}
if len(cfg.Token) < 24 {
log.Fatal("RUSTPROXY_DOCKER_AGENT_TOKEN must contain at least 24 characters")
}
func printUsage(w io.Writer) {
fmt.Fprintln(w, "RustProxy Docker Agent commands:")
fmt.Fprintln(w, " serve (default) Run the HTTPS Docker inventory server")
fmt.Fprintln(w, " init Generate missing token and certificate, preserve existing secrets")
fmt.Fprintln(w, " info [--show-token] Show endpoint, TLS certificate details and token status")
fmt.Fprintln(w, " show-token Print the full Bearer token (sensitive)")
fmt.Fprintln(w, " rotate-token Replace the Bearer token (service restart required)")
fmt.Fprintln(w, " cert-init Create a missing TLS certificate and key")
fmt.Fprintln(w, " print-fingerprint Print current TLS certificate SHA-256 fingerprint")
fmt.Fprintln(w, " version Print version")
fmt.Fprintln(w, " --env-file PATH Override /etc/rustproxy/docker-agent.env")
fmt.Fprintln(w, " --help Show command flags")
}
func serve(cfg config) error {
if !validToken(cfg.Token) {
return errors.New("RUSTPROXY_DOCKER_AGENT_TOKEN must be a non-placeholder value with at least 24 characters; run 'sudo rustproxy-docker-agent init'")
}
if _, err := os.Stat(cfg.DockerSock); err != nil {
log.Fatalf("Docker socket unavailable: %v", err)
return fmt.Errorf("Docker socket unavailable: %w", err)
}
if _, _, err := net.SplitHostPort(cfg.Listen); err != nil {
log.Fatalf("invalid listen address: %v", err)
return fmt.Errorf("invalid listen address: %w", err)
}
if cfg.TLS {
fingerprint, created, err := ensureSelfSignedCertificate(cfg.CertFile, cfg.KeyFile, cfg.Hostname)
if err != nil {
log.Fatalf("TLS certificate: %v", err)
return fmt.Errorf("TLS certificate: %w", err)
}
cfg.Fingerprint = fingerprint
if created {
log.Printf("generated self-signed TLS certificate; SHA-256 %s", fingerprint)
}
if details, err := describeCertificate(cfg.CertFile); err == nil {
log.Printf("TLS certificate: subject=%s valid_until=%s SHA-256=%s", details.Subject, details.NotAfter, details.SHA256)
}
}
server := &http.Server{Addr: cfg.Listen, Handler: handler(cfg), ReadHeaderTimeout: 3 * time.Second, IdleTimeout: 30 * time.Second, MaxHeaderBytes: 16 << 10}
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
@@ -103,10 +238,7 @@ func main() {
if cfg.TLS {
scheme = "https"
}
log.Printf("RustProxy Docker Agent %s listening on %s://%s", version, scheme, cfg.Listen)
if cfg.Fingerprint != "" {
log.Printf("TLS certificate SHA-256: %s", cfg.Fingerprint)
}
log.Printf("RustProxy Docker Agent %s listening on %s://%s; Bearer token configured (%d chars, redacted)", version, scheme, cfg.Listen, len(cfg.Token))
var err error
if cfg.TLS {
err = server.ListenAndServeTLS(cfg.CertFile, cfg.KeyFile)
@@ -114,6 +246,7 @@ func main() {
err = server.ListenAndServe()
}
if err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatal(err)
return err
}
return nil
}
+13 -1
View File
@@ -41,7 +41,7 @@ func TestServicesFromUsesStableComposeIdentity(t *testing.T) {
if len(got) != 1 {
t.Fatalf("got %d services", len(got))
}
if got[0].Key != "compose:lab/web/1:8080/tcp" || got[0].TechnicalName != "lab-web-8080" {
if got[0].Key != "compose:lab/web/1:8080/tcp" || got[0].TechnicalName != "web-1" {
t.Fatalf("unexpected service %#v", got[0])
}
if len(got[0].Bindings) != 1 || got[0].Bindings[0].PublicPort != 18080 {
@@ -64,6 +64,9 @@ func TestServicesFromKeepsComposeReplicasAndAllBindings(t *testing.T) {
if got[0].Key != "compose:lab/web/1:8080/tcp" || got[1].Key != "compose:lab/web/2:8080/tcp" {
t.Fatalf("unexpected replica keys %#v", got)
}
if got[0].TechnicalName != "lab-web-1" || got[1].TechnicalName != "lab-web-2" {
t.Fatalf("expected container-based hostnames, got %#v", got)
}
if len(got[0].Bindings) != 3 {
t.Fatalf("expected all published bindings, got %#v", got[0].Bindings)
}
@@ -243,3 +246,12 @@ func TestTLSInventoryReportsServingCertificateFingerprint(t *testing.T) {
t.Fatalf("inventory fingerprint = %q, want %q", got.CertificateSHA256, fingerprint)
}
}
func TestTechnicalNameIgnoresImageAndPort(t *testing.T) {
c := dockerContainer{Names: []string{"/My_App"}, Image: "registry.example.com/image:latest"}
one := servicesFrom([]dockerContainer{{Names: c.Names, Image: c.Image, Ports: []dockerPort{{PrivatePort: 80, PublicPort: 8080, Type: "tcp"}}}})
two := servicesFrom([]dockerContainer{{Names: c.Names, Image: "changed", Ports: []dockerPort{{PrivatePort: 8080, PublicPort: 9000, Type: "tcp"}}}})
if len(one) != 1 || len(two) != 1 || one[0].TechnicalName != "my-app" || two[0].TechnicalName != "my-app" {
t.Fatalf("technical names must use the container name: %#v %#v", one, two)
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
package main
const version = "0.29.2"
var version = "1.0.1" // overridden by the package builder using -ldflags -X main.version
type config struct {
Listen string
+106
View File
@@ -0,0 +1,106 @@
# Access, authentication and traffic protection
Access control can be configured globally, on a Vhost and on selected Custom locations. Policies are **cumulative**: a more specific allow rule does not cancel a denial at a broader level. Changes should be tested with both an allowed and a denied client.
## Global block / allow policy
**Settings → Rules & headers → Global block / allow** applies to public HTTP traffic across all vhosts, redirects and not-found hosts before upstream access. In addition to the existing User-Agent deny patterns, use the global country block/allow list (ISO two-letter codes), client IP/CIDR and allow-only DNS ACL, HTTP method policy, request Content-Type denylist and shared per-IP rate limit. All are disabled by default except previously configured User-Agent blocking. An explicit IP allow does **not** bypass the country policy: both must permit a request. Missing GeoIP country is allowed for country block mode and denied for country allow mode.
Global restrictions add to per-vhost and path rules. Rate-limit requests are counted for each client IP **across** vhosts. Prefer a conservative shared ceiling to avoid blocking shared NAT clients. A global method denial returns 405, the content-type/ACL/UA policy returns 403, and the shared rate limiter returns 429 with `Retry-After`. Admin and TCP/UDP stream listeners are outside this HTTP scope. For global response headers, use the adjacent **Security headers** subtab; see [Security](SECURITY.md).
## IP, country and DDNS rules
- **IP/CIDR ACL:** allow or deny IPv4/IPv6 networks. Use reusable ACL profiles for settings shared by several domains. Both ordinary and Docker Vhosts use a searchable multi-select with selected badges and a preview of IP/CIDR rules, country restrictions and the default policy; the selection changes presentation only, not ACL evaluation. A Vhost can select up to eight profiles (`acl_profile_ids`, or one legacy `acl_profile_id`); Docker assignments can override or inherit the agent's profile selection.
- **DNS/DDNS:** an ACL `allow` rule may use a hostname, such as `allow office.example.ddns.net`; `deny` with DNS is not supported. Records are refreshed periodically; unknown, failed or expired resolutions authorize nobody under the allow rule. This is appropriate for a trusted, controlled DDNS record, not a replacement for login.
- **Country access:** allow-only or blocking based on the configured GeoIP database. Allow-only mode fails closed when a country cannot be determined. Keep the GeoIP database updated in **Settings → GeoIP updates**.
- **Cloudflare client IP:** enable the integration only when traffic really arrives through a controlled Cloudflare/trusted-proxy boundary. Never trust arbitrary client-supplied forwarding headers as an identity proof.
**Multiple profiles on one Vhost:** For different explicit allowlists, allows are combined (logical OR). Explicit IP denies and blocked countries take precedence over allows from every selected profile. If no explicit allow or deny matches, fallback allows only when all selected profile defaults permit it (country allow-only requires a listed country). One selected profile uses the pre-existing ACL behavior exactly. The separate inline Vhost ACL, country policy, location ACL and global host ACL remain restrictive scopes (logical AND): a deny there cannot be overridden by a profile allow. Do not mix broadly permissive profiles with blocking profiles without checking the effective result from both an allowed and a blocked IP/country.
Default deny plus explicit allow exceptions is usually safer for private admin applications. Apply rules to a staging client before enforcing them on a live business-critical service.
In the **Vhost → Security** editor, Traffic policy groups reusable ACL profiles and the separate **inline Vhost IP/CIDR access list** together, followed by HTTP methods, request Content-Type filtering, per-IP rates, country policy, country-rate and User-Agent-rate controls. The inline list is not a second version of an assigned profile: both layers remain independently enforced; an allow in one does not cancel a deny in the other. The **Vhost → Authentication** tab first selects an identity provider, then a compatible integration method (Forward Auth, OIDC or Basic). A Vhost still uses only one active method; existing JSON settings are retained for compatibility. HSTS, security headers and User-Agent blocking are separate Security groups. **Custom locations → Access** separates its ACL profile, HTTP method, country and inline IP/CIDR fields. These are only layout changes; restriction scopes still combine as described above.
## Content-Type request denylist
Ordinary Vhosts can reject selected MIME types under **Vhosts → Security → Request Content-Type**. With the option enabled, an exact media type such as `multipart/form-data` (including parameters) or a family such as `application/*` is rejected with **403** before proxy/Web Server/PHP routing or cache handling. Multiple entries are supported; missing `Content-Type` is not rejected. Ambiguous/malformed headers are denied when filtering is enabled. Docker vhost controls do not expose this setting. See [Vhosts](VHOSTS.md#block-request-content-type-values-ordinary-vhosts) for a configuration example and limits.
## HTTP methods and rate limits
Method policies allow, block or leave methods unrestricted. A denied method returns `405`, while typical ACL/country denials return `403`. Per-client rate limits can configure request count, window and burst with optional country overrides; exceeding the limit returns `429` with `Retry-After`. Limits are application controls, not a replacement for upstream DDoS protection.
## Identity providers and authentication integration methods
Choose **Identity provider → Integration method** in **Vhost → Authentication**. Tinyauth, Authelia and authentik support both **Forward Auth** and **OIDC** when the installed identity service has the relevant endpoint enabled. Pocket ID and other generic OpenID Connect issuers use **OIDC** only. Built-in HTTP Basic remains available. Forward Auth uses the vendor-specific settings object and transport, while OIDC uses the shared `oidc` settings and an exact application callback. The protected app does not need to implement OIDC.
New Vhosts store an explicit `authentication` choice; old Vhosts with only `tinyauth.enabled`, `authelia.enabled`, `authentik.enabled`, `basic_auth.enabled` or `oidc.enabled` still load. An older OIDC configuration without a named provider appears as **Other OIDC provider**, retaining its issuer and client settings. The selected pair must agree with the enabled settings (e.g. `authentication: {provider: authelia, method: oidc}` requires `oidc.enabled: true` and `authelia.enabled: false`). Conflicting choices are rejected, not silently downgraded to an unprotected Vhost.
### Existing integrations
- **Basic Auth:** protect selected Vhosts with configured credentials. Use HTTPS so credentials are not sent over plaintext.
- **Tinyauth:** add a verification URL and an HTTPS login URL. RustProxy makes a bounded, body-free authorization request and can pass specifically allowed identity headers upstream. An origin URL automatically uses `/api/auth/nginx` (or set that path explicitly). This Nginx auth-request integration sends `X-Original-URL` and `X-Original-Method`, with a validated `X-Forwarded-For` and `X-Real-IP` for Tinyauth IP ACLs. It does **not** send `X-Forwarded-Host` or `X-Forwarded-URI` to the verifier, avoiding the `conflicting auth module headers` rejection introduced in Tinyauth 5.2.0 while keeping compatibility with earlier versions that implement the Nginx endpoint. Tinyauth has its own timeout and private-backend policy.
- **Provider-neutral gateway error pages:** One embedded `src/admin/web/error_auth_provider.html` template renders a self-contained HTML page for **Tinyauth, Authelia, authentik, and future providers**, using only the safe provider display name and the failure category. It replaces the three vendor-specific `error_tinyauth_*.html` source files while preserving the familiar Tinyauth response titles and statuses. For example: `502 Tinyauth connection error` if its HTTP client cannot reach Tinyauth (DNS/TCP/TLS/transport), `504 Tinyauth timeout` if the auth deadline expires, or `502 Tinyauth verification error` for unexpected verifier status or invalid/oversized data. The page always retains the HTTP error status, `Content-Type: text/html; charset=utf-8`, `Cache-Control: private, no-store` and `X-Content-Type-Options: nosniff`. Backend URLs, protocol errors and identity data are not shown to visitors. All of these cases **deny access** to the protected origin. HTTP `401`/`403` and configured login redirects remain unchanged. These auth-specific pages take precedence over custom Vhost/general HTTP error pages so the failure remains distinguishable. The generic failure marker also protects Basic Auth denials and provider login redirects from being replaced by Vhost pages. HTTPS-verifier transport errors are classified as connection failures, truncated/oversized/invalid verifier responses as verification failures, and verification deadlines as timeouts.
- Point the verifier URL **directly** at the private Tinyauth service (for example `http://tinyauth:3000/api/auth/nginx`), rather than routing it through a second public proxy that might inject conflicting headers. Do not disable Tinyauth's header-spoofing protections. Confirm that Tinyauth trusts only the actual RustProxy address/network when enabling Tinyauth IP-based access controls (`TINYAUTH_AUTH_TRUSTEDPROXIES`).
- **Authelia:** select **Authelia → Forward Auth** under **Vhost → Authentication**. Enter the internal service origin (for example `http://authelia:9091`) and the public HTTPS login portal (for example `https://auth.example.com`); RustProxy calls `/api/authz/forward-auth`. It forwards trusted `X-Forwarded-*`, `X-Original-URL` and `X-Original-Method` metadata, verifies the provider result, returns provider-approved `302/303` login redirects only to the configured portal origin, and forwards configured `Remote-*` identity headers after stripping client-forged values. A `200` verifier response alone grants access. `401`/`403` deny; transport failures return 502 and timeouts 504. The Authelia `session.cookies` domains and `authelia_url` are defined in Authelia itself: protecting **unrelated registered domains** generally requires a login URL/cookie configuration within each domain, not one cookie from a single unrelated site.
- **authentik:** select **authentik → Forward Auth** and enter the private outpost origin (for example `http://authentik:9000`), not an `/if/flow` login URL. RustProxy calls `/outpost.goauthentik.io/auth/nginx`, forwards verified `X-Authentik-*` identity headers, and responds to a `401` with a same-origin login start at `/outpost.goauthentik.io/start?rd=...`. RustProxy reserves `/outpost.goauthentik.io` (and descendants) on the protected application hostname and forwards them to the configured outpost **without checking authentication again**, after ordinary global/Vhost IP/country/rate rules. No manual Custom Location is needed for that path. In authentik create a separate **Proxy Provider → Forward auth (single application)** for each protected external application URL, assign it to the serving outpost, then enable that Vhost's integration. Multiple unrelated domains can share the same authentik installation; they do **not** share a browser cookie across domains.
- **OIDC (opt-in):** choose **Authelia → OIDC**, **authentik → OIDC**, **Pocket ID → OIDC** or **Other OIDC provider → OIDC** for the protected Vhost. This switches to the shared OIDC implementation instead of the vendor's Forward Auth adapter. Provide the exact HTTPS **Issuer**, Client ID and confidential Client Secret, a strong unique Session Secret (`openssl rand -hex 32`), and `https://YOUR-EXACT-APP-HOST/_rustproxy/oidc/callback` as the **Redirect URL**. Register that exact callback as an allowed redirect URI in the identity provider. The issuer can be `https://auth.example.com.cc` while the protected hostname is `app.linuxiarz.pl`; browser cookies do not cross between them. The issuer's discovery `/.well-known/openid-configuration`, authorization, token and JWKS endpoints must resolve from RustProxy and use the issuer's HTTPS origin with trusted TLS. Use split DNS or the private-address opt-in if the issuer resolves to internal addresses; do not disable HTTPS certificate validation. OIDC currently requires a confidential client using `client_secret_basic`, authorization-code flow, and an RS256 or ES256 signing key. Public PKCE-only clients are not supported.
- **OIDC session and identity:** RustProxy generates short-lived transaction state with S256 PKCE and nonce; requires a signed ID token with exact issuer, audience, validity and nonce; and encrypts a separate `__Host-` session cookie for the application host (default 3600 seconds, max 86400). The session is **not continuously revalidated with the issuer**; removing a user at the issuer does not invalidate an already-issued RustProxy session before its expiry or local logout (`/_rustproxy/oidc/logout`). Successful login injects `Remote-Sub`, `Remote-Issuer`, `Remote-User` and optional `Remote-Email`, `Remote-Name`, `Remote-Groups`; use `(Remote-Issuer, Remote-Sub)` for stable account identity and do not auto-link accounts solely by email or username. The backend must not be directly accessible from untrusted clients. Non-GET/HEAD requests without a valid session receive 401, and authentication endpoint failures return 502/504. The provider URL must not itself be protected by this Vhost's OIDC integration. For Authelia, enabling its Forward Auth portal alone is insufficient: also configure Authelia as an OIDC issuer under `identity_providers.oidc`, with at least one signing key, and register a confidential client for every callback URL (or separate clients per application). OIDC does not require any special support from the application being proxied, only from the remote identity provider.
- **Adding another provider:** Register the vendor and supported methods in `auth_provider::choice::IdentityProvider` and in the Vhost editor. OIDC-compatible vendors reuse the shared `oidc` integration and may need no new Rust transport adapter. New Forward Auth adapters belong in `src/<provider>/mod.rs` with tests and may reuse `forward_auth::Adapter`; their compiled implementation implements the object-safe `auth_provider::Provider` trait. Runtime request handling continues through one `CompiledFrontend.auth_provider` slot and provider-neutral `error_auth_provider.html`. Preserve legacy Vhost configuration keys; add new providers without copying OIDC protocol code or HTML error templates.
- **Per-provider security:** The verifier/outpost connection uses TLS certificate verification for HTTPS with optional additional CA, explicit private-backend opt-in and the existing safe-address/local-listener restrictions. Only allowlisted response identity fields and bounded session cookies reach applications/browsers. Inaccessible, malformed or timed-out authorization services **deny** origin access. Both Authelia and authentik are disabled by default; old Tinyauth settings are retained when inactive. Only one integration method can be enabled for one Vhost. Protected responses bypass shared caching. Confirm both allowed/denied requests, logins, sessions, redirects and WebSockets with the actual provider before rollout.
### Minimal Vhost configuration examples (inside a Vhost object)
Authelia with Forward Auth:
```json
"authentication": {"provider": "authelia", "method": "forward_auth"},
"authelia": {
"enabled": true,
"url": "http://authelia.internal:9091",
"login_url": "https://auth.example.com",
"allow_private": true
}
```
authentik with Forward Auth (same URL for the outpost check and reserved public path):
```json
"authentication": {"provider": "authentik", "method": "forward_auth"},
"authentik": {
"enabled": true,
"url": "http://authentik.internal:9000",
"allow_private": true
}
```
For central login from an unrelated application domain, select **Authelia → OIDC** and use the following Vhost fragment (also register this callback with the issuer):
```json
"authentication": {"provider": "authelia", "method": "oidc"},
"oidc": {
"enabled": true,
"issuer_url": "https://auth.example.com.cc",
"backend_url": "http://10.0.0.20:9091",
"redirect_url": "https://app.linuxiarz.pl/_rustproxy/oidc/callback",
"client_id": "app-client-id",
"client_secret": "CHANGE_ME_CLIENT_SECRET",
"session_secret": "CHANGE_ME_RANDOM_SECRET_AT_LEAST_32_BYTES",
"scopes": "openid profile email",
"allow_private": true
}
```
The `client_secret` and `session_secret` are illustrative placeholders and must be replaced with generated values. Disable other authentication settings for this Vhost. Set `basic_auth.enabled`, `tinyauth.enabled`, and the other provider's `enabled` to `false` for these examples. These are fragments, not whole Vhost documents. Protect the public provider portal with a separate Vhost **without** forward auth to avoid recursive redirects.
Do not use a User-Agent blocklist as the primary access control: HTTP clients can forge their User-Agent freely.
## Headers, sessions and internal services
Custom request/response headers support set/append/remove within allowed names. Security headers can be configured at Vhost scope; check interactions with HSTS and CSP in browsers. Hostnames, forwarding identity, request IDs, hop-by-hop framing and WebSocket negotiation are protected by validation. Never expose the administration HTTP listener through a public Vhost just to bypass its local-access default.
**Streams are separate.** TCP/UDP Stream listeners do not run HTTP auth, GeoIP Vhost rules, headers, cache or TLS termination. Restrict them with firewall rules and protections on the actual protocol/backend.
## Practical verification
Test allowed/denied IPs, private application login, denied HTTP methods, country policies, websocket handshakes and rate-limit recovery. Use [Logs](LOGS.md) to identify `403/405/429` responses. For origin-side client IP settings, see [Configuration](CONFIGURATION.md) and [Security](SECURITY.md).
-139
View File
@@ -1,139 +0,0 @@
> 0.7 update: configured managed certificates now have automatic renewal and
> validated hot deployment. Initial issuance/import remains explicit. Read
> [RENEWAL.md](RENEWAL.md) for defaults, retained profiles and opt-out.
# Certificate providers
Source implementation; real provider/DNS/CA issuance and Rust runtime have not been
verified in the packaging environment. Offline tests substitute Certbot, so they
cannot establish that a DNS token, zone policy or CA deployment works.
## Workflow
Open Certificates -> Add certificate. Choose one provider or import mode, enter
the domains and metadata, then request issuance. Production is selected by default
in the panel and still requires the separate explicit production confirmation. Staging
remains available for testing and its certificates are not browser-trusted.
After a successful job, import its validated pair into the library, then assign the
certificate to a vhost. No CA operation occurs on startup or merely opening a form.
| Selection | Mechanism and required configuration |
|---|---|
| HTTP-01 / domain validation | Certbot webroot; domain names and a publicly reachable plaintext HTTP listener |
| Cloudflare | DNS-01; scoped API token; `certbot-dns-cloudflare` installed |
| OVH | DNS-01; `ovh-eu` or `ovh-ca`, application key/secret, consumer key; `certbot-dns-ovh` installed |
| RFC2136 | DNS-01; server/port, TSIG name/secret/algorithm; `certbot-dns-rfc2136` installed |
| PEM import | Upload fullchain/certificate and corresponding private key, then validate |
| Existing server files | Advanced: reference a PEM pair already in the certificate directory |
RustProxy now owns its preferred Certbot environment under `<data-dir>/acme-python`.
When it is missing, RustProxy creates a virtual environment and installs pinned Certbot
5.8.0 plus `certbot-dns-cloudflare`, `certbot-dns-ovh` and
`certbot-dns-rfc2136` 5.8.0 with pip. No distribution Python files are modified and
RustProxy never executes sudo. If the stdlib `venv` module is unavailable but system
pip works, `virtualenv` is bootstrapped only inside the RustProxy data directory.
If local setup fails, a runnable system `certbot` from `PATH` is used as fallback. The
Certificates panel reports whether the managed environment, the system fallback, or no
ACME runtime is active. Systems without Python (for example some appliance layouts)
retain PEM import and existing-file certificate modes even though ACME issuance/renewal
is unavailable. `bash dev.sh acme --doctor` and `certbot plugins` inspect only the
current shell/system fallback; they do not validate provider credentials or DNS reachability.
The shipped OCI image contains Python, venv/pip and distribution Certbot packages as an
offline fallback. Its preferred managed environment is still created in the persisted
`/var/lib/rustproxy` data volume.
## HTTP-01 requirements and scope
Public TCP port **80** for every requested name must reach an active plaintext
RustProxy listener. NAT from public 80 to a private/custom HTTP port is allowed;
setting only port 8080 in public DNS is not sufficient. Check all published A/AAAA
addresses and multi-node routing. TLS-only listeners are not enough. There is no
TLS-ALPN-01 mode. This implementation accepts DNS names, not IP certificates.
Wildcard certificates require one of the DNS-01 providers, not HTTP-01.
During a running HTTP-01 job, RustProxy serves only
`/.well-known/acme-challenge/<token>` for that job's exact normalized hostnames.
Only GET/HEAD and bounded regular files from `certs/acme-http01` are accepted;
queries, traversal, unrelated names, HTTPS requests and inactive jobs cannot read
these files. The reserved path precedes application auth/cache/static pages so a
CA can validate ownership without logging in. This exception does not bypass
application authentication for any other path. Responses are no-store.
A CDN or upstream proxy must forward this path and the correct Host without
rewriting it. There is no automatic forwarding configuration at your DNS/CDN vendor.
Expired challenge jobs do not leave an open generic file server.
## DNS credentials and least privilege
Cloudflare: use an API token with `Zone:DNS:Edit` restricted to the zones used for
issuance. The adapter intentionally does not accept the account-wide Global API Key.
OVH: use a consumer key allowing `GET /domain/zone/` and GET/PUT/POST/DELETE for
`/domain/zone/<YOUR_ZONE>/*`. Repeat only for required zones, not the whole account.
Choose the region matching the account. Consult the official plugin docs if API
policies change. A domain registered at OVH but DNS hosted elsewhere must use the
actual authoritative DNS provider; registration alone is not sufficient.
RFC2136: retain the TSIG-limited update policy described in ACME_RFC2136.md. DNS
provider propagation delay is configurable. The helper rejects malformed tokens,
unknown/cross-provider fields and INI injection. It does not test your DNS policy
in advance. Wildcard DNS-01 issuance does not add wildcard HOST routing: vhost
hostnames remain exact matches.
## API input
Common fields: `provider`, `certificate_id`, `domains` (one or more DNS names), `email`,
`environment` (`staging`/`production`), `agree_tos`, `confirm_production`. RustProxy
does not impose its own domain-count ceiling; the selected CA/provider may still
apply its external certificate/SAN policy.
DNS providers accept `propagation_seconds`.
Provider-specific fields:
- `http01`: none; wildcard domains are rejected.
- `cloudflare`: `api_token`.
- `ovh`: `endpoint`, `application_key`, `application_secret`, `consumer_key`.
- `rfc2136`: `dns_server`, `dns_port`, `key_name`, `key_secret`, `key_algorithm`, `sign_query`.
`POST /api/acme/issue` is authenticated and CSRF-protected; one job runs at a time.
`GET /api/acme` is safe status metadata only. Job status is process-local; account
and credential files persist. A completed certificate has optional issuer metadata
with provider/environment, not a copy of request credentials.
## Secret storage, renewal and limitations
Provider secrets are sent to the private worker on stdin, not command arguments.
They are not stored in exported JSON, SQLite, localStorage or audit records.
Files are private (0600 credentials, 0700 directories); these permissions do NOT
encrypt them. The service user/root can read them. Certbot account/lineage state
and provider credential files persist for managed renewal under
`certs/acme/<environment>-<id>-<provider>`; RFC2136 keeps its old path without a
provider suffix. Issued PEM outputs use new immutable names before library import.
Back up these private profiles separately while issuance is idle: the standard
SQLite/configured-PEM backup does not include every account/credential profile.
Do not put real credentials into shell history or source-control examples.
Restrict the admin interface and service environment. Rotate exposed API credentials.
Configured managed certificates in 0.7 renew through the shared RustProxy worker;
validated changed material is published automatically while failed checks preserve
the active generation. Use the certificate's toggle to disable scheduled renewal.
PEM-only imports remain manual. A separate system Certbot timer may own a **different,
manual RustProxy certificate id** and publish it with `tls deploy --id ...`; it must not
also own a RustProxy-managed ACME profile. There is no external expiry alert, revocation
or certificate garbage collector. Cancellation
kills the worker group; interrupted DNS cleanup can leave TXT records an operator
must remove. See [renewal and recovery](RENEWAL.md) and test CA staging first.
## Primary references
- HTTP-01/DNS-01 constraints: https://letsencrypt.org/docs/challenge-types/
- Certbot webroot and operational use: https://eff-certbot.readthedocs.io/en/latest/using.html
- Cloudflare plugin/API scope: https://certbot-dns-cloudflare.readthedocs.io/en/stable/
- OVH plugin/API scopes: https://certbot-dns-ovh.readthedocs.io/en/latest/
- RFC2136 plugin: https://certbot-dns-rfc2136.readthedocs.io/en/stable/
These document upstream requirements, not evidence that this RustProxy build has
passed live provider or CA acceptance.
-160
View File
@@ -1,160 +0,0 @@
# Basic Let's Encrypt / RFC2136 integration - historical 0.5.0 record
> Historical implementation record. For current certificate-provider behavior, use [ACME_PROVIDERS.md](ACME_PROVIDERS.md) and [RENEWAL.md](RENEWAL.md).
For the multi-provider UI, HTTP-01, Cloudflare and OVH, see ACME_PROVIDERS.md.
Managed automatic renewal added in 0.7 is documented in RENEWAL.md.
The RFC2136 fields and private account paths below remain supported.
## Scope and prerequisites
This is an integrated control-plane adapter for **Certbot + certbot-dns-rfc2136**,
not a newly written ACME or DNS-update protocol client. The plugin creates and
cleans up DNS-01 TXT records through TSIG-signed RFC2136 updates. It must be
installed separately. No real CA issuance or authoritative-DNS update has been
performed in the packaging environment.
Rust compilation, process integration and actual certificate deployment remain
acceptance work (TEST_REPORT.md).
Use Linux/Unix with Python 3.10+, a current compatible Certbot/plugin installation,
and `python3` and `certbot` available in the RustProxy service's PATH. Check:
```bash
bash dev.sh acme --doctor
certbot --version
certbot plugins --authenticators
```
The doctor command reports executable discovery and the required plugin's name;
it does NOT verify the plugin, DNS permissions or network connectivity. Nothing
is installed automatically, and RustProxy never invokes sudo. A service hardening
policy must allow child processes, the cert directory, DNS updates/queries to the
chosen server and outbound HTTPS to the CA. Do not run the proxy as root just to
make Certbot available. Use a private, service-owned cert directory (0700).
The operator must control publicly valid DNS names and an authoritative DNS
server that permits the configured TSIG identity to change **only the necessary
_acme-challenge TXT names**. Do not grant zone-wide update authority merely for
certificates. A private update-server IP is acceptable when the resulting TXT
records are visible to the public validation resolvers. Split DNS/views, CAA,
replication delays and delegation must be checked on the operator's DNS setup.
DNS-01 does not require opening the application's HTTP listener for a challenge.
## Panel workflow
Open **Certificates > Let's Encrypt / RFC2136**. Enter a certificate library ID,
contact email, comma-separated DNS names (including optional `*.example.org`),
DNS update server IP/port, TSIG name, base64 secret, algorithm and propagation
wait. ASCII/punycode DNS names only; up to 16 names. SHA256 is the default TSIG
algorithm; SHA384/SHA512 are also allowed. SHA1/MD5 are not exposed. A TSIG key
name may be a single label such as `keyname.`. Optional signed SOA queries support
DNS servers/views that require them. Port defaults to 53; propagation defaults
to 60 seconds (allowed 10..900).
Start with **staging**. Its certificates are not publicly trusted. Production
requires a separate explicit confirmation, in addition to agreement with the
Let's Encrypt subscriber agreement. The CA directory is fixed for each mode;
arbitrary directory URLs, shell hooks and arbitrary extra Certbot options are
not accepted by this interface.
Requesting starts one bounded asynchronous job, not a proxy-path operation. Refresh
shows running, failed, or ready. At ready, the Rust control plane has checked PEM
parsing, matching key and every requested DNS name before exposing only metadata.
Click **Add issued certificate to library**, then select its ID in the vhost SSL
tab and save. Replacing an existing ID requires confirmation and retains its
legacy SNI host assignments; the whole current configuration is revalidated with
revision CAS. Failed issuance/validation/import leaves active configuration alone.
Certificates are loaded for new handshakes; changing listener protocols still
requires restart. Existing connections retain their negotiated certificate.
## Renewal and operator responsibilities
Configured RFC2136 managed profiles participate in the 0.7 renewal scheduler.
Validated changed certificate material is published automatically; there is a
per-certificate opt-out, explicit Check now and Dry run. Initial issuance/import
remains explicit. The old 0.5 helper alone was issuance-only; do not confuse
retained files or an unrelated OS Certbot timer with RustProxy publication.
See RENEWAL.md for prerequisites, timing, rollback boundaries and legacy profiles.
No external expiry notification, revocation UI or account manager is included.
Account state and TSIG credentials are kept privately under:
```text
data/certs/acme/<staging-or-production>-<certificate-id>/
rfc2136.ini # SECRET, mode 0600
profile.json # non-secret parameters, mode 0600
config/ # Certbot account, renewal, live and archive data
work/
logs/ # private Certbot diagnostics
```
The location follows the configured certificate directory, not always `data/certs`.
There is one job per RustProxy process and a filesystem lock per profile. Bounds:
16 KiB request, 128 profiles, 128 staged ACME pairs and 128 KiB per PEM file. The
provider timeout is 1200 seconds plus propagation wait; the Rust worker has a
2160-second outer deadline and stops its process group on cancellation/shutdown.
Normal TXT cleanup belongs to the plugin. A hard kill, machine crash or DNS error
can leave challenge TXT records: inspect DNS and clean only the failed challenge
values, not unrelated records. Only the latest job status lives in memory; restart
loses that status, not the private Certbot files.
TSIG/API credentials are supplied through stdin to the helper, never argv, exports
or job responses. RustProxy encrypts DNS-provider credentials in the Secret Store;
Certbot-compatible `.ini` files are recreated with private permissions only for the
external operation and removed afterwards. The browser clears submitted secret
fields and does not put them in local/session storage. Protect the Certbot account
state and private logs as secrets.
Use `rust-proxy backup create` for current installations. The encrypted full backup
includes the durable ACME profile/account state, certificates and Secret Store key
material needed for renewal. The historical `dev.sh backup` helper is not sufficient
after Secret Store migration. See SECRET_STORE_BACKUP.md.
## API and local helper
`GET /api/acme` returns the latest sanitized job, provider name and
`automatic_renewal: false`. `POST /api/acme/issue` requires the normal authenticated
session, Origin/CSRF protections and an explicit bounded JSON request:
```json
{
"certificate_id": "example",
"email": "operator@example.org",
"domains": ["example.org", "*.example.org"],
"environment": "staging",
"agree_tos": true,
"confirm_production": false,
"dns_server": "192.0.2.53",
"dns_port": 53,
"key_name": "rustproxy.example.org.",
"key_secret": "REPLACE_WITH_YOUR_PRIVATE_BASE64_TSIG_SECRET",
"key_algorithm": "HMAC-SHA256",
"sign_query": false,
"propagation_seconds": 60
}
```
This documentation example is intentionally not an executable credential. A
successful job submission returns 202; busy returns 429. Invalid input, consent
and provider failures use fixed `acme.*` diagnostics rather than returning
provider stderr or credentials. Look in the private profile logs for DNS/plugin
errors. A 202 response means accepted, not issued.
Advanced local staging without the running API:
```bash
# request.json must be private (0600), with real operator-owned parameters.
bash dev.sh acme --cert-dir /absolute/private/cert-directory < request.json
```
This CLI only stages files and prints metadata. Unlike the integrated API, it
cannot run Rust's key/SAN validation on its own and never activates a certificate.
Import through the normal validated library/config workflow. Do not post secret
JSON into shell arguments, source examples, tickets or logs.
## Primary references
- Plugin: https://certbot-dns-rfc2136.readthedocs.io/en/stable/
- Certbot CLI: https://eff-certbot.readthedocs.io/en/stable/man/certbot.html
- Staging: https://letsencrypt.org/docs/staging-environment/
- Challenge background: https://letsencrypt.org/docs/challenge-types/
+117 -646
View File
@@ -1,686 +1,157 @@
# Administration API
# Administration API and configuration automation
The API is served on the separate administration listener, normally
`http://127.0.0.1:9090`. It is never mounted on reverse-proxy HTTP/TLS listeners.
Loopback HTTP is the default. Settings can enable administration HTTPS, choose a
certificate and persist a non-loopback bind; plain remote HTTP requires the explicit
startup `--allow-remote-admin` gate. Responses carry no-store, CSP and other browser
protection headers.
RustProxy exposes an authenticated administration API on the **separate administration listener** (normally `127.0.0.1:9090`). Open **`/docs → API`** after signing in to view the bundled OpenAPI/Swagger schema, including request bodies and available actions. That schema is the endpoint reference; server validation remains authoritative.
## Public-vhost HTTP cache invalidation (opt-in)
## Selected API endpoints
`PURGE /path?query` and `BAN /prefix` operate on the normal public vhost
listener and **do not** use an admin session. They are independently denied by
default unless the administrator explicitly populates the cache's
`http_invalidation_allowed_cidrs` allowlist in **Settings → Cache**. Authorization
uses the existing trusted client-IP forwarding logic, vhost/host ACL and rate
limits. BAN is a lazy prefix rule; PURGE evicts an exact URL. Both are restricted
to the request scheme (`http` vs `https`), Host and matched vhost. A shared
two-worker maintenance guard also bounds administrator-driven invalidation;
additional requests receive 429. Ambiguous duplicate control headers and
request bodies are rejected. See
[CACHE](CACHE.md#http-ban--purge-from-authorized-client-ips) for request examples,
scoping, confirmation and reverse-proxy requirements, and
[0.31.0 security review](SECURITY_AUDIT_0.31.0.md) for audit findings. The authenticated
`/api/cache/invalidate` endpoint remains unchanged.
The current bundled `src/admin/openapi.json` declares these operations (among others):
## Browser documentation and anonymous surface
Open `/docs` **on the administration listener** after signing in. The default **Reader**
shows the bundled project Markdown documentation; the separate **API** tab renders the
OpenAPI/Swagger interface. The documentation bundle, OpenAPI JSON, JavaScript, styles and
Swagger vendor assets all require the same session. API writes are opt-in and CSRF-protected.
See [API_ACCESS.md](API_ACCESS.md).
Only GET/HEAD `/`, the panel routes `/overview`, `/vhosts`, `/streams`,
`/acl-profiles`, `/web-server`, `/configuration`, `/tls`, `/cache`, `/logging`, `/settings`,
`/assets/login.js`, `/assets/login-langs.json`, `/assets/style.css`, `/favicon.svg`,
`/healthz`, `/api/auth/status`, and POST `/api/auth/setup`, `/api/auth/login` are
anonymous. Panel routes serve the same guarded shell as `/`, so a browser refresh
preserves the selected subpage instead of falling back to the landing page. Root
optionally authenticates and selects login or operational HTML. `/healthz` is exactly `{"status":"ok"}`.
Unknown/private paths and method mismatches return a generic 401 without a session.
Only top-level `/docs` and `/docs/` navigations redirect to `/?next=docs`; API/schema
fetches never redirect to login HTML. Other invalid Host/Origin checks can return 403.
## Authentication
| Method / path | Input or result |
| Method and path | Purpose |
|---|---|
| `GET /api/auth/status` | Public `setup_required` boolean only; no version or operational state |
| `POST /api/auth/setup` | One-time `{token, username, password}`; creates no session |
| `POST /api/auth/login` | `{username, password}`; sets session cookie and returns session data |
| `GET /api/auth/session` | Username, CSRF token, absolute expiry and idle timeout |
| `POST /api/auth/logout` | Revoke the current session |
| `POST /api/auth/logout-all` | Revoke all administrator sessions |
| `POST /api/auth/password` | `{current_password, new_password}`; changes password and revokes sessions |
| `GET /healthz` | Minimal health response, without privileged state. |
| `GET /api/auth/status` | Whether first-time administrator setup is required. |
| `POST /api/auth/login` · `POST /api/auth/logout` | Start/end a browser admin session. |
| `GET /api/auth/session` | Validate current admin session. |
| `GET /api/config` · `PUT /api/config` | Read or replace validated, revisioned configuration. GET also returns read-only `storage` metadata (SQLite/JSON source and paths). |
| `POST /api/config/reload` | Revalidate and hot-reload manually changed `--config` JSON; revision-aware. |
| `GET/POST /api/api-tokens` · `DELETE /api/api-tokens/{id}` | List/generate/revoke general RO/RW API tokens (browser-admin-only). |
| `GET/POST/DELETE /api/prometheus-token` | Inspect/generate/rotate/revoke a standalone Prometheus scrape token (browser-admin-only). |
| `GET /api/config/{section}` · `PUT /api/config/{section}` | Read or atomically replace one of 23 supported configuration sections. |
| `POST /api/config/geoip/update` | Run a GeoIP download/check/reload immediately using already-saved GeoIP settings; does not change revision. |
| `GET /api/config/history` · `POST /api/config/history/restore` | Review/restore retained configuration snapshots. |
| `GET /api/status` · `GET /api/backends` | Operational status and backend observations. |
| `GET /api/cache` · `POST /api/cache/purge` · `POST /api/cache/invalidate` | Cache observations and authenticated invalidation. `GET` supports the lightweight `view=live` polling view. |
| `GET /api/transform` | Transform observations and resource usage. Supports the lightweight `view=live` polling view. |
| `GET /api/logs` · `GET /api/live-logs` | Recent and streaming request logs. |
| `GET /api/file-logs` · `GET /api/file-logs/query` | Stored-log exploration. |
| `GET /api/traffic` · `GET /api/system-info` | Traffic and system observations. Traffic supports the lightweight `view=live` polling view. |
| `GET /api/metrics/json` · `GET /api/metrics/summary` | Full JSON snapshot (includes all Prometheus samples and `summary`) or compact computed `summary` only, both pull-only. [Monitoring](MONITORING.md). |
| `GET /api/docker` · `POST /api/docker/refresh` | Docker-agent observation and refresh. |
| `GET /api/certificates` · `POST /api/certificates/upload` | Certificate inventory/import. |
| `POST /api/certificates/inspect` | Read DNS SAN coverage from a managed/external public PEM path for the editor; admin browser session only. |
| `GET /api/acme` · `POST /api/acme/issue` · `POST /api/acme/renew` | Managed certificate jobs. |
| `GET/PUT /api/preferences` | Personal UI settings, including theme/layout and optional `interface_icons` and `logo_animation` (both default `false`). |
| `GET /api/dashboard-presets` and named preset operations | Saved Overview presets. |
| `GET /docs/openapi.json` | Protected machine-readable API specification. |
Mutations, including setup/login, require `Origin` equal to the active administration scheme plus `{Host}` and
`X-RustProxy-Request: 1`. Authenticated mutations also need `X-CSRF-Token` from
session data. Send JSON as `application/json`. A permanent bearer access token
is not supported; the old setup capability does not authorize API reads/writes.
Some paths have additional DELETE/PATCH operations, job-status resources, query parameters or restricted actions. Use OpenAPI for the **actual fields/methods**, not guesses based on a table. `GET /metrics` here is not a replacement for the separate Prometheus exporter.
The `rustproxy_session` cookie is HttpOnly, SameSite=Strict and Path=/ with the configured
7–30 day absolute lifetime (default 30 days); server-side revocation still applies. HTTPS
administration adds the Secure attribute; behind a trusted HTTPS-terminating reverse proxy
the external forwarded scheme controls that flag. A protected SSH/VPN tunnel remains valid,
while direct remote administration should use HTTPS plus firewall/private-network policy.
Direct IP/localhost Host values must use the actual administration port. A DNS name explicitly listed in `administration.allowed_hosts`, or automatically
derived from the validated `administration.public_origin`, represents the
browser-facing authority and may use the trusted reverse proxy's external port. Foreign Origin/cross-site requests are rejected.
There is no permissive CORS mode.
Auth request bodies are limited to 4096 bytes.
For browser polling, `GET /api/cache?view=live`, `GET /api/transform?view=live` and `GET /api/traffic?view=live` omit static response members that do not need to be retransmitted on every interval. Their `revision` identifies the active configuration; when it changes, clients should fetch the corresponding endpoint without `view=live` and refresh their cached static fields. Requests without `view=live` remain the complete compatibility form. Cache live responses contain metrics/dashboard/bans, Transform live responses contain resources/metrics, and Traffic live responses retain current settings/GeoIP/analytics while omitting the static Vhost list. Unknown `view` values are rejected.
Typical errors include 401 for missing/invalid credentials, 403 for origin/CSRF
rejection, 409 for already-completed setup, 422 for password policy and 429 for
throttling/busy workers. Error objects usually contain an `error` code/string;
framework extraction failures are not guaranteed to have that JSON shape.
Certificate inspection accepts `{ "cert_file": "filename.pem" }` for a managed file or an absolute external path and returns `{ "cert_file": "...", "covered_hosts": ["example.com"] }`. It applies the existing certificate-file trust and size checks and reads no private key. It makes no configuration or permission changes. This POST requires the signed-in administrator's normal Origin, request-marker and CSRF checks; general RO/RW API tokens and certificate-only credentials cannot call it.
## Administrator Overview layout presets (0.33.7)
## Access and authentication
The dashboard preset endpoints are private control-plane routes and use the
existing administrator session cookie. The same Origin, request-marker and
CSRF rules as other settings writes apply. They do **not** change the proxy
configuration revision and cannot be called from public vhosts.
The administration listener is HTTP on loopback (`127.0.0.1:9090`) by default and is separate from the public Vhost listeners. No administrator password is bundled: complete first-time setup with the local CLI (`admin setup-token` or `admin init`). For remote access prefer an SSH tunnel; for direct access enable administration HTTPS with a trusted certificate and restrict the network before binding outside loopback.
| Method / path | Request or result |
| Credential | Scope |
|---|---|
| `GET /api/dashboard-presets` | Array of named layouts for the authenticated administrator |
| `PUT /api/dashboard-presets/{name}` | Create/overwrite a named layout; JSON body described below; returns 204 |
| `PATCH /api/dashboard-presets/{name}` | Rename using `{ "name": "new name" }`; returns 204 |
| `DELETE /api/dashboard-presets/{name}` | Delete the named layout; returns 204 |
| Browser session | Full administration. HttpOnly, SameSite=Strict cookie (Secure on HTTPS), absolute lifetime 7–30 days. Writes require matching Origin, `X-CSRF-Token` and `X-RustProxy-Request: 1`. |
| General API token (`ro` / `rw`) | Created in **Settings → Security → API tokens**. Displayed once, stored as a hash, bound to a mandatory IP/CIDR allowlist. Sent as `Authorization: Bearer <token>`. `ro` reads resources and sections; `rw` also writes configuration sections and selected operations. Cannot manage authentication, backups, tokens or certificate credentials. |
| Certificate API key | Key ID + Bearer token limited to specific certificate IDs and actions (reload by default, deploy opt-in). |
| Prometheus token | Scrape-only credential for the separate exporter; see [Monitoring](MONITORING.md). |
A layout is `{ "dashboard_widgets": ["..."], "dashboard_hidden_widgets": ["..."],
"dashboard_widget_sizes": null, "dashboard_density": "compact",
"overview_samples": 600 }`, using registered widget IDs and the same
width/density validation as administrator view preferences. History accepts
60, 120, 300 or 600 samples. For sizes, `null` preserves legacy grouped
placement; an object maps widget IDs to valid 12-column spans. The account
may store at most 16 named layouts; names are 1-64 UTF-8 bytes with no
control characters, surrounding whitespace or slash. Invalid values receive
422; full capacity or a conflicting rename receives 409. Unknown delete
returns 404. All write endpoints require a session-bound CSRF token.
IP checks use the transport peer, never forwarded headers. Outside loopback, Bearer credentials require administration HTTPS.
The data resides in SQLite schema 10 (`dashboard_presets`), separate from
`admin_preferences`; the factory preset is not stored as a mutable row.
The browser saves the chosen history sample count using its existing local
preference when loading a preset. The client does not use JSON configuration
files to store named presets.
### `/docs` Reader and Swagger
## Configuration and observations
The authenticated `/docs` page has two tabs: **Reader** with the guides embedded at build time, and **API** with the bundled OpenAPI schema rendered by Swagger UI. All assets are served locally; no CDN is used. Swagger uses the current session, executes read calls directly and asks for confirmation before any write.
| Method / path | Result |
### Publishing the panel behind a reverse proxy
This is a special-risk topology. Configure a fixed HTTPS public origin, an allowed hostname and a trusted forwarding boundary. Ordinary public Vhosts never forward administration cookies to an upstream. Re-test CSRF/Origin validation and cookie security after any topology change.
## Authentication and safe writes
- Ordinary API calls require an administrator browser/session cookie on the admin listener, or a scoped general API Bearer token for explicitly allowlisted API methods/paths. Browser-session writes require the matching Origin, `X-CSRF-Token` and `X-RustProxy-Request: 1`, in addition to Host validation. Scoped Bearer-token requests are exempt from browser CSRF requirements; the server enforces the token permission, source IP allowlist and HTTPS for non-loopback access instead.
- The Swagger **API** tab uses the signed-in session. Read calls can be executed; mutations require explicit confirmation. It cannot bypass server-side authorization.
- The serialized configuration limit is 2 MiB across all sections; JSON request bodies allow a bounded 64 KiB formatting overhead. Saves over the limit are rejected without applying partial changes.
- Configuration writes are revisioned (`expected_revision`) and rejected if based on a stale save. `GET /api/config` returns `{revision, config, storage}`; `storage` is runtime metadata and must not be copied inside the editable `config` object or included as a write parameter. PUT and history-restore responses remain `{revision, config}`. Prefer section updates over replacing the entire document when practical.
- Each supported section has the same **GET** and **PUT** value shape. `GET /api/config/cache` returns `{revision, section: "cache", value: {…}}`. `PUT /api/config/cache` accepts `{"expected_revision": 12, "value": {…}}`, using the **entire** section value from a fresh GET after modification. The response is the full updated `{revision, config}` envelope (not a section view). Even changes to different sections must use the latest global revision: concurrent stale writes return 409. This is replacement, not JSON Merge Patch.
- Section boundaries: `logging` uses `{"logging": {…}, "error_log": {…}}`; `response` uses `{"server_header": {…}, "diagnostic_headers": {…}, "compression": {…}}`; `global-security` uses a **single** `GlobalSecurity` object including `user_agent_blocking`. For this section, omitting `user_agent_blocking` is an invalid PUT rather than silently clearing the denylist. Independent `user-agent-blocking` and `error_log` section endpoints do not exist. The prior logging-only `PUT /api/config/logging` payload is no longer accepted: read the full logging section and send the paired value.
- The section names use kebab-case (e.g. `not-found-hosts`, `docker-agents`, `global-security`) even when the matching configuration JSON key is snake_case (`not_found_hosts`, `docker_agents`, `global_security`). GeoIP configuration lives at `GET/PUT /api/config/geoip` (serialized from `config.geoip_updates`). After saving, `POST /api/config/geoip/update` triggers a source refresh. The old standalone `/api/geoip/update` no longer exists. This action changes loaded database files, **not** the config revision. A read-only API token cannot execute it; a read/write token can.
- Full-document config imports remain backward compatible with older root-level `user_agent_blocking`; it is migrated into `global_security.user_agent_blocking` when validated. If both positions contain conflicting policies, the import is rejected. Canonical GET and saved config use the nested field. Per-vhost `user_agent_blocking` is unchanged. RO General API tokens may GET sections, RW tokens may PUT sections; the same existing authentication, Host/Origin, revision and audit checks apply.
- `/docs`, API schemas, configuration, logs, certificates and most observations are **not anonymous**. Keep the listener restricted and use HTTPS or a tunnel for remote access.
### Available configuration sections
The section name in the URL and its corresponding JSON member in a full configuration are:
| Section path (`/api/config/…`) | JSON member(s) in `config` |
|---|---|
| `GET /healthz` | Public process health/version; not a backend health probe |
| `GET /api/config` | `{revision, config}` |
| `GET /api/config/history` | Retained revision metadata; `?revision=N` returns that document plus the previous retained document |
| `POST /api/config/history/restore` | Input `{revision, expected_revision}`; validates the older retained document and publishes it as a new configuration revision |
| `PUT /api/config` | Input `{expected_revision, config}`; returns the applied envelope; retained for atomic multi-section changes |
| `PUT /api/config/{section}` | Input `{expected_revision, value}`; replaces one validated section and returns the applied envelope |
| `GET /api/backends` | Authenticated per-vhost backend observations and active/desired frontend plans |
| `GET /api/status` | Uptime, active revision, configured counts, listener addresses, feature flags, aggregate metrics, active/desired TCP/UDP streams with per-stream L4 counters, active/desired administration state, TLS policy/cipher inventory and structured-log sink counters |
| `listeners` | `listeners` |
| `docker-agents` | `docker_agents` |
| `http2` | `http2` |
| `pages` | `pages` |
| `cache` | `cache` |
| `transform` | `transform` |
| `forwarding` | `forwarding` |
| `headers` | `headers` |
| `prometheus` | `prometheus` |
| `geoip` | `geoip_updates` |
| `administration` | `administration` |
| `tls` | `tls` |
| `vhosts` | `vhosts` |
| `web-services` | `web_services` |
| `php-fpm-profiles` | `php_fpm_profiles` |
| `redirects` | `redirects` |
| `not-found-hosts` | `not_found_hosts` |
| `streams` | `streams` |
| `acl-profiles` | `acl_profiles` |
| `host-policies` | `host_policies` |
| `response` | `server_header`, `diagnostic_headers`, `compression` |
| `logging` | `logging`, `error_log` |
| `global-security` | `global_security`, including `user_agent_blocking` |
`GET /api/status?view=live` returns only volatile telemetry suitable for SSE fallback, without the heavy full configuration/OS/runtime objects. `GET /api/system-info` returns cached system diagnostics, host/runtime metadata and memory accounting under `system`, `runtime` and `memory_accounting`. Both are protected by the same cookie/origin admin guard as full status.
Every GET returns a `value` object or array whose shape can be sent unchanged to the matching PUT. For the three grouped sections the `value` contains exactly the named members; for the other sections it is the serialized value of the matching configuration member. Saving does not alter unrelated sections. An ordinary Vhost may configure mutually exclusive `oidc`, `authelia` and `authentik` objects alongside existing `tinyauth`/`basic_auth` settings; every optional provider defaults to disabled. OIDC uses `/_rustproxy/oidc/callback` on an exact Vhost HTTPS hostname and requires issuer/client/session secrets. See [Access](ACCESS.md).
`status.streams` contains `active`, `desired`, `restart_required` and a `metrics` map keyed by startup-active stream ID. Stream counters include active/total/completed sessions, rejects, errors, RX/TX bytes and UDP packet counts; the dashboard derives rates from byte-counter deltas.
| `GET /api/logs` | Recent bounded access entries, ring capacity and contention-drop count |
| `GET /api/audit` | Last 100 audit entries; database retention is 10,000 |
| `GET /api/events` | SSE `snapshot` events containing status and logs; the compact status includes `live_system` for CPU/RSS/network charts |
| `GET /metrics` | Prometheus text exposition |
| `GET /api/metrics` | Alias of `/metrics` |
### Complete section-update example
Except for health and authentication/bootstrap routes, these endpoints require a
session. These administrative previews are not anonymous. The separate configurable
exporter uses its own CIDRs and optional bearer token; see the scrape section below.
SSE has eight viewer slots, generates a snapshot every 1/2/3/5/10 seconds per authenticated client (via `?interval=`, default 3)
and rechecks session validity. It does not queue an unlimited per-viewer event log
or guarantee replay. The browser falls back to compact status/log polling only after SSE fails and stops polling after its recovery. Invalid SSE intervals are rejected with 422. An active dashboard renews
idle activity, not the absolute session deadline.
Configuration history, introduced in SQLite schema 8 and retained in current schema 10, keeps the newest 512 successful manual/configuration revisions. `GET /api/config/history` is session-protected like the rest of the administration API; `limit` is bounded to 512 and `revision` selects one retained revision for diff/inspection. `POST /api/config/history/restore` also requires the normal CSRF/request-marker protections, rejects stale `expected_revision`, and never rewinds the revision counter: restoring revision 85 while current revision is 120 creates revision 121.
Configuration updates return 409 for a stale revision, 422 for validation failure
and 429 when the eight-operation update admission bound is reached. Persistence and
snapshot publication finish before a successful response. Disconnecting the caller
after the detached writer starts does not cancel an accepted write.
The document limit is 256 KiB, with an additional 1024-byte request-envelope allowance.
For ordinary Settings saves prefer `PUT /api/config/{section}`. Supported sections are
`listeners`, `response`, `cache`, `logging`, `forwarding`, `headers`, `prometheus`, `geoip`,
`administration`, `tls`, `vhosts`, `web-services`, `redirects`, `not_found_hosts`, `streams`, `acl-profiles` and `host-policies`.
Each section write still uses revision CAS, full validation and atomic persistence.
Only a change to the derived cache-compatibility identity purges hot/cold cache;
unrelated section saves do not.
## Configuration fields
The canonical document uses `vhosts`, `web_services`, `redirects`, `listeners`, `administration`, `tls`, `logging`,
`prometheus`, `geoip_updates`, `cache`, `compression`, `server_header`, `headers`, `host_policies` and other defaulted
settings. Unknown fields are rejected. `backends`/`routes` at the top level are
input-only legacy migration fields, omitted from saved canonical documents.
```http
GET /api/config/global-security
```
```json
{
"vhosts": [{
"id": "application",
"frontend": {
"hosts": ["app.localhost", "alias.localhost"],
"listeners": [{"address":"127.0.0.1:8080", "tls":false, "http2":false}]
},
"backends": [{"id":"application", "url":"http://127.0.0.1:9000", "allow_private":true}],
"routes": [{"id":"root", "path_prefix":"/"}]
}]
"revision": 7,
"section": "global-security",
"value": {
"user_agent_blocking": {"enabled": true, "patterns": ["bad-bot"]},
"country_access": {"mode": "off", "countries": []},
"acl": null,
"method_policy": {"mode": "off", "methods": []},
"content_type_policy": {"enabled": false, "blocked": []},
"rate_limit": {"enabled": false, "requests": 100, "window_seconds": 60, "burst": 20},
"security_headers": {"hsts": {"enabled": false, "max_age_seconds": 31536000, "include_subdomains": false, "preload": false}, "nosniff": false, "frame_options": "preserve", "referrer_policy": null, "content_security_policy": null, "permissions_policy": null}
}
}
```
The backend service must already exist. Routes without `backend_id` select from
the vhost pool; an explicit ID pins that path to a member of the same pool.
`vhosts[].force_ssl=true` redirects matching cleartext requests with HTTP 308 to the
same hostname/path/query. Force SSL requires both a selected plain HTTP listener and a
selected TLS listener. Port 443 is preferred when several TLS ports are selected;
otherwise multiple non-standard TLS ports are rejected as an ambiguous redirect target.
`vhosts[].forward_client_ip` defaults to true. Set it to false only when an application
must see RustProxy's transport peer instead of the visitor: backend requests then omit
`Forwarded`, `X-Forwarded-For` and `X-Real-IP`, while `X-Forwarded-Host/Proto/Port/URI`
remain and RustProxy/Tinyauth still use the real client address. See examples and
VHOSTS.md. HTTPS upstream certificate verification is disabled when `tls_verify` is omitted. Set `tls_verify: true` to validate the origin certificate chain and identity. For verified IP-based origins, use a DNS `tls_server_name` present in the
certificate and provide `tls_ca_pem` when the issuer is private. Disabling verification
retains TLS encryption and all backend address/DNS policy checks. Certificate metadata references
files under `<data-dir>/certs`; it does not create a vhost or expose a listener.
First-class redirect hosts live under `redirects`; see `REDIRECTS.md` for matching, target, status, path/query and cache behavior.
Use the actual **complete** `value` returned from GET as the PUT `value` after editing it; the example is illustrative, not a substitute for fetching the active defaults. Then send `PUT /api/config/global-security` with `{"expected_revision":7,"value":{...}}`. A successful PUT returns the complete new configuration envelope; HTTP **409** requires re-reading the section and applying the intended changes to the newer revision. GET never writes. The UI and API use the same validation and transaction path.
ACL rules have `network` CIDR and `action` (`allow`/`deny`). Omitted ACL means no
restriction in that scope; an explicit empty ACL defaults to deny. Matching deny
wins. Host and route decisions are ANDed. Identity is the socket peer, not an
untrusted forwarded header. There are up to 128 rules per ACL.
## Local CLI alternatives
Headers run global, then vhost (where applicable), then route rules. `set` replaces, `append` adds a
separate value and `remove` accepts no value. Transport, WebSocket handshake and
client-identity headers are protected. Up to 32 rules per direction/scope, 128-byte
names and 8192-byte values are allowed. Response rules and matching vhost security policy also cover locally generated
proxy errors/denials. Multiple Set-Cookie fields remain separate.
Routing matches exact normalized hostnames, then Nginx-style location precedence: exact path,
longest priority prefix (`^~`), first matching regex in declaration order, then longest ordinary
prefix at a segment boundary (`/api` matches `/api/x`, not `/apix`). Case-insensitive regex is
also supported. Repeated literal slashes are merged for route/ACL selection, while an unrevised
backend request retains the original path/query. Locations can explicitly strip/replace/regex-
rewrite the upstream path or return a local response. Ambiguous encoded separators and traversal
forms remain rejected. Vhost pools support load balancing as described in VHOSTS.md; locations
may pin traffic to one member. Wildcard hostname matching is supported only in the documented
leading-label form; hostname regex remains unsupported.
## Metrics and logs
Metric names use `rust_proxy_` and bounded aggregate labels. Transport counters cover
HTTP/2 requests/connections, TLS handshakes/errors and WebSocket tunnel totals,
active tunnels, errors, frame bytes and closed-tunnel duration sum/count. Ordinary
HTTP body bytes and WebSocket frame bytes are separate measurements.
Access entries are emitted at exchange completion or tunnel closure, not only when
headers arrive. WebSocket exchanges retain status 101 and a `websocket_closed`
outcome when cleanly released. Errors/cancellation affect the outcome. No query
values, request bodies, cookies, password material or WebSocket payloads are logged
by the access logger. Warnings and errors use the configured error file, or stderr when that file is disabled/unavailable. The in-memory access ring retains up to 4096
completed entries; live SSE/UI snapshots use the newest 256 by default. The dashboard can
temporarily expand an open Live Logs window to the retained 4096-entry buffer and keep
merging new live snapshots until that window closes. This is not a durable lossless log archive.
## 0.1.0 cache, listener and diagnostics additions
| Method / path | Contract |
|---|---|
| GET /assets/langs.json | Public compiled language bundle: default ID, discovered language metadata/translations |
| GET /api/cache | Authenticated cache settings, metrics and bounded object metadata; never returns cached bodies |
| POST /api/cache/purge | Full/global or vhost-scoped purge with the normal mutation protections and audit |
| POST /api/cache/invalidate | Selective authenticated PURGE/BAN by exact URL, prefix, tag, safe regex or cache key; optional host/vhost scope |
| GET /api/cache/bans | List active in-memory BAN rules |
| DELETE /api/cache/bans/{id} | Remove one BAN rule with audit |
| POST /api/cache/cold/cleanup | Start an authenticated/CSRF-protected asynchronous selective Cold Cache cleanup job |
| GET /api/cache/cold/cleanup/{id} | Read status/result of an authenticated selective cleanup job |
Configuration includes defaulted `listeners`, `cache`, `transform`, `compression`, `server_header`
objects and optional per-route `cache` (`false` bypasses, null/omitted inherits).
The complete documented field contracts are in SETTINGS.md and CACHE.md. Listener
changes persist immediately but require restart; all other settings here are hot
published. Settings forms use revisioned section writes where one settings area can
be changed independently; full-document PUT remains for changes that must publish
multiple sections atomically.
Status and SSE snapshots include `cache`, `system`, and `listeners` with active,
desired, startup-default addresses and `restart_required`. Metrics include four
compression counters; log entries include `cache` (HIT/MISS/BYPASS, or `-` before
cache classification). OS measurements are optional/null and sampled off the
request path. Unsupported is not represented as zero. Prometheus adds fixed-name
cache/resource series; absent OS measurements are omitted. No arbitrary URL/cache-key
labels are introduced and the metrics endpoint remains authenticated.
The language bundle is generated by build.rs from langs/*.json and embedded into the
binary; uploading a new file requires recompilation/restart. It exposes public
translations only, not filesystem paths, administrator data or credentials.
### Web Services
`web_services` contains reusable static-file service definitions. Use `PUT /api/config/web-services` to replace that section with revision CAS. A vhost or location selects `{ "type": "web_server", "web_service_id": "..." }`; an omitted `source` remains `{ "type": "reverse_proxy" }` for compatibility. Filesystem settings remain on the Web Service and are not duplicated inside vhosts. See [WEB_SERVER.md](WEB_SERVER.md).
## 0.2.0 vhosts and backend state
`vhosts` defaults to an empty array. All models, enum values, defaults and bounds
are documented in VHOSTS.md, with complete examples under `examples/vhosts-*.json`.
Use the same revisioned PUT endpoint; there is no unauthenticated health-control
endpoint or separate unsynchronized frontend/backend write API.
`GET /api/backends` returns `{pools, active_frontends, desired_frontends}`. Pool state
includes `id`, `algorithm`, `ready`, `health_mode`, `sticky_mode`, `keepalive`,
`members`. Members expose ID/origin/weight, status, healthy flag, active exchanges,
request/error/byte totals, EWMA byte rate/header latency, last check/result, failure
and success counters, transition count, last upstream status and upstream-client
generation/request/age/rotation counters. It does not expose affinity tokens. Member status is up/down/checking/draining/disabled. In health-off
mode, eligibility is not proof that the origin is live. Draining describes operator
intent; inspect `healthy` as well. `ready` reflects new-assignment availability;
a pool containing only draining members may still serve existing cookie sessions.
Status contains `backend_pools` and `vhosts`; listener state additionally includes
`active_endpoints`, `desired_endpoints`. Each endpoint reports its address, TLS and
H2/H3 transport capabilities. `restart_required` covers socket and listener-capability
changes. Listener capabilities are configured only under `listeners.endpoints`. A vhost
then narrows them with `http2_enabled` and `http3_enabled`; both default true. For TLS,
SNI selects either H2+HTTP/1.1 or HTTP/1.1-only ALPN for the matched vhost, so disabling
H2 on one hostname does not disable it for another hostname sharing the listener.
Metrics add `rust_proxy_backend_*` per bounded vhost/member labels. Access entries
add nullable `backend` (`vhost/member` for a forwarded pool exchange). Cache hits
have no actual upstream member request and do not increase its request counters.
The `/assets/vhosts.js` frontend uses the same API/CSRF/revision
boundaries; the static script itself, like other frontend assets, is publicly readable.
## 0.3.0 certificate upload and scoped operations
`POST /api/certificates/upload` accepts JSON
`{"cert_pem":"PEM chain","key_pem":"PEM private key","hosts":["app.example"]}`.
It requires the same session, Origin and CSRF guards as configuration updates.
The request limit is 300 KiB; each PEM value is at most 128 KiB. At most two
blocking upload workers run, and the directory holds at most 128 upload-prefixed
pairs. This endpoint is staging only, not a configuration mutation.
Success: `{"certificate":{"id":"upload-...","covered_hosts":["app.example"],
"hosts":[],"cert_file":"upload-....pem","key_file":"upload-....key"},"staged":true}`.
`covered_hosts` contains the normalized DNS names extracted from the leaf certificate
SubjectAltName extension; `hosts` remains the configured active SNI bindings. No key or
certificate PEM is returned. Add the metadata to `config.tls.certificates` and its ID
to a vhost's `certificate_id`, then save `tls`/`vhosts` as needed. The panel keeps a
combined full-document Save only when staged certificate metadata and vhost assignment
must be published atomically. Unknown certificate references, mismatched keys,
uncovered DNS names and conflicting same-name selections fail validation.
There is no ACME enrollment, expiry/chain-trust guarantee or remote PEM retrieval.
`GET /api/cache` adds `vhosts: [{id, usage: {entries, resident_bytes}, settings}]`.
`POST /api/cache/purge?vhost=app` purges only that configured vhost and invalidates
its pending fills. Missing/unknown IDs return 404, not an accidental global purge.
Omitting the query purges all entries. Both actions are audited and require CSRF;
an empty body is accepted. Configuration updates purge only when cache compatibility
changes; unrelated section saves retain both hot and cold objects.
`POST /api/cache/cold/cleanup` accepts a JSON selection with optional
`unused_for_seconds`, optional `oldest` (maximum count) and `hits_zero`. At least one
criterion is required. It returns HTTP 202 with a `ColdCleanupJob`; poll
`GET /api/cache/cold/cleanup/{id}` until `status` is `done`. The result contains
`removed_entries` and `freed_bytes`. This is intentionally separate from full/scope purge
but reuses the same internal Cold Cache candidate/removal machinery as automatic GC.
### 0.22.1 selective cache invalidation
`POST /api/cache/invalidate` accepts an `InvalidationRequest`:
```json
{
"operation": "purge",
"selector": "exact",
"value": "/assets/app.js?v=42",
"host": "app.example",
"vhost": "application",
"ttl_seconds": null,
"confirm_wide": false
}
```
`operation` is `purge` or `ban`. `selector` is `exact`, `prefix`, `tag`, `regex` or
`cache_key`. `host` and `vhost` are optional independent scope restrictions. BAN accepts
`ttl_seconds` (1..604800, default 3600) and the runtime keeps at most 1024 active rules.
Every regex operation and prefix `/` requires `confirm_wide=true`; the browser panel asks
for a danger confirmation before sending it. Regex input is length-bounded and compiled
with Rust's non-backtracking `regex` engine plus a compiled-size limit.
The response includes `result` with operation/selector/value/host/vhost,
`affected_entries`, optional `ban_id`, `elapsed_ms` and `status`, plus current cache metrics
and active BAN rules. Selective PURGE removes RAM and Cold Cache matches, removes the full
`Vary` family under a matched primary key and prevents fills that started before the PURGE
from publishing afterward. BAN creation does not synchronously scan the store: matching
objects created before the rule are rejected on lookup and removed incrementally by cache
maintenance; fresh objects created after the rule may be stored. BAN rules are runtime
state and are not persisted across process restart.
`GET /api/cache/bans` returns the active rule views. `DELETE /api/cache/bans/{id}` removes
one rule. All invalidation mutations use the standard administrator session, same-origin
request marker and CSRF protections; requested operator/scope information is written to the
existing audit log.
The `transform` configuration section is replaceable through the normal revision-CAS
`PUT /api/config/transform` endpoint. Vhost objects carry a `transform` override and
Docker assignments carry `transform_enabled`; both default to disabled per vhost.
Global transform settings also expose resource controls: `memory_budget_bytes` (1 MiB..2 GiB,
default 128 MiB), `max_body_bytes` (1 KiB..32 MiB, default 2 MiB), and `max_inflight`
(1..1024, default 32). `preset` is optional UI intent metadata with values
`conservative`, `recommended`, `performance`, or `uber`; numeric limits remain authoritative.
Resource pressure is fail-open: work that cannot reserve the configured memory/concurrency
capacity is left unchanged rather than queued behind full-body transformation work.
`GET /api/transform` returns in-memory transformation observability for the administration
Transform workspace: totals and per-vhost counters for examined/applied/skipped/error
responses, transformed cache HITs, original/transformed/saved bytes, accumulated transform
time, applied counts by content kind and bounded skip/fail-open reasons. These counters are
runtime diagnostics and reset when the RustProxy process restarts.
`GET /api/logs` accepts optional `vhost`, `q`, exact numeric `status`, `cache` and
`limit` parameters. Limit clamps to 1..4096 and defaults to 256; search length is at most
256 bytes. Filters run over the retained ring before the limit is applied. `cache` accepts
HIT, MISS, BYPASS or `-`. Entries now include nullable `vhost` independently of `backend`:
cached and static responses can be attributed without claiming that an origin was contacted.
SSE still sends the newest 256 entries per global snapshot. The Live Logs UI can seed a
temporary 4096-entry buffer on demand and then merge subsequent live snapshots into it until
the window closes. This is not a durable log stream or a per-tenant authorization boundary.
The status endpoint keeps existing field names, adds feature flags for new
capabilities, and assembles small values rather than one deeply expanded macro.
The management script `/assets/management.js` is a public static asset; APIs remain
protected. Pages and per-vhost cache fields use the standard configuration endpoint.
## 0.4.0 certificate inventory, forwarding, auth and listeners
`GET /api/certificates` requires the existing admin session and returns revision,
metadata-only certificates including `covered_hosts`, active `hosts`, `assigned_vhosts`,
file names and the per-entry default flag, plus `acme:false`. Inventory reads only the
public certificate PEM on a blocking worker and derives current DNS SubjectAltName values,
so older/manual entries and in-place renewals do not depend on stale persisted coverage
metadata. Persisted `covered_hosts` is only a fallback when the public PEM cannot be read.
It never reads a private key for this enrichment and never returns PEM contents or private-key bytes. Existing upload remains a
CSRF-protected staging operation. The inventory UI saves its returned metadata in
`config.tls.certificates`; vhost assignment is a later independent configuration
save. Every configuration mutation retains revision CAS and validation.
Vhost additions: `tinyauth`, `server_header`, `diagnostic_headers`,
`backend_header`, `backend_timing_header`, and
`frontend.listener_ids`. Global additions: `forwarding`,
`diagnostic_headers`, and `listeners.endpoints`. Each effective
TLS binding can carry `http3` with the five fields described in HTTP3.md. These
are full-config fields, not independent unaudited mutation endpoints. Unknown or
invalid references, trust ranges, URLs, limits and protected header names fail
validation before publication.
Status contains named_endpoints and active/desired endpoint transport metadata,
H3 connection counters and feature flags. Feature flags describe included source
capabilities, not evidence of successful build/conformance testing. Existing
Prometheus metrics add rust_proxy_http3_connections_total,
rust_proxy_http3_active_connections and rust_proxy_http3_rejected_connections_total
and rust_proxy_http3_requests_total. See source `metrics.rs` for exact HELP/type
contracts and INTEGRATIONS_040.md for configuration examples.
## 0.32.4 backend Host and HTTP/2 authority
Existing backend `preserve_host` is now valid for all `protocol` values:
`http1`, `auto`, `http2` and `h2`. HTTP/2 :authority follows the request Host,
while socket/DNS use the configured origin and TLS SNI uses
`tls_server_name` if present. No new JSON fields, endpoints, database migration
or extra retries. HTTP health checks maintain their own Host override.
## 0.32.3 Overview registry and compatible upstream modes
The backend `protocol` wire values are `http1` (default), `auto`, `http2`
(legacy HTTPS H2/H1 ALPN fallback or cleartext H2C) and new `h2` (strict
HTTPS H2 or cleartext H2C). Old `http2` configurations are not rewritten.
See [Vhosts](VHOSTS.md).
The authenticated `/api/preferences` Dashboard card ID lists are backed by a
shared server/frontend registry with **49** IDs: the original 16, fourteen
optional HTTP backend cards, twelve optional performance charts and seven existing
full-width panels (live traffic, system, connections, cache, process, network,
backend diagnostics). OpenAPI `dashboard_widgets` and
`dashboard_hidden_widgets` enum, `maxItems` and defaults reflect that registry.
Existing saved card order is retained. New performance charts are opt-in,
while previously displayed full-width sections remain visible by default.
Unknown saved IDs are ignored rather than failing a whole profile.
The visual editor offers a default-layout preview and a confirmed Reset layout,
both using the existing per-account preference endpoint.
No new routes, tokens or permissions are introduced.
## 0.22.3 certificate hot reload and external deployment
`POST /api/certificates/reload` is an administrator-session-only, Origin/CSRF-protected control-plane
operation. Certificate API bearer tokens are intentionally not accepted by this global endpoint. It re-reads every certificate/key file referenced by the current desired
configuration, validates a fresh immutable routing/TLS snapshot and publishes it at the
**same configuration revision**. Invalid or mismatched material returns an error and the
previous snapshot stays active. The response contains `reloaded`, `revision`, `public_tls`,
`http3_new_handshakes` and `administration_restart_required`. New public H1/H2 TLS and HTTP/3
handshakes use the refreshed certificates; established connections are not renegotiated.
Administration TLS is startup-fixed and therefore reports when a process restart is needed.
`POST /api/certificates/{certificate_id}/reload` is the targeted variant for a manual/file-backed certificate (`issuer == null`). It validates the requested configured ID and atomically republishes the immutable TLS snapshot at the same configuration revision. It is intended for external renewers that replace the files already referenced by RustProxy. A manual entry may use simple filenames from `<data-dir>/certs` or two absolute external paths such as `/etc/letsencrypt/live/example/fullchain.pem` and `/etc/letsencrypt/live/example/privkey.pem`. Absolute paths are resolved again on every reload, so Certbot's `live/` symlinks follow the newly renewed generation. Application-managed ACME certificate IDs are rejected with `409` because their renewal pipeline owns those files. The endpoint accepts either the normal administrator session + CSRF flow or a Certificate API token whose grant explicitly contains that certificate ID.
`POST /api/certificates/deploy` accepts
`{"certificate_id":"shared-cert","cert_pem":"...","key_pem":"..."}` under the same
authenticated Origin/CSRF boundary. It is a convergent upsert for externally owned certificate
ids: a missing id creates a manual certificate entry, an existing manual id is rotated in place,
and an identical RustProxy-owned generation is a no-op. The pair is validated **inside the running
RustProxy process** and changed material is staged under immutable generation-specific files before
a revision-CAS configuration publish. This is important for root-run Certbot hooks when RustProxy
itself runs as an unprivileged service account: final generations are created with the service's
filesystem identity rather than `root:root`. Managed ACME ids are rejected, and failed
configuration publication removes any newly staged generation.
For a certificate owned by a local external issuer such as system Certbot, prefer the local
CLI deploy hook, which calls that loopback API after bounded local file checks:
The included `rust-proxy` program supports configuration and backup operations without turning browser sessions into automation tokens:
```bash
/opt/rustproxy/current/rust-proxy --data-dir /var/lib/rustproxy tls deploy --id shared-cert \
--cert /etc/letsencrypt/live/example/fullchain.pem \
--key /etc/letsencrypt/live/example/privkey.pem
rust-proxy --data-dir /var/lib/rustproxy config get
rust-proxy --data-dir /var/lib/rustproxy config validate config.json
rust-proxy --data-dir /var/lib/rustproxy config apply config.json
rust-proxy --data-dir /var/lib/rustproxy config reload
rust-proxy --data-dir /var/lib/rustproxy backup create --output /private/site.rpb
```
The command resolves Certbot's local `live/` symlinks, checks bounded regular files/private-key permissions, then sends the pair to `POST /api/certificates/deploy`. The running service validates the certificate/key and DNS SANs, stages generation-specific files inside RustProxy's private certificate directory, and creates or switches the **manual** certificate id through the normal revision-CAS configuration path. A single id may already be selected by multiple vhosts, so those vhosts switch together
without individual edits. RustProxy-managed ACME entries (`issuer != null`) are rejected to
avoid two renewal owners for the same lineage. The existing `/api/certificates/upload` remains
the generic PEM staging endpoint; deploy/upload request bodies never accept host filesystem
paths. Absolute paths may only be saved explicitly in administrator TLS configuration as a
manual certificate source, where they are validated and never returned as PEM contents.
Run CLI commands as an authorized local operator with the correct private data directory. The `admin` subcommands provide first setup, password recovery, session revocation and status; `tls` supports certificate generate/import/deploy/reload. Run `rust-proxy --help` for exact options.
## 0.28.2 least-privilege Certificate API credentials
## Restricted Certificate API credentials
External automation does not need a full administrator browser session. From **Settings -> Certificate API**, an administrator can create multiple dedicated credentials (up to 32), each consisting of a Key ID, Bearer token, optional non-secret comment and an explicit allowlist of 1-32 certificate IDs. New credentials are **reload-only by default**. Deploy is an independent opt-in permission.
**Settings → Certificate API** can create credentials containing a **Key ID + Bearer token** with explicit certificate-ID grants. New keys default to reload-only; deploy permission is separate. The clear token is displayed at creation/rotation and is not a general admin API token.
The clear token is returned only by the create/rotation response; SQLite stores only its SHA-256 hash plus metadata and the grant. Credentials created before 0.28.2 migrate to a reload-only compatibility grant for all certificate IDs (`*`). Rotate those credentials after upgrading to replace the wildcard with explicit IDs.
API clients send both headers:
An authorized client sends:
```text
Authorization: Bearer <token>
Authorization: Bearer <secret>
X-RustProxy-Certificate-Key: <key-id>
```
A reload-only token may call only the targeted endpoint for an allowlisted certificate:
```bash
curl --fail --show-error -X POST \
-H 'Authorization: Bearer TOKEN' \
-H 'X-RustProxy-Certificate-Key: certbot-main' \
-H 'Content-Type: application/json' \
--data '{}' \
https://proxy-admin.example/api/certificates/shared-cert/reload
```
Certificate API tokens are **not** accepted by the global `POST /api/certificates/reload` endpoint. This prevents a compromised renewal token from forcing reads/reloads of unrelated certificate material. `POST /api/certificates/deploy` is available only when the credential was explicitly created with deploy permission, and the `certificate_id` in the request body must also be present in that credential's allowlist.
A Certificate API credential cannot read configuration, logs, metrics, users or any other administration endpoint. Non-loopback use requires the configured HTTPS administration surface. The normal administrator session + Origin/request-marker + CSRF path remains valid for certificate operations. Credential management remains administrator-only: `GET /api/certificate-api` lists metadata/grants; `GET /api/certificate-api/{key_id}` returns a single credential's **non-secret** settings (Key ID, comment, created/last-used timestamps, allowed certificate IDs and deploy grant); `PATCH /api/certificate-api/{key_id}` edits the comment/grants without changing or returning the bearer token; and `POST /api/certificate-api/{key_id}/regenerate` creates a fresh token for the same Key ID, immediately invalidates the old token, retains metadata/grants and returns the new token exactly once. The original `PUT /api/certificate-api` still creates/rotates credentials, `DELETE /api/certificate-api/{key_id}` revokes one and `DELETE /api/certificate-api` revokes all. **Only administrator sessions** may use these management endpoints. Writes require the existing CSRF, origin and request-marker checks, including regeneration. Legacy wildcard grants cannot be saved in `PATCH`; replace them with explicit IDs first.
Example session-authenticated metadata edit (existing token stays valid; the `token` field is deliberately forbidden):
```json
{
"comment": "renewal hook on edge-02",
"certificate_ids": ["shared-cert", "backup-cert"],
"allow_deploy": false
}
```
Submit this JSON to `PATCH /api/certificate-api/certbot-main` with the normal administrator cookie + CSRF/Origin headers. Regenerate through `POST /api/certificate-api/certbot-main/regenerate` with the same session controls and securely record the one-time `token` response.
Example external deploy for a token that has **Also allow deploy** enabled and contains `shared-cert` in its allowlist:
```bash
curl --fail --show-error \
-H 'Authorization: Bearer TOKEN' \
-H 'X-RustProxy-Certificate-Key: deploy-hook' \
-H 'Content-Type: application/json' \
--data-binary @deploy.json \
https://proxy-admin.example/api/certificates/deploy
```
For an on-host Certbot hook, a token is optional: invoke the installed binary directly instead:
```bash
/opt/rustproxy/current/rust-proxy --data-dir /var/lib/rustproxy tls deploy --id shared-cert \
--cert /etc/letsencrypt/live/example/fullchain.pem \
--key /etc/letsencrypt/live/example/privkey.pem
```
`dev.sh` is development tooling and is not required for production certificate rotation.
## 0.5.0 cache inspector and certificate jobs
`GET /api/cache` additionally returns `dashboard: {domains, objects,
matching_objects, offset, limit, statistics_revision}`. Domain rows flatten
hits/misses/bypasses/stored/saved_body_bytes/reasons beside vhost/domain and
resident counts. Object rows contain vhost/domain/path/body_bytes/resident_bytes/
hits/age_seconds/remaining_ttl_seconds, never bodies, request headers or queries.
`vhost=ID` filters, `vhost=` selects legacy only; omitted selects all. Limit 1..200
(default 50), offset <=16384. Unknown vhost ->404. The same scope semantics apply
to POST /api/cache/purge. Existing global metrics remain process-lifetime values.
The historical 0.5 `GET /api/acme` response did not support renewal; see the
current 0.7 response below.
`POST /api/acme/issue` accepts at most 16 KiB of JSON with certificate_id, email,
domains, environment, agree_tos, confirm_production, dns_server, dns_port, key_name,
key_secret, key_algorithm, sign_query and propagation_seconds. See
ACME_RFC2136.md for exact constraints. It returns 202 `{job}` only after admission;
one process-wide job, 429 when busy. Job fields: id, certificate_id, status,
environment, message, certificate. The ready certificate is immutable PEM metadata,
not private bytes. Import is a separate normal config CAS write. Both endpoints
are behind the administrator session; POST retains Origin/CSRF and audit.
LogEntry adds `cache_reason`, a fixed string token. Debug response headers add
X-Cache-Reason when X-Cache is enabled, and standardized RFC 9211 `Cache-Status` detail
when enabled. Transformed cached representations add the `transformed` Cache-Status
extension parameter. `X-Cache-Status` is stripped if supplied by an origin but is not
emitted by RustProxy.
`diagnostic_headers.hide_mock_origin` is removed and old documents containing it are rejected on
export; `request_id` is the only diagnostic-header switch. The generated request ID is a 64-hex-character SHA-256 value and the same protected value is forwarded to the origin as `X-Request-ID`. No revision response-header behavior remains. TLS listener
http3 omission is default-on; explicit null is persisted off. Legacy listeners use
https_http3 with identical default/null behavior.
## 0.6 provider and scrape additions
`POST /api/acme/issue` accepts `provider`: `http01`, `cloudflare`, `ovh` or
`rfc2136` (omitted retains RFC2136 compatibility). Common fields include
`certificate_id`, `domains`, `email`, `environment`, `agree_tos` and
`confirm_production`. DNS providers also accept `propagation_seconds`.
Cross-provider/unknown fields are rejected. Provider-specific fields, secret
handling and request examples are in ACME_PROVIDERS.md. `GET /api/acme` returns
only public job status and validated certificate metadata, not input credentials.
Issuer metadata on certificates is optional `{ "provider": "cloudflare",
"environment": "staging" }`. Do not confuse metadata with CA trust validation.
`config.prometheus` controls the new dedicated HTTP scrape endpoint, which uses
actual-peer CIDRs and optional bearer authentication, not administrator cookies.
The exact configured GET path only is accepted (query strings rejected). Disabled
or changed/restart-pending settings return 404; denied peers 403; invalid tokens
401; non-GET 405; exhausted scrape capacity 429. No administrative actions are
available there. Admin `/api/metrics` and `/metrics` remain session-protected
previews on the admin listener, even when the dedicated endpoint is disabled.
See PROMETHEUS.md for the full settings and metric contract. Traffic analytics is an independent collector group (`include_traffic`) that exports bounded country, fixed User-Agent/method/source, status/cache and configured-route aggregates without raw IP/path/full-UA labels. Docker discovery is an independent collector group (`include_docker_vhosts`) and exports only dedicated `rust_proxy_docker_vhost_*` / `rust_proxy_docker_backend_*` families; ordinary vhost/backend families exclude dynamic Docker routes.
## 0.7 renewal and host access
`GET /api/acme` now returns `{job, providers, automatic_renewal:true,
interval_seconds:43200, renewals}`. `renewals` is keyed by certificate ID and
contains last_checked, next_check, last_renewed, not_after (Unix seconds), failures,
status and a public message token. No credential/account data is returned.
`GET /api/certificates` also includes renewal observations. Statuses are observations,
not a promise of a successful future CA operation.
`POST /api/acme/renew` accepts only `{certificate_id, dry_run:false}`. It uses an
existing managed lineage and returns 202 `{job}` after admission, or 404 unknown ID,
409 unmanaged/invalid profile or disabled prerequisites, 429 worker busy. Actual
worker/CA failures arrive in job state. No force-renew flag is offered. Dry runs
never deploy their test certificate and never postpone a due real renewal.
The usual session, Origin/CSRF protections and audit apply.
A certificate's `issuer` supports `auto_renew` (default true for saved managed
metadata, explicit false opts out). Toggle through ordinary revisioned config;
no provider credentials need resubmitting. Old immutable generations are retained.
`POST /api/basic-auth/hash` accepts `{username, password}` with at most 4096 bytes
of request JSON. It requires an administrator session and the same mutation/CSRF
guards, returns `{username, password_hash}`, and never saves the plaintext password.
Passwords are 12..128 UTF-8 bytes with no control characters. Usernames are 1..128
bytes, no colon/control characters. Hashing has two shared blocking-worker slots;
no available slot returns 429. Invalid input returns 400. This is NOT a public
login endpoint and does not create a browser session for a vhost.
A vhost accepts `websockets` and `basic_auth` with enabled, realm, require_tls,
forward_credentials and users[{username,password_hash}]. Basic and Tinyauth cannot
be enabled simultaneously. Hash import accepts only the bounded Argon2id policy
created by the hash endpoint, not arbitrary-cost PHC strings or htpasswd formats.
Missing `websockets` retains the previous true behavior; the new-host UI selects
false. Basic TLS defaults true and forwarding defaults false.
Backend pool members have `pool_eligible` (default true). False excludes that
member from default load balancing and sticky selection without blocking an
explicit location `backend_id`. Visual custom locations edit existing vhost
`routes`: ID, path_prefix, backend_id, enabled, cache, ACL and advanced headers.
This is prefix routing, not regex, rewrite or nginx configuration evaluation.
See [host access](VHOST_ACCESS.md) for cumulative ACL and authentication semantics.
## 0.18 Docker discovery additions
| Method / path | Contract |
|---|---|
| `GET /api/docker` | Authenticated cached in-memory Docker-agent observations; no agent poll and no configuration write |
| `POST /api/docker/refresh` | Authenticated + Origin/CSRF immediate poll of enabled agents followed by ephemeral route reconciliation; no configuration revision write |
| `PUT /api/config/docker-agents` | Revision-CAS replacement of desired Docker-agent settings, per-agent domains and service hostname/alias/IP assignments |
`config.docker_agents` stores desired agent endpoint/token/base-domain/IP override/listener references, a per-agent 5..3600 second inventory poll interval, and assignments. Inventory data returned by agents is not persisted in the configuration document. The normal configuration API remains authoritative for desired state; the Docker observation API is operational state only. Docker assignments may also carry an ACL profile, inline ACL, health-check settings and per-vhost proxy timeouts. Publishing/unpublishing is therefore an ordinary CAS configuration update and does not require polling the agent.
## HTTP/2 lifecycle settings
As of 0.24.3 the preferred configuration is per listener: `listeners.endpoints[].binding.http2_settings` contains `idle_timeout_seconds`, `max_connection_lifetime_seconds` and `drain_timeout_seconds` for that TLS H2 or cleartext H2C endpoint. Update these values by revision-CAS replacement of the `listeners` section (`PUT /api/config/listeners`), the same ownership model used by per-listener HTTP/3 limits. `max_connection_lifetime_seconds: 0` disables the absolute lifetime limit. Idle and lifetime expiry both use graceful GOAWAY/drain shutdown.
The top-level `http2` object introduced in 0.24.0 and `PUT /api/config/http2` remain accepted for backward compatibility. They are a fallback only for listeners that do not yet have explicit `binding.http2_settings`; the structured 0.24.3 UI writes listener-scoped values.
The runtime system diagnostics object includes `platform`, supplied by the shared system-statistics backend (`Linux`, `FreeBSD`, `OpenBSD`, `NetBSD`, or the generic/unsupported fallback). The browser only renders that value; it does not detect the operating system itself.
## Per-vhost User-Agent rate limiting (0.31.7)
The revisioned `/api/config` vhost rate-limit object accepts optional `user_agent_limits`, up to 64 entries with `pattern` (1..256 bytes, case-insensitive substring or `*` wildcard), `requests` (1..1000000), `window_seconds` (1..3600) and `burst` (0..1000000). The first matching rule replaces the standard and country rate for that request and uses a separate fixed-window counter for each (vhost, rule index, client IP). Requests with no matching User-Agent use the existing limiter. A User-Agent is trivially spoofable; these overrides are operational shaping, not access control. Global and vhost User-Agent blocklists continue to deny matching requests before rate limiting.
## Stream pools and dashboard widgets (0.32.0)
`GET /api/config` and `PUT /api/config/streams` preserve the existing revisioned JSON and session/CSRF gate. A Stream host may include `backends[]` (ID, address, weight, enabled, drain, max_sessions, health, metrics_enabled), `balance` and nullable `metrics_enabled`. With no `backends`, `upstream` retains its legacy single-backend behavior. Backend edits remain desired state until controlled restart. See [STREAMS.md](STREAMS.md).
`GET /api/status` retains `streams.active`, `streams.desired`, `streams.restart_required` and `streams.metrics`. Each active Stream metric view includes `backends[]` for health/active information and optional detailed counters. `/api/status?view=live` and the existing authenticated SSE snapshots carry the same backend observations inside `streams.metrics` without a separate polling source. Backend status is `unverified`, `checking`, `healthy`, `unhealthy` or `disabled`.
In the 0.32.0 Stream addition, `GET`/`PUT /api/preferences` own `dashboard_widgets` and `dashboard_hidden_widgets` (at that stage 16 supported IDs, adding `stream_overview`, `stream_traffic`, `stream_sessions`, `stream_backend_health`). Legacy accounts have their missing Stream widget IDs appended **hidden**. Preferences affect only Dashboard presentation, not proxy configuration or metrics export. No new endpoint or permission was introduced.
Detailed backend metrics default to off in v0.32.0; per-Stream `metrics_enabled` sets the inherited value for its backends, and explicit per-backend `metrics_enabled` overrides it. Basic per-Stream counters remain available regardless of these flags.
The credential can be used only for allowed certificate reload/deploy operations, not configuration, users, metrics or secret management. Non-loopback use requires the appropriate administration HTTPS security boundary. Rotate broad or legacy credentials. See [Certificates](CERTIFICATES.md).
-243
View File
@@ -1,243 +0,0 @@
# Administration API access - 0.29.1
## Sign-in boundary
The administration listener remains separate from reverse-proxy listeners and uses
loopback HTTP by default. Settings -> Administration can enable HTTPS, select either
the application-managed default certificate or an inventory certificate, persist a
new bind address and allow explicit DNS hostnames. A persisted non-loopback bind is
accepted when HTTPS is enabled. The legacy startup flags remain only for deliberately
plain remote HTTP. Public vhost `/docs` paths are not captured by the admin listener.
When the dashboard is published through a trusted reverse proxy, the hostname of
the optional `administration.public_origin` is automatically allowed for panel
Host validation. Any extra `administration.allowed_hosts` DNS entry is also
matched against the external authority rather than the private admin socket port. `X-Forwarded-Host` is trusted only from loopback or a
configured trusted proxy; the external `X-Forwarded-Proto` additionally requires the
forwarded-proto trust setting except for loopback. Origin/CSRF checks and Secure cookies use
that external origin. Direct IP/localhost access still requires the real administration port.
### Publishing the panel through RustProxy itself
0.16.0 supports the explicitly opted-in same-process topology where a public HTTPS vhost proxies to the local administration listener. The reserved `rustproxy_session` cookie is passed through only when all of these conditions are true: the browser-facing request is TLS, its DNS hostname either matches the pinned `administration.public_origin` or is explicitly listed in `administration.allowed_hosts`, the selected backend directly addresses the configured administration socket by IP, and that backend has `accept_local_listener_risk=true`. The control-plane route is forced out of shared cache.
This is a narrow exception to the normal isolation rule. For every ordinary backend RustProxy still removes `rustproxy_session` before forwarding requests and strips an upstream attempt to set that cookie in responses. DNS backend names are not accepted for the special self-admin cookie path. This prevents a normal application backend from reading or replacing the administration session.
The anonymous page contains only the sign-in/setup form, logo, shared stylesheet,
a dedicated login script, and filtered language strings. It does not fetch the
configuration, status, metrics, logs, certificate inventory, dashboard modules or
the full translation bundle. Adding a new language JSON still discovers that
language at build time; login strings use the same fallback rules.
Exact anonymous methods/paths:
| Method | Paths | Content |
|---|---|---|
| GET/HEAD | `/`, `/overview`, `/vhosts`, `/redirects`, `/streams`, `/acl-profiles`, `/configuration`, `/tls`, `/cache`, `/logging`, `/settings` | Minimal login when no valid cookie is present; the same URL returns the authenticated application shell when the supplied session is valid. `/configuration` is a compatibility alias for Settings -> Manual config. |
| GET/HEAD | `/assets/login.js`, `/assets/login-langs.json`, `/assets/style.css`, `/favicon.svg` | Login assets; no runtime state |
| GET/HEAD | `/healthz` | `{"status":"ok"}` only |
| GET/HEAD | `/api/auth/status` | `{"setup_required":false}` or `true` only |
| POST | `/api/auth/login`, `/api/auth/setup` | Credential/capability validation, exact Origin and request marker required |
The document allowlist above is intentionally session-aware: a browser refresh on a protected
panel path first validates the cookie server-side instead of rendering a false logged-out state.
The login document also rechecks `/api/auth/session` when restored from browser history, preventing
a stale back/forward-cache copy from contradicting a still-valid session. The authenticated shell
also treats `auth.invalid_origin` during startup/session revalidation as a stale restored-page signal
and performs at most one reload of the current same-origin URL. A repeated failure is shown normally;
server Host/Origin, request-marker and CSRF validation is unchanged. Every other admin
path/method, including full assets, `/api/metrics`, `/metrics`, unknown endpoints and the OpenAPI
schema, requires an active database session.
Missing/invalid/expired cookies return `401 {"error":"auth.required"}` before
query/body extraction. Invalid Host/Origin may return 403 first. Only GET/HEAD
`/docs` and `/docs/` navigate to login through a fixed `303 /?next=docs` redirect;
API/spec/assets never return login HTML instead of a status error.
Administrator browser sessions have a configurable absolute lifetime of **7–30 days**
(default 30) through `administration.session_days`. The server stores only a hash of the
session secret, keeps the cookie HttpOnly/SameSite=Strict (and Secure on HTTPS), and
never extends a session past its creation-time absolute expiry. The cookie is refreshed
with only the remaining lifetime so an upgraded still-valid short session can adopt the
new configured bound. Logout, password change and revoke-all still invalidate server-side
sessions immediately.
Responses are no-store. Headers include CSP, nosniff, frame denial, no-referrer and
restricted camera/microphone/geolocation permissions. Error responses do not expose
request-body excerpts for malformed anonymous login/setup input. Anonymous responses contain no application version; fingerprint/revision
response headers are stripped on the admin surface. Authenticated status and the
protected schema intentionally retain the current version. The application name/logo and the single setup-needed bit are intentional
public information, not claims of anonymity or software fingerprint prevention.
Authenticated `GET /api/status` also returns a small `runtime` object with the platform,
statistics adapter name, resolved data directory and SQLite database path. This is used by
the Settings UI so Linux, BSD/OPNsense and custom installations show their actual runtime
paths instead of browser-side guesses. The object is not available anonymously and does
not expose credentials, environment values, certificate contents or arbitrary filesystem
listings.
## Docs Reader and API view
`/docs` opens an authenticated documentation workspace. The default **Reader** tab loads
`/docs/content.json`, a build-time bundle of the project Markdown documentation listed in
`docs/README.md`. The bundle contains documentation source text only; it does not contain
live configuration, credentials or runtime state. Reader links between bundled Markdown
documents stay inside the same authenticated documentation surface.
The separate **API** tab uses Swagger UI. `/docs/openapi.json` describes the local endpoints and common request schemas; it
contains no live configuration, actual credentials, generated keys, user accounts,
cache entries or listener addresses. Extensible schemas summarize configuration;
the Rust validator remains authoritative for the full configuration model.
Documentation uses the same HttpOnly `rustproxy_session` cookie as the panel.
Do not paste session cookies into the schema or store them in browser storage.
GET execution is enabled by default; write execution requires a checkbox and an
explicit confirmation for each request. The script validates the current session,
allows only documented same-origin operations, and sends the current session's
`X-CSRF-Token` plus `X-RustProxy-Request: 1`. The browser supplies Origin. The server
performs the real authorization checks regardless of any browser options.
Login/setup operations and indefinite SSE streams are documented but not executed
by Swagger UI. Native logout/password writes can invalidate the browser session; reload or
sign in again afterward. Ordinary API mutations still require the administrator session,
Origin/request marker and CSRF token.
0.28.2 keeps the deliberately narrow Certificate API exception for automation and makes it least-privilege. An operator can create one or more **Certificate API Key ID + Bearer token** credentials in Settings -> Certificate API. Each new credential requires an explicit allowlist of 1-32 certificate IDs and is reload-only by default. Deploy access must be enabled separately.
A reload-only credential authorizes only:
- `POST /api/certificates/{certificate_id}/reload` when that ID is allowlisted.
Certificate API credential inspection (`GET /api/certificate-api` or `GET /api/certificate-api/{key_id}`) is likewise administrator-session only and returns no hashes/secrets. `PATCH /api/certificate-api/{key_id}` changes metadata/grants without touching the secret; `POST /api/certificate-api/{key_id}/regenerate` replaces the hash and invalidates the old token. Both writes require session + CSRF/Origin/request-marker protection. The replacement secret is revealed only once in the regeneration response. External Key ID + Bearer clients are not authorized to call any credential-management endpoint.
`POST /api/certificates/reload` is administrator-session only and never accepts a Certificate API token. `POST /api/certificates/deploy` accepts a Certificate API token only when the credential has explicit deploy permission and the request's `certificate_id` is also allowlisted.
Both `Authorization: Bearer <token>` and `X-RustProxy-Certificate-Key: <key-id>` are required. The clear token is returned only on creation/rotation; the database stores its SHA-256 hash. Up to 32 Key IDs can coexist, and each can be revoked independently or all at once. Credentials created before 0.28.2 migrate to reload-only compatibility access for all certificate IDs (`*`); rotate them to narrow the allowlist. The credential does not authorize configuration, logs, metrics, users or any other endpoint. Non-loopback use is accepted only on the configured HTTPS administration surface. Revocation from Settings invalidates that credential immediately.
Local host automation does not need this token. The installed binary can run
`rust-proxy tls deploy ...` or `rust-proxy tls reload` directly; it obtains short-lived local
CLI authority from the local database and calls the guarded loopback administration API.
`dev.sh` is a development helper and is not required in production.
The external validator, query-supplied schema/server configuration and persisted
Swagger authorization are disabled. Runtime JS/CSS are served from the same admin
origin behind its guard. Only the two necessary Swagger vendor asset filenames, the documentation bundle and a plain-text license notice
(`/docs/licenses.txt`) are routed; all require an administrator session.
Swagger's inline layout styles have a document-scoped CSP exception; scripts stay
self-only with no unsafe-eval allowance. The ordinary dashboard CSP is unchanged.
Logout broadcasts an in-memory notification between tabs. The panel drops its
operational document on logout/revocation; documentation clears its schema,
responses and write opt-in. Docs recheck sessions on actions, visibility and every
30 seconds while visible. Browser history restoration revalidates; this is not a
promise to erase browser/OS screenshots or data intentionally downloaded by an admin.
## First build / offline assets (fixed in 0.7.2)
No `SWAGGER_UI_DOWNLOAD_URL` environment variable or `.cargo/config.toml` is required.
The default is Swagger UI **5.33.1**, selected by `scripts/build/swagger_assets.py` and
called from this application's `build.rs`. The former `utoipa-swagger-ui` adapter
is removed: there is no separate dependency build silently choosing its older
historical browser default. Direct Cargo builds using `--manifest-path` use the
project's absolute manifest directory, not the caller's working directory.
Python **3.10+ is now also required for direct Cargo builds**, as it already was
for `dev.sh`. `build.rs` tries `python3`, then `python` if that executable is absent.
Use `RUST_PROXY_PYTHON=/absolute/path/to/python3` to select an interpreter explicitly.
The helper uses Python's verified HTTPS transport and standard-library ZIP reader;
`curl` is no longer a Swagger build requirement. Missing tools/network/assets give
an actionable build error rather than an absent-environment-variable panic.
Normal build (no Swagger environment setup):
```bash
cd RustProxy
bash dev.sh check
bash dev.sh test
bash dev.sh release
```
The first build still needs the normal Cargo registry and the pinned upstream
archive. Swagger JS/CSS and license notices are embedded in **both debug and
release binaries**, served behind the existing admin guard. No runtime download,
CDN, public asset endpoint or alternate authentication mechanism was introduced.
An offline or GitHub-restricted build can use a reviewed local copy of the same
GitHub **tag archive**, with its original `swagger-ui-5.33.1/` directory structure:
```bash
export SWAGGER_UI_DOWNLOAD_URL=file:///srv/build-assets/swagger-ui-5.33.1.zip
# Optional: require the archive digest independently verified by your build team.
# export SWAGGER_UI_ARCHIVE_SHA256=<64-hex-digit-verified-digest>
bash dev.sh check
bash dev.sh test
bash dev.sh release
```
Unset/empty source values use the default pin. Different remote URLs and old tag
archives are rejected. Local file URLs support escaped spaces and must be absolute.
`SWAGGER_UI_ARCHIVE_SHA256`, when set, checks the entire archive before publication.
No upstream checksum is fabricated: HTTPS provenance, the archive's declared tag
and its layout are not themselves a cryptographic authenticity guarantee for an
operator-supplied ZIP. Independently review/pin local inputs.
The helper only reads exact, bounded members; it does not extract arbitrary ZIP
paths. It retains licenses/notices, validates package name/version, rejects duplicate
required members, symlinks and oversized assets, caches only validated downloads in
Cargo's output directory, and writes outputs atomically. Failed preparation blocks
compilation instead of embedding stale assets. A `source.json` receipt records
archive and output SHA-256 values in `OUT_DIR/swagger-ui/`.
Cargo tracks changes to the helper, source/digest/interpreter environment variables,
local or cached ZIP and generated outputs. Repeated identical preparation preserves
output timestamps. `CARGO_NET_OFFLINE=true` forbids a new helper download; ordinary
Cargo dependencies must also be cached for a fully offline build. A local archive
is the recommended explicit setup for `cargo --offline`; Cargo CLI offline flags
are not assumed to propagate automatically into subprocess environments.
0.24.1 adds cross-platform statistics dependencies. This packaging workspace has no Cargo toolchain, so it does not fabricate or hand-edit a resolver result. Generate and preserve `Cargo.lock` with Cargo on the compiler-enabled release host, then run `cargo check --locked` / the normal `dev.sh` gates before deployment.
Build-helper tests use synthetic ZIPs; they do not claim to render Swagger. This
environment lacks Rust/Cargo and cannot fetch the upstream assets. Compilation,
real embedding/rendering and live access-control acceptance remain to be run on a
build server. Browser tests still label their simulated API/Swagger renderer.
## Focused live regression probe
After running the built server:
```bash
python3 tools/audit/admin_audit.py --url http://127.0.0.1:9090
```
The script sends anonymous read-only loopback probes, follows no redirects, ignores
proxy environment variables, prints no response bodies, and fails on unexpected
public content/statuses. It does not send credentials or mutate configuration. It
is also called by the real-binary security integration suite. Rust router tests
add write/method/duplicate-cookie/CSRF checks. A passing probe is a regression gate,
not a complete pentest, dependency scan or guarantee against all vulnerabilities.
## Custom location headers
Vhosts -> edit -> Custom locations -> Location headers. Add rules independently
for proxy requests (to backend) and responses (to client). `set` replaces, `append`
adds another field value, `remove` has no value. Maximum 32 rules per direction.
The existing JSON editor is synchronized and remains optional. Invalid JSON,
newlines, unknown operations and protected names cannot silently save an old draft.
Rules apply global -> vhost -> matching location. Proxy transport, forwarding,
verified authentication identity, configured Server/security/diagnostic policies
remain system-managed and can override or forbid a conflicting custom rule.
Values are literal, not Nginx variables; changing Host/forwarded identity still uses
the designated backend/forwarding controls. Headers for sibling locations remain
unchanged. No configuration schema migration is needed for existing header rules.
## References
- OWASP REST Security Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/REST_Security_Cheat_Sheet.html
- OWASP Session Management Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html
- Swagger cookie authentication: https://swagger.io/docs/specification/v3_0/authentication/cookie-authentication/
- Swagger UI configuration: https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/
- Cargo configuration discovery: https://doc.rust-lang.org/cargo/reference/config.html
- Cargo build scripts: https://doc.rust-lang.org/cargo/reference/build-scripts.html
- Pinned browser release: https://github.com/swagger-api/swagger-ui/tree/v5.33.1
-233
View File
@@ -1,233 +0,0 @@
# Architecture
## Control plane and forwarding plane
SQLite stores administrator intent, including vhosts/paths, policies, backend definitions
and certificate metadata. `config::compile` validates this document and builds an
immutable Snapshot with routing tables, compiled ACL/header policies, per-backend
clients and an optional rustls ServerConfig. ArcSwap publishes the active snapshot.
No public request consults SQLite or a provider to choose its route.
A configuration mutation obtains a bounded admission slot and builds a candidate on
a blocking worker. The slot remains held if the HTTP caller is cancelled while the
worker is running. A serialized detached writer checks the expected revision,
commits the desired document in SQLite and swaps the active snapshot with no await
between commit completion and publication. Invalid candidates leave the old state
active. A crash between persistence and publication is recovered by loading the
committed document at startup; this is not one cross-memory/SQLite transaction.
## Module responsibilities
| Module | Responsibility |
|---|---|
| `server` | Bounded listeners, TLS acceptance, HTTP/1.1/HTTP/2 selection, connection leases and shutdown |
| `proxy` | Host/authority routing, ACL sequencing, forwarding and streaming instrumentation |
| `proxy::websocket` | Handshake validation, upgrade tasks and bounded bidirectional byte transport |
| `web_server` | Reusable static-file services, safe document-root traversal, validators/ranges and streamed local file bodies |
| `upstream` | Per-backend pools, guarded resolver, connection deadlines and verified HTTPS clients |
| `tls` | Local PEM files, generation/import, validation, SNI and loaded certificate snapshots |
| `config` | Desired models, normalization, validation and compiled snapshots |
| `vhost` | Host-owned frontend/member models, actual-address selection and listener planning |
| `balance` | Full-exchange leases, health/recovery, weighted scheduling, connection/affinity settings |
| `security` | Compiled per-vhost response protection, TLS-only HSTS |
| `acl`, `headers` | Generic access and header policies, no application-specific exceptions |
| `auth`, `admin` | Administrator lifecycle, local API, browser sessions, CSRF, SSE and static UI |
| `db` | SQLite schema, migrations, desired-state persistence and revision checks |
| `metrics`, `logging` | Aggregate instrumentation, bounded access ring and trace output |
| `transform` | Bounded decode/transform stage for eligible static identity representations before cache/compression, with live process-wide memory and concurrency ceilings |
| `docker` | Desired Docker-agent settings, in-memory observations and ephemeral vhost reconciliation |
| `runtime` | Shared process state, snapshot updates, admission limits and tunnel tracking |
| `demo` | A separate opt-in demonstration backend with its own listener |
### Rust source organization
Production Rust modules are kept as small responsibility-oriented files, with a
**500-line engineering limit** for production modules. Unit and
integration test files are explicitly exempt: related tests may live together in
large `tests.rs` files rather than arbitrary numbered fragments. Large logical modules use a thin facade (`config.rs`, `docker.rs`,
`server.rs`, `runtime.rs`, `balance.rs`, `prometheus.rs`, and similar) plus files in a
matching directory. `web_server` uses the standard `web_server/mod.rs` facade with
submodules for configuration, filesystem confinement, responses, routing, FastCGI and
`.htaccess`.
Unit-test implementations are not embedded in production source files. They live in
`tests.rs` or `tests/` files and are included or declared only behind `#[cfg(test)]`.
Keep `include!` targets, split fragments and attributes/doc comments alongside
the items they describe. After removal of the old source-check scripts, review
these invariants in code review and verify compilability with `./dev.sh check`.
Docker discovery is an optional provider boundary implemented through the standalone
`docker-agent/`; the RustProxy process never opens the Docker socket. Cloudflare Tunnel
remains a future module. Certbot-based HTTP-01/Cloudflare DNS/OVH DNS/RFC2136 issuance and managed renewal are optional
control-plane adapters. Provider work stays outside proxy request paths and
publishes certificates only through validated configuration snapshots.
## Request lifetime
A listener owns a shared TCP connection lease. Each admitted request owns an
in-flight permit and an Exchange, which remains alive through streamed request and
response bodies. HTTP/2 connections share the public TCP cap with HTTP/1.1 and TLS.
Upgraded WebSocket tasks retain both the Exchange and TCP lease after Hyper stops
managing the HTTP connection, preventing upgrades from escaping limits or counters.
Routing captures one snapshot. Socket peer identity, host and route ACLs plus configured
authentication are checked before source dispatch, DNS or upstream connection reuse. A selected
Web Server source opens only its compiled Web Service root and returns through the shared response
pipeline; a Reverse Proxy source continues to a stored backend origin. The public client never
supplies an arbitrary upstream destination or filesystem root. Header
rules cannot change the identity already used by ACL. Ordinary bodies pass through
without full aggregation. For an explicitly transform-enabled vhost, eligible bounded
static responses use the logical pipeline `origin -> decode supported Content-Encoding
-> transform -> cache identity representation -> downstream compression`. Origin
`no-transform` is authoritative; changed bytes receive a RustProxy representation ETag
while origin validators are retained separately for revalidation. WebSocket data uses
fixed-size copy buffers.
New requests on existing keep-alive/HTTP/2 connections load the current snapshot.
Existing bodies and tunnels retain the old snapshot/backend. Each accepted TLS
connection captures the current certificate configuration before its handshake.
Certificate validation and file reads are never performed inside the SNI resolver.
## Startup and shutdown
Startup opens/migrates SQLite, loads existing intent, prepares required certificates,
compiles the snapshot and binds the requested listeners. The demo only seeds a new
database. A corrupt configuration is not automatically reset. After the runtime is published,
the optional Docker poll loop contacts enabled agents; failures remain observations and do not reset desired configuration or block ordinary proxy service.
Shutdown cancels listeners and tracked tunnels, drains admitted HTTP connections
within the configured grace period, drains accepted configuration writers and
closes SQLite. Bounded runtime shutdown avoids waiting forever for cancelled OS DNS
workers. This does not turn a blocking getaddrinfo call into a killable operation.
## Deliberate development tradeoffs
The panel remains loopback HTTP. All active configuration changes currently rebuild
backend pools and certificate state, even if unrelated fields changed. Compatible health/counter/affinity observations are retained independently; idle
connection and certificate reuse optimization still requires measurements. Backend loop protection checks local listener addresses by default while allowing remote origins on any valid port. A per-backend `accept_local_listener_risk` opt-in can deliberately allow a local RustProxy listener (for example port 9090) without bypassing private/special-address policy. DNS special-range restrictions
are basic safeguards rather than an independently audited network policy engine.
No performance target has been established or measured for this package.
## 0.1.0 response pipeline and resources
Observe pins one immutable snapshot for the exchange. Host/route ACLs precede the
cache and all upstream I/O. Eligible GET lookup uses effective request headers
and a revisioned representation key. On a miss the origin response is validated
before and after global/route header rules; a bounded tee records complete bodies
while still yielding frames immediately. No request-path SQLite/provider lookup is
introduced. Server policy and optional streaming compression apply after cache
lookup/fill wrapping. The outer MeteredBody counts delivered encoded payload frames.
Cache publication uses a short mutex section and LRU/byte/entry bounds; fill buffers
have eight semaphore slots. Purge increments a generation, preventing older in-flight
fills from publishing. Config commit invalidates cache before snapshot publication,
without awaiting between those operations. New listener intent is persisted but
active sockets remain unchanged until restart; original CLI defaults are retained
separately so clearing an override has accurate desired-state reporting.
`cache`, `transform`, `compression`, `settings`, `diagnostics`, and `system_stats` are dedicated modules. `diagnostics` owns periodic sampling/export while `system_stats` adapts the shared `systemstat` host API, `sysinfo::Networks` network API, and one `metrics-process` process API. Network inventory/counters use `sysinfo` as the primary source and merge `systemstat` as a secondary library source, so missing/partial primary inventory is supplemented without shell commands; no BSD/Linux shell-command network parser is maintained. `metrics-process` uses its documented dummy fallback where no native process collector exists, so unsupported fields remain absent without adding another platform parser. Linux adds its compatibility-only `/proc`/cgroup fields after the common sample. The
sampler runs blocking OS/library reads off the async request path roughly every two
seconds and stops through the process cancellation token. Language discovery runs
in build.rs, not in browser hardcoded PL/EN logic. The result is an embedded public
JSON asset; existing logo resources remain intact.
## Vhost pool publication and observations
The immutable routing snapshot contains actual-address-constrained routes, compiled
frontends, pools and both TLS ALPN configurations. The process separately retains
its actually bound listener plan. New routing uses desired bindings immediately;
new sockets/ALPN take effect only at restart. This can temporarily deny moved hosts,
but never silently broadens them to still-active old bindings.
Each pool owns immutable member definitions and clients. Small synchronized health,
scheduling and traffic observations live separately. Compatible publication reuses
observations/tokens by vhost/member/origin/health identity; changed or re-enabled
members are quarantined. Request leases retain the old member/client until both
body directions or the upgraded tunnel finish. Changes do not replay requests.
A weak-App, cancellation-aware supervisor schedules a rotating, bounded window of
probes outside the data path. DNS and configured-origin policies are shared with
normal forwarding; HTTP checks use a dedicated connection and discard the body.
Traffic sampling and status snapshots operate on these observations, not SQLite.
Request order is actual frontend selection -> host/vhost/path ACL/auth -> location/source selection. Web Server then performs confined local filesystem handling; Reverse Proxy continues through eligible member selection -> member-isolated cache lookup -> guarded upstream request.
Cookie affinity bypasses cache. Cache hits release the selection without recording
an actual upstream request. Response protection is applied after cache selection,
and per-vhost HSTS is removed on plaintext, including synthetic responses.
## Integrations in 0.4.0
`forwarding` compiles the explicit trust boundary and reconstructs canonical
metadata. `tinyauth` owns immutable verifier configuration and an outbound client;
it authorizes before the existing origin/cache path. `http3` adapts bounded Quinn
streams to the same Axum router and response Body without a separate cache/auth
implementation. Named listener IDs resolve to actual bindings while compiling
vhosts; accepted destination addresses still control route visibility.
TLS and QUIC use the same validated certificate resolver. Active endpoint limits
remain fixed until restart; new QUIC handshakes can use the latest certificate
snapshot without changing Retry token keys. Graceful shutdown signals H3 GOAWAY
and bounds drain before endpoint close. Runtime conformance remains to be tested.
## 0.5.0 cache inspection and certificate jobs
Cache decision counters and entry metadata live in the bounded cache store. A
separate authenticated paginated read API returns metadata, not bodies, queries or
request headers. Expiry cleanup also runs during inspection; generation barriers
still prevent old in-flight fills from republishing after purge/config changes.
The ACME manager is outside proxy request paths: one job semaphore, a tracked
child-process group, JSON over stdin, sanitized status and private Certbot/RFC2136
profiles. Only validated immutable certificate metadata can enter the existing
library through explicit revision CAS publication. See ACME_RFC2136.md for the
required external tools and unverified integration boundary; RENEWAL.md documents
the 0.7 scheduler and automated publication.
## 0.6 boundaries
`legacy` is an input-only migration module, not a traffic dispatcher. It emits the
same canonical vhost model used by the UI. Validated startup migration uses one
CAS write; failure before validation completion leaves the stored JSON untouched.
`acme` manages one private worker job; `scripts/runtime/acme_provider.py` adapts Certbot
webroot/Cloudflare/OVH/RFC2136. Only the running HTTP-01 job's names and token files
are exposed by the reserved challenge middleware. Provider files remain private,
not part of SQLite or generic configuration exports. Issuance/import are separate.
`prometheus` owns a dedicated read-only HTTP router, actual-peer CIDRs, environment
bearer token and bounded scrape work. It does not share administrator cookies.
Instrumentation is process-local; per-vhost counters reuse stable-ID observations
across configuration changes. Domain cache gauges intentionally reset with revision.
Admin request timing measures response headers, while proxy/vhost timing measures
complete body/tunnel lifetime. Neither renderer queries a provider or SQLite to
handle public proxy requests.
## 0.7 renewal, access and deployment boundaries
A shutdown-tracked weak-reference scheduler selects due configured managed
certificates. It shares the issuance semaphore and per-profile advisory lock.
Provider work remains outside request paths. Publication validates PEM/key/names
and the complete current configuration, then uses the same SQLite CAS/snapshot
path. Only changed generations publish; failures do not touch the active snapshot.
TCP and QUIC handshakes consult current TLS state. Certificate publication also
uses the existing cache-generation invalidation. See RENEWAL.md for timing/limits.
Basic Auth is a separate vhost gate after cumulative IP ACLs and before static
responses, cache and upstream selection. Argon2id verification is bounded by a
shared semaphore held by blocking workers and an IP failure table. Hashes, not
passwords, are persisted. A verified Authorization header is optional and reapplied
after custom request headers; unverified identity headers are removed. Protected
responses bypass the shared cache and authentication failures cannot become custom
200 responses. WebSocket admission uses the same gates before opening a tunnel.
`pool_eligible=false` distinguishes a location-pinned backend from ordinary pool
membership. The GUI edits the same prefix route model as the configuration API;
there is no second nginx-style parser or unvalidated location execution path.
Server deployment uses root-owned immutable release directories, an atomic current
symlink, a non-root systemd service and private service-owned data. A stopped-state
backup precedes update publication. Failed health/version checks attempt to restore
both old code and data; backups and failed-generation state remain for recovery.
Live systemd/database recovery acceptance is not replaced by helper mocks.
+39
View File
@@ -0,0 +1,39 @@
# Encrypted backup and recovery
RustProxy can create a password-protected **full application backup** while the proxy is running. Restore is a separate, CLI-only operation that requires the service to be stopped. A configuration-history entry is **not** a substitute for a full backup.
## Create a backup
```bash
rust-proxy --data-dir /var/lib/rustproxy backup create \
--output /private/rustproxy-backup.rpb
```
The tool asks for a password in interactive operation or may provide a generated password according to CLI mode; preserve it independently of the archive. Store the encrypted `.rpb` file **off-host** with access controls. Never place backup archives in an exposed Web Service document root.
Backups include database state, RustProxy's private Secret Store material, managed certificates and other durable files under the data directory. They **exclude** rebuildable COLD/HOT cache data, most logs/temporary files and the managed Python environment. Certificates/keys referenced through **external absolute paths**, such as `/etc/letsencrypt/live`, must be backed up and restored separately with their permissions.
## Restore
**Stop RustProxy first** and perform restore using an interactive terminal:
```bash
sudo systemctl stop rustproxy
rust-proxy --data-dir /var/lib/rustproxy backup restore \
/private/rustproxy-backup.rpb
sudo systemctl start rustproxy
```
Use an appropriate service user/elevation and keep ownership/permissions intact. RustProxy validates the password, encrypted package, manifest/checksums, SQLite data, Secret Store consistency and compatibility before activating staged restored data. A failed validation should not replace the running state, but an external off-host copy is still essential.
After restore, reattach any external certificate mounts/files and service secrets, check network listeners, then verify the panel login, Vhosts, certificates, Docker agents, cache policy and API credentials. Test a backup restore periodically on an **isolated** environment; do not first test recovery on the live server.
## Secret Store
The application stores sensitive management records using a private local encryption key. Copying only `config.sqlite3` is not enough for disaster recovery: without the associated key, encrypted records may be unreadable. Do not commit the key, database, backup password or unredacted provider credentials to source control. A lost backup password cannot be recovered by RustProxy.
External systems (system Certbot, PHP-FPM, reverse proxies, Docker host agents, DNS credentials) may need their own backups because their complete state is not owned by RustProxy.
## Before an update
Create and verify an off-host backup, record release version and current listener state, plan a maintenance window, update, then run application-level checks. Managed installation rollback is useful but should not be relied on as the sole recovery plan. See [Installation](INSTALL.md) and [Migration](MIGRATION.md).
+111 -349
View File
@@ -1,381 +1,143 @@
# Shared hot/cold cache
# HTTP cache — HOT, COLD and invalidation
See [CACHE_DIAGNOSTICS.md](CACHE_DIAGNOSTICS.md) for practical HIT/MISS/BYPASS diagnosis.
RustProxy provides an optional shared HTTP response cache with a bounded in-memory **HOT** tier and optional persistent-disk **COLD** tier. Both the global cache and a Vhost's effective policy must permit caching. **COLD storage is disabled by default**, and newly created Vhosts do not automatically cache dynamic content.
## Scope
### Cache controls in Settings
The static-extension cookie-bypass warning sits with its extension list, separate from **Cache response headers** options. Only enable this optimization for files whose bytes do not depend on the user's session. `Cookie` can then be omitted from the shared cache key for the configured extensions; responses with `Set-Cookie`, `private`, `no-store` or `Vary: Cookie` remain uncacheable.
RustProxy implements a bounded reverse-proxy cache with a hot RAM tier and an optional
persistent cold disk tier. Version 0.11.5 aligned request-method handling and most logical
hash behavior with Varnish built-in semantics. Since 0.19.1 the logical primary identity
includes the frontend scheme as well as Host + exact request URL, so `http://host/path`
and `https://host/path` cannot reuse the same stored response. RustProxy still isolates
different vhost/config scopes internally and uses response `Vary` to select representation
variants.
## Enable caching safely
This is not a VCL implementation. Generic Varnish grace, ESI, public unauthenticated
unrestricted public BAN/PURGE and distributed cache state are not implemented. RustProxy 0.22.1 adds bounded
foreground request collapsing, authenticated selective PURGE/BAN and optional proactive
refresh while keeping directive-controlled stale delivery through RFC 5861 and request
`max-stale`; it is not an unconditional availability/grace layer. RustProxy also keeps its
validator-based handling of response `no-cache` instead of reproducing Varnish hit-for-miss
behavior exactly.
1. Open **Settings → Cache**, enable the global cache and choose memory limits. Enable COLD only if you want durable disk storage and have enough private disk capacity.
2. On the appropriate Vhost, enable cache for public, repeatable responses. Restrict or disable it for login, personalized pages, uploads, APIs and responses carrying secrets.
3. Ensure the origin supplies a valid public freshness policy, or configure a safe default TTL where supported. `Cache-Control: private` and `no-store` must prevent shared storage.
4. Optionally enable HIT-only diagnostics. Send repeated identical requests using `curl`, then inspect **Cache → Domains / Stored objects** for `MISS → HIT`.
5. Review **Varnish-compatible cache policy**, enabled by default, then check TTL, revalidation and invalidation after updating the origin's content or configuration.
## Web Server origins (0.29.4)
## Varnish-compatible cache policy
**Settings → Cache → Varnish-compatible cache policy** (`cache.varnish_compatible`, **enabled by default**) avoids needless cache replacement on browser hard reloads. On a fresh object it ignores only the client's `Cache-Control: no-cache`, `max-age=0` and `Pragma: no-cache` for cache **lookup/freshness**. It does not remove or modify these request headers when fetching from the origin, and it does not alter cache identity or request conditionals.
A Web Service selected as the vhost source now uses the normal RustProxy shared
object-cache pipeline when both global and vhost cache policy permit it.
`Cache-Control: public, max-age=...` (or another eligible freshness policy)
can produce `MISS` followed by `HIT` instead of a browser-only cache header.
Vhost response rules run before cache fill, and transformed output is what is
stored. Private/`Set-Cookie`/`no-store` origin responses remain ineligible,
even if a later response-header rule replaces their visible cache headers.
Set **Default grace (seconds)** in Settings → Cache (default **60**, range 0..604800). This affects only eligible objects in compatibility mode and never overwrites explicit origin `stale-while-revalidate`, including `=0`. Use the adjacent **i** information icon for the short operator explanation (PL/EN). Disabling the option restores the original strict client reload semantics. Changing modes invalidates currently stored representations across HOT/COLD so they are refilled under one consistent policy; no process restart is needed. Existing JSON configurations with no field inherit `true`. The setting also applies to eligible resources of the embedded Web Server.
For Web Server sources (0.32.2), requests with `Cache-Control: no-cache`,
`max-age=0`, or an expired/inadequately fresh representation check the local
origin using the stored origin `ETag` / `Last-Modified`. When the Web Server
returns `304 Not Modified`, RustProxy refreshes the same shared-cache object,
keeps its body and increments `X-Cache-Hits`. The original browser conditional
headers determine whether the client receives 304 or the cached 200 body.
Changed static files cause a new response/cache fill; `no-store`/`private`
origin policy continues to prevent shared storage. HEAD and single-range
requests can reuse a successfully revalidated full object. A Web Server
source does not run the remote-backend background revalidation worker or its
stale-if-error fallback. A PHP handler that ignores validators may still
return 200, which must be handled as a real origin response rather than an
assumed unchanged object.
| Condition | Enabled: Varnish-style optimized, RustProxy protections retained | Disabled: legacy strict |
|---|---|---|
| Client `no-cache`, `max-age=0`, `Pragma: no-cache` | **Fresh HIT** without replacing object or resetting `Age` / HIT counter | Can force origin revalidation (and `200` replacement without an origin validator) |
| Client `no-store` | **BYPASS**: do not use or populate a shared representation | Same |
| Client `only-if-cached` | **Fresh/stale eligible HIT**, or **504** if no reusable representation; never fetch origin on MISS | Same |
| Client `min-fresh`, `max-stale`, positive `max-age` | Keep constraints; an otherwise fresh object can be rejected by `min-fresh`, while eligible stale entries can be served by `max-stale` | Same |
| Origin `no-cache` with `ETag`/`Last-Modified`, fallback TTL = `0` | Store for **conditional revalidation** (`304`) but never fresh-HIT without checking origin | Same |
| Origin **`public, no-cache`** and explicit fallback TTL `> 0` | Treat the administrator's configured **Fallback TTL for public responses** as shared freshness (capped by max TTL), even when the origin has no validator; this explicitly overrides origin `no-cache` | Validator-only revalidation, no override |
| Origin `max-age=0` / already expired, with `stale-if-error`, a validator or client `max-stale` | Retain the expired representation; it does not become a fresh HIT. Can serve stale on an eligible error, revalidate, or honor explicit client allowance | Same |
| Origin `Surrogate-Control: no-store` | Do not store, even when a fallback TTL is configured; this is an additional compatibility-mode safeguard | Legacy response policy |
| Origin `private`, `no-store`, `Set-Cookie`, invalid `Vary` | Must not enter shared cache; configured static-cookie exceptions do not override response privacy | Same |
| Eligible cached object after TTL | Implicit background-refresh grace up to **60 seconds by default** (configurable with `cache.varnish_grace_seconds`, `0` disables it) **only** for entries initially fresh with **no** origin `stale-while-revalidate` directive; explicit `stale-while-revalidate=0` disables implicit grace. Existing `must-revalidate` / `proxy-revalidate` restrictions still apply | No implicit grace |
| Cookies, Authorization, `Vary`, per-vhost exclusions/force, query/range restrictions, BAN/PURGE, request collapse, transforms, compression, HOT/COLD | Existing RustProxy mechanisms remain in effect | Same |
## HTTP BAN / PURGE from authorized client IPs
**Important:** using a positive fallback TTL to reuse an origin response marked `public, no-cache` is an **explicit administrative freshness override**. It can serve outdated content for that duration; leave fallback TTL at `0` if the backend's `no-cache` must always be honored. The fallback override applies only to responses carrying `public`, not to private/no-store/Set-Cookie responses or `Surrogate-Control: no-store`. The configured `max_ttl_seconds` still caps remaining freshness. Without fallback TTL, origin `no-cache` still requires an origin validator to be stored. An origin `max-age=0` is never made fresh merely because `stale-if-error` is present.
The **Settings → Cache → HTTP BAN / PURGE IP allowlist** control sets
`cache.http_invalidation_allowed_cidrs`. The default is **empty (disabled)**.
Allow individual client IPs or small CIDR ranges, one per line. Saves validate
IPv4/IPv6 CIDR syntax and reject `/0` networks. An allowed request must also
pass the vhost listener binding, global/vhost ACL, and rate limits. The
operation is restricted to the matched external HTTP/HTTPS scheme, `Host` and
vhost; it cannot choose other tenants or the opposite scheme through headers. Commands use the existing
cache invalidation engine, are audit-logged before execution and share a
two-worker maintenance limit with authenticated admin invalidation (429 when
busy). They are never forwarded upstream.
This is **a Varnish-inspired policy subset, not full Varnish binary/VCL compatibility**. RustProxy deliberately keeps an explicit zero-by-default fallback TTL instead of Varnish's implicit 120-second default, retains client controls beyond hard reload, enforces multi-tenant and cookie/Authorization safety rules, and does not implement hit-for-miss/hit-for-pass markers. The current compatibility-specific `Surrogate-Control` safeguard handles `no-store`; it does **not** interpret every Surrogate-Control extension or use Surrogate-Control `max-age` as a TTL override. Different VCL configurations can behave differently.
- `PURGE /path?query` immediately invalidates exact cached representations of
that URL. Returns `200` with JSON result/count. Other query variants are not
matched.
- `BAN /prefix` records a lazy prefix ban of older cached representations
for that host/vhost. Returns `202` and ban ID, normally expires after one hour.
Optional `X-RustProxy-Ban-Ttl: 30` sets 1..604800 seconds. Newly filled
representations are allowed (Varnish-like lazy BAN).
- `BAN /` requires `X-RustProxy-Confirm-Wide: 1` to prevent accidental
whole-host invalidation. Request bodies, upgrades, arbitrary selectors and
wildcard cross-vhost operations are rejected.
To verify with real GET requests (not `curl -I -X GET`):
Examples from an allowlisted machine (replace the domain and path):
```sh
curl -i -X PURGE 'https://site.example/assets/app.js?v=3'
curl -i -X BAN 'https://site.example/assets/'
curl -i -X BAN 'https://site.example/' -H 'X-RustProxy-Confirm-Wide: 1'
```bash
URL=https://example.com/ui/style.css
curl -sS --compressed -D - -o /dev/null "$URL" | grep -iE '^(HTTP/|age:|x-cache:|x-cache-hits:|x-cache-reason:)'
curl -sS --compressed -H 'Cache-Control: no-cache' -H 'Pragma: no-cache' -D - -o /dev/null "$URL" | grep -iE '^(HTTP/|age:|x-cache:|x-cache-hits:|x-cache-reason:)'
curl -sS --compressed -D - -o /dev/null "$URL" | grep -iE '^(HTTP/|age:|x-cache:|x-cache-hits:|x-cache-reason:)'
```
**Behind a reverse proxy:** forward nonstandard `BAN`/`PURGE` methods to the
RustProxy public listener; do not let an upstream cache intercept them. Configure
RustProxy's existing `forwarding.trusted_proxies` accurately and ensure the
edge sanitizes/replaces untrusted `Forwarded`/`X-Forwarded-For` headers. When a
trusted proxy does not provide a usable real-client chain, BAN/PURGE fails
closed. Do **not** allowlist an entire proxy NAT network unless all its users
should have destructive cache access. Unlike full Varnish VCL, this interface
intentionally provides only exact PURGE and bounded URL-prefix BAN, not an
arbitrary untrusted regex/VCL expression.
With the policy enabled, the middle request stays `HIT` if the entry is fresh. Check safeguards separately: an uncached URL with `Cache-Control: only-if-cached` must return **504** without reaching the backend; a stale `only-if-cached` HIT must **not** start background validation/proactive refresh. `min-fresh` and `max-stale` still constrain stale reuse: an explicit `max-stale=0` must not be bypassed by the configured implicit grace. `Cache-Control: no-store` must bypass an existing fresh entry.
## Settings
## Vhost editor options
| Field | Default | Bounds / meaning |
|---|---|---|
| enabled | true | Global master switch; vhosts may disable cache but cannot re-enable a globally disabled cache |
| max_bytes | 268435456 | Hard HOT allocation budget (body backing allocation + charged entry metadata); minimum 64 KiB, no artificial high ceiling |
| max_object_bytes | 16777216 | Maximum logical body accepted by one fill; minimum 1 KiB and at most `max_bytes` |
| max_entries | 4096 | 1..16384 hot objects |
| transient_max_bytes | 67108864 | Hard RAM budget for fill buffers and complete bodies moving between HOT/COLD |
| cold_queue_max_bytes | 33554432 | Byte ceiling for complete demoted objects waiting for the COLD writer; at most `transient_max_bytes` |
| hot_eviction_max_objects | 64 | 1..4096 LRU removals attempted for one HOT admission before the new object passes uncached |
| hot_auto_trim_enabled | true | Periodically trim sufficiently idle HOT objects only while HOT usage is above the target waterline |
| hot_auto_trim_after_seconds | 300 | 5..86400; minimum idle age before an object can be removed by automatic HOT trim |
| hot_auto_trim_target_percent | 80 | 10..95; automatic trim stops once HOT usage reaches this percentage of `max_bytes` |
| cold_enabled | false | Enable persistent disk tier; never implied by hot-cache enable |
| cold_path | cache/cold | Relative path under the private RustProxy data directory |
| cold_max_bytes | 4294967296 | 64 KiB..1 TiB disk budget |
| cold_max_entries | 50000 | 1..1000000 cold objects |
| cold_gc_interval_seconds | 60 | 5..86400; interval between asynchronous pressure-GC checks |
| cold_gc_max_delete | 256 | 1..10000; maximum objects removed by one automatic GC pass |
| cold_after_seconds | 300 | 5..604800; idle hot objects are eligible after this delay |
| cold_on_idle | true | Move idle eligible hot objects to cold storage |
| cold_max_hot_seconds | 3600 | 5..2592000; total RAM-residency age used when `cold_on_age=true` |
| cold_on_age | false | Demote objects after the maximum hot-tier age even if they remain frequently accessed |
| cold_on_lru | true | Move objects displaced by hot-tier LRU pressure to cold storage |
| cold_on_shutdown | true | Best-effort direct hot-to-cold flush during ordinary graceful shutdown/restart |
| default_ttl_seconds | 0 | Only explicit public responses without origin freshness |
| max_ttl_seconds | 86400 | 1..604800; maximum configured retention/freshness cap |
| bypass_query | false | Bypass requests containing a query when enabled |
| bypass_prefixes | [] | Up to 64 normalized absolute path prefixes |
| http_invalidation_allowed_cidrs | [] | Opt-in IP/CIDR allowlist (max 32) for HTTP BAN/PURGE. Empty blocks all HTTP methods; `/0` is invalid. |
| static_ignore_cookies | true | For configured static extensions only, ignore request Cookie for eligibility/keying |
| expose_status | false | Emit `X-Cache: HIT`, `MISS` or `BYPASS` |
| expose_hits | false | Emit per-object `X-Cache-Hits` |
| expose_cache_status | false | Emit standardized `Cache-Status` (RFC 9211) |
| expose_resident_age | false | Emit `X-Cache-Resident-Age` on reused stored responses |
| via_header | `RustProxy` | Append this `Via` response value; `null` disables it |
| collapse_enabled | true | Collapse concurrent foreground fills for the same primary cache key |
| collapse_wait_timeout_ms | 1500 | 10..60000 ms follower wait before fallback |
| collapse_timeout_fallback | origin | `origin` or `stale_if_available` after follower timeout |
| stale_if_error_timeout | true | Allow RFC 5861 stale fallback for timeout failures |
| stale_if_error_connection | true | Allow stale fallback for connection failures |
| stale_if_error_dns | true | Allow stale fallback for DNS failures |
| stale_if_error_tls | true | Allow stale fallback for TLS failures |
| stale_if_error_statuses | [500,502,503,504] | Up to 32 unique eligible HTTP 5xx statuses |
| proactive_refresh_enabled | false | Schedule sampled cached objects for validator refresh before expiry |
| proactive_refresh_before_expiry_seconds | 15 | 1..604800 seconds before expiry |
| proactive_refresh_percent | 10 | 0..100 deterministic percentage of primary keys |
| proactive_refresh_max_inflight | 4 | 1..64 concurrent proactive refreshes |
In **Vhosts → Cache**, overrides for static extensions, bypass path prefixes and unsafe force targets each place the enable/override checkbox immediately above the associated multiline list. The fields are disabled while their individual override is off; their saved values and cache policy are unchanged. Never enable unsafe forced caching for personalized responses.
The hot tier enforces `max_bytes` and `max_entries` before publication. HOT accounting uses the retained body allocation (the fill buffer capacity transferred into `Bytes`) plus directly owned entry metadata and a conservative per-entry charge for the HOT hash/LRU/variant indexes, while `hot_logical_bytes` reports only visible body lengths. Rust container/allocator APIs do not expose every bucket/node allocation exactly, so `hot_allocated_bytes` is intentionally a conservative charged estimate rather than a promise that it equals process RSS. In-progress fills reserve a separate bounded transient budget before growing their buffers. When HOT pressure would exceed the budget plus a small allocator-rounding headroom (roughly 1/64 of HOT, capped at 1 MiB), RustProxy removes LRU objects first; if admission still cannot be made safe within `hot_eviction_max_objects`, the response continues normally without being cached. `max_object_bytes` is checked incrementally before every body-buffer growth, so an oversized response is not fully accumulated before rejection.
## Stored object rules
Automatic HOT trim is a separate low-pressure maintenance path. Every maintenance pass first checks whether HOT allocation is above `hot_auto_trim_target_percent` of `max_bytes`; if so, only least-recently-used objects that have been idle for at least `hot_auto_trim_after_seconds` are eligible. One pass is capped by `hot_eviction_max_objects`. Active/recent objects are never removed by this mechanism. Eligible objects follow the existing COLD-on-LRU policy when enabled; otherwise they are evicted. This is intended to release stale working-set memory after traffic bursts without periodically purging the cache or reloading vhosts.
- Reuse is scoped by frontend scheme, Host, exact client URL, Vhost/content policy and eligible `Vary` dimensions. HTTP and HTTPS objects do not cross-share.
- GET and suitable HEAD requests may reuse a full object; changing request methods do not silently invalidate all cached GET results.
- Authorization, dynamic cookies, `Set-Cookie`, sticky-affinity cookies, Tinyauth and other private signals normally cause **BYPASS**. A limited static-file cookie exception exists for configured static extensions and does not override `private`, `no-store`, `Set-Cookie` or `Vary: Cookie`.
- Origin status, response size, TTL, unsupported metadata, range/conditional semantics and request/client cache directives can prevent fill or require validation.
- A Web Service selected as a Vhost source uses the same cache policy for eligible local files. File changes can be checked with `ETag` / `Last-Modified`; a 304 validation may refresh a stored body without downloading it again.
When cold storage is enabled,
`cold_on_lru`, `cold_on_idle` and `cold_on_age` independently choose which hot objects are demoted.
`cold_after_seconds` measures time since the last access. `cold_max_hot_seconds` measures total
hot-tier residency, so it can move a frequently accessed object to disk after a configured age.
The administration UI exposes these as separate policies to avoid conflating "old" with "unused".
Demotions use an asynchronous queue bounded by both object count and `cold_queue_max_bytes`. A demoted `Entry` is removed from HOT accounting and moved, not copied, into a `ColdDraft`; its `Bytes` payload remains charged to `transient_max_bytes` until persistence completes and the draft is dropped. The persisted `ColdEntry` contains compact lookup/invalidation/LRU metadata rather than the body allocation. Since 0.27.3 it also does not retain the full response `HeaderMap` or origin validator values per COLD object; those are validated and loaded from the existing metadata JSON only for an actual COLD read. The in-memory body digest is 32 raw bytes and the map-owned cache key is not duplicated inside the entry. This reduces long-lived COLD index RAM without changing Cold format 3 on disk. This prevents the COLD writer queue from becoming a second unbounded RAM cache. If an HTTP response is already using a clone of the same `Bytes`, that last transport-owned reference can keep the backing allocation alive until the response finishes; the cache-owned HOT/COLD reference itself is released after persistence. Process RSS is therefore monitored separately from cache accounting. Supported
glibc/Linux builds ask the allocator to release unused pages after bulk demotion. During
ordinary graceful shutdown, `cold_on_shutdown` performs a best-effort direct flush of remaining hot
objects before the store is closed. A **managed software update** (`update.sh` / `scripts/deploy/deploy.py`)
uses a private one-shot marker and, on RustProxy 0.13.2+, forces this hot-to-cold handoff whenever
the cold tier is enabled even if `cold_on_shutdown=false`. The updater gives the process a larger
stop budget and removes the marker before backing up state. If the cold tier is disabled there is
nowhere persistent to hand the hot objects, so RAM-only cache is still lost on process exit. The
body is stored as a raw `.body` file and metadata as JSON with a SHA-256 body digest. A fresh cold hit is integrity-checked and promoted to
RAM. Cold format 3, introduced in 0.21.0, stores objects below the first two hexadecimal
characters of the SHA-256 cache key, for example `cache/cold/7f/<hash>.body` and
`cache/cold/7f/<hash>.json`. Shard directories are created lazily. The previous flat
Cold Cache layout is deliberately not migrated; legacy flat objects are removed and can
be refilled from origin. Format 3 metadata also carries RFC 5861 windows, revalidation
flags, origin validators, last access and hit count.
## Freshness and diagnostics
Automatic Cold Cache GC is asynchronous. When either byte or entry usage reaches about
90% of its configured limit, it selects old/rarely used candidates and cleans toward
about 80%, removing at most `cold_gc_max_delete` objects in one pass. Metadata is
snapshotted in small batches and disk deletion is queued instead of holding request-path
state across a full-cache sort/delete loop.
| State | Interpretation |
|---|---|
| `MISS` | No suitable fresh cached representation was found; the origin was consulted. The response may still be ineligible for storage. |
| `HIT` | A matching stored object was reused. |
| `BYPASS` | Cache was disabled or a policy intentionally skipped it (for example authorization, cookies or a path exclusion). |
| Revalidated | An existing representation was checked against the origin; a `304` can retain the body. |
## Varnish-style identity and variants
`Age` is total HTTP age, not only the time spent in RustProxy. The optional `X-Cache-Resident-Age` reports the cache residency interval. Debug headers such as `X-Cache`, `X-Cache-Hits` and reason labels are optional and should be enabled deliberately. With `varnish_compatible=true`, browser hard reloads do not force revalidation while an object remains fresh. In strict mode hard reloads can force origin revalidation; compare with `curl` when investigating.
The logical primary cache identity is:
Example, replacing the listener and Host values with yours:
- a stable cache-compatibility revision derived only from settings that can change
cached representation/origin semantics, plus vhost scope (internal isolation),
- normalized frontend scheme (`http`/`https`),
- normalized original Host authority, including an explicit port,
- exact request path and query.
```bash
for n in 1 2 3; do
curl -sS -D - -o /dev/null -H 'Host: app.example.com' \
http://127.0.0.1:8080/public.css
sleep 2
done
```
Selected backend-pool member, route/backend IDs and unrelated request headers are not
part of the representation hash. This means two healthy members of the same pool can
serve/fill the same public cache object, while HTTP and HTTPS remain isolated. Headers
such as User-Agent, X-Forwarded-For or browser client hints no longer create unnecessary
variants by themselves.
Look for cache reason labels including `request_cookie`, `request_authorization`, `response_cache_control`, `route_disabled` and `cache_hit` when diagnostic headers are enabled. A `MISS` alone does not prove the object was admitted to HOT/COLD.
The response `Vary` header provides the secondary representation identity. For example,
`Vary: Accept-Language` stores independent language variants under the same scheme + Host + URL
primary hash. `Vary: *`, malformed Vary fields or an excessive Vary field set are not
stored. If an origin changes the Vary schema for a URL, incompatible old variants are
removed so an obsolete no-Vary object cannot shadow a newer Vary representation (or
vice versa).
## Cache response headers and upstream diagnostics
**RFC 9211:** `Cache-Status` is a structured list of cache hops, ordered from the origin-side cache toward the client. RustProxy **does not replace or suppress** an upstream `Cache-Status`. When **Settings → Cache → Expose Cache-Status** is enabled and RustProxy makes a cache decision, it appends its own `RustProxy` member to the existing list; multiple HTTP header fields are an equivalent wire representation of the same list. There is no `backend=hit/miss` parameter: the upstream's own members already describe its cache decisions.
Cookie and Authorization requests still pass by default, matching Varnish built-in
safety behavior. RustProxy's static-cookie optimization can ignore Cookie only for the
configured static file extensions; response `Set-Cookie`, `private`, `no-store` and
`Vary: Cookie` still prevent unsafe shared reuse.
### Per-vhost forced-cache targets
A vhost can opt into an explicitly unsafe `cache.unsafe_force` list for narrowly selected public request targets. It is intentionally not available as a global cache setting. Each configured target starts with `/`; a target without `?` matches by normalized path prefix, while a target containing `?` matches the original path+query prefix.
For matching GET/HEAD requests, forced mode may ignore request-side bypasses caused by a query string, Cookie, Authorization, `Cache-Control: no-cache`/`no-store`/`max-age=0`, and `Pragma: no-cache`. Protocol-safety exclusions such as Upgrade, unsupported Range/preconditions and request bodies remain in force. Cookie and Authorization values are added to the cached representation's `Vary` identity in forced mode so different credentials/cookies cannot reuse each other's object.
Forced mode does **not** override response-side privacy policy. `Set-Cookie`, `Cache-Control: private`, `no-store`, `Vary: *`, partial/trailer/SSE/gRPC responses and the normal size/lifetime validation still prevent storage. Use this only for URLs whose origin semantics are understood; ordinary cache defaults remain safer.
## Request methods and invalidation
GET and HEAD participate in lookup. They share one representation identity. On a
cacheable HEAD miss RustProxy fetches the backend representation as GET, may populate
the shared object, and strips the response body before delivering the original HEAD
request.
POST, PUT, PATCH, DELETE, OPTIONS and TRACE pass to the backend and are not stored.
They do **not** implicitly purge the whole cache. This fixes the pre-0.11.5 behavior
where any successful unsafe request could erase unrelated static objects and destroy
the hit ratio.
Cache invalidation remains explicit:
- authenticated full purge can clear all objects or one vhost scope,
- selective PURGE supports exact URL, URL prefix, `Cache-Tag`, safe regex and primary cache key, with optional host and vhost restrictions,
- exact/prefix matching uses the stored request target including query; a match removes the whole `Vary` family under that primary cache key,
- selective PURGE operates on RAM and Cold Cache and advances a publication generation so no fill that started before the operation can publish afterward, even if the selector depended on response metadata such as `Cache-Tag`,
- BAN records a bounded TTL rule without synchronously scanning the entire store; objects created before a matching active BAN are rejected during lookup/insertion and are removed later by bounded GC,
- new fills created after a BAN rule are allowed, so BAN invalidates the old representation set rather than permanently blocking a URL,
- regex rules and prefix `/` are treated as broad operations and require explicit confirmation,
- only cache-compatibility changes advance the normal cache generation/revision; unrelated logging/TLS/admin/metrics/response-presentation saves retain objects, and cache request-bypass edits plus response-only `X-Cache`/`Cache-Status`/resident-age/`Via` diagnostics also apply without purging stored objects.
Unrestricted anonymous cache-maintenance access is not exposed: the public
HTTP BAN/PURGE interface is explicitly IP/CIDR-authorized (empty by default),
separate from the administrator-only selective invalidation API. If an
application changes a GET resource after POST/PUT/PATCH/DELETE, use origin
freshness/validators or one of the explicit invalidation paths; RustProxy
does not guess the affected URLs. For exposure review and test scenarios, see
[0.31.0 security audit](SECURITY_AUDIT_0.31.0.md).
## Eligibility and freshness
Hard request bypasses include Cookie (outside the static exception), Authorization,
Upgrade, `If-Match`, `If-Unmodified-Since`, nonempty request bodies, configured path/query
exclusions and request `Cache-Control: no-store`.
A single `Range: bytes=...` request is special rather than a normal bypass. When a complete
fresh cached representation is available, RustProxy evaluates `If-Range` and can return
`206 Partial Content` directly with `Content-Range`, `Accept-Ranges`, the correct sliced
`Content-Length`, validators and normal cache `Age`; an unsatisfied range returns `416`
with `Content-Range: bytes */<length>`. A mismatching `If-Range` returns the complete cached
representation. If origin access is needed, RustProxy forwards the Range request as an
identity, non-transformed stream. Multiple/multipart, duplicate or otherwise unsupported
Range forms are passed through to origin and are never stored as separate range objects.
A fresh cached representation can satisfy `If-None-Match`/`If-Modified-Since` locally.
Request `no-cache`, `max-age=0` and legacy `Pragma: no-cache` require validation when a
safe stored validator exists. `max-age=N` and `min-fresh=N` are freshness constraints:
they reuse the cached response when its current Age/remaining freshness satisfies the
client limit and otherwise drive normal revalidation/fetch behavior. Request `max-stale`
can explicitly allow a retained stale response: bare `max-stale` accepts any retained
staleness, while `max-stale=N` bounds it. Freshness expiry does not itself physically
delete an object; normal RAM LRU/Cold Cache GC may still evict it. `must-revalidate` and
`proxy-revalidate` override client stale permission. With a safe validator, an origin
304 refreshes metadata and reuses the stored body.
Responses with `Set-Cookie`, `private`, `no-store`, `Content-Range`, trailers,
SSE/gRPC, `Vary: *`, invalid freshness metadata or unusable size/lifetime are not
stored. Response `Cache-Control: no-cache` may be retained only with ETag or
Last-Modified and must validate before every reuse. Both origin and transformed response
policy must remain eligible.
`s-maxage` takes precedence over `max-age`, then Expires/Date. `Age` uses the RFC 9111
current-age model and includes origin age; it is not a measure of how long the object has
been in RustProxy. Cache metadata keeps normal TTL, `stale-while-revalidate`,
`stale-if-error`, `must-revalidate` and `proxy-revalidate` separately. A representation
is therefore **fresh**, **stale-but-servable** only under an applicable directive, or
**expired/unusable**.
Inside `stale-while-revalidate=N`, RustProxy may return stale immediately and starts one
deduplicated background refresh for that cache identity. Inside `stale-if-error=N`, stale
is used only after an eligible origin failure. Version 0.22 makes timeout, connection, DNS
and TLS classes independently configurable and uses an explicit configurable 5xx status
allowlist; defaults preserve the previous behavior (`500`, `502`, `503`, `504` plus all four
transport classes). After TTL plus the corresponding stale window, that mode is unavailable.
`must-revalidate` and `proxy-revalidate` still permit storage, but prohibit all stale delivery
after expiry, including RFC 5861 and request `max-stale`.
Optional proactive refresh schedules a deterministic percentage of cache keys before normal
expiry. It reuses the same validator-based refresh logic and background-revalidation guard,
keeps only one schedule per key, and enforces a global concurrent-refresh bound. A newly
completed fill can schedule this work immediately, so sampled objects do not require another
client request to start the pre-expiry timer. The feature is disabled by default.
## Resident age
`X-Cache-Resident-Age` is the RustProxy-specific whole-second clock for how long the
stored representation has remained in this cache. In 0.11.5 that clock is independent
from the HTTP freshness `Age` clock and survives:
- ordinary hot-cache HITs,
- successful backend 304 revalidation,
- hot-to-cold demotion,
- cold-to-hot promotion.
A value of `0` is valid only during the first sub-second of residency. Revalidation or
tier movement must no longer reset a long-lived object to zero.
## Streaming and capacity
RustProxy tees response frames while delivering them; it does not wait for the complete
origin body before sending the first bytes. Only a complete successful body is
published. Oversize, body error, trailer and cancelled-fill paths discard the candidate.
Large buffers grow incrementally and consume multiple bounded fill permits.
The configured resident byte limit accounts for body bytes, estimated header storage
and fixed per-entry overhead; it is not total process RSS. Active HIT references,
in-flight origin responses, compression buffers and runtime allocations also use memory.
With `collapse_enabled=true`, the first foreground MISS for a primary cache key owns the
fill while concurrent followers wait for publication. The fill guard is held through the
actual response-body tee, so followers cannot wake merely because origin headers arrived.
Leader error/cancellation releases waiters. A follower that wakes to a different `Vary`
representation re-enters the single-flight decision instead of immediately stampeding the
origin. After `collapse_wait_timeout_ms`, the configured fallback is either an independent
origin request or an eligible stale response. SWR/background revalidation keeps its separate
existing deduplication guard.
## Diagnostics and API
`GET /api/cache` reports settings, HIT/MISS/BYPASS, hot/cold counts and bytes,
configured COLD byte/object limits, promotions/demotions, errors, fill pressure, object
views and per-domain reason counters. The `/cache` dashboard shows HOT and COLD
occupancy against the configured object-count and byte limits with progress bars and
explicit percentages. HOT byte occupancy uses the charged/allocated HOT value rather
than logical body bytes; COLD byte occupancy uses persisted storage bytes. Hit ratio is
`hits / (hits + misses)` and intentionally excludes BYPASS traffic.
`POST /api/cache/purge` requires the existing administrator session, same-origin marker
and CSRF token. `?vhost=<id>` purges only that vhost scope; omitting it purges all cache
objects. Purge is audited.
`POST /api/cache/invalidate` performs authenticated selective `purge` or `ban` using
`exact`, `prefix`, `tag`, `regex` or `cache_key`. Optional `host` and `vhost` restrict scope;
BAN accepts a bounded TTL. Regex and prefix `/` require `confirm_wide=true`. The result
reports the selector/scope, affected entry count, elapsed time, status and BAN id when one
was created. `GET /api/cache/bans` lists active rules and `DELETE /api/cache/bans/{id}`
removes one. These operations use the normal administration audit path. The Cache panel
exposes the same controls plus per-object **Purge URL**.
`POST /api/cache/cold/cleanup` starts a selective asynchronous Cold Cache cleanup using
the same candidate/removal machinery as automatic GC. Criteria can select entries unused
for a duration, cap removal to the oldest N objects, and/or require `hits == 0`.
`GET /api/cache/cold/cleanup/{id}` reports job status, removed object count and freed
bytes. Purge-all and purge-vhost remain the full-clear mechanisms; selective invalidation
is deliberately separate.
`X-Cache`, `X-Cache-Hits`, `Cache-Status`, `X-Cache-Reason` and
`X-Cache-Resident-Age` are optional diagnostics. `Via` is a separate configurable response header, enabled by default with value `RustProxy`; RustProxy appends it instead of deleting an upstream `Via` chain. `Cache-Status` is the standardized
RFC 9211 field name; RustProxy does not rename it to the legacy/custom `X-Cache-Status`.
HTTP/2 and HTTP/3 serialize field names in lowercase, so `cache-status:` on the wire is
normal. When the cached representation was produced by the transform pipeline,
RustProxy appends the extension parameter `transformed`, for example:
For example, with an upstream HIT and a RustProxy HIT:
```http
Cache-Status: RustProxy; hit; ttl=556488; detail="cache_hit"; transformed
Cache-Status: BackendCache; hit; ttl=500
Cache-Status: RustProxy; hit; ttl=482; detail="cache_hit"
X-Cache: HIT
```
Origin lookalike values (`Cache-Status`, `X-Cache-Status` and the other RustProxy
cache diagnostics) are removed before RustProxy emits its own diagnostics. HTTP `Age`
is generated from cache freshness metadata on reused responses. For 0.26.6 configuration
compatibility an explicit response-header rule may set/append/remove `Age`, but that rule
runs only after cache lookup/revalidation/storage decisions and therefore cannot alter the
internal freshness clock. The Cache dashboard also marks transformed objects explicitly.
On first fill, RustProxy uses `RustProxy; fwd=miss; detail="cache_miss"` (unless another forwarding reason applies). A cache policy BYPASS uses `fwd=bypass`, and reuse of a stored body following origin `304` validation is reported as `fwd=stale; fwd-status=304`, **not** a fresh unforwarded RFC 9211 HIT. The existing `X-Cache: HIT` for that validation remains for legacy monitoring compatibility. The `ttl` parameter counts remaining seconds for a RustProxy HIT, not the upstream's TTL, and `detail` is a fixed RustProxy reason code. The optional `transformed` extension on RustProxy's member is applied only to its own entry.
## Pool behavior
**Legacy collisions:** `X-Cache`, `X-Cache-Hits`, `X-Cache-Reason` and `X-Cache-Resident-Age` are non-standard response fields. They do not define interoperable append/list semantics. When RustProxy is configured to emit a particular field, it owns that field and copies all origin values (if any) into `X-Upstream-Cache`, `X-Upstream-Cache-Hits`, `X-Upstream-Cache-Reason`, or `X-Upstream-Cache-Resident-Age` respectively. For example:
Backend selection and health policy still run before an origin fetch. The selected pool
member no longer fragments the cache identity in 0.11.5, so a fresh public object can be
reused regardless of which healthy member would have been selected for a MISS. A pool
with no eligible member still returns 503 before RustProxy can use the cache; this cache
is not a stale-availability layer.
```http
X-Cache: HIT
X-Upstream-Cache: MISS
Cache-Status: BackendCache; fwd=miss
Cache-Status: RustProxy; hit; ttl=482; detail="cache_hit"
```
Cookie affinity and protected/authenticated traffic keep their existing privacy bypass
rules. Per-vhost response/security policy also applies to HIT responses.
Here, the upstream MISS is the **original backend state when RustProxy filled the object**; it is not a fresh backend measurement on every RustProxy HIT. `X-Cache-Status` is forwarded unchanged because RustProxy does not generate that field. The `X-Upstream-*` aliases are a compatibility convenience, **not RFC 9211**; for new integrations use `Cache-Status` instead. Upstream headers are relayed as provided, without verifying the upstream's diagnostic accuracy.
**Display switches:** `Expose cache status`, `Expose cache hits`, `Expose cache resident age`, `Expose Cache-Status` and `Only on HIT` control **only RustProxy-generated headers**, not origin-supplied headers. Even when RustProxy caching is disabled, or a MISS/BYPASS is hidden by the Only on HIT option, backend-provided cache diagnostics remain visible. The `Via` header is governed separately by its configured cache policy. Backend diagnostics persist with eligible cached objects in HOT and COLD; responses which were never cacheable are still forwarded with their upstream fields intact.
To verify on a cacheable URL, use **GET** twice, without forced reload:
```bash
URL=https://example.com/public.css
for n in 1 2; do
curl -sS -D - -o /dev/null "$URL" | grep -iE '^(HTTP/|age:|cache-status:|x-cache:|x-upstream-cache:|x-cache-hits:|x-upstream-cache-hits:)'
done
```
The initial request can show `RustProxy; fwd=miss`; an eligible repeat should show `RustProxy; hit; ttl=...`. If the origin sends no `Cache-Status`, there is no upstream member to display. If RustProxy diagnostic output is off, any visible `Cache-Status` is from upstream; absence of a RustProxy member is expected. The actual cache outcome should also be checked in server request logs.
## Advanced behavior
- **Budget limits:** HOT RAM capacity, object count/size, transient fill memory and optional COLD byte/object limits. Configure based on real workload and monitor disk/RSS.
- **HOT/COLD movement:** entries may be demoted, recovered or expired; a periodic/explicit COLD cleanup is available. Cache storage is not a permanent archive.
- **Request collapsing:** eligible concurrent MISS requests can share a bounded fill, limiting origin bursts.
- **Stale-if-error and refresh:** conditional options can help on eligible backend failures, but are not an unconditional stale-serving guarantee. Web Server local-file validation has a distinct policy.
- **Compression/transformation:** cached variants and validators must remain compatible with client encoding and content-modification settings.
## Clear or invalidate content
Authenticated Cache page/API operations can purge selected entries, domains or larger scopes. The optional public HTTP `PURGE`/`BAN` interface is **disabled by default**. Enable it only by listing trusted client IPs/CIDRs under **Settings → Cache → HTTP BAN / PURGE IP allowlist**; operations are additionally subject to Vhost binding and normal access controls. `PURGE` addresses an exact URL; `BAN` applies the supported bounded matching rules. Never allow untrusted Internet clients to purge all tenants' objects.
Changing operational Vhost settings is intended to preserve compatible HOT/COLD objects; content-affecting changes invalidate affected scopes and COLD carries an additional fingerprint against stale resurrection. **This new behavior still needs release-level verification**, so validate cache after configuration edits and during upgrades. Earlier persistent cache entries may need refilling following format/compatibility transitions.
## Troubleshooting
- **Always BYPASS:** check both cache enable switches, login/cookies, exclusion rules, methods, Tinyauth and sticky sessions.
- **MISS never becomes HIT:** check response `Cache-Control`, status, object size, `Vary`, `Set-Cookie`, incomplete origin streams and whether the same scheme/Host/query is used.
- **Content unexpectedly old:** inspect Age, TTL, validators, stale rules and previous invalidation; purge only affected content after confirming its identity.
- **Memory/disk pressure:** review HOT/COLD budgets and cleanup, rather than increasing limits without measurements.
See [Monitoring](MONITORING.md) for cache telemetry and [Security](SECURITY.md) for privacy boundaries.
-223
View File
@@ -1,223 +0,0 @@
# Cache diagnostics and behavior
This describes implemented source, not a verified Rust runtime. See
[TEST_REPORT.md](TEST_REPORT.md).
## 0.29.7: show diagnostic headers only on cache HIT
Set `cache.expose_only_hits: true` globally (Settings > Cache) to emit
`X-Cache`, `X-Cache-Hits`, `X-Cache-Reason`, `Cache-Status` and
`X-Cache-Resident-Age` only when the response is actually served from
RustProxy cache. MISS and BYPASS continue to be recorded in internal metrics
and access logs but these diagnostic headers are absent from those responses.
Cached stale/revalidated/range/conditional responses use the HIT outcome and
continue to show cache diagnostics. Individual vhosts can override the global
flag with `vhosts[].cache.expose_only_hits: true | false | null` (`null`
inherits). Changing the flag alone does not purge cached objects.
The independent `Via` proxy-chain header can still appear on MISS/BYPASS when
`cache.via_header` is configured. Set it to `null` to turn off that header.
Existing `expose_status`, `expose_hits`, `expose_cache_status` and
`expose_resident_age` switches still select which individual HIT headers are
emitted.
## 0.19.1 cache identity safety
The frontend scheme is part of the primary cache identity. `http://host/path` and
`https://host/path` therefore cannot share an object, preventing responses that depend
on `X-Forwarded-Proto` or equivalent origin logic from crossing schemes.
## 0.11.5 cache changes
The cache follows the Varnish built-in model for the behaviors that most directly
affect hit ratio, with the 0.19.1 scheme-isolation difference noted above:
- GET and HEAD use the cache; other ordinary methods pass without clearing unrelated objects.
- The logical primary identity is frontend scheme + normalized Host authority + exact URL inside the current vhost/config scope.
- Selected backend-pool member and unrelated request headers do not fragment the primary object.
- Response `Vary` chooses representation variants under that object.
- GET and HEAD share the same stored representation; a cacheable HEAD miss can fill it from a backend GET.
- `X-Cache-Resident-Age` survives 304 revalidation and hot/cold tier movement.
RustProxy still deliberately bypasses Cookie and Authorization requests by default,
matching Varnish built-in safety behavior. The configured static-extension optimization
can ignore Cookie only for selected static paths, subject to response privacy guards.
## Why objects used to disappear after POST/PUT/etc.
Before 0.11.5 a successful non-safe request could call a global cache purge. A periodic
POST, API heartbeat or form submission could therefore erase every cached static object
even though the request was unrelated to those URLs. In 0.11.5 POST/PUT/PATCH/DELETE,
OPTIONS and TRACE pass through and do not implicitly purge the shared cache.
Only configuration changes that alter cache/origin compatibility invalidate incompatible cache
state; unrelated control-plane saves keep reusable hot/cold objects. Explicit administrator purge
still invalidates the selected cache state. Use the purge action when the application really needs
explicit invalidation.
## `Age` versus `X-Cache-Resident-Age`
These are different clocks:
- `Age` is HTTP freshness age. It can already be thousands of seconds when RustProxy
receives an object because the origin/CDN `Date`/`Age` contributes to it. On an ordinary
fresh HIT it advances from RustProxy's stored receive time even when the backend never
supplied an `Age` header. A successful validator revalidation (backend `304`) starts a
new freshness calculation, so the next `Age` can legitimately be `0`. Requests carrying
`Cache-Control: no-cache`, `max-age=0` or `Pragma: no-cache` can therefore make repeated
validated HITs appear with `Age: 0`.
- `X-Cache-Resident-Age` is RustProxy-specific and counts whole seconds that the stored
representation has remained resident in this cache.
Resident age is preserved across a successful backend 304 and across hot-to-cold/cold-to-hot
movement. An immediate first HIT can legitimately show `0` because less than one full second
has elapsed; a long-lived resident object should then keep increasing rather than repeatedly
resetting to zero. Use this header when the diagnostic question is cache residency rather than
HTTP freshness.
## Practical HIT test
Enable cache plus `X-Cache`, `X-Cache-Hits` and `X-Cache-Resident-Age`, then make
repeated identical requests that read the complete body:
```bash
for n in 1 2 3; do
curl -q -sS -D - -o /dev/null \
-H 'Host: app.localhost' \
http://127.0.0.2:8888/cache
sleep 2
done
```
For an eligible response, expect MISS then HIT/HIT. `X-Cache-Resident-Age` should grow
between later HITs. A browser is a poorer diagnostic because its own cache may satisfy
a navigation without sending any request to RustProxy.
HEAD is now eligible too. It shares the GET representation, but GET is still the best
basic test because it proves that the body was completely consumed and published.
## Vary test
A response with:
```text
Vary: Accept-Language
```
stores language variants under one primary scheme + Host + URL object identity. Two English
requests can HIT each other; a Polish request must not receive the English body. If the
origin later changes between no `Vary` and `Vary: Accept-Language`, incompatible older
variants are removed rather than used as a fallback.
`Vary: *` is not cacheable.
## Cookie/static behavior
`request_cookie` is a BYPASS by default. With `static_ignore_cookies=true`, configured
static extensions such as `.css`, `.js`, fonts and images ignore the request Cookie for
cache eligibility/keying. This is intentionally limited to static paths. A response
with `Set-Cookie`, `Cache-Control: private/no-store`, or `Vary: Cookie` still cannot be
shared through that optimization.
This setting is useful for raising the asset hit ratio for logged-in users without
turning dynamic personalized pages into shared objects.
## Conditional requests and reloads
Fresh cached objects can satisfy `If-None-Match`/`If-Modified-Since` locally. Request
`Cache-Control: no-cache`, `max-age`, `min-fresh` and legacy `Pragma: no-cache` select
validation instead of creating a separate cache key. If a stored validator exists and
the backend returns 304, RustProxy refreshes metadata and reuses the body.
Hard browser reloads commonly add revalidation directives, so they may produce
`cache_revalidated` instead of a plain `cache_hit`. This is different from a cache miss.
## Reason tokens
When cache diagnostics are enabled, these fixed reason tokens explain decisions:
| Token | Meaning |
|---|---|
| `cache_disabled` | Effective cache is disabled. |
| `route_disabled` | Selected route explicitly disables cache. |
| `request_method` | Method is not GET/HEAD. It passes and does not purge the cache. |
| `request_cookie`, `request_authorization` | Private request state bypasses shared cache. Static Cookie exception may apply. |
| `request_no_store` | Client forbids use/storage of shared cache. |
| `request_no_cache`, `request_max_age`, `request_min_fresh`, `request_pragma_no_cache` | Client requests validation. |
| `request_body`, `request_range`, `request_precondition` | Request is outside reusable shared-object semantics. |
| `query_excluded`, `path_excluded` | Configured exclusion matched. |
| `tinyauth`, `sticky_cookie` | Authentication/affinity policy deliberately bypasses shared cache. |
| `response_set_cookie`, `response_cache_control` | Origin response is not safely shareable. |
| `response_vary`, `response_ttl_size_or_metadata` | Vary/freshness/size/metadata prevents storage. |
| `response_headers_policy` | Transformed client-response policy is not cacheable. |
| `cache_miss`, `cache_hit` | No reusable variant was found, or a variant was reused. |
| `cache_conditional_hit` | Fresh cache metadata satisfied the client validator locally. |
| `cache_revalidated` | Backend 304 validated the stored body and it was reused. |
| `cache_revalidated_uncached` | Validation succeeded but refreshed policy no longer permits storage. |
A MISS only means lookup found no reusable object. The later origin response can still
fail storage because of privacy headers, size, incomplete streaming, trailers or a
changed policy.
## Memory accounting in 0.16.0
The process **RSS total** is an operating-system measurement and is intentionally shown separately from RustProxy's own cache accounting. Do not add the rows together: they overlap. The process block reports:
- `RSS total` (`VmRSS`) - all resident pages currently attributed to the process;
- `RSS anonymous` (`RssAnon`) - anonymous resident pages, which typically include heap/stacks and other private allocations;
- `RSS file-backed / shared` (`RssFile` / `RssShmem`) - resident mapped file/shared-memory pages;
- `data address space / swap` (`VmData` / `VmSwap`) - address-space and swapped-page context, not extra resident bytes.
The cache block separately reports memory RustProxy can attribute to the cache subsystem:
- `hot logical` - visible cached body bytes (`body.len()`);
- `hot allocated` - the hard-budget charge for retained backing allocations plus entry metadata (`resident_bytes` remains a compatibility alias);
- `transient` - bounded cache allocations temporarily outside the hot store;
- `fill` - active response buffers being assembled before publication;
- `cold queue` - complete bodies waiting for cold-tier persistence;
- `cold I/O` - bodies currently being written to or read from the cold tier.
This explains why, for example, a process RSS around 630 MiB can coexist with only about 100 MiB of reported hot cache: the old `Pamięć hot` card was never a whole-process memory counter. TLS/QUIC/Hyper/Tokio allocations, thread stacks, configuration/routing state, active request bodies and allocator-retained pages are also part of RSS but are not falsely assigned to a RustProxy subsystem when the source cannot measure them directly.
The existing Linux/glibc allocator-release path remains in place: large cache demotion/purge paths call `malloc_trim(0)` through `release_allocator_memory()`. Since 0.24.5, HOT allocation, active fills and bodies moving to/from COLD are explicitly budgeted. COLD queue admission is byte-bounded as well as count-bounded. RSS is still a process-wide view containing the same cache memory plus TLS/QUIC/Hyper/Tokio, stacks and allocator retention, so cache gauges must not be added to RSS. Long-running acceptance should verify that HOT stays within `hot_limit_bytes`, transient stays within `transient_limit_bytes`, demotion drains transient bytes, and process RSS reaches a practical plateau under representative traffic.
### Sustained traffic, vhost saves and RSS
A vhost configuration save can make RSS fall abruptly for two independent reasons. First, the current cache compatibility identity includes vhost configuration; when that identity changes, publication performs a cache purge so representations created under the old origin/configuration contract cannot survive incorrectly. Second, publishing the newly compiled routing snapshot retires the previous snapshot and its upstream clients; once outstanding references finish, idle connection-pool state and related buffers can be released. A visible RSS drop after saving a busy vhost is therefore a useful diagnostic clue, but saving/reloading a vhost is not a memory-maintenance requirement.
The 0.25.0 development runtime adds two maintenance mechanisms that do not reload configuration:
- every 60 seconds on Linux/glibc, RustProxy calls `malloc_trim(0)` from a blocking maintenance task; this can return allocator pages that are already free, but cannot and does not evict live Rust objects;
- HOT cache maintenance trims only least-recently-used entries that have been idle for the configured delay and only while HOT allocation is above the configured target waterline. The pass is bounded by `hot_eviction_max_objects`; recent/active entries are not purged.
Upstream HTTP clients now also have bounded generations. Each backend Hyper client is recycled after 25,000 requests or 3,600 seconds by default (both limits are configurable under vhost keepalive and either can be disabled with `0`). Recycling replaces only the backend client/pool and leaves the vhost snapshot, HOT cache and configuration untouched. HTTP/1 upstream read buffers are capped at 128 KiB by default. This bounds long-lived Hyper/TLS/pool high-water allocations while preserving normal keep-alive reuse inside each generation. The existing 60-second allocator trim can then return already-free glibc pages to the OS.
## Hot and cold tiers
HOT RAM is bounded by allocated/charged `max_bytes` and `max_entries`; `hot_logical_bytes` separately exposes payload length. Fill and COLD handoff/read memory are bounded by `transient_max_bytes`, and queued demotions also obey `cold_queue_max_bytes`. Optional cold storage demotes LRU
or sufficiently idle hot objects asynchronously. Cold hits verify the body digest and
promote the object back to RAM. Version 0.11.5 uses cold metadata format 2 to persist
Vary identity and resident age; old-format metadata is not reused.
Cold is not required for normal hot-cache stability. With cold disabled, hot objects leave
RAM because of expiry, hot capacity/LRU pressure, explicit purge, configuration generation
changes, replacement of the same logical representation, or the optional HOT auto-trim
waterline. Auto-trim is not a blind TTL: it acts only when HOT is above its configured target
and only on entries idle longer than the configured threshold; it can be disabled independently.
## Cache panel and API
The Cache panel shows hot/cold object counts, RAM/disk usage, HIT/MISS/BYPASS, hit ratio,
fill pressure, evictions/expiry, per-domain decisions and stored objects. Live polling
refreshes the view; it does not itself purge cache objects.
`GET /api/cache?offset=0&limit=50` returns aggregate and object data. Add `vhost=ID` to
filter. `POST /api/cache/purge?vhost=ID` invalidates one vhost; omitting the query purges
all cache objects. Mutations remain authenticated, CSRF-protected and audited.
Hit ratio is `HIT / (HIT + MISS)`. BYPASS requests are excluded, so a large number of
cookie/authenticated requests can reduce backend offload without mathematically lowering
that displayed ratio. For the highest safe reusable-object ratio, give static assets
long explicit public TTLs, keep their URLs stable/immutable, leave the static-cookie
optimization enabled when appropriate, and avoid unnecessary `Vary` fields.
+102
View File
@@ -0,0 +1,102 @@
# Certificates, ACME providers and renewal
Certificates are managed under **Certificates**. You can import a PEM pair, reference accessible external certificate files or request a managed ACME certificate. Issuing a certificate does **not** automatically assign it to a Vhost; choose the certificate in the intended frontend/Vhost afterwards.
On Linux, the updated DEB/RPM service allows **read-only** access to
external certificate paths according to the `rustproxy` user's Unix group/
ACL permissions, including paths inside `/home`. A separate safety rule still
requires the actual PEM files and real parent directories to be owned by root
or `rustproxy`; user-owned `/home/alice` is therefore unsuitable as a PEM
ancestor even though it can host a Web Server document root. See
[Installation: external paths](INSTALL.md#linux-debrpm-read-only-access-to-external-paths)
for scoped ACLs and Certbot renewal handling.
### Certificate file permission command preview
The permissions explanation now appears next to generated read/traversal ACL commands, rather than alongside the path inputs. The **Copy commands** action is offered only after a valid path selection produces a command preview. These commands grant read access to the certificate/private key and traversal of parent directories; they do not grant write permission.
## Supported certificate methods
| Mode | Requirements |
|---|---|
| **HTTP-01 (Let's Encrypt)** | DNS for every name and a public TCP port **80** reaching a plaintext RustProxy listener; HTTP challenge endpoint is handled by RustProxy. |
| **Cloudflare DNS-01** | A zone-limited `Zone:DNS:Edit` API token and functioning Certbot Cloudflare plugin. |
| **OVH DNS-01** | Correct regional API endpoint plus application key/secret and consumer key scoped to the DNS zone. |
| **RFC2136 DNS-01** | DNS server, TSIG name/secret/algorithm and an update policy restricted to the requested records. |
| **PEM import** | A matching certificate chain and private key, validated before activation. |
| **Existing local files** | Trusted absolute certificate/key paths accessible to the RustProxy service account. Suitable for a separately managed Certbot lineage. |
HTTP-01 cannot create wildcard certificates; use DNS-01. A staging certificate is for testing and is not generally trusted by browsers. Production issuance requires explicit confirmation of the production operation and provider terms.
## First issuance and assignment
1. Open **Certificates → Add certificate** and choose the provider/import method.
2. Enter DNS names, a unique certificate ID, contact email where requested and provider-specific credentials or files.
3. For HTTP-01, confirm public DNS and HTTP port 80 reach RustProxy even when a separate public HTTPS port is active. For DNS-01, validate authoritative DNS provider and propagation permissions.
4. Run staging first when testing new credentials/DNS, then confirm production issuance deliberately.
5. Wait for the job result; inspect errors under Certificates and the service logs.
6. Assign the issued/imported certificate to the intended Vhost/listener and verify a real TLS handshake with the public hostname.
Expand a certificate in the Certificates inventory and use **Assign Vhosts** to check/uncheck multiple Vhosts before applying one revisioned Vhost-section save. This updates Vhost certificate mappings, not the PEM files themselves. Verify frontend listener and certificate hostname coverage separately.
The reserved HTTP-01 challenge path is available only during eligible issuance/renewal work. It is not a generic public file server and does not bypass authentication for unrelated URLs.
## Managed ACME runtime
RustProxy prefers a **private Certbot/Python environment** under the application data directory (typically `/var/lib/rustproxy/acme-python`), with support for the Cloudflare, OVH and RFC2136 plugins. A working system Certbot may be used as fallback; systems without a usable runtime can still import or reference PEM files. The application does not run system-wide `sudo` to install plugins or modify distribution-owned Python packages.
Provider tokens and TSIG secrets are security credentials: use least privilege, protect files and the data directory, rotate compromised values and restrict backups. ACME DNS credentials may be stored in private provider/Certbot files; do not assume they are stored in the same way as RustProxy administration Secret Store records.
## Automatic renewal
Managed certificates have configurable automatic renewal. RustProxy runs the scheduler while the service is active; this is **not** a separate cron service. Certificate status and next checks are visible in Certificates. The scheduler uses spaced checks and retry/backoff on failure, without automatically forcing a CA reissue on every check.
After successful renewal, RustProxy validates the new pair and publishes it to new TLS handshakes without interrupting existing connections. Invalid/rejected material should leave the previous active pair in place. Keep room for archived ACME generations and monitor expiry; this is not an independent external expiry-alert service. **Do not run an overlapping system Certbot timer** against the RustProxy-managed ACME profiles.
## Externally managed Certbot / PEM files
When another service owns the certificate, add a manual certificate with the actual certificate and key paths, for example:
```text
/etc/letsencrypt/live/example.com/fullchain.pem
/etc/letsencrypt/live/example.com/privkey.pem
```
### One vhost, several certificates (SNI selection)
A vhost normally uses one certificate for all of its names. When a single vhost serves domains that have **different** certificates (for example several `autodiscover.<domain>` names, each with its own certificate), leave the vhost certificate on **Global SNI / default certificate**. RustProxy then picks the certificate for every TLS connection by the requested server name (SNI) from the stored certificates whose **SNI names** include it. Wildcard names match one label (`*.example.com` matches `a.example.com`, not `a.b.example.com`).
1. Open **Certificates**, edit the certificate and open **Existing files / advanced**.
2. Check **SNI names**: it is filled automatically from the certificate's SAN entries and can be edited.
3. Repeat for each certificate and keep the vhost on **Global SNI / default certificate**.
While you add host names to a new vhost, the editor selects a stored certificate automatically when one certificate covers every name. When no single certificate does but each name is covered by some stored certificate, it tells you to keep Global SNI. Selecting a certificate explicitly in the vhost overrides the SNI names for that vhost's hosts; if it does not cover them, saving asks whether to force it.
### Permission commands and automatic SNI names
Select **External files (absolute paths)** and enter both paths. A permission-command block is generated **below the certificate and private-key fields** and changes as the paths change. It uses the supplied paths, resolves symlink targets on the server when run, removes group/other write permission on the resolved PEM files and other-user read permission on the private key, then grants traversal of the relevant parent directories and read access to the files for `rustproxy`. Install the Linux `acl` tools if needed and review/run the commands as an administrator on that server. The panel generates commands; it does not execute them or change filesystem ownership.
The paths must be absolute, without `.`/`..` components or control characters, and resolve to existing regular files when the commands run. For a custom deployment, change **Service user** under **Options and help** before copying the commands; the default is `rustproxy`. This field only changes the generated instructions, not RustProxy's process identity or saved configuration.
The existing trusted-path rules still apply: real PEM files and ancestors must be owned by root or the RustProxy service user, ancestors must not be group/world-writable, and a private key must not be world-readable or group/world-writable. ACLs alone do not make an untrusted path acceptable. Container mounts and mandatory access-control rules need matching deployment settings; see [Installation](INSTALL.md#linux-debrpm-read-only-access-to-external-paths).
After a public certificate becomes readable, the editor automatically fills **SNI names** from its DNS Subject Alternative Name (SAN) entries. Wildcard SANs are retained; filenames and a certificate's Common Name are not used to invent DNS names. Inspection applies to managed certificate filenames and external paths, reads only the public PEM, and does not activate or save the certificate. The certificate/key pair is still validated when applied.
Automatic reads preserve manually edited names, intentional clearing and existing saved mappings. Results from an earlier path or form are ignored. Use **Read names from certificate** to retry after fixing filesystem permissions or to deliberately replace the current field with the certificate's names. Prefer explicit certificate assignment in the intended Vhost; auto-filled legacy SNI names do not create Vhosts or replace their assignments.
Keep the original Certbot renewal system and use the RustProxy certificate reload/deploy API or local CLI after rotation. RustProxy resolves relevant symlinks again during reload. The service account must have directory traverse and narrow read permissions to the **current** key file; `ReadOnlyPaths` alone does not grant filesystem permission. Renewal hooks may need to reapply ACLs to replaced target files.
### Restricted certificate API
Create an appropriately scoped credential in **Settings → Certificate API**. Its allowed certificate IDs and action permissions control **reload** or **deploy**; it is not a general administration token. A caller supplies both a Bearer credential and its configured `X-RustProxy-Certificate-Key` ID. Use HTTPS outside local/trusted transport and never store the clear token in the repository. See [Administration API](API.md).
## Problems to check
- **HTTP-01 validation fails:** public TCP 80, all A/AAAA DNS answers, Host forwarding and challenge path handling through any CDN/router.
- **DNS-01 fails:** authoritative DNS provider, correct zone, TSIG/API scope and propagation interval.
- **Certificate available but wrong site certificate displayed:** Vhost assignment, listener binding, hostname and restart/reload state.
- **Renewal stops:** Certbot runtime, private profile files, DNS secrets, clock skew, permissions and CA limits.
- **External file reload fails:** rotated symlink targets, certificate/key match, SAN coverage and read permissions.
- **SNI field does not auto-fill:** check the read status below the paths, grant access to the public certificate, then use **Read names from certificate**. A manually edited/cleared field is intentionally preserved until this explicit action; a certificate without DNS SAN entries supplies no names.
Keep a private encrypted application backup and a separate copy of any external certificate/key material. See [Backup and recovery](BACKUP.md) and [Security](SECURITY.md).
-876
View File
@@ -1,876 +0,0 @@
# Historical developer-level changelog archive
> Archived during the 0.25 documentation cleanup. This file preserves the pre-cleanup
> detailed release record, including source refactors, regression notes and
> packaging details. It is historical technical evidence, not the operator-facing changelog.
> Use [`../CHANGELOG.md`](../CHANGELOG.md) for user/operator-visible release changes.
# Changelog
## 0.26.7
### Backend client lifecycle and cache diagnostics
- Preserve compiled upstream clients when an active-snapshot rebuild leaves the backend origin, address policy, keepalive policy and connect timeout unchanged. This retains Hyper connection pools together with generation age, per-generation request counters and cumulative rotation totals instead of resetting those diagnostics on unrelated configuration rebuilds.
- Keep incompatible backend changes isolated: changing the origin or any client-construction input still creates a fresh upstream client and fresh generation state.
- Add a pool regression proving compatible snapshot rebuilds reuse the existing backend/client object; existing upstream-client tests continue to cover age-triggered recycling.
- Extend `/cache` with explicit HOT and COLD occupancy meters for both configured object limits and byte/disk budgets. Percentages are clamped for progress rendering while the raw counters remain visible.
### Container certificate tooling
- Build the OCI runtime image with a dedicated Certbot virtual environment containing the Cloudflare, OVH and RFC2136 DNS plugins supported by RustProxy, all pinned with the same Certbot release.
- Refresh container documentation so it no longer tells operators that those provider plugins must be added separately.
## 0.25.1
### Source modularization and release guards
- Enforce a 500-line ceiling for Rust implementation/test source files and bounded dashboard JavaScript, Docker Agent Go and operational deployment/migration modules.
- Keep Rust tests in dedicated `tests.rs` / `tests/` sources and Docker Agent tests in `_test.go`; split `config`, `docker`, Web Server, dashboard bundles, Docker Agent, deployment and NPM migration source by responsibility.
- Preserve public browser asset URLs by assembling the bounded app/vhost source fragments once inside the admin router.
- Add compiler-independent regressions for dangling Rust attributes, broken `include!` targets, missing Web Server `htaccess`/`server_protocol`/`anyhow::Context` wiring and browser split-boundary syntax.
- The package workspace still has no Rust toolchain, so `cargo fmt/check/test --locked --all-targets` remains a release-host gate.
## 0.25.0
- Add reusable Web Services with document root, index-file list and optional directory listing, managed from the new **Web Server** dashboard section.
- Add `Reverse Proxy` / `Web Server` source selection for vhosts and custom locations, including mixed static/proxy routing in one vhost and reuse of one Web Service by multiple vhosts.
- Add static `GET`/`HEAD`, streaming responses, MIME/length headers, directory index/redirect/listing behavior, HTTP validators and single-range requests (`206`/`416`).
- Enforce document-root confinement for Web Server paths, reject ambiguous/encoded traversal forms and prevent symlink escape by not following symlinks during Unix filesystem traversal.
- Reuse the existing vhost ACL/authentication/header/error/TLS/HTTP pipeline for local files without adding a separate router, error-page system or object cache.
- Add an authenticated **Docs Reader** for bundled project documentation alongside the existing API/OpenAPI view, and separate the current documentation index, release audit and open TODO material.
- Split the 0.25 Web Server implementation from the former monolithic `src/web_server.rs` into the responsibility-based `src/web_server/` module (`config`, filesystem, security, response, routing, FastCGI, dispatcher, `.htaccess` and tests), keeping `mod.rs` as the stable public facade and updating source/package gates for the new layout.
- Extend the source-layout cleanup across the Rust tree: every checked-in `.rs` file is capped at 500 lines, large `config`/`docker`/`server`/`runtime`/`balance`/`prometheus`/`proxy`/cache/admin/etc. modules are split by responsibility, unit-test bodies live in separate test files, and compiler-independent contracts reject dangling attributes/doc comments, broken `include!` targets and unbalanced split fragments.
## 0.24.10
- Keep Error File inside the existing revisioned `logging` configuration section while retaining its own Settings form; both access-log and error-file forms save atomically through `/api/config/logging`, so enabled/path/format persist without introducing a special endpoint.
- Preserve backward compatibility for historical `/api/config/logging` clients that send only the access-logging object.
- Keep the Error File panel free of the redundant ERROR/logrotate help sentence.
- Remove collapsing from Response time history and add an accessible information control explaining the cumulative mean metric.
- Update package/source/UI/OpenAPI/release identity to 0.24.10.
## 0.24.9
- Added composite Settings > Logging support for `error_log.enabled`, path and format while retaining legacy `/api/config/logging` payload compatibility.
- Removed the Error Log help sentence from the panel while keeping the existing controls and behavior unchanged.
- Harden the built-in release checker for FreeBSD/OPNsense and other Unix platforms: supplement bundled Mozilla roots with common system CA bundles/`SSL_CERT_FILE`/`CURL_CA_BUNDLE`, follow bounded non-downgrading HTTP(S) redirects, and report failed checks as normal application `ERROR` events without changing dashboard behavior.
- Add a central AES-256-GCM encrypted Secret Store with private local master-key storage and migration of existing ACME DNS-provider credentials.
- Add encrypted full backup/restore with Argon2id, authenticated encryption, explicit manifests/checksums, staging validation and rollback-safe activation.
- Add an optional ERROR-only JSON/RAW file sink with bounded asynchronous writes, central secret redaction and system `logrotate` integration.
- Extend source/release regressions and update release identity to 0.24.9.
## 0.24.8
- Expand the overview Cache RAM diagnostics with HOT usage against limit, transient RAM history for total/fill/COLD queue/COLD I/O, and COLD storage occupancy against its disk budget.
- Hide the long RSS/cache accounting explanation behind an accessible information control in the Process heading.
- Keep COLD disk occupancy explicitly separate from transient process RAM accounting.
- Update release identity to 0.24.8.
## 0.24.7
- Make the transform temporary-memory ceiling configurable from Settings and preserve a 128 MiB default for existing configurations.
- Add a configurable process-wide transform concurrency ceiling; excess work fails open unchanged rather than waiting behind transformation work.
- Add persisted RAM saver, Recommended, Performance and Uber performance transform resource presets without changing enabled transform formats or extension allow-lists.
- Show transform-resource warnings when the selected budget cannot fit the worst-case buffers for one maximum compressed body or exceeds half of detected host/cgroup RAM.
- Add live dashboard resource accounting: current transform RAM reservation/budget and active slots, HOT/transient cache RAM, non-cache process RSS, plus COLD disk occupancy and object limits.
- Include global transform representation/eligibility settings in cache compatibility identity while excluding resource-only preset/budget/concurrency controls.
- Extend SQLite/OpenAPI/offline memory/release regressions and update release identity to 0.24.7.
## 0.24.6
- Persist the selected memory and COLD cache preset identities in the SQLite-backed cache configuration and restore their selected/custom state in Settings.
- Add a Recommended COLD policy preset with bounded queue sizing and conservative demotion/GC defaults while preserving operator-selected disk path, disk budget and object limit.
- Add a 128 MiB process-wide transform temporary-memory budget, bounded body aggregation independent of claimed Content-Length, and bounded decompression growth.
- Validate/compact JSON without a full DOM tree, remove routine HTML raw-tag string allocations and avoid repeated CSS output scans.
- Cap stale-while-revalidate background work at 8 keys and proactive sleeping refresh tasks at 256; keep COLD cleanup single-flight and candidate memory proportional to the requested deletion limit.
- Bound COLD delete fallback tasks and deletion metadata, and add Rust-independent regression checks for memory/task bounds across background workers.
- Update release identity to 0.24.6.
## 0.24.5
- Normalize actually empty, length-unknown HTTP/3 request bodies before HTTP/1.1 upstream forwarding so Hyper emits a zero-length request instead of chunked framing for methods such as DELETE.
- Keep non-empty HTTP/3 bodies streaming by replaying the inspected first frame, leave declared `Content-Length` requests untouched, and do not alter HTTP/2 upstream framing.
- Add focused regression coverage for empty/non-empty H3 DELETE and generic empty H3 methods forwarded to HTTP/1.1.
- Keep the source package consistent with its existing release contracts by dropping the stale unreferenced `src/system_stats/bsd.rs` file and updating the offline container check to the already-shipped Debian 13/Trixie base images.
- Harden cache RAM admission without changing the HOT -> COLD storage model: HOT now has a 256 MiB allocation-aware default budget, fills reserve memory before buffer growth, and a small bounded allocator headroom replaces max-object-sized overshoot.
- Add a separate 64 MiB transient RAM budget plus a 32 MiB byte ceiling for the COLD writer queue; demoted bodies stay charged until persistence drops the `ColdDraft`, while persisted `ColdEntry` values retain only disk paths/metadata.
- Preserve body backing-allocation accounting across fill, revalidation and COLD promotion, expose HOT logical vs allocated bytes and the HOT/transient/COLD-queue limits, and add memory-pressure/LRU-admission counters.
- Make global cache disable authoritative over per-vhost enablement, remove the artificial 1 GiB HOT/object configuration ceiling, and hot-apply reduced HOT capacity with bounded LRU demotion/eviction.
- Add Settings memory sizing based on effective host/cgroup RAM with conservative, recommended and performance presets, a live recommended profile, host/cgroup headroom warnings, and controls for transient RAM, COLD queue RAM and bounded LRU admission. Automatic presets are disabled below 256 MiB effective RAM.
- Update release identity to 0.24.5.
## 0.24.4
- Disable Linux Transparent Huge Pages for the RustProxy process with `prctl(PR_SET_THP_DISABLE)` before the Tokio runtime is created, avoiding a host-wide THP policy change while preventing 2 MiB anonymous huge pages from amplifying allocator RSS retention.
- Treat the process-local THP operation as best-effort: unsupported/rejected kernels emit a startup warning and RustProxy continues to run.
- Update release identity to 0.24.4.
## 0.24.3
- Add a per-certificate reload operation for manual/file-backed certificate IDs and authorize it through the scoped Certificate API as well as the normal administrator session.
- Allow multiple Certificate API tokens (bounded to 32), operator comments, individual revocation and revoke-all while continuing to persist token hashes only.
- Replace the overview Core State block with a live connection/activity chart, move uptime into the Process block below process start, add RX/TX-style upload/download detail under Payload transferred, and persist per-browser hidden network-interface charts.
- Add `MALLOC_ARENA_MAX=2` to managed Linux/systemd service units to reduce glibc arena growth and long-running RSS retention while retaining the environment-file override.
- Remove an identity-body copy from the transformation pipeline, remove `Vec<char>` plus final output-copy allocations from the JavaScript/CSS minifiers, release successful-transform temporary buffers earlier, and use a throttled `malloc_trim(0)` after large Linux/glibc transforms.
- Make HTTP/2 lifecycle tuning listener-scoped like HTTP/3: keep H2/H2C and H3 enablement in **Settings -> Listeners**, expose per-listener H2 GOAWAY/lifetime limits under **HTTP connections -> HTTP/2**, retain per-listener QUIC limits under **HTTP/3**, and link listener rows to the matching tuning page. Existing top-level `http2` values remain a compatibility fallback for listeners without explicit H2 settings.
- Rename **Proxy process** to **Process** and dynamically hide unsupported process diagnostics and history charts instead of showing unknown values.
- Remove the stale, unreferenced legacy `src/system_stats/bsd.rs` parser file from the source package; shared library collectors remain the only BSD statistics implementation.
- Update release identity to 0.24.3.
## 0.24.2
- Version-only release bump from 0.24.1; no functional, configuration or protocol changes.
## 0.24.1
- Restore system network interface discovery and RX/TX counters after the shared-statistics refactor: use the cross-platform `sysinfo::Networks` API as the primary network inventory/counter source, merge `systemstat` as a library-level supplement/fallback for missing or partial inventory, preserve per-interface rate calculation, and stop silently truncating the interface list.
- Remove RustProxy's artificial hostname-count ceilings from certificate SNI bindings, local certificate generation/import validation, ACME domain validation, vhost/redirect/404 hostnames, administration allowed hosts, host ACL policies and Docker aliases. Input validity and bounded control-plane/resource limits remain enforced.
- Replace per-BSD command/parsing statistics code with shared library collectors: `systemstat` for host metrics, `sysinfo::Networks` for network inventory/counters, and `metrics-process` for process metrics, using the documented dummy process fallback on targets without a native collector.
- Preserve Linux `/proc` and cgroup-specific details as additive compatibility metrics while using the common library API for portable host/process values.
- Add portable swap, system uptime, process virtual memory/limit, descriptor limit and process start-time diagnostics where the libraries support them.
- Add authenticated runtime metadata for platform, statistics source, resolved data directory and exact SQLite database path.
- Make local CLI data-directory discovery understand both `/var/lib/rustproxy` and the BSD/OPNsense `/var/db/rustproxy` convention.
- Move Logging into **Settings -> Logging** while retaining legacy `/logging` navigation compatibility.
- Update release identity to 0.24.1.
## 0.24.0
- Replace abrupt downstream H2 idle termination with a GOAWAY-based graceful shutdown that rechecks current activity and waits for accepted streams.
- Add independent H2 idle timeout, maximum connection lifetime and drain timeout settings; lifetime may be disabled with `0`.
- Bound H2 draining so stalled streams cannot keep a retiring connection open indefinitely.
- Keep H2 Extended CONNECT/WebSocket streams under the same active-stream accounting and graceful drain path.
- Extract system statistics into platform backends while preserving Linux metric names, units and behavior; add native FreeBSD/OpenBSD/NetBSD sampling where available and explicit nulls otherwise.
- Add runtime platform to Proxy Process and Prometheus platform info.
- Add Settings UI/API/OpenAPI/language-pack support plus H2 lifecycle/configuration regressions.
- Reorganize **Settings -> HTTP connections** into HTTP/2 and HTTP/3 sub-tabs; move listener-scoped H3/QUIC enablement and limits out of the Listeners editor while preserving restart semantics and the existing listener configuration model.
- Update release identity to 0.24.0.
## 0.23.3
- Make external certificate deployment convergent: create missing manual certificate ids, rotate existing manual ids, no-op identical RustProxy-owned generations, and keep managed ACME ids protected from external ownership.
- Make the OPNsense os-acme-client hook reconcile on every run so RustProxy remains the source of truth and deleted/drifted external certificate entries self-heal.
- Add per-vhost **Enable WebSocket legacy support** for older appliance/embedded WebSocket implementations.
- Force legacy-mode WebSocket upstream handshakes to HTTP/1.1 and force the WebSocket `Host` to the configured backend authority and align `Origin` to the matching backend scheme + authority.
- Tolerate legacy `101 Switching Protocols` response framing while keeping `Sec-WebSocket-Accept` and subprotocol validation strict.
- Add UI/OpenAPI/translations, documentation and regression contracts for the compatibility mode.
- Update package/source/UI identity and release verification contracts to 0.23.3.
## 0.23.2
- Add selectable 60/120/300/600-sample history windows to the Cache and Transformation live charts, with bounded per-workspace browser persistence.
- Fix `rust-proxy --livelogs` on HTTPS administration listeners by detecting the HTTP 308 redirect and reconnecting over TLS instead of looping.
- Pin local CLI HTTPS to leaf certificates present in the private `data-dir/certs` directory and reuse the HTTP/HTTPS-aware client for config and certificate CLI API calls.
- Update package/source/UI identity and release verification contracts to 0.23.2.
## 0.23.1
- Add default-collapsed, full-width live activity charts to the Cache and Transform workspaces.
- Plot Cache HIT/MISS/BYPASS rates for the selected scope using deltas from the existing cache runtime counters.
- Plot Transform applied/cache-HIT/skipped/error rates for the selected scope using the existing transform metrics endpoint.
- Reuse the administration dashboard SVG/grid/legend visual language, keep the existing 3-second live refresh, and add English/Polish language-pack labels.
- Update package/source/UI identity and release verification contracts to 0.23.1.
## 0.23.0
- Bound inactive inbound HTTP/2 connections to five minutes so idle TLS/H2 sessions cannot accumulate indefinitely toward the process connection limit; export `rust_proxy_http2_active_connections` for direct diagnosis.
- Account hot-cache response-body backing allocation capacity rather than only visible body length, preventing `BytesMut` growth slack from bypassing the configured RAM budget and under-reporting resident cache memory.
- Add a scoped external Certificate API credential managed from a new **Settings → Certificate API** tab. RustProxy stores only the token hash plus a non-secret Key ID; the cleartext token is returned only on rotation.
- Allow the scoped Key ID + Bearer Token only on `POST /api/certificates/deploy` and `POST /api/certificates/reload`, require HTTPS for non-loopback token use, track last use, support immediate revoke, and retain normal administrator-session/CSRF access as an alternative.
- Keep production certificate operations available directly from the installed binary (`rust-proxy tls deploy` / `rust-proxy tls reload`) without requiring `dev.sh`.
- Add `rust-proxy --livelogs`: an interactive varnishlog-style all-vhost live access stream with structured filters (`host`, `vhost`, `backend`, `route`, `client`, `method`, `transport`, `cache`, status classes, path prefix, errors and slow requests), pause/resume, per-request JSON inspection and detailed error/WebSocket/H3/cache fields.
- Back live logs with a bounded non-blocking broadcast stream and authenticated SSE endpoint; slow consumers receive an explicit `gap` event with the dropped-entry count instead of silent loss.
- Split the large **Settings → Cache** form into **General**, **Resilience**, **Cold cache**, and **Static & diagnostics** sub-tabs while retaining one atomic cache-policy save.
- Add SQLite schema v5 for the scoped certificate credential, OpenAPI/security schemes, UI translations, audit events and 0.23.0 regression/release documentation.
## 0.22.3
- Add downstream HTTP/1.1 `Upgrade: h2c` on listeners that explicitly enable cleartext HTTP/2, preserving the existing prior-knowledge path and rejecting malformed/body-bearing upgrade attempts before protocol switch.
- Reconstruct the accepted H1 upgrade request as HTTP/2 stream 1 and hand subsequent framing/streams to the existing Hyper H2 server path; include raw-TCP interoperability regression source.
- Add authenticated `POST /api/certificates/reload` and `tls reload` to re-read configured public certificate/key files and hot-publish a fresh TLS/H2/H3 snapshot without changing the desired configuration revision.
- Add `tls deploy --id ... --cert ... --key ...` and authenticated `POST /api/certificates/deploy` for externally issued local certificates such as Certbot. The CLI performs bounded local reads while the running service owns pair/SAN validation, immutable generation staging, revision-CAS publication and rollback on failure, avoiding root-owned staged PEMs when Certbot hooks run as root.
- Keep all vhosts assigned to one manual certificate id synchronized during external rotation; reject RustProxy-managed ACME ids to avoid dual renewal ownership and report the administration-TLS restart caveat explicitly.
- Add Certificates-panel reload UX, OpenAPI contracts, audit events, translations and 0.22.3 audit/test documentation.
## 0.22.2
- Add bounded replay-safe backend failover retry for body-free GET/HEAD/OPTIONS requests, including retry policy controls, passive-health integration, logs and per-backend retry counters.
- Correct ordinary explicit-HTTP/2 upstream requests and add a real Hyper h2c prior-knowledge regression path.
- Add per-backend request-concurrency limits with bounded capacity waiting, `503` rejection and capacity/queue-timeout metrics. This limits full proxy exchanges, not TCP socket count.
- Add detailed upstream failure classes, last failure detail/timestamp, and backend health ejection/recovery timestamps to runtime diagnostics and Prometheus.
- Expand the backend administration table with concurrency capacity, last failure and last recovery columns.
- Parse public X.509 leaf metadata for issuer, SANs, validity, remaining days and expiry warning state, and surface ACME renewal status/last attempt/error in certificate inventory.
- Extend OpenAPI, language packs, source regressions, audit/TODO and release documentation for the expanded 0.22.2 scope.
## 0.22.1
- Vhost UX: replaced separate WebSocket/H2 RFC 8441/H3 RFC 9220 checkboxes with one transport selector; H2/H3 modes keep HTTP/1.1 Upgrade as an explicit fallback and unavailable transports are disabled from listener capabilities.
- UI polish: replaced the overview TLS listener tile with compact HTTP protocol and WebSocket protocol mini charts; Target and Cache key/tags cells remain copyable with full values.
- Keep the overview metrics in one shared stats grid so the WebSocket/H2/H3/TLS row uses exactly the same card geometry as the primary metrics, without a transport-specific dashboard layout. Make Cache **Target** and **Cache key / tags** cells copy their full untruncated value on click/keyboard activation.
- Polish the 0.22.1 administration UI: move **Active tunnels** into the WebSocket dashboard card, flatten the H1/H2/H3 tunnel breakdown to match the standard transport-card styling, and align Cache modal close actions with the vhost modal header pattern.
- Fix Rust ownership in tunnel registration by releasing the client-IP mutex guard before moving the shared exchange into the active tunnel, and remove an unnecessary mutable H2 upstream reader binding.
- Move selective cache PURGE/BAN and the active BAN list into dedicated Cache modals, keep their launch actions beside **Purge selected scope**, and use null-safe inline operation feedback consistent with login errors.
- Add opt-in RFC 8441 WebSocket Extended CONNECT for HTTP/2, with per-vhost controls beside RFC 9220, listener-scoped SETTINGS advertisement, H2-to-H1 bridging, explicit H2 upstream forwarding, cancellation/idle timeout and transport-specific metrics.
- Add opt-in cleartext HTTP/2 prior-knowledge (`h2c`) listeners. Legacy HTTP/1.1 `Upgrade: h2c` remains deliberately unsupported because the current Hyper integration cannot safely reconstruct the required HTTP/2 stream 1 bootstrap.
- Add stable active-tunnel IDs, authenticated tunnel inventory/kill API, operator-supplied close reasons, audit records, RX/TX/duration metadata and an administration modal.
- Consolidate active WebSocket/tunnel visibility on the main dashboard as total plus H1 / H2 RFC 8441 / H3 RFC 9220 counters while retaining ordinary H2/H3 request statistics separately.
- Preserve `Accept-Ranges` from origins and synthesize it only for RustProxy-generated cached range responses; transformed/compressed representations continue to remove incompatible range advertising.
- Update source/package/UI/OpenAPI/Debian/Grafana/tooling identity to 0.22.1.
## 0.22.0
- Fix a Rust ownership regression in the upstream request path: retain a cloned origin URI for proactive refresh after the request URI is assigned.
- Add per-cache-key request collapsing for foreground MISS/fill traffic with bounded wait, configurable timeout fallback, cancellation-safe leader release and collapse metrics.
- Add single `Range: bytes=...` handling from complete cached objects, including `206`, `416`, `Content-Range`, `Accept-Ranges` and `If-Range`; multipart/unsupported ranges remain origin pass-through and are not cached as separate objects.
- Extend stale-if-error with configurable transport failure classes and an explicit 5xx status allowlist while preserving the previous default behavior.
- Add bounded proactive cache refresh with pre-expiry scheduling, deterministic sampling, concurrency limits and reuse of the existing validator revalidation path.
- Add selective cache PURGE by exact URL, prefix, tag, regex or cache key, including query/Vary handling, RAM + Cold Cache invalidation and a publication barrier preventing pre-PURGE fills from republishing stale data.
- Add lazy BAN rules with lookup-time enforcement, bounded rule count/TTL, incremental garbage collection, rule deletion and metrics.
- Add authenticated administration API and Cache panel controls for PURGE/BAN, broad-operation confirmation, per-object Purge URL actions, BAN listing/removal and audit entries.
- Extend cache diagnostics, OpenAPI, translations and regressions for the 0.22 cache behavior.
- Update package/source/UI/OpenAPI/Debian/Grafana/tooling identity to 0.22.0.
- Preserve the resolved dependency graph; compiler/live acceptance remains an explicit release-host gate when Cargo/Rust are unavailable in the packaging workspace.
## 0.21.0
- Fix browser refresh on the dedicated **Transform** route by serving the dashboard document at `/transform` and covering it in the panel-route regression.
- Align the Cache **Transformed** marker with the dashboard geometry by replacing the capsule outline with the standard badge border and a 3px corner radius.
- Give the Live traffic **Transformed** series a dedicated violet chart token in dark/light themes so it remains distinct from HTTP/2 green.
- Add a dedicated Transform administration workspace with global/per-vhost runtime counters for applied/skipped/error transformations, transformed cache HITs, byte savings, reduction, transform time, kind breakdown and bounded skip reasons.
- Add a transformed-response series to **Live traffic** (fresh transformations plus transformed cache HITs) and render **Core state** uptime in compact human-readable units.
- Rename the proxy-owned backend timing diagnostic to `X-RustProxy-Backend-Time`; the legacy lookalike `X-Rust-Proxy-Backend-Time` is stripped but is no longer emitted.
- Fix Docker-vhost Passive Backend Health checkbox sizing so enabling the expanded editor cannot make fields overlap.
- Remove the unused mutable `origin` binding from the background revalidation path.
- Replace the monolithic `src/cache.rs` with the responsibility-based `src/cache/` module (`model`, lifecycle, cold storage, dashboard, tickets, lookup, policy, fill and tests) and update source/package gates for the new layout.
- Persist transformed-representation metadata through hot/Cold Cache, mark transformed objects in the Cache dashboard, and append `transformed` to standardized RFC 9211 `Cache-Status` diagnostics; origin `X-Cache-Status` lookalikes are stripped rather than emitted.
- Shard Cold Cache storage and add bounded asynchronous garbage collection plus selective manual cleanup.
- Add RFC 5861 stale windows, request `max-stale`, age-aware request `max-age=N`, and correct `must-revalidate` / `proxy-revalidate` behavior while preserving `s-maxage` priority.
- Harden transformed cache fills so transformed Content-Length is authoritative, incomplete streams cannot publish partial objects, and canonical transformed identity representations do not fragment on origin `Vary: Accept-Encoding`.
- Add the static-response transformation pipeline before cache/client compression, with strict `no-transform`, separate origin/representation validators and JS/CSS plus optional conservative HTML/SVG and parsed JSON transforms.
- Keep route cache overrides tri-state and add effective route/cache/transform diagnostics.
- Make Passive Backend Health explicit opt-in (default OFF) for ordinary and Docker vhosts, independent from active TCP/HTTP health, with per-error-class policy, separate counters and persisted Docker assignment settings.
- Extend administration UI/OpenAPI and regression coverage for Cold Cache cleanup, transform settings, passive health and cache semantics.
- Keep release/package/UI/OpenAPI/Debian/Grafana/tooling identity at 0.21.0.
- Close the 0.21.0 feature scope for release packaging. Parser-backed JS/CSS minifiers are deferred; compiler/live acceptance still has to be executed on a Rust-enabled release host.
## 0.20.0
- Rebuild **Certificates** into Installed / Renewal / Generate tabs using the same accessible keyboard tab pattern as Settings.
- Keep certificate issuance, PEM import and existing-file registration inline in the Generate tab; successful imports and edits return to Installed.
- Add explicit ACME operation state cards and include the exact private Certbot log directory in failed issuance/renewal messages.
- Expose per-managed-certificate Certbot log directories from the authenticated ACME status endpoint so persisted renewal failures remain actionable after refresh/restart.
- Rework renewal entries into readable cards with provider/environment, last check, next check, expiry and grouped actions.
- Update release/package/UI/OpenAPI/Debian/Grafana/tooling identity to 0.20.0.
## 0.19.4
- Align the Docker-vhost Aliases editor with the standard 38px Docker form controls and remove the inherited `host-chip-field` label margin that created an extra visual line below `Aliases`.
- Preserve backend traffic display semantics: idle renders as `0.0 KiB/s`, and positive rates below 0.1 KiB/s render as `<0.1 KiB/s` in both backend rows and aggregate vhost traffic.
- Add a presentation filter to **Cache -> Domains and effectiveness** with All / Cache enabled / Cache disabled choices, using each vhost's resolved cache settings while leaving aggregate statistics unchanged.
- Update release/package/UI/OpenAPI/Debian/Grafana/tooling identity to 0.19.4.
## 0.19.3
- Simplify the dashboard footer to API latency and dropped-log count, and add a fail-soft built-in release checker using embedded `UPDATE_CHECK.toml` defaults. The checker reads the official Cargo manifest immediately and then hourly, exposes a newer-version link in the footer, and never performs automatic upgrades.
- Allow runtime release-check overrides through `--update-repository-url`, `--update-version-url` and `--update-check-interval-secs` (plus matching `RUST_PROXY_*` environment variables); interval `0` disables checks.
- Fix the remaining oversized Live Logs column checkboxes by excluding checkbox/radio inputs from both broad `.log-tools input` text-field sizing blocks that were overriding the shared 15px checkbox rules.
- Add an account-scoped light/dark administration theme stored in the existing SQLite `admin_preferences` document; dark remains the default and the last authenticated choice is cached locally only to avoid login/dashboard first-paint theme flicker.
- Refactor theme-sensitive UI colors into shared CSS variables and keep checkbox/radio controls out of generic text-input sizing/density rules.
- Rework the Live Logs column picker into a compact scrollbar-free list with standard-size checkboxes; short viewports use two columns instead of an internal scroll area.
- Keep expired-session re-authentication on the exact panel and Settings sub-tab, use the minimal public login document without flashing the authenticated shell, and show embedded login errors inline instead of as global toasts.
- Canonicalize the legacy `/configuration` shortcut to `/settings?tab=manual-config` and keep later Settings tab switches in the URL so F5 restores the exact page.
- Add one shared saving-state helper for settings/security/certificate forms: disable the active submit button, show `Saving…`, restore its translated label, and prevent duplicate submissions while a save is in flight.
- Highlight and focus the actual invalid control for local validation failures in Logging JSON, named listeners, Docker-agent listener selection, TLS policy, ACL profiles, manual JSON and certificate forms.
- Preserve selected certificate/key files after a failed certificate import/save so the administrator can correct the problem without reselecting both files.
- Mark Settings tabs with a subtle unsaved-change dot, including Manual config, and keep dirty values from being overwritten by live config refreshes.
- Update release/package/UI/OpenAPI/Debian/Grafana/tooling identity to 0.19.3.
## 0.19.2
- Restore the vhost local-listener risk control for administration backends when the administration socket is startup-locked or otherwise reported only by live status; treat `localhost`, `127.0.0.0/8` and `::1` as equivalent loopback forms for the same administration port.
- Make **Discard changes** reload the persisted vhost being edited instead of replacing the editor with a new blank/default vhost.
- Add descriptive HTTP reason phrases to the default-response status selectors and headings.
- Add a temporary, continuously updated 4096-entry Live-log buffer that resets when the log window closes.
- Align inline checkbox labels and compact Docker-vhost alias controls.
- Stop backend traffic EWMA tails on an idle sampling window, show idle consistently as `0.0 KiB/s`, and render positive sub-0.1 KiB/s traffic as `<0.1 KiB/s` instead of a rounded zero.
- Make the Live Logs column picker substantially smaller with subtle 11px checkboxes; let the menu escape the log-dialog clipping boundary and open upward on short viewports.
- Default newly created vhosts to the standard HTTP `:80` listener when available; HTTPS listeners are selected explicitly.
- Left-align the Settings -> Docker agents save action to match the other settings forms.
- Suppress dashboard/login first-paint flicker and preserve the active Settings sub-tab across F5, including a re-login redirect back to `/settings?tab=...` and `/docker-vhosts`.
- Replace native browser confirmations with application-styled confirmation dialogs and add panel-area context to API error messages.
- Present login failures as an inline styled error below the form, including invalid-credential responses.
- Install a login-shell PATH profile for the managed `current` release directory on first install; updates check and warn about the profile without changing it.
- Enforce `no-store` and suppress cache diagnostics/validators for local placeholder/static/default pages.
- Allow vhost-ID renames with duplicate protection, confirmation and an explicit runtime-identity warning.
- Update source, UI, OpenAPI, regressions and package identity to 0.19.2.
## 0.19.1
- Fix Default responses section saves by passing the normal `{expected_revision, value}` payload to `/api/config/pages` and add server-side `pages` section handling/regression coverage.
- Add frontend scheme to the primary cache hash so otherwise identical HTTP and HTTPS requests cannot reuse one object.
- Retain 4096 completed access-log entries while keeping the 256-entry live window; add one-shot on-demand history loading and document the API limit.
- Render backend traffic with KiB/s as the minimum unit, simplify the Live Logs column picker, align generic checkboxes and move Docker-vhost Advanced switches beside Backend protocol on desktop; remove the redundant alias help line.
- Remove stale simulated dashboard screenshots, retain only two explicitly labelled legacy references and add a documentation index for historical/version-labelled material.
- Update source, UI, OpenAPI, regressions and package identity to 0.19.1.
## 0.19.0
- Persist per-administrator Live Logs column visibility in SQLite schema v4 without changing the revisioned proxy configuration; JSON export remains complete regardless of visible columns.
- Keep the Live Logs column selector visually consistent with the existing compact `check vh-check` controls instead of rendering oversized standalone checkbox widgets.
- Add authenticated `GET/PUT /api/preferences` endpoints for dashboard-only account preferences, protected by the existing session/CSRF control-plane guards.
- Remove the duplicate version badge from the top-right dashboard toolbar while keeping the footer release identity.
- Retain the full disabled-verification legacy X.509 fix and expanded upstream connector diagnostics introduced during the 0.18.8 follow-up.
- Update release/package identity to 0.19.0.
## 0.18.8
- Make backend `tls_verify=false` a complete certificate-authentication bypass instead of delegating TLS CertificateVerify to webpki. This restores compatibility with legacy appliance certificates such as X.509 v1 while keeping TLS encryption and all backend address/DNS/loop protections in force; `tls_verify=true` is unchanged.
- Expand upstream connection diagnostics with the full nested Hyper/rustls/io error chain, backend/TLS context, 2 KiB UTF-8-safe detail storage and warning-level journal entries for connect failures.
- Replace Docker-vhost alias comma input with hostname-style chips for add/remove/paste workflows.
- Add per-Docker-vhost HTTP-to-HTTPS redirect, WebSocket and cache controls; new compatible assignments default HTTP-to-HTTPS redirect on while legacy assignments retain their prior redirect-off behavior until explicitly saved.
- Align Docker-vhost boolean controls with the application's standard checkbox classes and keep checkbox inputs from inheriting full-width text-input sizing.
- Add a Docker-agent default ACL-profile selector. Generated Docker vhosts inherit it unless the application selects its own ACL profile; existing per-app inline ACL configuration is unchanged.
- Remove the compression-codec negotiation explainer from the Overview dashboard.
- Update release/package identity to 0.18.8.
## 0.18.7
- Collapse **Response time history** by default on the Overview dashboard while keeping live sampling active.
- Restyle per-interface RX/TX histories to use the same chart geometry and visual treatment as the aggregate RX/TX history, without nested panel backgrounds.
- Align expanded Docker-vhost routing and ACL controls with uniform field blocks; Preserve Host and per-app ACL switches now occupy the same label/control rhythm as neighboring inputs, and ACL selects no longer stretch to textarea height.
- Update release/package identity to 0.18.7.
## 0.18.6
- Add bounded per-interface RX/TX diagnostics from `/proc/net/dev` to the administration status payload and render separate live interface histories without adding per-interface Prometheus labels.
- Strengthen compression negotiation regressions with explicit `q=` preference cases for gzip, Brotli, Zstd and deflate; clarify that dashboard counters track the codec actually selected per response.
- Align Docker-agent settings inputs/actions, keep Enabled/status/remove controls on one stable row, and add spacing between listener selection and the Add Docker agent action.
- Update release/package identity to 0.18.6.
## 0.18.5
- Make compression accounting follow an explicit RustProxy-applied codec marker instead of re-reading `Content-Encoding`; add independent gzip/br/zstd/deflate counter regression coverage.
- Add an explicit HTTP/1.1 request counter and render total + HTTP/1.1 + HTTP/2 + HTTP/3 request-rate series on the Overview chart.
- Add an Open app action to published Docker vhosts, using the assigned frontend listener and expanded proxied hostname.
- Label Docker-generated pools in the Backends view with their primary proxied domain.
- Remove the global Open proxy shortcut from the page header.
- Make Compact density materially tighter than Dense across cards, controls, tables, Docker rows and charts.
- Default vhost/Docker proxy timeout controls to explicit 10/120/60/60-second connect/header/read/send limits instead of blank inherited values.
- Replace free-form Docker-agent listener-ID entry with the configured named-listener selector; move Enabled left of the centered connection-status badge.
- Update release/package identity to 0.18.5.
## 0.18.4
- Default backend health checks to TCP for newly created or otherwise defaulted ordinary and Docker vhosts; explicit existing HTTP/off health modes are preserved.
- Replace the dense Docker-vhost service editor with summary-first rows. Publish state, service identity, backend runtime health and Hide/Show stay visible; routing, ACL, timeouts and health tuning are expanded only for the selected service.
- Collapse secondary Docker-agent inventory metadata behind Agent details while keeping host, service count, last observation and base domain immediately visible.
- Rework Settings -> Security into focused account/session and host-policy cards with on-demand password and policy editors.
- Move Administration audit into a dedicated modal and stop fetching audit entries merely by opening the Security settings tab.
- Update release/package identity and EN/PL UI copy to 0.18.4.
## 0.18.3
- Add Docker-vhost search plus persistent per-browser Hide/Show Hidden view controls, and allow each Docker agent to select a configured frontend certificate inherited by its generated vhosts.
- Keep Docker inventory polling driven by each agent's configured 5..3600 second interval; the 5-second dashboard refresh reads cached observations only.
- Move redirect HTTP/2 and HTTP/3 toggles below listener selection and keep Cache `Updated` + `Live` together at the right side of the toolbar.
- Remove the unused `config::yes` helper warning.
- Add an Open-in-browser action beside each vhost Enabled state; wildcard-only hosts open the wildcard base domain, while mixed host lists prefer the first non-wildcard name.
- Restore the documented HTTP/1.1 backend default for newly created ordinary and Docker vhosts: preserve the public `Host`; existing explicit backend settings are not rewritten.
- Write HTTP/1 upstream header names in conventional title case for compatibility with legacy/custom origins that incorrectly treat header names as case-sensitive; HTTP/2 is unaffected.
- Add bounded `upstream_error` diagnostics to access logs/live logs so TCP/TLS/HTTP-client failures that previously appeared only as 503 with no upstream status can be distinguished.
- Restrict local/error response selectors to supported error statuses; application-style 404 hosts offer only bundled 401/403/404/429/503/504 templates, including a new dedicated 404 template.
- Make Docker Publish/Unpublish an immediate per-service configuration action, add per-service Apply, and replace unconditional UI rerendering with an operator-controlled Live view that reads only RustProxy's in-memory state.
- Add per-agent Docker inventory polling intervals (5..3600 seconds), while manual refresh still polls immediately.
- Add reusable ACL-profile plus inline IP/CIDR ACL controls per Docker application, and protect referenced profiles from deletion.
- Add per-vhost proxy connect/response-header/read/send timeout controls for ordinary and Docker-backed vhosts.
- Print the Docker-agent bearer token after installation alongside the certificate fingerprint.
- Improve 404 Hosts response UX with supported error-status selectors, application-style built-in status pages, cache-diagnostic suppression and protocol toggles beside listener selection.
- Add optional per-vhost JSONL access-log files under a configurable directory while retaining the combined-file mode, Logstash and Loki sinks.
- Lock HTTP/HTTPS/admin listener addresses that are supplied explicitly by systemd/CLI/environment so the control plane cannot remove, disable or rewrite process-owned binds; listeners not supplied at startup remain editable.
- Remove the separate remote-admin risk-acknowledgement requirement; plain remote administration now requires only `--allow-remote-admin`, while legacy `--accept-remote-admin-risk` is accepted as a hidden no-op so older systemd units still start.
- Tighten Compact/Dense dashboard density, rename the Shared RAM cache surface to **Cache**, expose redirect targets directly on cards, and keep 404 Hosts aligned with the application error-code set.
- Clarify that TLS Policy is inherited globally and show currently effective cipher suites alongside all available rustls suites.
- Treat ACME-issued certificates as managed/read-only in the custom-file editor, require certificates to be unassigned (including the administration panel/default binding) before deletion, and default new ACME issuance to the production CA environment.
- Make classic HTTP/1.1 Upgrade the default WebSocket path; RFC 9220 Extended CONNECT over HTTP/3 is now an explicit experimental per-vhost opt-in and is advertised only on QUIC listeners with an opted-in vhost.
- Split Docker discovery/vhost observability into dedicated `rust_proxy_docker_vhost_*` and `rust_proxy_docker_backend_*` Prometheus metric families; Docker pools no longer appear in ordinary vhost/backend series.
- Add a separate live **Docker vhosts** Prometheus collector group, enabled by default, with configured agent/assignment/domain gauges plus Docker vhost traffic/status/duration and full backend health/probe/runtime metrics.
- Extend Prometheus/OpenAPI/UI documentation and regression coverage for the Docker metric boundary.
- Extend the bundled Grafana operations dashboard with a dedicated Docker-vhost section and per-agent/service filters using only the separate Docker metric families.
## 0.18.2
- Secure Docker-agent transport by default with a persistent self-signed ECDSA certificate and SHA-256 certificate pinning in RustProxy; public-CA HTTPS remains available without a pin and plain HTTP is compatibility-only.
- Add certificate lifecycle helpers, hardened systemd state storage, installer fingerprint output and TLS regression tests for the standalone Docker agent.
- Enable per-Docker-vhost HTTP/TCP backend health checks using the same bounded backend-pool health machinery as ordinary vhosts, with live checking/up/down, latency, probe, request, active and error diagnostics in Docker vhosts.
- Rework Docker-agent and Docker-vhost UX around connection security, last observation, detected addresses, route controls and collapsible health-check tuning while retaining per-agent domains, aliases and per-service IP selection.
## 0.18.1
- Extend the Docker agent inventory with all usable host addresses (interfaces, routed address, public lookup and optional advertised address) and preserve every published binding per service.
- Add per-service Docker backend-IP selection, per-agent default backend IP, arbitrary FQDNs and multiple aliases while retaining short-name expansion below each agent's own base domain.
- Support many Docker agents with independent domains such as `docker1.lab.local` and `docker2.lab.local`, plus operator-controlled custom domains.
- Remove all special `X-Rust-Proxy-Revision` response-header behavior; the name is no longer inserted, stripped, protected or accepted as a legacy diagnostic setting.
- Keep cache-Live placement, production password recovery and Settings navigation improvements from 0.18.0.
## 0.18.0
- Add a standalone Go Docker discovery agent with a hardened systemd unit, token authentication, published TCP-port inventory and host/public-IP detection.
- Add per-agent Docker configuration, in-memory observations and dynamic Docker vhosts with operator-controlled service/subdomain assignments.
- Add Docker vhost/admin UI and OpenAPI endpoints for cached observations and explicit refresh.
- Move the cache Live toggle to the action-toolbar edge.
- Remove configurable `X-Rust-Proxy-Revision` response exposure and always strip the header.
- Use the installed `rust-proxy admin reset-password` command in production recovery guidance.
- Update EN/PL strings, documentation, release checks and package identity to 0.18.0.
## 0.17.0
- Add Debian packaging assets, a hardened systemd unit, `rustproxy` service user/state/log directories, three-day maximum-gzip log rotation, local `.deb` build tooling and signed APT-repository generation suitable for `rustproxy.linuxiarz.pl`.
- Expand Prometheus coverage across all current bounded process/OS diagnostics plus GeoIP, certificate renewal, backend health-probe detail, stream, logging and frontend inventory metrics; add explicit token-free scraping for trusted actual-peer CIDRs.
- Add a Grafana operations dashboard under `grafana/`.
- Add dedicated listener/certificate-aware local-response / 404 hosts with built-in placeholder, empty/status-only and custom-HTML modes.
- Keep the default `RustProxy is running` placeholder uncacheable and intentionally free of optional proxy/cache/backend/diagnostic/security/compression headers.
- Reuse vhost-style hostname chips for Redirect source domains.
- Update OpenAPI, EN/PL UI, release checks and package identity to 0.17.0.
## 0.16.1
- Remove internal configuration revision numbers from successful save feedback in dashboard editors/manual configuration while retaining revision-based CAS and stale-write protection internally.
- Update source, UI, OpenAPI, release tooling and packaging identity to 0.16.1.
## 0.16.0
- Decouple Custom locations from the main vhost backend fallback. Unmatched paths now use the main pool even when custom rules exist; untouched legacy synthetic root routes are normalized automatically.
- Fix same-process public administration login by allowing `rustproxy_session` across only a guarded HTTPS self-admin backend that directly targets the configured administration listener and whose public host is explicitly allowed.
- Make RFC 9220 bootstrap wait for the backend HTTP/1.1 upgrade before downstream H3 success, and extend access logging with transport/WebSocket phase/error/reset/upstream status diagnostics.
- Add cache transient-memory gauges and procfs RSS/data/swap breakdowns to the dashboard and Prometheus. Existing allocator trimming/release behavior is unchanged; no new transient-memory throttle is introduced.
- Reorganize the Vhost editor around clearer backend cards, Authentication subcards and compact protocol controls. New vhosts select standard HTTP :80 plus HTTPS :443 together when available; Redirects reuse the same listener selector; disabled vhost cache hides its subordinate controls and new vhost drafts default cache to off.
- Show the local-listener risk acceptance only when the entered backend URL directly targets the configured administration listener. Add protected opt-in `X-RustProxy-Backend` and `X-RustProxy-Backend-Time` response diagnostics for selected member identity and origin response-header latency.
- Bump source, UI, OpenAPI and packaging identity to 0.16.0.
## 0.15.1
- Fix Custom locations EN/PL translation loading by keeping all UI strings inside the language-pack `translations` map and add a regression check that rejects stray top-level translation keys.
- Make visual header rules easier to edit with numbered cards, clear field grouping, empty-state guidance and localized help.
- Add a live per-location behavior preview for matching, action, rewrite, cache, ACL and header rules, and reorganize the inline documentation with common examples.
- Add one live three-series HIT/MISS/BYPASS activity chart to the RAM cache card and CPU/RSS history charts to the Proxy process card.
- Extend offline source/release checks for the new location UX and dashboard charts; package identity is 0.15.1.
## 0.15.0
- Expand vhost Custom locations with Nginx-style exact/prefix/`^~`/regex/regex-insensitive matching, upstream path preserve/strip/replace/regex rewrites and backend-free custom responses.
- Replace the location header JSON-only workflow with a visual request/response header builder, keep Advanced JSON as an escape hatch, add in-panel documentation and reorganize the location editor into compact functional sections.
- Add `nagios/` API checks for administration response time, unavailable/checking backends, backend latency and dropped logging/cold-cache work, with reusable protected session cookies and Nagios performance data.
- Move per-vhost `Server-Timing` and verified Cloudflare client-IP diagnostics into the main frontend/details configuration.
- Add compact dashboard history charts for RAM-cache residency, network RX/TX and mean response latency, plus administration API RTT and low-priority queue/drop information in the footer.
- Add independent cold-cache age policy controls: demote by time since last access, by total hot-tier residency, by LRU pressure and on graceful shutdown.
- Add verified effective client/source IP to live logs and logging field mappings; early responses still record at least the transport peer.
- Remove the runtime legacy renewal-observation JSON importer. Stopped source upgrades remove the obsolete sidecar as the final upgrade step without importing it or retaining a second legacy copy; SQLite remains the only live renewal-observation store.
- Normalize repeated literal path separators for routing/ACL decisions, matching Nginx `merge_slashes` behavior, while preserving the original request target to the backend when no rewrite is configured. URLs such as `/static/js//events.js?v=...` are therefore accepted without weakening encoded-separator/dot-segment protections.
- Bump source, panel, OpenAPI, upgrade tooling and package identity to 0.15.0 and extend offline regression contracts for the new controls.
## 0.14.0
- Fix administration reverse-proxy Host/Origin handling: explicit DNS allowlist entries describe the external authority instead of the private admin socket port; direct IP/localhost access remains bound to the actual admin port.
- Trust forwarded administration host/scheme only from loopback or configured trusted proxy peers, use the external scheme for Origin/CSRF checks, and set/clear Secure session cookies consistently behind HTTPS termination.
- Implement RFC 9220 WebSocket over HTTP/3 Extended CONNECT, including `SETTINGS_ENABLE_CONNECT_PROTOCOL`, `:protocol=websocket` validation, HTTP/1.1 backend Upgrade translation, downstream 200 bootstrap, bidirectional H3 DATA transport and FIN/reset cancellation handling.
- Replace the separate `h3`/`h3-quinn` pair with `hpx-h3 2.5.19` and its integrated Quinn feature to expose the RFC 9220 protocol extension through the HTTP/3 stack.
- Rename dashboard backend, health, security, error-page and recommended-preset labels in English and Polish.
- Update release/static checks, HTTP/3 documentation and package identity. The source contains no `certificates.json` certificate metadata path.
## 0.13.2
- Fix authenticated dashboard refresh/back-forward session restoration so valid cookies are honored on every panel document route instead of briefly rendering the login shell.
- Make managed software updates force a bounded hot-to-cold cache handoff whenever the cold tier is enabled, independent of the ordinary `cold_on_shutdown` preference, and extend the managed systemd stop budget.
- Persist non-secret automatic-certificate-renewal observations in SQLite schema v3, preload them before administration listeners are exposed, and import the older `certs/renewals.json` sidecar once for upgrade continuity.
- Move the raw full-document editor from primary navigation into **Settings -> Manual config** while retaining `/configuration` as a compatibility browser route.
- Update OpenAPI, EN/PL help, cache/renewal/API documentation, migration/backup checks and release identity for 0.13.2.
## 0.13.1
- Make administrator browser sessions configurable from 7 to 30 days (30 days by default), replacing the previous short browser lifetime/idle behavior while preserving explicit logout/password/revoke invalidation.
- Refresh authenticated cookies only to the remaining absolute server-side expiry so session persistence cannot become an unbounded sliding session.
- Add per-backend `accept_local_listener_risk` with a dashboard confirmation and validation path for deliberate local-listener targets such as `127.0.0.1:9090`; `allow_private` and special-address protections remain independent.
- Update OpenAPI, transport/security/admin-session documentation, EN/PL strings and release regressions for 0.13.1.
## 0.13.0
- Add first-class redirect hosts with dedicated dashboard navigation, named listeners/certificates, exact/prefix and scheme matching, URL/host/path targets, 301/302/303/307/308, path/query policies and redirect cache controls.
- Resolve redirect traffic locally before proxy routes while preserving listener ownership and global host ACLs.
- Change omitted backend `tls_verify` to default false and update UI/API/documentation accordingly.
- Compact TLS certificate cards: show DNS SAN coverage as the same policy-style badges used on vhosts, collapse bindings/files/assigned-vhost details, and inspect older/manual public PEM files when persisted coverage metadata is missing.
- Extend OpenAPI, English/Polish translations, regressions and release tooling for 0.13.0.
## 0.12.1
- Add revisioned per-section configuration PUT endpoints and move ordinary dashboard forms to them, retaining full-document updates only where multiple sections must publish atomically.
- Decouple cache compatibility from the SQLite configuration revision so logging, administration, TLS inventory, metrics and other unrelated saves do not purge hot/cold objects.
- Add explicit dashboard subpage routes plus History API navigation so browser refresh preserves the selected panel.
- Add independent cold-tier policies for idle objects, LRU evictions and graceful shutdown, reduce the cold-write body queue, flush remaining hot objects before process exit, and trim released allocator memory on glibc/Linux.
- Expose certificate DNS SubjectAltName coverage separately from active SNI bindings for uploaded and ACME-managed certificates.
- Preserve secure-by-default backend TLS verification when omitted; keep the explicit debug-origin verification bypass and improve corrective validation/help text.
- Extend regression/OpenAPI/release checks and bump source/package identity to 0.12.1.
## 0.12.0
- Move raw TCP/UDP stream management from Settings to a dedicated **Streams** main-navigation page directly below Vhosts.
- Add vhost-style searchable stream cards and a modal editor with Details, Listener & upstream, and Limits tabs plus an explicit **Add TCP/UDP listener** action.
- Add fixed-cardinality runtime telemetry per active stream: connections/sessions, completed sessions, active sessions, rejects, errors, RX/TX bytes and UDP packet counters.
- Add per-stream live RX/TX rate charts in the dashboard while keeping desired stream changes visibly pending until the process restarts and the listener is actually active.
- Change named HTTP/HTTPS listener bind-address editing from a textarea to chip-style addresses matching the Vhost hostname interaction, preserving existing multi-address validation.
- Keep stream forwarding as raw Layer 4 with no HTTP vhost, ACL, cache, headers, compression or authentication envelope.
- Extend authenticated-asset and offline release/source regressions for the new Streams page and telemetry contracts; bump Cargo, panel, OpenAPI and upgrade/package identity to 0.12.0.
- Extend the Debian 13+ one-line bootstrap with `auto`/`install`/`update` modes; managed updates build the new source as an unprivileged user and delegate to the existing backup/health-check/rollback update path while preserving saved listeners and private data.
## 0.11.5
- Accept percent-encoded spaces (`%20`) in otherwise unambiguous request paths while preserving rejection of encoded separators, double-decoding hazards, controls and dot-segment ambiguity.
- Avoid the `h3-quinn 0.0.10` `stop_sending()` panic path when an HTTP/3 request body is dropped while a receive read is pending.
- Add raw layer-4 TCP/UDP stream hosts on dedicated listeners with bounded TCP connections / UDP client sessions and restart-bound configuration in the admin panel.
- Add a Debian 13+ source bootstrap that installs build dependencies and Rust, clones/builds the repository and installs the systemd service with the explicitly requested public admin profile.
- Match Varnish built-in request-method behavior for the shared cache: `GET`/`HEAD` participate in lookup while successful POST/PUT/PATCH/DELETE/OPTIONS/TRACE requests no longer trigger an implicit global purge.
- Replace the conservative per-request-header/backend-member cache identity with a Varnish-style Host + URL primary identity, isolated by RustProxy vhost/config scope, and use response `Vary` as the secondary representation selector.
- Share cached objects across load-balanced members and across GET/HEAD, reducing avoidable MISSes while retaining Cookie/Authorization pass behavior and response privacy guards.
- Preserve `X-Cache-Resident-Age` across backend 304 revalidation and hot/cold tier movement. Cold metadata format 2 stores variant identity and resident age explicitly.
- Add cache regressions for Vary variants/schema changes, Host/URL hashing, explicit ports, HEAD eligibility and resident-age continuity; update release/package/API/UI metadata to 0.11.5.
## 0.11.4
- Fix Force SSL frontend validation and messaging: require an actual non-TLS listener for the incoming redirect, prefer HTTPS port 443 when multiple TLS listeners are selected, and reject only ambiguous non-standard HTTPS target ports.
- Add a visual reusable ACL-profile rule editor with add/remove/action/CIDR rows. Keep bulk paste/import as an advanced path, normalize invisible Unicode whitespace and report malformed rules with their source line.
- When a vhost disables cache, strip and suppress RustProxy `X-Cache`, `X-Cache-Hits`, `Cache-Status`, `X-Cache-Reason` and `X-Cache-Resident-Age` diagnostics for that response.
- Render backend pools belonging to disabled vhosts as muted `Disabled` rows in the main dashboard instead of classifying them as unavailable.
- Add source/offline regression coverage and bump release/package/UI/OpenAPI/update metadata to 0.11.4. Rust compilation/runtime acceptance remains open in this packaging environment.
## 0.11.3
- Add per-vhost control over forwarding the effective client IP to the application. Disabling it removes `Forwarded`, `X-Forwarded-For` and `X-Real-IP` only on the backend request; ACLs, GeoIP, rate limiting and Tinyauth continue to use the real client identity.
- Add a compact Vhost search/filter for vhost IDs, hostnames/wildcards and backend IDs/URLs.
- Fix JSONL access logging under the hardened systemd unit by provisioning `/var/log/rustproxy` as a writable service log directory while keeping `ProtectSystem=strict`.
- Add source/offline regression coverage and bump release/package/UI/OpenAPI/update metadata to 0.11.3. Rust compilation/runtime acceptance remains open in this packaging environment.
## 0.11.2
- Fix administration HTTPS access negotiated over HTTP/2. The admin guard now derives the effective authority from H2/H3 `:authority` (via the request URI) when no legacy `Host` header is present.
- Preserve strict HTTP/1.x Host handling and reject conflicting HTTP/2 `Host` versus `:authority` values to avoid ambiguous authority validation.
- Add per-vhost Force SSL beside frontend selection. Cleartext requests receive a non-cacheable HTTP 308 to the same hostname/path/query on the vhost's unique selected TLS port; ambiguous multi-port TLS selections are rejected.
- Map NPM `ssl_forced` (and migrator `--force-https`) to RustProxy `force_ssl`, retaining HTTP+HTTPS listener bindings when available instead of importing forced hosts as TLS-only.
- Add unit/integration regression coverage for HTTP/2 administration authority, `/healthz`, Force SSL redirect construction/validation and NPM `ssl_forced`; bump release/package/UI/OpenAPI/update metadata to 0.11.2. Rust compilation/runtime acceptance remains open in this packaging environment.
## 0.11.1
- Fix release update health checks when the administration panel uses persisted HTTPS or a persisted bind different from the install-time CLI address. The check now resolves the active admin listener/scheme from `config.sqlite3` and honors the admin Host allowlist.
- Add a standard-library-only Nginx Proxy Manager SQLite migrator. It reads `proxy_host`, creates RustProxy vhosts/backends, preserves wildcard names/WebSocket/enabled/H2 intent, uses the RustProxy default certificate for imported TLS hosts unless a named certificate is selected, and keeps unsupported NPM policy/custom-config items as explicit review warnings.
- Make NPM migration dry-run by default, emit `npm-migration-plan.json`, detect hostname conflicts, split source records above RustProxy's 16-host limit and use revisioned authenticated API writes only with explicit `--apply`.
- Add offline regression coverage and bump release/package/UI/OpenAPI/update metadata to 0.11.1. Rust compilation/runtime acceptance remains open in this packaging environment.
## 0.11.0
- Add reusable ACL Profiles with optional IP/CIDR ACL and country allow/block rules. Vhost profile, inline vhost ACL and route ACL enforcement remains cumulative.
- Add an ACL Profiles navigation page and per-vhost profile selection without removing individual vhost policy controls.
- Add chip-style hostname editing and validated leading `*.` wildcard domains. Exact hosts win before the longest matching wildcard on the same listener.
- Add compact browser-session request-rate history charts to vhost cards, with progressive compaction of older samples.
- Add the built-in HTTP 401 page and classify normal WebSocket tunnel EOF/reset/abort/broken-pipe termination after a successful 101 upgrade as `websocket_closed` rather than a body/proxy error.
- Redirect plaintext requests received on the HTTPS administration socket to HTTPS while keeping application content unavailable over plaintext. Permit valid DNS Host values when the HTTPS admin hostname allowlist is empty; explicit allowlists remain strict.
- Extend named listeners to up to 16 explicit local addresses under one protocol policy. Multiple-address configurations reject unspecified `0.0.0.0`/`::` binds.
- Align the Logging field-builder dialog with the Vhost configuration modal and reposition the Configure fields action next to the format preset.
- Make source ZIP generation deterministic and verify byte-identical archives in release smoke tests.
- Bump Cargo, panel, OpenAPI, installer/update metadata and package identity to 0.11.0. Rust compilation/runtime acceptance remains open in this packaging environment.
## 0.10.0
- Add an optional persistent cold cache tier under the private data directory. Hot LRU/idle objects demote through a bounded asynchronous queue; fresh cold hits are SHA-256 checked and promoted back into the bounded RAM tier.
- Add hot/cold object, byte, promotion, demotion, cold-hit, eviction/error and queue-drop observability to the Cache API, panel and Prometheus output.
- Add cold-tier path, byte/object budgets and idle demotion settings; purge/config publication invalidates both tiers and startup rebuilds the same-revision cold index.
- Document browser hard-reload revalidation behavior and add translations for the exact `request_no_cache`, `request_max_age`, `request_min_fresh` and `request_pragma_no_cache` reasons.
- Add structured-log Standard, Nginx-style, ECS-style and Minimal presets plus a visual field picker/renamer with JSON preview; preserve advanced raw JSON mappings.
- Reuse the shared application card component for Logging sinks and remove release-specific fieldset border styling.
- Add baseline multi-stage Docker/OCI packaging with an unprivileged runtime user and persistent data volume. Docker discovery remains deferred.
- Bump Cargo, panel, OpenAPI, installer/update metadata and package identity to 0.10.0. Rust compilation/runtime acceptance remains open in this packaging environment.
## 0.9.0
- Redesign the vhost list as readable cards with listener/protocol/TLS/cache identity and live pool health, request, active, error, latency and traffic metrics.
- Add bounded asynchronous structured access-log sinks for JSONL files, Logstash TCP and Loki HTTP(S), with configurable JSON field mapping/static fields and sink delivery/drop/error counters.
- Expand cache object inspection with body bytes, resident bytes and metadata overhead; add optional `X-Cache-Resident-Age`.
- Add administration HTTPS with either the application-managed default certificate or a selected inventory certificate. Persisted non-loopback administration binds are accepted when HTTPS is enabled.
- Keep loopback HTTP as the default and retain the explicit legacy risk gate for plain remote HTTP. Secure session cookies are marked `Secure` on HTTPS administration.
- Add global TLS 1.2/1.3 and rustls cipher-suite policy plus per-vhost TCP TLS overrides. HTTP/3 vhosts must use the global TLS 1.3 policy.
- Add dedicated Logging navigation and full-width Settings tabs with new Administration and TLS policy pages.
- Complete roadmap items for remote HTTPS administration and `X-Cache-Resident-Age`; bump release identity, OpenAPI and packaging metadata to 0.9.0.
- Rust compilation/runtime acceptance remains unverified in this packaging environment.
## 0.8.1
- Enable the shared RAM cache by default.
- Replace blanket conditional-request bypass with local conditional HITs for fresh `If-None-Match` / `If-Modified-Since` requests.
- Add validator-based backend revalidation: request `no-cache`, freshness constraints and legacy `Pragma: no-cache` can revalidate a stored representation; backend 304 refreshes metadata and reuses the cached body.
- Keep request `no-store` as a hard bypass. Allow response `Cache-Control: no-cache` with ETag/Last-Modified to be stored only for mandatory validation; `no-store` and `private` still forbid shared storage.
- Retain expired validator-bearing objects for a bounded one-hour revalidation keep window without stale delivery.
- Raise the configurable object ceiling from 8 MiB to 1 GiB, still bounded by the global 1 GiB resident budget.
- Grow fill buffers incrementally and serialize very large fills through the existing fill semaphore to avoid preallocating the configured object ceiling.
- Exclude cache-control/Pragma and conditional/precondition headers from representation-key hashing because they govern reuse rather than representation identity.
- Add cache diagnostics and regressions for conditional HIT, revalidation, no-cache/no-store semantics and the 1 GiB configuration ceiling.
- Bump Cargo, panel, OpenAPI, installer/update metadata and package identity to 0.8.1.
## 0.8.0
- Global static-cache defaults now include common image, stylesheet/script, font, media and download extensions, with request Cookie ignored for matching assets by default. The global and per-vhost controls are visible in the cache UI; `Set-Cookie`, `private`/`no-store` and `Vary: Cookie` still prevent shared caching.
- HTTP/2 and HTTP/3 are per-vhost frontend opt-outs next to listener selection. Controls are enabled only when a selected named listener supports the protocol; TLS H2 ALPN is narrowed by SNI per vhost.
- Settings -> Listeners uses normal page scrolling instead of an extra nested scrollbar.
- Add per-vhost request throttling with graphical request/window/burst controls, country-specific limits and country block/allow-only policies. Return built-in HTML 429/403 responses while preserving custom error-page overrides.
- Download the PDDL `sapics/ip-location-db` user-country IPv4/IPv6 data during first installation over HTTPS and verify publisher SHA-256 checksums; expose load status without exposing client data.
- Verify the installed GeoIP database against current publisher SHA-256 files on every managed update and download only stale/missing assets before downtime.
- Make managed updates regenerate the systemd unit and restore it together with code/private data on failure; preserve and report the original failure reason and add a development-only `--reset-config` recovery path for intentionally incompatible saved vhost/settings documents.
- Add an opt-in per-vhost Cloudflare client-IP mode that accepts `CF-Connecting-IP` only when the direct peer belongs to Cloudflare's published edge CIDRs.
- Add Server-Timing response diagnostics. Disabled per-vhost H3 removes `Alt-Svc` and rejects HTTP/3 requests for that vhost.
- Move vhost listener binding exclusively to named listener selection. Listener addresses are configured under Settings; remove the pre-production inline/global listener compatibility importer and reject the old vhost format instead of migrating it.
- Make health-check mode `off` fully disable active probes and passive health classification instead of quietly learning health from production requests.
- Add a new-origin debugging preset for HTTPS-by-IP origins: default pool/private-origin/HTTP1/preserve-host enabled and backend TLS verification disabled for newly created panel backends, with explicit warnings about the certificate-verification risk and mismatched settings.
- Keep per-vhost inherit/override controls for static-cookie cache behavior while the global defaults cover common static extensions. Cookie is ignored only for the cache key/request bypass decision.
- Fix cache `Age` calculation to retain sub-second response delay and apply RFC 9111 corrected received age before adding response delay.
- Bump Cargo, panel, OpenAPI, installer/update metadata and package identity to 0.8.0.
- Rust compilation/runtime acceptance remains unverified in this packaging environment.
## 0.7.3
- Generate the one-time administrator setup token from the release CLI even before
the first server start; retain the rule that it is invalid after setup completes.
- Add explicit remote-panel opt-in for `0.0.0.0`/`::`, requiring both
`--allow-remote-admin`; keep plain-HTTP risk visible.
- Make Linux/systemd install defaults listen on HTTP 80 and HTTPS 443; add
`--no-https` for explicit HTTP-only installation.
- Add deployment and admin-host regressions and bump release/package/UI/schema
identifiers to 0.7.3.
- Hotfix backend HTTPS-by-IP with optional `tls_server_name`: TLS SNI and hostname
verification may use a DNS name while the validated TCP target stays pinned to
the configured backend URL.
- Add an explicit per-backend `tls_verify` switch, secure by default. Administrators
may turn verification off for an HTTPS origin without weakening DNS/address/self-loop
policy. Add matching 503/504 HTML gateway pages while preserving custom-page overrides.
- Rust compilation/runtime acceptance remains unverified in this packaging environment.
## 0.7.2
- Publish a distinct patch release; keep both previous 0.7.1 deliveries historical.
- Remove the build.rs panic on absent SWAGGER_UI_DOWNLOAD_URL. The asset preparer
defaults to pinned Swagger UI 5.32.11 independently of Cargo config discovery.
- Remove the independently downloading utoipa-swagger-ui adapter and embed the
prepared JS/CSS directly, for debug and release. Existing admin guards stay in place.
- Prepare assets with the existing Python 3.10+ tool requirement, now also needed
for direct Cargo builds. Support absolute local tag ZIPs, optional archive SHA-256,
bounded exact-member reads, validated download caching and retained license notices.
- Resolve build input paths from CARGO_MANIFEST_DIR, track local sources and outputs,
and report missing tools/assets explicitly instead of silently changing UI versions.
- Add executable synthetic-archive regression tests and protected license notices.
- Update release identifiers, packaging/update metadata and migration/build notes.
- Rust compilation and actual Swagger rendering remain unverified in this environment.
## 0.7.1
- Add visual request/response header editing to each custom vhost location, with
set/append/remove, validation, isolation and synchronized optional JSON.
- Split anonymous sign-in from the operational document and module/language bundles.
Remove the login tagline; preserve the original brand and compact square style.
- Reduce public health to `status` and auth discovery to `setup_required` only.
Make the public allowlist exact by method and path; private/unknown requests are
denied before body/query extraction, including method mismatches and aliases.
- Keep Origin/Host/CSRF/session guards; reject duplicate CSRF/marker headers and
hide login/setup JSON parser details. Clear UI data across tabs and on revocation.
- Add authenticated `/docs` and OpenAPI with pinned, build-embedded Swagger UI,
same-session authorization, read-only execution by default and CSRF-confirmed writes.
- Replace updater reliance on a public version with local MainPID/executable checks.
- Add source contracts, browser script regressions, actual-router Rust tests and a
read-only anonymous-access probe. Rust/build/vendor-renderer acceptance not completed.
## 0.7.0
- Renew managed ACME profiles automatically with shared worker bounds, jitter,
backoff, private observations and validated CAS hot deployment; expose opt-out,
check-now and dry-run controls. Keep previous certificates active on failure.
- Add install.sh/update.sh for Linux/systemd, root-owned release switching,
stopped full-data backups and code/data rollback on failed startup/version checks.
- Add per-vhost Basic Auth with Argon2id-only storage, optional verified upstream
credentials and cumulative graphical IP ACLs. Keep protected content out of cache.
- Add WebSocket toggles and graphical custom path locations, including explicit
exclusion of dedicated path origins from normal pool/sticky selections.
- Replace the sample welcome page with neutral information, make the protocol
fixture opt-in, unify square modal and Settings-tab styling, preserve branding.
- Add renewal/deployment helper tests and Rust/browser regressions. Rust compilation,
live CA renewal and real systemd deployment remain unverified in this environment.
## 0.6.0
- Replace separate legacy routes with vhosts and a one-way, validated compatibility importer; preserve path-pinned origins, ACLs and existing listener scope.
- Add HTTP-01 webroot domain validation, Cloudflare DNS-01 and OVH DNS-01 alongside RFC2136 using private Certbot workers.
- Unify certificate creation/import in a provider-picker modal; record optional issuer metadata in the library and keep secrets outside exported configuration.
- Add an independently configurable Prometheus endpoint with actual-peer CIDRs, optional environment-sourced bearer token, bounded scrapes and collector switches.
- Export application/admin/listener/vhost/backend/cache/system data and full-exchange vhost histograms with bounded configured labels.
- Split Settings into functional switching cards; preserve dirty values, keyboard access and translation discovery; remove Security and legacy routing sidebar entries.
- Add offline provider/browser regression tests and Rust tests for migration, ACME challenge isolation and scrape authorization. Rust compilation/runtime and actual CA/provider integrations remain unverified.
- Preserve the original brand assets. Automatic certificate renewal, Cloudflare Tunnel and Docker discovery remain deferred.
## 0.5.0
- Add a compact dedicated cache panel with vhost/domain counters, object pagination,
TTL/Age/hits, memory/fill diagnostics and authenticated selective purge.
- Fix reserved administrator-cookie ordering and empty terminating DATA frames;
emit fixed cache decision reasons without exposing request secrets.
- Replace special X-Mock-Origin hiding with the explicit global/vhost revision
switch; ordinary response rules still handle arbitrary removable headers.
- Default HTTP/3 on TLS bindings, retain explicit null opt-outs, and explain
overlapping sockets with ownership and concrete protocol/limit differences.
- Return 503 for upstream connection/unavailability, 502 for invalid responses
and 504 for header timeouts; generated errors use no-store.
- Add basic asynchronous Certbot DNS-01/RFC2136 issuance, staging-first consent,
private TSIG/account profiles, output validation and explicit library import.
No automatic renewal scheduler or real CA validation is claimed.
- Add offline helper and UI checks plus Rust regression source. Rust/Cargo remain
unavailable in the packaging environment; compilation/runtime unverified.
## 0.4.0
- Rebuild standard Forwarded and X-Forwarded metadata at an explicit trusted-proxy boundary; use verified client IP for ACL/affinity, never for destination routing.
- Add per-vhost Tinyauth subrequests, identity header allowlists, safe same-login-origin redirects and fail-closed checks before origin/static/cache/WebSocket use.
- Move certificate upload into an independent inventory; assign certificates by ID per vhost and expose metadata-only inventory reads. ACME is not implemented.
- Add named multiple-IP listeners with descriptive scope, opt-in legacy routes, reusable vhost references and actual/desired plans.
- Add Quinn/h3-based HTTP/3 UDP transport, TLS 1.3/SNI, Alt-Svc, per-endpoint connection and per-connection stream/idle limits, shared proxy policies and bounded shutdown. Preserve TCP HTTP/2 plus HTTP/1.1.
- Hide request-ID/revision headers by default; add individual overrides and optional mock-origin stripping. Apply effective Server policy across all response paths, including extra sockets.
- Partition cache by actual frontend IP/port and safely probe unknown-length empty requests; preserve nonempty body frames instead of discarding them.
- Raise toast above active dialogs without making it inert under the modal; retain the logo, compact UI and discovered language packs.
- Add a loopback-only fake-auth development peer and Rust integration sources for forwarding, auth, extra-socket cache, listener ownership and real QUIC/TCP fallback.
- Execute frontend/helper/static checks documented in TEST_REPORT.md. Rust/Cargo are unavailable: no passing compilation, real proxy run or full RFC-conformance claim.
## 0.3.0
- Split the large status JSON macro into small objects and an explicit feature map;
preserve response fields without increasing Rust's macro recursion limit.
- Add optional per-vhost certificate inventory selection and PEM file upload.
Validate key/certificate pairing and names, write private immutable files, and
activate only through the existing revisioned configuration update.
- Add per-vhost cache overrides and selective invalidation, including stale
in-flight fill cancellation while leaving other host entries intact.
- Add administrator-authored, bounded global/vhost error pages, static vhost and
missing-path responses, direct-IP and unknown-host responses. Retain ACL checks,
HEAD handling and authentication challenges; never execute HTML in the admin UI.
- Move access logs into a separate modal/docked viewer with fixed filters,
vhost identity, pause, scoped search/status/method/cache controls and JSON export.
- Add a compact vhost list, tabbed dialog, live health/latency summaries and visual
per-host response/request headers; keep the original logo and favicon.
- Add density preferences and a Settings shortcut to Security.
- Add a Python-only mock origin with random data, cache/no-store/private/cookie
paths, compression-size payloads, controllable delay/health and error statuses.
- Add Rust regression source for pages, cache scoping, TLS staging and API guards.
- Rust compilation/runtime acceptance remains unverified: toolchain unavailable.
Executed checks and their exact limitations are recorded in TEST_REPORT.md.
## 0.2.0
- Replace SizeAbove(u16) with a custom u64 predicate; add >64 KiB threshold regressions.
- Add IP/port/TLS-bound virtual hosts with frontend(s), backend pool and path policies.
- Enforce real accepted destination matching; isolate legacy routes from extra sockets.
- Add weighted round-robin, least-connections, least-traffic, adaptive and IP hashing.
- Add bounded TCP/HTTP health checks, separate passive failures, recovery and slow start.
- Add connection keepalive controls, backup members, drain and opaque cookie/IP affinity.
- Preserve compatible observations/tokens across hot edits; recheck changed/re-enabled members.
- Keep leases through full uploads/responses/tunnels; never replay failed requests.
- Add per-vhost TLS-only HSTS, nosniff, frame/referrer/CSP/permissions headers.
- Retain HTTP/1.1 ALPN with HTTP/2 for TLS frontends and HTTPS origins.
- Separate cache identities by selected member; bypass cookie-affine pools.
- Add compact vhost editor, atomic legacy migration, backend observations/API/metrics/log IDs.
- Extend discovered English/Polish translations; preserve original logo and favicon.
- Add Rust regression source and a real-binary vhost/keepalive/health acceptance suite.
- Execute 63 simulated-API UI, 24 SQLite, 21 upgrade, 4 language-mirror, 10 protocol-peer
and 7 backend-fixture checks. None is a passing Rust build or runtime test.
- Rust/Cargo remain unavailable in the packaging environment; compilation/runtime
acceptance and a resolved Cargo.lock remain mandatory before deployment.
## 0.1.0
Development source advancing directly from 0.0.6. Cloudflare is deferred.
### Added
- Persisted public listener addresses/ports, 80/443 presets, startup defaults and restart status.
- Bounded streaming shared RAM cache, TTL/Age, LRU, privacy/Vary protections, per-route bypass, optional HIT/MISS headers and audited purge.
- Streaming gzip, Brotli, Zstandard and deflate controls, final Server policy and a global client-response header editor.
- Compact dashboard with system/process/cache diagnostics, log filters and export.
- Compile-time language discovery, JSON packs, metadata/RTL/fallback, and preservation of extra packs during upgrade.
- Example unprivileged low-port service and cache/settings real-binary integration test source.
### Changed
- Allow ordinary remote-origin 80/443 while guarding local proxy/admin listener targets.
- Keep original logo/favicon; reduce panel spacing and improve data density.
- Invalidate cache and older fills on config changes, explicit purge and successful unsafe methods.
- Expand source/SQL/upgrade/language/frontend checks; actual execution scope is in docs/TEST_REPORT.md.
### Verification boundary
Rust/Cargo and real Rust integration were unavailable. This is a source candidate,
not a successfully compiled or production-certified binary. No lockfile is fabricated.
## 0.0.6
Cumulative development source release. The planned 0.0.3, 0.0.4 and 0.0.5 scopes
are included here, not represented as separately published archives.
### Fixed
- Explicitly select `src/auth/mod.rs` to prevent the reported flat/folder module
conflict when obsolete 0.0.1 source remains in an existing checkout.
- Detect mixed source layouts and preserve the known legacy auth file; refuse to
delete unrecognized local changes.
- Support interactive username input for `admin init`.
- Replace managed source directories with rollback on copy failure rather than
encouraging overlay extraction.
### Added
- DNS and HTTPS manual backends with per-backend pools, bounded DNS workers,
validated DNS answers, optional CIDR allowlists and additional PEM trust anchors.
- rustls public TLS listener, persistent development default certificate, exact SNI
bindings, local generate/import commands and explicit hot reload.
- Public HTTP/2 over TLS ALPN and HTTP/2/auto backend protocol options.
- Validated HTTP/1.1 WebSocket handshakes and bounded bidirectional tunnels,
including TLS on either side, connection leases and a separate idle timeout.
- Transport metrics, WebSocket lifetime logging, TLS editor and advanced backend
controls in the English/Polish panel.
- Backup of configured/default certificate files alongside the SQLite snapshot.
- Upgrade regression tests, transport configuration tests, real protocol test
scripts and CI steps. Test execution limits are recorded in TEST_REPORT.md.
### Changed
- Public TLS is enabled by default by `dev.sh`; direct binary startup keeps it optional.
- Listener ports are protected across HTTP, HTTPS and administration.
- All Markdown documentation is maintained in English.
- The roadmap identifies the combined transport release and upcoming Docker stages.
## 0.0.2
- Introduced RustProxy naming, administrator setup, Argon2id passwords, sessions,
CSRF checks, login throttling, password recovery and credential revocation.
- Added host/route ACLs, generic request/response header policies, administration
audit, SSE dashboard updates and security controls in the bilingual panel.
- Added an additive SQLite migration, offline validation and online database backup.
- Rust build/runtime verification was unavailable for the source package.
## 0.0.1
- Initial manual-backend HTTP/1.1 proxy, streaming and keep-alive.
- SQLite configuration, dynamic host/alias/path routing and local token control plane.
- English/Polish interface, bounded access log, counters, CLI, demo backend and dev.sh.
- Rust build/runtime verification was unavailable for the source package.
+101
View File
@@ -0,0 +1,101 @@
# Configuration and settings
Most settings are managed in the authenticated panel. Save changes after validation; use **Configuration history** to compare or restore an earlier revision. Some listeners are process-owned and require restart to change. Configuration updates are not a replacement for encrypted full backups.
> Cache compatibility: **Settings → Cache → Varnish-compatible cache policy** (`cache.varnish_compatible`) defaults to `true`, also for configurations without the field. Turning it off restores legacy client freshness handling. Implicit grace defaults to **60 seconds** (`cache.varnish_grace_seconds`, editable in Settings → Cache). See [HTTP cache](CACHE.md#varnish-compatible-cache-policy) for semantics and safety exceptions.
## Configuration storage: SQLite or JSON file
The serialized configuration has a 2 MiB limit across all sections, including Vhosts. The administration API accepts compact configuration updates and bounded JSON formatting overhead; individual Vhost and backend limits still apply.
**Settings → Manual config** shows the active storage mode and path, sourced from the read-only `storage` member of the authenticated `GET /api/config` response (`revision`, `config`, `storage`). With no `--config`, the authoritative configuration is in `<data-dir>/config.sqlite3` (normally `/var/lib/rustproxy/config.sqlite3`). With `--config`, that JSON file is authoritative, while SQLite still stores revision history, accounts and audit records. Switching modes requires a service restart; the panel does not change the startup argument.
### Optional JSON configuration source
Start the process with an **existing**, absolute JSON config filename, for example:
```bash
rust-proxy --data-dir /var/lib/rustproxy --config /etc/rustproxy/config.json serve
```
`RUST_PROXY_CONFIG=/etc/rustproxy/config.json` is an equivalent global setting. The file must be a regular file (not a symlink) within the configuration size limit and readable/writable by the service account; its parent directory must permit creation/renaming for atomic updates. The file is the canonical desired configuration. Panel and API edits, including history restore, rewrite it; SQLite retains revisions and audit. RustProxy rejects panel saves after an unimported external file edit rather than clobbering it. Use `rust-proxy reload` or `rust-proxy config reload` to import such edits after validation. No `--config` flag keeps the original SQLite-owned configuration behavior.
For a systemd deployment, use `systemctl edit rustproxy` to add `--config /etc/rustproxy/config.json` to `ExecStart` (with a reset line `ExecStart=`), or supply `RUST_PROXY_CONFIG` through the unit's private EnvironmentFile. **Use an exported, complete JSON document before enabling the flag; an absent/empty file prevents startup.** The example below is in [Installation — optional systemd JSON file](INSTALL.md#optional-systemd-json-configuration). It includes `chown`, POSIX ACLs on both file and parent directory, and `ReadWritePaths=` for hardened services: RustProxy atomically replaces the JSON and therefore needs **write plus traversal permissions on the directory**, not merely a writable file. The resulting file will normally be owned by `rustproxy` with mode `0600` after its first panel save.
Check the effective unit with `systemctl cat rustproxy`, paths/ACLs with `getfacl`, and storage mode under **Settings → Manual config**. Manual file edits require `systemctl reload rustproxy` to import changes; a successful panel save already updates the file. Listener binds/ALPN still require a restart. Back up both the JSON source **and** the SQLite database/other private state; history is not a complete backup.
## Configuration map
| Panel area | Main settings | Operational note |
|---|---|---|
| **Settings → Listeners** | Named HTTP/HTTPS frontends, addresses, HTTP/2, H2C, HTTP/3, transport limits | The selected Vhost must bind the correct listener. Binding changes may require restart. |
| **Settings → Administration** | Dedicated admin address, HTTPS/certificate, accepted hostnames/public origin | Local HTTP by default. Remote admin requires deliberate security configuration. |
| **Settings → Security** | Admin sessions, passwords, RO/RW API token creation and revocation, host policies | Administration includes a shortcut to API tokens here; token secret is displayed only once. |
| **Vhosts** | Domains, certificates, source, backends, health, load balancing, custom locations | The primary place to publish a service. |
| **Web Server** | Document roots, static delivery, rewrites, PHP-FPM, cache headers | Attach a Web Service to a Vhost as its source. |
| **Access / ACL profiles** | IP/CIDR, DNS/DDNS allow rules, country policy, HTTP methods, rate limits | Access decisions combine across applicable levels. |
| **Settings → Cache** | HOT/COLD budgets, TTL, exclusions, diagnostics, invalidation | Global and Vhost permissions must both allow caching. |
| **Settings → Compression / Transform / Headers** | Response codecs, content modification, headers and security settings | Avoid changing personalized content in a shared cache. |
| **Certificates** | PEM import, managed ACME issuance, renewal and assignment | Issuing a certificate does not automatically publish a Vhost. |
| **Docker / Streams** | Docker agent hosts and published services; TCP/UDP forwarding | Streams bypass HTTP Vhost policy. |
| **Overview / Appearance** | Dashboard cards, presets, layout, charts and theme | Account-specific display preferences do not change proxy behavior. |
| **Logs / Monitoring** | Live/stored logs, error logs, analytics, Prometheus, GeoIP updates | Logs and metrics may need dedicated limits/network permissions. |
Read the dedicated guide before enabling advanced options: [Vhosts](VHOSTS.md), [Cache](CACHE.md), [Web Server](WEB_SERVER.md), [Monitoring](MONITORING.md), [Security](SECURITY.md).
## Listener and domain setup
1. Create or review a named HTTP/HTTPS listener in **Settings → Listeners**.
2. For HTTPS, choose the certificate and protocol capabilities. HTTP/3 is optional and requires UDP access.
3. In a Vhost, set exact domains or supported leading wildcards (`*.example.com`), select listener IDs and certificate.
4. Set the source: HTTP reverse proxy or an existing Web Service. Test DNS, TLS and backend health.
5. Add HTTP → HTTPS (`308`) or www/non-www redirection if needed. Check routing before turning on caching or restrictions.
Listener bindings set by startup CLI/systemd cannot be removed, disabled or modified through saved configuration. Saved settings may therefore be valid but not active until a service restart. For H3, the `Alt-Svc` lifetime and reductions in runtime transport limits update for new requests/connections without restart; changed UDP binds, protocol toggles and H3 connection-capacity increases still need restart. See [HTTP transport](TRANSPORT.md). Administration and public frontends use different security boundaries.
## Backend protocols
`http1` uses HTTP/1.1. `auto` selects H2/H1 over HTTPS and ordinary HTTP/1.1 over plaintext. The legacy `http2` value retains compatible HTTPS negotiation and H2C for HTTP. Explicit `h2` requires H2/H2C; do not use it with an origin that only supports HTTP/1.1. Backend TLS certificate verification is **off by default** in the source model; enable it deliberately for trusted origin identity. A private origin, loopback origin or origin on a local RustProxy listener also requires an appropriate explicit exception.
## Access and header policies
ACLs support IP/CIDR and **allow-only** DNS/DDNS hostname rules, plus reusable profiles and country rules. Host, Vhost and Custom-location checks are cumulative: an allowed location does not undo a host denial. Configure forwarding identity only behind an explicit trusted proxy. Optional Basic Auth or Tinyauth is attached to Vhosts; WebSockets should be enabled only where used. See [Access](ACCESS.md).
Global response headers, per-Vhost rules and Web Service-specific headers are distinct scopes. Security and proxy-protocol headers may be protected from rewriting. Compression supports eligible response encodings; availability is still constrained by global limits and the requesting client.
### Global HTTP block / allow and security headers
Under **Settings → Rules & headers**, choose **Global block / allow** to configure rules applying to every public HTTP vhost, redirect and 404 host. Independent controls cover **country block/allow**, global **User-Agent deny patterns**, **IP/CIDR and allow-only DNS ACL**, **HTTP method allow/block**, **request Content-Type denylist** and a **shared per-client-IP rate limit**. Disabled policies are neutral. If country allow mode is selected, an unknown GeoIP country is denied; block mode permits unknown countries. Global IP and country checks are cumulative, as are global rules and more restrictive per-vhost policies. Verify the trusted client-IP and GeoIP configuration before enabling country or rate controls. The global rate limit uses the same bucket for a given client across all public vhosts. It does not affect TCP/UDP streams or the separate administration listener.
The adjacent **Security headers** subtab applies a global response baseline: **HSTS** (HTTPS only), **nosniff**, **X-Frame-Options**, **Referrer-Policy**, **Content-Security-Policy** and **Permissions-Policy**. Explicit per-vhost security-header values take precedence; absence of a vhost value does not cancel a global header. Test HSTS and CSP against all sites before enabling. **Global custom headers** remains a distinct subtab for arbitrary request/response header rules.
Global User-Agent blocking is nested at `config.global_security.user_agent_blocking`; it is no longer a separate root-level setting. Example (only the relevant fragment):
```json
{
"global_security": {
"user_agent_blocking": {"enabled": true, "patterns": ["bad-bot"]},
"country_access": {"mode": "off", "countries": []}
}
}
```
`GET /api/config/global-security` returns `{revision, section, value}` where `value` is the entire `global_security` object. To update it, send `PUT /api/config/global-security` with `{ "expected_revision": <current-revision>, "value": <complete-global_security-object> }`; the User-Agent member is **required** in the section PUT. Both Global block / allow and Security headers forms save this complete object, preserving unchanged controls from the current revision. Validation and persistence are atomic. The formerly separate `PUT /api/config/user-agent-blocking` is no longer supported.
On upgrade, older JSON, database rows, backups and revision history containing a root-level `user_agent_blocking` are migrated at validation/compilation to the nested policy. If both old and new keys are supplied with differing values, validation fails instead of choosing one. Old configurations without `global_security` retain disabled defaults for all newly added policies. See [Administration API](API.md), [Access](ACCESS.md) and [Security](SECURITY.md).
## Cache and transforms
Caching is opt-in. HOT holds bounded memory entries; COLD is an optional disk tier (off by default). Freshness follows eligible HTTP policy and `Vary`; private/no-store responses and protected requests are not shared. Configuration saves selectively invalidate only the affected cached responses. HTTP BAN/PURGE is **disabled by default** and uses an explicit source-IP allowlist. See [Cache](CACHE.md).
Response transformations are optional and apply only to configured eligible MIME types and scopes. See [Response delivery](RESPONSE_DELIVERY.md) for codec defaults, transformation limits and header precedence. Content rewriting can affect validators, compression and cache entries; test actual representations after configuration changes.
## Administration preferences
Appearance settings, Dashboard Presets and Overview cards belong to the administrator profile. **Settings → Appearance → Data refresh** keeps the automatic-refresh toggle separate from four intervals arranged in two equal columns (one on narrow screens). The saved corner style (Square, Subtle, Rounded or Extra) applies to controls and surfaces in the main panel and the authenticated Reader/OpenAPI page. **Original** keeps the legacy appearance. Up to 16 named presets can store card visibility, order, widths, density and sample count. Changing the dashboard does not restart proxy listeners or rewrite routing rules. Source sections under `config` have a revision guard to prevent accidental overwrite by a stale editor.
## Sample files and safe changes
Example JSON is in `examples/`. Treat it as a starting point: addresses, certificates, paths, backend health endpoints and service IDs must match your installation. The API uses full validated settings and rejects unknown fields; it is **not** a generic patch store. Use the current authenticated `/docs → API` OpenAPI reference to inspect field schemas.
For planned changes, export configuration, create an encrypted backup, make one logical change, save, verify real traffic and confirm metrics/logs. Revert via configuration history only when rollback of the config (not filesystem/secrets) is sufficient.
-44
View File
@@ -1,44 +0,0 @@
# Container / OCI deployment
RustProxy ships a baseline multi-stage `Dockerfile`. It compiles the locked Rust
release and runs it as the unprivileged `rustproxy` user (UID 10001). The core image does
not mount the Docker socket. Docker discovery uses the separate `docker-agent/` process on
each Docker host, so the proxy container never needs Docker-socket access. See
[Docker discovery](DOCKER_DISCOVERY.md).
Build locally with a normal OCI builder:
```bash
docker build -t rustproxy:0.29.1 .
```
Persist `/var/lib/rustproxy`; it contains the SQLite database, certificates and the
optional cold-cache tier. The default container proxy listener is `0.0.0.0:8080`.
Administration remains `127.0.0.1:9090` inside the container and is intentionally
not remotely reachable by merely publishing port 9090.
```bash
docker volume create rustproxy-data
docker run --rm -d --name rustproxy \
-p 8080:8080 \
-v rustproxy-data:/var/lib/rustproxy \
rustproxy:0.29.1
docker exec rustproxy rust-proxy admin setup-token
```
To expose administration, first enable administration HTTPS in the persisted
RustProxy configuration and choose a certificate, then bind the administration
listener to a non-loopback address. Publish the admin port only after HTTPS is
active. Plain remote HTTP keeps the legacy explicit risk gate.
The runtime image includes Python, venv/pip, OpenSSL and distribution Certbot packages
for Cloudflare, OVH and RFC2136 fallback. RustProxy prefers its own pinned Certbot
environment under `/var/lib/rustproxy/acme-python`; because `/var/lib/rustproxy` is the
persisted volume, that environment survives container replacement. If pip setup is not
possible, the image can fall back to the distribution Certbot binaries already in `PATH`.
For access-log files, prefer a writable path under `/var/lib/rustproxy` or mount a
separate writable log volume. The built-in JSON file sink is disabled by default.
Low ports such as 80/443 require an explicit container capability or host port
mapping; the image itself does not run as root.
+23 -114
View File
@@ -1,132 +1,41 @@
# Custom locations
# Custom locations and path rules
RustProxy 0.22.3 supports the common location-level controls that are typically used for application reverse proxies. The UI intentionally exposes a safe subset instead of accepting arbitrary Nginx configuration text.
Custom locations add exceptions to a Vhost's normal source. They are **not** required for the root path `/`: any request not matched by a custom rule continues to the Vhost's main backend pool or Web Service. Configure them under **Vhosts → Edit → Custom locations**.
## Routing model in 0.16.0
## Path matching
The vhost's main backend pool is an internal fallback and is **not** one of the Custom locations. Custom locations are optional overrides only. Request routing is therefore:
```text
exact custom location
-> priority/regex/prefix custom location according to the documented precedence
-> main vhost backend pool
```
An empty Custom locations list is valid. Adding `/test`, a regex, a local response or a dedicated-backend location cannot disable `/`, `/abc` or other unmatched paths. Older saved documents that contain the former untouched synthetic `root` prefix `/` entry are normalized automatically. If `/` was deliberately customized with a backend, cache/ACL/header rule, rewrite or local response, it remains a real Custom location and intentionally overrides the main fallback.
## Dashboard editor
The visual editor is the primary workflow. Every location card now shows a live **Effective behavior preview** at the bottom with the match mode/pattern, proxy or local-response action, selected backend, rewrite behavior, cache policy, ACL composition and request/response header-rule counts. The preview is calculated from the unsaved form state so an administrator can verify intent before saving.
Request and response header rules use numbered visual cards with localized **Operation / Header name / Header value** controls. `Set`, `Append` and `Remove` are supported. The Advanced JSON view remains available for automation/copy-paste, but is no longer required for normal editing. The documentation button in the location tab explains precedence, rewrites, local responses, policy composition and common examples.
## Match modes
Each location has an ID, a pattern and one match mode:
| RustProxy mode | Nginx-style equivalent | Behaviour |
| Match mode | Purpose | Priority |
|---|---|---|
| `prefix` | `location /path/` | Longest matching path prefix. A matching regex may still win. |
| `prefix_priority` | `location ^~ /path/` | Longest prefix and, when selected, skips regex locations. |
| `exact` | `location = /path` | Exact normalized path only. Exact matches have highest priority. |
| `regex` | `location ~ pattern` | Case-sensitive Rust regular expression. Regex locations are tested in configuration order. |
| `regex_insensitive` | `location ~* pattern` | Case-insensitive Rust regular expression. Regex locations are tested in configuration order. |
| `exact` | One exact normalized path | Highest |
| `prefix_priority` | Longest prefix that suppresses regex evaluation | Ahead of regex when matched |
| `regex` / `regex_insensitive` | Case-sensitive or insensitive regular expression | Tested in configured order after priority-prefix rules |
| `prefix` | Ordinary longest matching path prefix | Fallback among matching rules |
Routing and ACL checks collapse repeated literal slashes, matching Nginx's usual `merge_slashes on` behaviour. For example `/static/js//events.js` is routed as `/static/js/events.js`. Unless a rewrite is configured, the original request target is still sent to the backend.
Encoded separators (`%2f`, `%5c`), encoded `%`, dot segments and other ambiguous path forms are still rejected instead of being silently normalized.
Encoded `/` separators (`%2F` / `%2f`) are normalized to real `/` only for location matching and security policy. Other ambiguous or unsafe encoded path forms remain rejected. In `preserve` rewrite mode the original encoded request target is forwarded unchanged to the upstream, so `/a%2Fb?x=1` is matched as `/a/b` but reaches the backend as `/a%2Fb?x=1`. Rewritten modes generate a new normalized target instead. Avoid overlapping regex rules when order matters; test paths with and without trailing `/`.
## Actions
A location can proxy to a backend, serve an existing Web Service, return a local response, or redirect the client to another absolute HTTP(S) URL.
- **Proxy:** use the Vhost backend pool or target a specific backend. A location can have an explicit cache override.
- **Web Server:** reference an existing Web Service with its saved document root and PHP/static settings.
- **Local response:** return a chosen `200–599` status, content type and body without connecting to an origin. `HEAD`, `204` and `304` do not return a body.
- **Redirect:** issue `301`, `302`, `303`, `307` or `308` to a valid absolute HTTP(S) target. Query preservation is configurable. Redirect rewrite expressions may reference regex captures.
### Proxy
### Path rewriting
The location can use the vhost pool or pin traffic to one backend. A dedicated backend can be created from the location editor. Cache policy can inherit from the vhost or be explicitly enabled/disabled for the location.
`preserve` forwards the original path, `strip_prefix` removes the matched prefix, `replace_prefix` substitutes a new normalized prefix and `regex` rewrites using captures such as `$1` or `${name}`. Queries remain intact unless a redirect explicitly changes handling. Public cache keys are based on the client-facing URL, not an internal rewritten backend path.
Supported path transformations:
**Example:** send `example.com/api/*` to a dedicated application while `/` remains on the main website. Create a `/api/` prefix location, select its backend and choose whether to preserve or strip `/api/`. Inspect the editor's effective-behavior preview before saving.
- **Preserve** — send the original client path unchanged.
- **Strip prefix** — remove the matched prefix before proxying.
- **Replace prefix** — replace the matched prefix with another normalized path prefix.
- **Regex rewrite** — available for regex locations; Rust regex captures can be referenced as `$1` or `${1}` in the replacement.
## Headers, access and method rules
The query string is preserved. Rewrites change only the selected source path; reverse-proxy cache identity remains based on the public client URL.
Each location can add request/response header operations (`set`, `append`, `remove`), reusable ACL profiles, inline IP/CIDR rules, country access and HTTP-method allow/block lists. Every applicable host and Vhost policy must still pass; a location cannot remove a higher-level denial. Transport/forwarded-identity headers are protected from unsafe overrides. Authentication, rate limits, WebSocket policy and other Vhost settings continue to apply.
### Web Server
## Internal error fallback
`Web Server` selects one reusable service previously created in the **Web Server** workspace. The location stores only the Web Service ID; document root, index list and directory-listing policy remain in that shared service. Existing location matching and path rewriting are reused, but backend pinning and reverse-proxy object-cache controls do not apply to local files. See [WEB_SERVER.md](WEB_SERVER.md).
Mark a location **Internal** to make it unavailable to ordinary public URI matching. A Vhost or location may direct specific origin responses (`403`, `404`, `500`, `502`, `503`, `504`) to a named internal location ID in the same Vhost. The dispatch is internal, not a browser redirect; request context and access evaluation are preserved. Invalid targets or loops are rejected.
### Local response
If all you need is a different error **page body**, use **Vhost → Pages → Intercept application error responses**, which keeps the original status without making a second origin request.
`Return custom response` is the equivalent of a simple Nginx `return`: choose an HTTP status from 200 through 599, a Content-Type and a response body. RustProxy does not open a backend connection for this action. `HEAD`, `204` and `304` responses do not send a body.
## Not an Nginx configuration parser
### Redirect
`Redirect` is a backend-less action. Choose `301`, `302`, `303`, `307` or `308`, set an absolute `http://` or `https://` target, and optionally preserve the incoming query string. RustProxy validates the target and does not allow capture expansion inside the target scheme/authority.
For capture-based path rewriting, configure a separate **Redirect rewrite regex**. Captures can be referenced as `$1` or `${name}` in the redirect target path/query. This separation mirrors an Nginx `location` selector followed by a separate `rewrite` expression. For example, the Nginx pattern:
```nginx
location ~* \.(?:tar\.gz|zip?)$ {
rewrite ^/(.*)$ https://objectstorage.example.invalid/b/repo/o/$1 permanent;
}
```
corresponds to a case-insensitive regex Custom location `\.(?:tar\.gz|zip?)$`, action **Redirect**, status `301`, target `https://objectstorage.example.invalid/b/repo/o/$1`, and redirect rewrite regex `^/(.*)$`. Query preservation is controlled independently.
## Request and response headers
Every location has a visual header configurator and an Advanced JSON view. Both edit the same data.
Supported operations are:
- `set` — replace the header with the configured value;
- `append` — append another header value;
- `remove` — remove the header.
Example JSON:
```json
{
"request": [
{"op": "set", "name": "X-App-Route", "value": "api"}
],
"response": [
{"op": "set", "name": "Cache-Control", "value": "no-store"},
{"op": "remove", "name": "X-Legacy-Header"}
]
}
```
Hop-by-hop/framing headers, forwarding identity headers, RustProxy request IDs and WebSocket handshake headers are protected and cannot be overwritten by custom rules. This prevents a location rule from changing transport framing or spoofing the verified client identity.
## Access control
A location can define its own IPv4/IPv6 CIDR ACL, select a reusable ACL profile, add a location-specific country block/allow-only rule, and configure an HTTP method policy (`off`, `allow` or `block`). Method names are normalized to uppercase and validated as HTTP method tokens.
All access layers are additive. The request must pass the vhost method policy, location method policy, host/profile/vhost IP ACLs, location profile ACL, inline location ACL and every applicable country policy. A location therefore cannot override a denial from its vhost. Method denial returns `405 Method Not Allowed` (with `Allow` for allow-list mode); IP/country denials retain the normal `403` pipeline. These checks occur before backend selection/connection.
Authentication, vhost rate limits, security headers, compression, custom error pages and WebSocket policy still apply through the normal vhost pipeline.
## Internal locations and error fallbacks
RustProxy 0.29.1 can mark a Custom location as `internal`. An internal location is addressed by its stable location `id` and is never selected by a public URI match. The vhost main source or another Custom location can use `error_fallback` to internally dispatch configured `403/404/500/502/503/504` responses to that target. The default intercepted status is `404`.
This is an internal request handoff, not an HTTP redirect. The original method, URI/query and request context are retained. The fallback target re-enters normal access/header/cache/compression/response processing. Fallback targets must be enabled internal locations in the same vhost; validation rejects cycles and runtime dispatch is depth-limited. See [current vhost documentation](VHOSTS.md).
### Replace origin error bodies without an internal location (0.29.7)
The independent, optional **Vhost → Pages → Intercept application error responses**
setting (`pages.intercept_statuses`, for example `[404, 503]`) replaces a
backend or Web Server/PHP origin's error *body* with the configured vhost
error page, then global error page, or a built-in page. It retains the HTTP
status and sets `Cache-Control: no-store`. Unlike `error_fallback`, it does
**not** forward the request to a different backend. The existing internal
`error_fallback` action has priority and its final response remains intact.
See [Web Server – application HTTP error interception](WEB_SERVER.md#intercept-application-http-errors-0297).
## What is intentionally not an Nginx parser
Custom locations do **not** execute arbitrary Nginx directives or configuration snippets. RustProxy does not currently implement location-level `try_files`, per-location filesystem aliases/roots, embedded scripting, arbitrary `if` expressions or arbitrary module directives. Static roots are defined only by reusable Web Services. Use the explicit UI controls and the documented JSON schema instead; unknown fields are rejected on save.
The editor does not run arbitrary Nginx snippets, filesystem aliases, arbitrary `if` rules or per-location static roots. Configure static paths in a reusable Web Service, then assign that service to the location. Inspect [Vhosts](VHOSTS.md) and [Access](ACCESS.md) for the policies applied before connecting to the backend.
+91
View File
@@ -0,0 +1,91 @@
# Administration dashboard and preferences
The authenticated panel is the primary way to manage RustProxy. Pages cover **Overview**, **Vhosts**, **Web Server**, **Certificates**, **Docker vhosts**, **Streams**, **Traffic**, **Cache**, **Logs**, **Settings** and **Documentation**. Availability of individual cards depends on enabled features and the account's display preferences.
## Stylesheet updates
The panel and login page load one versioned stylesheet embedded in the Rust
binary. Source styles are split into eight modules and assembled during the
build. Rebuild the binary to apply CSS changes; copying individual CSS files to
an installed server does not update the panel. The authenticated documentation
page has its own stylesheet.
## Overview cards and layouts
The Overview contains live counters, charts and optional backend, cache, PHP, Docker and Stream cards. Under **Settings → Appearance → Dashboard layout** or the Overview editor, configure visibility, order, card width, density and the number of history samples. A hidden card stops consuming its optional browser-side visualization work but does not turn off its underlying proxy feature.
Optional quick-health tiles (since 0.35.2, hidden by default; enable them in the layout editor or **Settings → Appearance → Dashboard layout**):
| Tile | Shows |
|---|---|
| **Healthy backends** | Monitored backend members passing health checks as `up / total`, with the names of failing members. |
| **Error share** | Percentage of HTTP requests that ended with an error during the last sampling interval. |
| **Rejected by limits / s** | Requests and connections refused by `--max-connections` / `--max-inflight`; sustained values mean the limits are too low for the load. |
| **Nearest certificate expiry** | Days until the earliest installed certificate expires; the bar is full at 90 days or more. |
Enabled tiles are added at the end of the Overview, so the standard block layout does not change.
The Overview history sample count is saved in the browser immediately when the selection changes; no separate confirmation button is required.
The sidebar application-status indicator runs a lightweight self-check from the live status already used by the dashboard. Green means healthy, amber means attention is needed, and red means a problem was detected; click it to see API, listener, backend and logging details.
The unlocked Overview layout exposes a single **Save and lock** action (plus Cancel), removing the redundant lock-and-save button.
### Arranging blocks
After **Unlock layout**, every block shows a compact toolbar: the **⠿** handle, width **− / +**, and **×** to hide it (wider blocks also show **← / →**).
- **Drag:** grab the ⠿ handle (mouse, pen or touch). The block lifts and follows the pointer, and a dashed frame of the same width shows exactly where it will land; neighbouring blocks glide aside instead of jumping. Release to drop, or press **Esc** to return the block to its previous place. Dragging near the top or bottom edge of the window scrolls the page.
- **Keyboard:** focus the ⠿ handle and use the arrow keys to move the block one position.
- **Fill gaps:** a saved custom layout keeps blocks in exactly the order you arranged them, so a wide block that does not fit at the end of a row leaves a visible gap instead of other blocks silently moving into it. Use **Fill gaps** to pack blocks into empty space once; nothing is stored until **Save and lock**.
**Dashboard Presets** supports up to **16 named layouts per administrator**. Save, load, rename, overwrite and delete presets; a factory layout can be restored. Presets preserve the dashboard arrangement, not the live traffic values. Interface theme, scale and other display settings are also account-scoped and do not alter proxy routing or rewrite shared configuration.
### SVG interface icons
Under **Settings → Appearance → Interface**, enable **Show SVG interface icons** to add semantic line icons to the main navigation, action buttons, tabs and dialogs. The preference is stored per administrator as `interface_icons: true|false` in `/api/preferences` and is **off by default** for both new and upgraded accounts.
The icons are inline SVGs bundled with RustProxy; no web font, CDN or external icon library is loaded. They use `currentColor`, so the active theme, danger/primary states and disabled-state colors remain authoritative. Text labels and existing ARIA labels remain the source of accessible meaning; the SVGs are `aria-hidden`. Dynamic controls are reclassified when their state changes, so actions such as sort direction, save/loading, show/hide and refresh keep a matching icon. Turning the option off removes the injected icons and restores the original text-only presentation without restarting the panel.
Controls whose label is already a single symbol (`×`, `−`, `+`, `←`, `→`, `↑`, `↓`) keep that glyph and do not receive a second SVG. Verbs take precedence over the surrounding feature: a **Close** button in the health or history dialog shows a close icon, **Previous/Next** use chevrons, and **Unlock layout** uses an open padlock.
### Logo animation
Under **Settings → Appearance → Interface**, enable **Animate logo** to let the sidebar `r/p` logo play a short, subtle effect (rotating slash, letter flip, wave, glow, sheen, glitch or wobble) at random intervals of roughly 10–25 seconds and when the pointer moves over it. The preference is stored per administrator as `logo_animation: true|false` in `/api/preferences` and is **off by default**. Animations pause while the browser tab is hidden and are suppressed entirely when the operating system or browser requests reduced motion.
## Editing configuration safely
1. Open the relevant workspace and inspect existing state before editing.
2. Change a logically related group of settings and review the editor's effective-preview/status where available.
3. Save against the expected current configuration revision. A stale edit should be reloaded instead of silently overwriting a newer change.
4. Confirm whether the saved setting is active immediately or requires a process restart (notably listener binds and administration HTTPS).
5. Check the live page and request logs after sending test traffic.
Unfinished editors may be minimized into a draft dock; this is a UI convenience and **not** a durable change or backup until saved.
## History and restore
**Configuration history** stores retained validated snapshots. Restoring a snapshot creates a new configuration revision. It does not restore every filesystem file, external certificate, Secret Store key or accumulated cache/log history. For disaster recovery, use a full encrypted backup; see [Backup](BACKUP.md).
## Optional X/Y scales
Under **Settings → Appearance → Charts**, turn on **Show X/Y scales on charts**. It is **off by default**, stored per administrator through `chart_axes: true|false` in `/api/preferences`, and can be reset with **Reset section** or **Reset appearance**. No routing or persistent metric collection changes are required. The optional labels cover sampled SVG line charts in Overview (including backend, cache, Web Server, Stream and optional extra panels), Vhosts, Docker Vhosts, Streams, Web Server live metrics, Cache and Transform activity. Donuts, progress meters and categorical breakdowns have no X/Y sample axes.
- **Y:** `0` through the exact range used to plot the series, using suitable units such as `%`, `ms`, `B`, `B/s` or `/s`. Existing auto-ranging, fixed 0–100% scales and adaptive Vhost/Docker percentile scales are **not recalculated or replaced**. If an isolated Vhost peak is clipped, its actual maximum remains in the legend and the scale annotation identifies the clipped range. On HTTP and WebSocket protocol mini charts where each line is independently normalized, separate Y ranges for H1/H2/H3 are shown rather than an incorrect shared axis.
- **X:** numbers **1 ... N** identify samples in the displayed history, not elapsed seconds or minutes. Sampling can pause and Vhost/Docker/Stream lifetime histories can compact older readings into buckets. The X axis never pretends that sample spacing is uniform time.
- Labels are HTML overlays/footers rather than SVG text so they remain legible with `preserveAspectRatio="none"`. The original SVG path geometry, visible data, styling and refresh rates are unchanged. Switching axes off unwraps the existing SVGs; charts return to their previous appearance without restarting the panel.
If axis labels look crowded in a small card or on mobile, disable the setting; the default presentation is unchanged.
## Interpreting charts
- The **Cache hit ratio** donut shows cumulative `HIT / (HIT + MISS)`: BYPASS and requests outside cache are deliberately excluded from its denominator.
- The **Cache outcomes** compact horizontal 100% bar is **hidden by default**; enable it in **Settings → Appearance → Dashboard layout** or the Overview layout editor. It summarizes **cumulative request counters since the current service process started**, not recent polling intervals. The upper-right number shows the total HTTP requests since service start (full description in its tooltip and accessible label); the legend shows counts and percentages for HIT, MISS, BYPASS and OTHER. The percentages use all HTTP requests as the denominator. BYPASS includes requests where the cache path was considered but disabled/skipped (including globally disabled cache); OTHER covers requests without a cache decision, e.g. early rejected or redirect paths. These are request counts, not bytes; service restarts reset them. Values appear immediately with the first HTTP request; refreshing or pausing the browser does not reset totals. If independently counted cache outcomes exceed the request counter (e.g. while snapshots are inconsistent or a request incurs multiple cache events), the summary shows unavailable rather than fabricating OTHER or a normalized 100% breakdown. The separate recent cache trend chart remains unchanged.
- Live views are process/sampling-based and can show no-data when an integration has not reported yet.
- Per-backend protocol badges can distinguish configured HTTP mode from the protocol actually observed in connection traffic.
- Response-header timing excludes the full streamed body duration. HTTP server/client percentages refer to the specified layer.
- A data sampling gap, a reset after restart and a true zero are not always equivalent.
See [Monitoring](MONITORING.md) for metric details and [Configuration](CONFIGURATION.md) for persistent service settings.
-74
View File
@@ -1,74 +0,0 @@
# Factory Overview dashboard preset (0.33.3)
`src/admin/web/dashboard-defaults.json` is the single factory configuration
for the Rust admin preference API and the authenticated JavaScript panel.
The preset contains:
- `dashboard_widgets`: complete card identifier order, including hidden cards;
- `dashboard_hidden_widgets`: identifiers hidden in the factory Overview;
- `dashboard_density`: `normal`, `compact` or `ultra`;
- `overview_samples`: `60`, `120`, `300` or `600` samples for first use in a browser.
Card IDs and labels are defined in `src/admin/web/dashboard-widgets.json`.
The preset may omit newly registered card IDs: the server/client will append
those cards and apply the registry's `default_hidden` flag. The preset must
not contain unknown or duplicate IDs, or hidden IDs absent from its order.
The bundled 0.33.1 preset reproduces the supplied operator layout: 78 ordered
cards, 51 hidden, compact density and 600 initial samples. The packaged
Web Server cards remain hidden by default.
## User-selected card widths (0.33.3)
The factory preset intentionally retains the original 0.33.1 widths and
legacy grouped presentation. Version 0.33.3 adds a separate nullable preference:
- `dashboard_widget_sizes: null` (including omitted in an old record): retain
previous size groups, so upgrades do not spontaneously rearrange cards.
- `dashboard_widget_sizes: {}`: freely order all card types, while leaving
their widths at the original values.
- `dashboard_widget_sizes: { "http_protocol": 4, "wide_transform_activity": 3 }`:
also override individual widths, in units of the 12-column Overview grid.
Permitted widths: 2, 3, 4, 6 and 12 columns; regular charts need at least 3,
full diagnostic panels at least 4, and the backend table at least 6. At the
largest canvas four 3-column charts fit alongside each other. On small screens
the renderer reduces to a six- or two-column grid; the saved dimensions do not
change. In **Unlock layout**, click − / + or drag cards across their old groups;
**Arrange cards visually** provides the same controls. Save persists order,
visibility and sizes in the administrator's current preference document.
**Cancel** restores the saved layout and sizes. **Restore defaults** restores
the original factory order/visibility and width defaults, but does not change
the independently saved Overview sample limit. Dense packing reduces gaps,
so when columns do not divide evenly, a later narrow tile may fill an earlier
free space without changing the saved DOM or keyboard traversal order.
## Deploying an edited preset
1. Edit `src/admin/web/dashboard-defaults.json` in the source tree.
2. Validate the JSON and identifiers (e.g. run
`python3 scripts/tests/dashboard_layout_0331_browser_test.py`).
3. Rebuild RustProxy (`./dev.sh check` / the normal release build) and deploy
the newly built binary. The preset is **embedded**; editing the installed
source JSON alone does not update a running binary.
## Existing installation and reset behavior
- Admin accounts with stored preferences in SQLite retain their chosen
ordering, card visibility and density on upgrade. The preset never rewrites
these saved records. Accounts without saved preferences receive the new
factory defaults, even on upgraded installations.
- In Overview, **Arrange cards visually → Restore defaults** restores the
factory card order/visibility after the user confirms; it does not reset
unrelated appearance or sample-history settings.
- The history selector uses browser-local storage key
`rustproxy.overview.liveChartSamples`. An explicitly saved choice (including
60) wins over the factory sample count. A browser without a saved choice
starts with 600. The sample limit does not change polling frequency.
- While layout is unlocked, hide/show/reorder changes are temporary and stay
effective during backend/Stream status refresh. **Cancel changes** restores
the saved layout; **Save and lock** persists the current draft.
No SQLite schema migration or additional HTTP endpoint is required.
+71 -113
View File
@@ -1,150 +1,108 @@
# Docker discovery and dynamic vhosts
# Docker discovery and generated Vhosts
RustProxy 0.22.3 includes an optional standalone Docker discovery agent. The RustProxy core never opens the Docker socket. Run `docker-agent/rustproxy-docker-agent` on every Docker host that should be discovered; RustProxy polls each agent over HTTPS by default using that agent's Bearer token and certificate pin.
RustProxy discovers published Docker services through a **separate read-only Docker agent** on each Docker host. The RustProxy core does not need Docker-socket access. The agent observes Docker containers; RustProxy turns enabled assignments into dynamic, ordinary HTTP Vhosts.
## State model
## Set up a Docker host
- **Desired state** is stored in the normal RustProxy SQLite configuration: agent endpoint/token, per-agent base domain, optional default backend IP, listener IDs, optional frontend certificate and service-to-vhost assignments.
- **Observed state** stays in memory: Docker host addresses, container metadata and every non-loopback published TCP binding.
- A successful poll reconciles enabled assignments into ephemeral vhosts. Polling does not write a new configuration revision.
- If a published host port, binding or detected host address changes, the next poll rebuilds the effective route.
- If a service disappears, its dynamic vhost disappears until the service is observed again.
- Docker discovery is optional; ordinary vhosts and all other proxy functions remain independent of Docker.
Install the **optional Docker agent on each Linux Docker host**, not on the RustProxy server. It is distributed as a separate `rustproxy-docker-agent` DEB/RPM package and is **not built as FreeBSD `pkg`**. Docker Engine, systemd and access to `/var/run/docker.sock` are required at runtime.
The agent returns only running containers with Docker-published TCP ports. Bindings limited to loopback (`127.0.0.0/8` or `::1`) are excluded. Multiple bindings for the same container port are retained instead of being collapsed to one address/port pair.
### Option A: prebuilt DEB/RPM (recommended)
In the dashboard, an enabled Docker agent is **WAITING** until its first background observation arrives after RustProxy starts. This is not a connection failure. Opening **Docker vhosts** fetches the current *cached* status immediately, even if the per-page **Live view** option is off. While no enabled agent has been observed, the page briefly checks cached status every five seconds (when global automatic refresh is enabled), and stops once all agents have a first result. A genuine failed poll is reported with the returned error, and the configured TLS certificate pin is marked as verified only after a successful observation. **Refresh agents** explicitly triggers an immediate agent poll. No TLS pin, token or routing verification is bypassed.
## Install an agent on a Docker host
The agent is written in Go and uses only the standard library.
After configuring the signed APT/DNF RustProxy repository (see [Installation](INSTALL.md)):
```bash
cd docker-agent
sudo ./install.sh
# Debian / Ubuntu
sudo apt update && sudo apt install rustproxy-docker-agent
# Or on RHEL / Rocky / Alma / Fedora
sudo dnf install rustproxy-docker-agent
```
The installer runs the Go tests, builds one binary, creates the `rustproxy-docker-agent` system user, generates a random token on first install, starts `rustproxy-docker-agent.service` and prints the persistent self-signed certificate SHA-256 fingerprint.
For offline/local installation use `sudo apt install ./rustproxy-docker-agent_<version>-<release>_amd64.deb` or `sudo dnf install ./rustproxy-docker-agent-<version>-<release>.x86_64.rpm`. The post-install script generates a random token and self-signed TLS certificate on first install, creates a systemd service, and preserves credentials across upgrades. It does not print the token to unattended installation logs.
Manual build:
```bash
sudo rustproxy-docker-agent info # endpoint, TLS fingerprint, validity, token status
sudo rustproxy-docker-agent info --show-token # full secret token: run locally as root
sudo systemctl status rustproxy-docker-agent
```
### Option B: build and install manually (optional)
```bash
cd docker-agent
go test ./...
go build -trimpath -ldflags='-s -w' -o rustproxy-docker-agent .
sudo ./install.sh
```
Agent configuration is read from `/etc/rustproxy/docker-agent.env`:
Go 1.22+ is required only for the manual build, not for a DEB/RPM installation. The manual installer uses the same Go CLI to create credentials and TLS identity and displays the token interactively. Detailed custom systemd setup and migration between manual/package-managed services are in [docker-agent/README.md](../docker-agent/README.md).
```text
RUSTPROXY_DOCKER_AGENT_LISTEN=0.0.0.0:9876
RUSTPROXY_DOCKER_AGENT_TOKEN=<long-random-token>
RUSTPROXY_DOCKER_AGENT_TLS=true
RUSTPROXY_DOCKER_AGENT_CERT_FILE=/var/lib/rustproxy-docker-agent/agent.crt
RUSTPROXY_DOCKER_AGENT_KEY_FILE=/var/lib/rustproxy-docker-agent/agent.key
RUSTPROXY_DOCKER_ADVERTISE_IP=
RUSTPROXY_DOCKER_PUBLIC_IP_URL=https://api.ipify.org
### Connect to RustProxy
1. Allow port `9876` **only from the RustProxy server** in the Docker-host firewall. The default example listens on `0.0.0.0:9876`.
2. In **Settings → Docker agents**, create an agent with an endpoint such as `https://10.10.0.21:9876`, its Bearer token and certificate SHA-256 fingerprint, base domain, selected listener IDs, and optional certificate/ACL/default backend IP.
3. Use **Docker vhosts → Refresh agents** and check that discovery succeeds before publishing.
The self-signed certificate is persistent. RustProxy pins its exact fingerprint; if replaced, update the pin. A trusted CA-issued certificate can use normal CA and hostname verification instead. Plain HTTP is available only by explicit opt-out and exposes the token in transit.
To inspect or rotate credentials after installation:
```bash
sudo rustproxy-docker-agent info --json
sudo rustproxy-docker-agent print-fingerprint
sudo rustproxy-docker-agent rotate-token
sudo systemctl restart rustproxy-docker-agent
sudo rustproxy-docker-agent show-token # secret: update it in RustProxy settings
```
`RUSTPROXY_DOCKER_ADVERTISE_IP` is only a preferred address hint; it does not hide the other host addresses. The inventory includes usable addresses found on active host network interfaces, the primary routed address, the optional public-IP lookup result and the advertised address. Loopback/link-local and obvious Docker-generated bridge/veth interfaces are omitted; explicit Docker bind addresses are still preserved with each service. Each host-address entry includes its source and, where available, interface name/scope.
A token change invalidates the previous token after service restart. The agent logs certificate details and **redacted** token status at startup. The Docker socket is root-equivalent access; isolate the service account and host even though the HTTP inventory endpoint is read-only.
The inventory endpoint is `GET /v1/inventory` and requires `Authorization: Bearer <token>`. `GET /healthz` exposes no inventory. HTTPS is enabled by default. The agent creates an ECDSA P-256 self-signed certificate once and keeps it in `/var/lib/rustproxy-docker-agent/`; the key is mode `0600`.
## What discovery reports
`install.sh` prints both the generated/reused bearer token and the SHA-256 certificate fingerprint. Copy the SHA-256 fingerprint printed by `install.sh` into the agent settings in RustProxy. You can print it again with `sudo rustproxy-docker-agent --print-fingerprint`. With a fingerprint configured, RustProxy accepts only that exact certificate and still checks the TLS handshake signatures. If the agent uses a certificate issued by a normal public CA, leave the fingerprint empty to use standard CA/hostname verification. Plain HTTP is retained only for explicit compatibility setups and exposes the Bearer token to the network.
- Running containers with **Docker-published, non-loopback TCP ports**; it does not turn an internal-only container port into a directly reachable service.
- Usable Docker-host interface/IP addresses and each published host binding, allowing an explicit backend address selection when the host has multiple networks.
- Default technical hostname from the Docker container name (including a Compose `container_name` or its generated container name), without image name or port suffix. Existing publications using the previous auto-generated port-suffixed name switch to the container name during reconciliation; manually selected hostnames and aliases are preserved. The agent does not install applications, change Docker network rules or deploy TLS on their behalf.
Docker-socket access is privileged. Restrict access to the agent port to the RustProxy host even when TLS is enabled.
Agent inventory polling is separate from the panel's live status refresh. A new agent may initially show **WAITING** until the first poll, not a failure. **Refresh agents** requests an immediate agent update. Actual failures report a reason; a temporary agent outage can preserve the last successful inventory while a successful missing-service observation can lead to a generated route returning `503` until the service returns.
## Configure multiple Docker hosts
## Publish a discovered application
Open **Settings -> Docker agents** and create one entry for every Docker host. Each agent also has its own inventory **poll interval** (5..3600 seconds, 30 seconds by default); manual refresh bypasses the interval. Agents are independent and may use different domains, for example:
In **Docker vhosts**, select the service and set:
```text
docker-a -> docker1.lab.local
docker-b -> docker2.lab.local
docker-c -> apps.example.net
```
| Option | Guidance |
|---|---|
| **Hostname / aliases** | By default, the sanitized Docker container name expands under the agent base domain, without an image or port suffix. A custom short name or full FQDN overrides it, and aliases remain configurable. Containers exposing multiple published TCP ports require distinct manually assigned hostnames to publish both ports. Configure public DNS separately. |
| **Backend IP/port** | Pick a detected, reachable Docker-host address or the configured automatic option. Docker host/firewall binding must permit RustProxy to connect. |
| **HTTP / HTTPS upstream** | Match the container's actual service protocol; review origin TLS verification and Host behavior. |
| **Frontend listeners / certificate** | Selected through the agent defaults and RustProxy configuration; ensure certificate covers the generated name. |
| **Frontend protocols** | HTTP/1.1 is always available. Enable/disable HTTP/2 and HTTP/3 independently per Docker vhost (both enabled by default). H2/H3 require corresponding frontend listener support; switching them does not change the selected HTTP/HTTPS backend scheme. H3 disabled also removes its advertisement. |
| **Force HTTPS / WebSockets** | Enable only when needed and when the right frontend listeners exist. |
| **Cache / compression / transforms** | Optional; obey the global limits and privacy policy of the application. |
| **Health / ACL / timeouts** | Select up to eight reusable ACL profiles per app or inherit the agent's defaults. A searchable multi-select previews the profile ID, default action, IP rules and country filters; selected profiles appear as removable badges. Optional inline ACL remains independently enforced. Explicit denies and country blocks beat merged allows. Test allowed and blocked clients. |
For each agent configure:
To deliberately assign a domain not covered by the agent's **named frontend certificate**, enable **Accept certificate/domain mismatch** immediately below **Backend host IP** in the per-service editor and confirm the risk. This records `allow_certificate_mismatch: true` on that publication only. Without consent, the server rejects the incompatible mapping. The client browser may still reject the TLS handshake; a matching SAN certificate is strongly preferred. The agent's own outbound TLS fingerprint validation is unaffected.
- stable agent ID and display name;
- agent endpoint, normally `https://10.10.0.21:9876`;
- the Bearer token from that Docker host;
- the SHA-256 certificate fingerprint printed by the default self-signed agent installer, or an empty fingerprint only for a public-CA certificate;
- that agent's base domain;
- optional default backend IP;
- one or more RustProxy listener IDs;
- an optional configured frontend certificate to apply to every generated vhost for that agent;
- an optional default ACL profile inherited by generated vhosts unless a service selects its own profile.
Use the per-service **Publish** on/off switch to expose or remove the generated Vhost; toggling it saves immediately. Test the resulting hostname with **Open app**. Edits to an already published service use its own **Apply changes** action; switching Publish off removes the proxy publication but does not stop the Docker container. Hidden rows in the page are only a browser view preference, not a security policy.
RustProxy polls each enabled agent according to its configured interval (30 seconds by default, checked by the scheduler in bounded steps). The 5-second Live-view refresh reads only cached RustProxy memory and does not force a Docker request. **Docker vhosts** shows the latest cached observation for every configured agent. A failed agent request retains the last successful inventory when available, so a temporary control-plane outage does not withdraw working routes. A successful inventory that omits an already published service keeps its hostname and changes the generated route to 503 until discovery sees the service again.
### Live HTTP request logs
## Publish a discovered service
For each published Docker Vhost, **Live logs** appears directly beside **Open app**. It opens RustProxy's authenticated **Live access logs** dialog with this publication's generated Vhost ID already selected. Logs for every hostname alias of the same publication are shown together. The filter stays selected even when no requests have yet been recorded; you can switch back to **All vhosts** inside the dialog. The action waits until an agent status refresh has provided the server-generated Vhost ID, so it may be disabled briefly immediately after publishing.
Use the search field in **Docker vhosts** to filter by agent, service, hostname or alias. Individual service rows can be hidden from the normal view; **Show Hidden** beside Docker agents settings reveals them so they can be restored. This is a per-browser view preference and does not disable or unpublish the vhost.
These are **HTTP requests processed by RustProxy**, not the application's Docker container stdout/stderr. The viewer uses RustProxy's bounded in-memory live request history; requests appear when completed, not as a container terminal stream. Stored Docker stdout/stderr are not retrieved from the agent. The dialog is administrator-only, and no Docker log-reading endpoint is exposed.
For each discovered service you can change the mapping directly in **Docker vhosts**:
### Per-vhost traffic statistics
- **Hostname**: a short name such as `app1`, or a complete FQDN such as `portal.example.com`;
- **Aliases**: zero or more additional short names/FQDNs, edited with the same add/remove chips as normal vhost hostnames;
- **Backend host IP**: Auto/default or one concrete address detected on the Docker host;
- **Backend protocol**: HTTP or HTTPS;
- **Preserve Host**: forward the requested hostname to the container when enabled; new HTTP/1.1 assignments default to enabled, while an existing explicit disabled value is preserved;
- **Redirect HTTP to HTTPS**: per-app Force SSL switch. New assignments default on when the agent has a usable plain-HTTP + TLS listener pair; assignments saved before 0.18.8 retain their previous redirect-off behavior until explicitly saved with the new switch;
- **WebSockets**: enable or disable classic HTTP/1.1 Upgrade forwarding for this application;
- **Cache**: enable or disable normal RustProxy caching for this generated vhost;
- **Compression**: one on/off checkbox for this published vhost. When enabled, Docker vhosts use the codecs and priority configured globally under **Settings -> Response**; there are no per-Docker-vhost codec selectors;
- **Transform**: opt this generated vhost into the global static-response transform engine; it is off by default and still requires global transformation to be enabled;
- **Backend health check**: HTTP/TCP/off active-probe mode, path/Host override, interval, timeout, slow threshold, rise/fall counters and accepted HTTP status range.
- **Passive Backend Health**: independent opt-in policy, OFF by default, with separate connect/timeout/I/O/HTTP-5xx/slow-response switches and optional passive rise/fall overrides.
- **Access control**: an optional reusable ACL profile plus an optional inline IP/CIDR ACL for this application. A per-app profile overrides the agent default profile; inline rules remain cumulative with the effective profile.
- **Proxy timeouts**: optional connect, response-header, read-idle and send-idle timeouts; blank values inherit RustProxy defaults.
Every published vhost on an enabled Docker agent has a **Traffic statistics** disclosure directly below **Configure vhost**. It starts collapsed, uses the same six status/requests/active/errors/latency/traffic-per-second tiles and **Lifetime request rate** chart as an ordinary Vhost, and can be opened individually. The **Expand all statistics / Collapse all statistics** toolbar button toggles every published Docker vhost at once (including rows hidden by search or the local Hide action). The chosen disclosure state survives live list refreshes during the current panel session; it resets on sign-out.
Publish/Unpublish is applied immediately. Editing a published service uses the per-service **Apply changes** action; there is no page-wide assignment save step. The optional **Live view** switch refreshes only cached RustProxy observations/health state and does not contact the Docker agent.
Counters come from RustProxy's generated Vhost metrics, not the Docker agent or just the upstream backend: they include requests handled at the proxy frontend, even those rejected before reaching a backend. The request-rate graph is sampled in the browser from cumulative counters **since the panel session started observing this host**; its history is not stored on disk. Traffic/s is the observed delta of ingress plus egress bytes. Missing metrics show `-`. Live updates follow the administration status stream/polling settings; expanding the statistics does not prevent Docker inventory refreshes.
A short name is expanded below that agent's base domain. With base domain `docker1.lab.local`, `app1` becomes `app1.docker1.lab.local`. A full FQDN is not rewritten. Aliases use the same rule, so `app-old` becomes `app-old.docker1.lab.local`, while `legacy.example.com` remains unchanged.
The Docker request-rate chart uses the **same adaptive Y range as ordinary Vhosts**. It tracks the recent typical request rate instead of retaining the largest ever plotted value as its scale. Exceptional peaks above that range are visually capped at the top; the numeric **max/peak** label retains the actual sampled maximum. Hover it for the plotted scale and clipping indication. No agent update or configuration change is needed. See [Vhosts](VHOSTS.md) for the exact scaling policy.
This makes renaming and aliasing a Docker-backed vhost a normal RustProxy configuration change; the container/service identity remains the stable link to discovery.
## Troubleshooting
## Multiple Docker-host IPs and bindings
The agent reports all usable addresses it can see on the Docker host. This is intended for hosts that have separate public, management and LAN addresses.
RustProxy also keeps every published Docker binding. For example, one container port may be published as:
```text
10.21.0.15:18080
192.168.50.15:28080
```
Selecting `10.21.0.15` for the Docker vhost uses port `18080`; selecting `192.168.50.15` uses `28080`. For Docker wildcard bindings (`0.0.0.0` / `::`), RustProxy uses the selected host address with the wildcard binding's published port.
Backend selection order is:
1. the Docker vhost's explicit `target_ip`;
2. the agent's optional `default_target_ip`;
3. a concrete Docker bind address;
4. the agent's preferred/primary detected address when compatible with the binding;
5. another compatible detected host address.
An explicit target that is not compatible with the service's Docker bindings does not silently fall back to another IP. In 0.27.0 the published hostname remains materialized and returns `503 Service Unavailable` until the selected service/address/binding is available again. Disabling the Docker agent stops discovery but keeps already published hostnames claimed as 503 routes; re-enabling the agent resumes reconciliation. Publication is removed when the administrator explicitly unpublishes the assignment or removes the agent configuration that owns it.
The generated backend is pinned to the selected IP through an address allowlist and uses the matching Docker-published host port. Cache, Transform, Compression, WebSockets and HTTP-to-HTTPS redirect are materialized from the per-app settings. Compression has inherit/on/off plus per-codec overrides. Global Response codec switches remain the hard availability ceiling and the minimum size remains global. If the Docker agent has a frontend certificate selected, that certificate ID is copied to every generated vhost and participates in the normal TLS/SNI certificate validation and ownership rules. The effective ACL profile is the per-app profile when selected, otherwise the agent default profile. Active backend health checks default to TCP. Passive Backend Health is materialized from the Docker assignment separately and defaults to OFF, so discovery refresh/restart does not silently re-enable production-traffic quarantine. The Docker vhosts page exposes the same passive policy as ordinary vhosts. Normal 5xx/timeouts/connect/I/O/slow-response observations remain visible in counters while passive health is OFF. Active checks can be switched between HTTP/TCP/off per service without changing the passive policy.
## DNS
DNS for Docker vhost names must resolve to the RustProxy frontend/listener that should receive the HTTP(S) request. For a per-agent base domain this is normally a wildcard record, for example:
```text
*.docker1.lab.local -> <RustProxy frontend IP>
*.docker2.lab.local -> <RustProxy frontend IP>
```
Custom FQDNs and aliases likewise need DNS records pointing at the relevant RustProxy frontend. The Docker-host addresses detected by the agent are backend routing candidates; they are not automatically published to DNS.
RustProxy does not change DNS records itself.
## Prometheus metrics
Docker discovery has its own Prometheus collector switch, `prometheus.include_docker_vhosts`, enabled by default. It is intentionally separate from `include_vhosts` and `include_backends`. Dynamic Docker routes never appear under ordinary `rust_proxy_vhost_*` or `rust_proxy_backend_*` families.
The Docker collector exports bounded configured labels only: agent ID, generated Docker-vhost ID, assigned service key, configured domains/aliases and backend ID. It includes `rust_proxy_docker_vhost_*` traffic/status/duration metrics and `rust_proxy_docker_backend_*` readiness, health, active work, requests/errors/bytes, current/EWMA latency, failure/success counters, health transitions and last-probe observations. No token, agent endpoint, backend URL or arbitrary request Host value is exported.
- **WAITING:** first poll not yet observed; refresh and inspect the agent endpoint.
- **Agent error:** verify firewall, TLS fingerprint/CA, bearer token, service state, advertised host and TCP route.
- **503 generated Vhost:** verify running container, published TCP port, selected IP/bind address, backend protocol and health check.
- **Wrong application hostname:** check base-domain expansion, aliases, DNS and conflicting ordinary Vhosts.
- **Docker service not listed:** agent reports only eligible running containers with published non-loopback TCP bindings.
For agent operations and its security model see `docker-agent/README.md`. For normal routing, health and cache controls see [Vhosts](VHOSTS.md) and [Cache](CACHE.md).
-47
View File
@@ -1,47 +0,0 @@
# Error and warning logging
RustProxy routes diagnostics according to the **Error file** switch under Settings > Logging. When the file is enabled, `ERROR` and `WARN` events, including PHP-FPM stderr and Rust worker panics, go to the configured error file rather than also being printed to the systemd journal. When the file is disabled (the default), those events go to the console/stderr (journald under systemd). `INFO` and lower-severity messages continue to use the console and any independently configured access sinks are unaffected.
A graceful upstream HTTP/2 `GOAWAY(NO_ERROR)` may coincide with a Hyper `Canceled -> connection closed` error. RustProxy treats the cancel as expected H2 churn **only when** it is correlated with a recently observed graceful GOAWAY on that backend client (one-second window). Safe empty methods may retry once. An unrecovered request remains an HTTP 502 and retains its detailed access/error context; its redundant upstream diagnostic uses `TRACE`, not `DEBUG`. Uncorrelated cancellations still follow normal error processing and may influence Passive Health. The text `connection closed` alone is not evidence of a graceful GOAWAY. This is not a global error-log suppression rule.
## Configuration
The settings document contains:
```json
{
"error_log": {
"enabled": true,
"path": "/var/log/rustproxy/error.log",
"format": "JSON"
}
}
```
`format` is `JSON` or `RAW`. The feature is disabled by default. The administration Settings UI exposes the same enable/path/format controls and applies changes at runtime, without restarting RustProxy.
The sink accepts `WARN` and `ERROR` events. JSON is one object per line (including its actual `level`); RAW is one human-readable line per event.
## Runtime behavior
Events are handed to a bounded queue and written by one asynchronous worker. The normal request path does not perform file I/O. When the queue is full or closed, the event is counted as dropped by the file sink but immediately written to the console as a redacted emergency fallback; memory usage stays bounded.
Filesystem errors do not stop RustProxy. The worker writes the affected diagnostics to stderr, rate-limits sink-failure messages, and retries opening the file. If the switch is disabled while events remain queued, they are sent to stderr rather than silently discarded. Changing the path or disabling the sink closes the current file; a format change takes effect without a restart.
RustProxy installs a panic hook after initializing tracing so worker panics are recorded as `ERROR` events including thread, source location and panic payload. It intentionally does not install the default Rust hook in parallel, which would duplicate the message on stderr. It cannot capture forced termination (`SIGKILL`, `abort`) or messages written directly to stderr by external processes.
Created log files use mode `0600` on POSIX. Symlink/non-regular targets are refused.
## Secret redaction
The common logging pipeline redacts sensitive keys and values before they reach the error-file sink. This includes authorization credentials, provider tokens/passwords, backup passwords, private-key PEM material and Secret Store/master-key related values. Structured secret fields are replaced with `[REDACTED]`.
Error messages from dependencies should still be treated as untrusted diagnostic input; do not deliberately embed secrets in error text.
## Rotation
RustProxy does not implement file rollover, retention or compression. Use the system `logrotate`. Debian packaging is maintained outside this source tree and ready packages are published at `https://rustproxy.linuxiarz.pl`; package-specific logrotate policy belongs to that packaging repository.
## Stored-log explorer
The authenticated `/logs` workspace can read the configured error file alongside structured access/protocol files. It remains independent from Live Logs and can inspect an existing error file even while `error_log.enabled` is false. The browser API does not accept arbitrary paths; it resolves only the configured error-log path and applies the same bounded, read-only regular-file/symlink protections documented in [FILE_LOGS](FILE_LOGS.md).
+109
View File
@@ -0,0 +1,109 @@
# Feature list
RustProxy is a single service that combines a reverse proxy, a static/PHP web server, certificate management, an HTTP cache and traffic monitoring, all managed from a browser panel or an API. Optional features are disabled by default; enable only what you need.
![Overview dashboard](images/overview.png)
## Reverse proxy
| Feature | Details |
|---|---|
| Vhosts | Exact and wildcard hostnames, aliases, several listeners per Vhost, per-Vhost HTTP/2 and HTTP/3 switches |
| Backends | HTTP and HTTPS origins, HTTP/1.1, automatic ALPN (H2/H1), strict HTTP/2 and H2C, optional upstream certificate verification with custom CA and server name |
| Load balancing | Least connections (default), round robin, least traffic, adaptive and IP hash; member weights, backup and draining members, sticky cookie or IP affinity |
| Health checks | Active TCP or HTTP probes with thresholds, optional separate probe port, expected response text and live health status per member |
| Connection reuse | Bounded keep-alive pools with idle and age limits, pool recycling and protocol telemetry |
| Custom locations | Prefix, exact and regex paths, path rewriting, per-location backends, Web Services, local responses, redirects and internal error fallbacks |
| WebSockets | HTTP/1.1 Upgrade, RFC 8441 (HTTP/2) and RFC 9220 (HTTP/3) Extended CONNECT |
| Forwarding headers | `X-Forwarded-*`, `Forwarded`, `X-Real-IP`, trusted proxy ranges, Cloudflare client IP and country |
| Redirects and pages | Redirect hosts, canonical host and HTTPS redirects, 404 hosts, custom error pages and direct-IP pages |
| TCP/UDP Streams | Layer-4 forwarding with backend pools, health checks, session limits and idle timeouts |
## Frontends and TLS
- HTTP/1.1, HTTPS with HTTP/2, optional HTTP/3 (QUIC) with `Alt-Svc` management and configurable UDP buffers.
- Named listeners with per-listener protocols; Vhosts bind to listener IDs.
- TLS 1.2/1.3 policy with cipher-suite allowlist and per-Vhost overrides.
- Optional hybrid post-quantum key exchange (X25519MLKEM768).
- SNI certificate selection, hot certificate reload without restart.
## Web Server
- Static sites and downloads served from reusable **Web Services** shared by several Vhosts.
- Index files, optional directory listing, MIME mapping and attachment extensions.
- `try_files`, regex rewrites and a safe subset of Apache `.htaccess` rules.
- Conditional requests (`ETag`, `Last-Modified`), byte ranges, precompressed `.br`/`.gz` sidecars and optional Linux `sendfile`.
- Symlink policy, attack-probe blocking and header hardening presets.
- PHP through external PHP-FPM with shared profiles, per-application pools and resource limits.
- Presets for single-page apps, WordPress and Roundcube.
![Web Server editor](images/web-server.png)
## Certificates
- Self-signed certificates, PEM import and externally managed certificate paths (for example Certbot).
- Managed ACME with HTTP-01 and DNS-01 (Cloudflare, OVH, RFC 2136), automatic renewal and job history.
- Certificate inventory with expiry, coverage check and bulk assignment to Vhosts.
- Restricted certificate API keys for deploy/reload from external systems such as OPNsense.
- Prometheus metrics for the expiry date and remaining days of every installed certificate, ready for alerting.
![Certificates](images/certificates.png)
## Access and security
- IP/CIDR and DNS/DDNS allow/deny rules, reusable ACL profiles (up to eight per Vhost).
- Country allow/deny lists with automatic GeoIP database updates.
- Request rate limiting per Vhost and globally, with burst and `Retry-After`.
- HTTP method policy, request Content-Type denylist and User-Agent blocking.
- Security headers: HSTS, CSP, X-Frame-Options, Referrer-Policy, Permissions-Policy, `nosniff`.
- HTTP Basic authentication with Argon2 hashes and brute-force limits.
- Forward Auth with Tinyauth, Authelia and authentik; OIDC (Authorization Code + PKCE) with Authelia, authentik, Pocket ID and compatible providers.
- Fail-closed authentication errors, identity header stripping and private/no-store responses for authenticated content.
- Protection against private/loopback/control-plane upstream destinations unless explicitly allowed.
## Cache and delivery
- Shared HTTP cache with a memory tier (HOT) and an optional disk tier (COLD).
- RFC 9111 freshness, validators and `Vary`; stale-while-revalidate, stale-if-error and request coalescing.
- Varnish-compatible policy option, forced caching rules and per-location cache switches.
- RFC 9211 `Cache-Status` and optional `X-Cache` diagnostics.
- Purge and ban from the panel, the API or HTTP PURGE/BAN from allowlisted addresses.
- Live compression with gzip, Brotli, Zstandard and deflate.
- Optional minification of JavaScript, CSS, HTML, SVG and JSON with memory budgets.
![Cache](images/cache.png)
## Monitoring and logs
- Overview dashboard with requests per second, bandwidth, status codes, error share, cache hit ratio, protocol mix, system load, backend health, requests rejected by limits and nearest certificate expiry.
- Optional **Memory breakdown** card: process RSS split into cache data, cache bookkeeping, cache buffers, allocator-held free memory, mapped files and the rest of the process, with glibc heap statistics and an "outside cache" trend for spotting leaks.
- Customizable dashboard layouts (drag-and-drop with a live drop placeholder, keyboard moves, per-block width, explicit gap filling) and saved presets.
- Per-Vhost, per-backend, per-Web-Service and per-Stream statistics.
- Live request log in the browser and in the terminal (`--livelogs`).
- Stored JSON access logs with search syntax, per-Vhost log files and an error log.
- Prometheus exporter with token/CIDR protection, JSON metrics and a compact summary endpoint.
- Lightweight revision-aware polling views for Cache, Traffic and Transform keep the administration panel responsive without repeatedly transferring static configuration.
- Grafana dashboard and Nagios checks included.
![Live logs](images/logs.png)
## Operations
- Configuration stored in SQLite or in a JSON file, with validation before activation and hot reload.
- Revision history with diff and one-click restore.
- Encrypted full backups and validated offline restore; encrypted Secret Store for credentials.
- Docker discovery through a separate agent: publish containers as Vhosts with one switch.
- Nginx Proxy Manager import with a dry-run plan.
- Managed install/update scripts with rollback, DEB/RPM packages, Docker image and an OPNsense plugin.
- Panel in English and Polish, light and dark themes, adjustable density and corner style.
- Optional account-scoped semantic SVG icons for navigation, actions, tabs and dialogs; off by default.
## Administration and API
- Panel served on a separate, loopback-only listener by default; optional HTTPS for remote administration.
- Single administrator with Argon2 password, session revocation and CSRF/Origin protection.
- Complete REST API described by a bundled OpenAPI schema with Swagger UI at `/docs`.
- Scoped `ro`/`rw` API tokens bound to IP allowlists.
- Command-line tools for setup, configuration, certificates, backups and live logs.
See the [screenshots](SCREENSHOTS.md) for a visual tour and the [documentation index](README.md) for detailed guides.
-109
View File
@@ -1,109 +0,0 @@
# Stored log explorer
RustProxy provides an authenticated, read-only **Logs** workspace at `/logs`. The top bar has one **Logs** menu with separate **Stored logs** and **Live Logs** actions; the two data paths remain independent.
## Separation from Live Logs
Live Logs uses the bounded in-memory request history and Tokio broadcast channel. It remains available even when file logging is disabled and is optimized for current routed request activity.
Stored Logs reads only paths derived from the active RustProxy logging configuration: the combined structured access log or per-vhost JSONL directory plus the optional error log. Existing files remain browsable after their writer is disabled. Low-level `protocol_error` records remain persistence-only and do not enter Live Logs.
## Search syntax
The dashboard uses one search field and a page-size selector. Search is case-insensitive and supports field clauses, negation, quoted values, wildcards and numeric comparisons.
Examples:
```text
req: GET AND !country: Poland
status:>=500 AND path:/api/*
error:"connection reset"
client_ip:192.0.2.* AND status:5xx
```
Rules:
- whitespace and explicit `AND` join clauses;
- `!field:value` excludes matching records;
- quotes keep spaces in one value;
- `*` acts as a wildcard;
- numeric fields accept `>`, `>=`, `<` and `<=`;
- `status:5xx` matches a status class;
- bare terms search all stored values;
- `req`/`request` aliases `method`, `ip`/`client` aliases `client_ip`, and `source` aliases the internal log-source ID;
- `OR` is not supported and is rejected explicitly.
The API continues to accept the older `source`, `event`, `status_class`, `client_ip` and `vhost` parameters for compatibility, but the dashboard no longer exposes those separate controls.
## JSON parsing and dynamic columns
JSON/JSONL lines are parsed into record objects. Canonical access-log fields are reconstructed from the active JSON field mapping where possible, so fields such as `client_ip` remain usable even when their output key was renamed.
The query response includes a bounded list of discovered leaf field names. The browser builds the column picker from those fields. The default concise view is time, event, status, client IP, vhost, record summary and source; any returned JSON field can be selected dynamically. Fields such as `matched_by`, `sni`, `authority`, `country`, `request_id` or custom mapped fields therefore appear only when the data actually contains them.
At least one column must remain visible. Stored-log column selection is saved per administrator account and accepts up to 64 bounded field names.
Selecting a row highlights it and expands parsed field/value pairs directly below the row. A nested **Raw JSON** section shows the normalized complete record.
## Sorting columns (0.31.8)
Click a Stored Logs column header (desktop/table layout) to sort by that column; click again to reverse direction. The selected field and direction are saved with the administrator's column preferences. Numeric fields sort numerically, text case-insensitively, and absent values come last. Both standard columns (`time`, `record`, `source`) and discovered nested JSON fields are supported.
Sorting takes place **on the server across the entire bounded matching scan, before page offset/limit is applied**. Thus page two continues the same sorted result instead of sorting only the current 100 displayed rows. Server bounds and truncation notices still apply; this is not an unbounded full-log historical index. The default remains newest first. The mobile card layout retains the selected sort order, set via the desktop table view.
## Narrow-screen navigation (0.31.5)
At viewport widths up to 700px, `/logs` uses a vertically scrolling list of
compact cards in place of a horizontally expanding results table. Each card
shows every enabled column, with long values wrapped or shortened until
expanded; tap the card (or focus and press Enter/Space) to inspect all parsed
fields and the complete Raw JSON. Search, source, page size, column selection,
pagination and repeated-value analysis stay available. Large raw text and
aggregate tables scroll **inside their own containers**, never across the entire
administration page. Tablet/desktop results keep the normal tabular layout.
## Repeated-value analysis
The **Repeated values** tab (advanced parser) can count repeated values for any parsed field returned by the explorer. Typical security-investigation fields are `user_agent`, `client_ip`, `host` and `path`. Counts are calculated across the bounded matching scan before pagination, so changing the page does not change the count set. Minimum repetitions and top-result count are configurable within server limits. Values in the result table are directly copyable.
Aggregation does not remove the Stored Logs safety bounds: at most 4,096 distinct aggregate values are retained and at most 200 results are returned. The response marks an aggregate as truncated when a cardinality or result cap is reached.
## RAW error logs
RustProxy's built-in RAW error-log format is also normalized for browsing. The explorer extracts timestamp, level, target, message and recognizable `key=value` fields, while retaining the original line in `raw`.
RAW is not a lossless structured serialization, so extraction is best-effort. Fields that cannot be separated safely remain visible through the message/raw record rather than being invented.
## Vhost-aware protocol records
Ordinary completed requests use `event: "request"`. Low-level failures use `event: "protocol_error"`. When RustProxy can safely identify a configured vhost, the record is persisted through that vhost's existing sink. In per-vhost file mode an unresolved record uses `_unmatched.jsonl`.
Attribution priority remains routed vhost, HTTP `:authority`, HTTP `Host`, TLS/QUIC SNI, then unmatched. Matching reuses normal exact/wildcard frontend resolution and listener binding. Client Host/SNI text never becomes a filesystem path.
## Browser security and bounds
The dashboard no longer exposes the configured-file inventory. The Source selector is optional: `All configured files` is the default, while selecting one source narrows the same bounded query to that log only.
The browser still never supplies a filesystem path. The server derives sources from the active configuration and reads only regular files. On Unix it opens with `O_NOFOLLOW`; per-vhost enumeration rejects a symlinked directory and accepts only direct regular `.jsonl` children.
Logrotate-style gzip rotations are discovered next to configured combined access/error files and inside the configured per-vhost directory. Only regular non-symlink files matching the active filename rotation pattern (for example `access.jsonl.1.gz`) are eligible. The browser still refers to them by server-generated source IDs rather than paths.
For per-vhost logging, the active `vhost.jsonl` and matching `vhost.jsonl.*.gz` files share one logical group ID. The panel enables **Consolidate rotated logs per vhost** by default, so selecting that vhost searches the active and compressed members together as one logical source. Disable the switch to inspect individual files. Physical source IDs and metadata remain separate internally, and the existing global source/byte/line scan limits still apply.
Gzip reading is bounded before and after decompression. A compressed source larger than 8 MiB is not decompressed for a query, decompressed output is capped at 64 MiB, and only the bounded tail is parsed/searched. These limits prevent a gzip rotation from becoming an unbounded memory/decompression workload.
Per-vhost directory discovery now retains only the newest **256** matching JSONL/gzip candidates while scanning, and a combined log’s gzip-rotation scan retains only the newest **64** matches. Both caps apply before sorting, so directory size no longer causes a proportional memory allocation for the inventory. The usual 32-source query budget still applies. Filesystem enumeration still takes time proportional to the number of directory entries.
The reader remains bounded:
- up to 2 MiB of retained tail data from one source;
- up to 8 MiB across one query;
- up to 20,000 lines across a query;
- at most 32 sources in an all-source query;
- page size at most 500 and offset at most 10,000;
- search input at most 512 bytes and 32 parsed clauses;
- dynamic field discovery at most 64 leaf fields.
If more than 32 sources are eligible, the newest 32 are scanned and the response is marked truncated. File reads run in a blocking worker rather than the async proxy runtime.
The explorer is diagnostic rather than a retention/indexing system. RustProxy does not create rollovers or compress files itself; use operating-system log rotation for retention. Existing gzip rotations can be read as described above.
-188
View File
@@ -1,188 +0,0 @@
# HTTP/3 transport
**Uncompiled development source in this environment.** This release adds an actual
QUIC/HTTP/3 adapter, not only an Alt-Svc header. It does not claim independent full-RFC
conformance, interoperability or production performance until the acceptance tests run.
## Stack and configuration
The adapter delegates QUIC transport/TLS to Quinn 0.11.12 and HTTP/3/QPACK plus the
Quinn adapter to `hpx-h3 2.5.20` with its `quinn` feature. The crate is imported as `h3`
in source. These are Cargo dependencies, not bundled binaries.
Rustls provides TLS 1.3 for QUIC; SNI certificates come from the existing inventory.
HTTP/1.1 and optional HTTP/2 remain on TCP at the same port. HTTP/3 is on UDP.
There is no HTTP/3 upstream client in this revision: origins still use HTTP/1.1 or HTTP/2.
Each vhost has `http3_enabled` (default true). When false, RustProxy removes
`Alt-Svc` from responses for that vhost and rejects HTTP/3 requests for it with 421,
while HTTP/1.1 and any permitted HTTP/2 traffic on the selected TLS listener keep
working. The UDP listener is still a listener-level resource; the vhost switch narrows
use of that shared capability rather than hot-unbinding the UDP socket.
HTTP/2 follows the same UI model through `http2_enabled` (default true). A vhost may
only opt into H2 when one of its selected named listeners enables H2. For TLS, RustProxy
reads ClientHello/SNI before finishing the handshake and selects H2+HTTP/1.1 or
HTTP/1.1-only ALPN for that vhost. The listener remains the capability ceiling.
Since 0.5.0, an omitted `http3` enables QUIC with defaults on TLS bindings.
Explicit `http3: null` disables it and is preserved when serialized. An object
enables it with chosen limits. Plain HTTP defaults to no QUIC. In 0.24.3 the
**Enable HTTP/3 over QUIC (UDP)** switch is edited with the TLS endpoint under
**Settings -> Listeners**. The listener row links to **Settings -> HTTP connections
-> HTTP/3**, which contains only advertisement, connection, stream and idle-timeout
tuning for listeners where H3 is enabled. Both enablement and tuning remain
restart-required. Configure this only on named `listeners.endpoints[].binding`:
```json
{
"address": "127.0.0.1:8443",
"tls": true,
"http2": true,
"http3": {
"alt_svc_max_age": 3600,
"max_concurrent_connections": 512,
"max_concurrent_bidistream": 100,
"max_concurrent_unistream": 100,
"max_idle_timeout": 10
}
}
```
| Field | Meaning and accepted limits |
|---|---|
| alt_svc_max_age | Advertisement lifetime, seconds, 0..604800 |
| max_concurrent_connections | Established/pending accepted connection slots per endpoint, 1..4096 |
| max_concurrent_bidistream | Peer bidirectional streams per connection, 1..1024 |
| max_concurrent_unistream | Peer unidirectional streams per connection, 3..1024 |
| max_idle_timeout | QUIC idle timeout, seconds, 0..86400; zero means no local QUIC idle timeout |
Three unidirectional streams are required for the peer control and QPACK streams.
These stream limits are NOT global to all listeners. The existing global in-flight
request and application body/header deadlines still apply. Setting QUIC idle to zero
does not disable application deadlines, and the peer can negotiate its own idle timeout.
All changes to socket/ALPN/QUIC transport parameters require restart.
## Transport behavior and safety
- Bind UDP before announcing readiness; failure to bind is a startup failure.
- Advertise `Alt-Svc: h3=":port"; ma=N` only on an ACTUAL active TLS+H3 endpoint.
Origin Alt-Svc is stripped so it cannot advertise the origin or a disabled service.
- UDP address validation/Retry, bounded pending handshakes, a 10-second handshake
deadline, per-endpoint permits, stream flow control and bounded application concurrency.
- 32 KiB decoded HTTP field section limit; request content-length checks and rejection
of forbidden connection/framing headers; streamed bodies and trailers.
- Shared routing pipeline: real local socket -> validated authority -> trusted client
address -> ACL -> Tinyauth -> cache -> origin -> response/security/Server policies.
- HTTP cache semantics are shared with HTTP/1.1 and HTTP/2. In 0.27.3, body-less
`HEAD` requests use the same bounded empty-body probe as `GET`, so an initially
unpolled HTTP/3 receive stream does not cause a false `request_body` cache bypass.
- 0-RTT is disabled to avoid replaying writes; no retry to another origin or transport.
- Endpoint active migration is disabled. Missing real local destination data on a
wildcard QUIC connection fails closed rather than substituting a forwarded IP.
- Configuration reload changes SNI certificates for new QUIC handshakes without
changing active socket limits. Graceful shutdown requests GOAWAY then bounded drain.
- Counters: http3_requests, http3_connections, http3_active_connections and
http3_rejected_connections in status/Prometheus; H3 request count also in dashboard.
A connection receives a maximum aggregate 8 MiB QUIC window and 256 KiB per-stream
window; these limits are not process memory guarantees. Many simultaneous connections
can use substantial memory. Size connection/stream limits to the machine and measure.
No active path migration, 0-RTT, server push, WebTransport or HTTP datagrams are enabled.
RFC 9220 WebSocket Extended CONNECT is experimental and disabled by default per vhost.
RustProxy advertises SETTINGS_ENABLE_CONNECT_PROTOCOL on a QUIC listener only when at least
one vhost bound to that listener explicitly enables `websocket_rfc9220`; request routing
still rejects RFC 9220 for vhosts that did not opt in. Generic CONNECT and other Extended
CONNECT protocols remain unsupported and are answered with 501. The downstream H3 request must
not carry HTTP/1.1-only `Connection`, `Upgrade`, `Sec-WebSocket-Key` or
`Sec-WebSocket-Accept` fields. RustProxy creates a private RFC 6455 key for the HTTP/1.1
backend handshake; a valid backend `101` becomes downstream `200`, after which H3 DATA
bytes are the WebSocket byte stream. Client FIN half-closes the backend write side;
backend EOF finishes the H3 send side; abnormal/forced termination uses
`H3_REQUEST_CANCELLED`. No side TCP listener or protocol bypass is used.
### WebSocket bootstrap ordering and diagnostics in 0.16.0
For RFC 9220, a validated backend `101` is no longer enough to send the client-facing H3 success immediately. RustProxy first awaits Hyper's backend `OnUpgrade` and obtains the actual upgraded stream. Only then is downstream `200` returned and the H3 DATA bridge started. If backend takeover is cancelled or fails, the client receives a normal 503/502 response instead of `200` followed by an immediate `H3_REQUEST_CANCELLED` reset.
Access logging can now distinguish this path with `http_version`, `transport`, `websocket_bootstrap`, `websocket_phase`, `websocket_error`, `h3_reset_code` and `upstream_status`. This makes Safari/iOS interoperability failures distinguishable from backend Socket.IO responses and from H3 tunnel resets. Query-string logging remains subject to the normal privacy policy; do not use access logging to capture session secrets.
The advertised port is the real listener port. Behind port-rewriting NAT, use a
matching externally reachable UDP/TCP port or do not enable this advertisement; there
is no separate external-port override. Open UDP as well as TCP in your firewall/load
balancer. Scope labels do not perform those operations. Clients lacking QUIC or blocked
UDP can still connect using TCP; no guarantee is made about browser protocol preference.
## H3 full-page navigation to HTTP/1.1 backend (0.31.6)
The RustProxy logo remains a normal `href="/"` link, not a client-side panel change. On an H3-enabled frontend it produces a new document GET through the proxy. When forwarding an H3 GET/HEAD to an HTTP/1.1 backend, RustProxy now probes for the stream's end for at most 35 ms per initial frame. A finished empty request is passed to H1 with a confirmed complete receive half rather than an unknown body state. Hyper already special-cases H1 GET/HEAD framing; no claim is made that ordinary GET requests were chunked. A nonempty frame and all subsequent bytes are replayed without buffering the whole request. The slow-peer case falls back to regular streaming.
If `h3.accept()` fails, existing request tasks get the normal bounded drain instead of immediate mass abort. The new diagnostics use status=0 for cancellations (not a real HTTP response), and persist interrupted-response details including `phase=`, `stream_id=`, `method=`, `path=`, `response_status=`, `body_bytes_sent=` and `error=` in the bounded, redacted `error_detail`. These are stored per vhost when file logging is enabled; the normal access logs remain available for successful document and asset GETs.
**iOS validation procedure:** keep H3 on, click the logo repeatedly on an authenticated panel behind the real RustProxy vhost, and collect timestamps for `http3_peer_cancelled_before_headers`, `http3_peer_cancelled_after_headers`, `http3_response_write_error`, `http3_accept_error`, and the GET `/` and asset access records. Collect browser Web Inspector Network/QUIC trace if a blank document persists. Correlate the **new GET/** status and transmitted bytes with older cancelled streams. Mere cancellation of an obsolete API or SSE stream is not proof that the new document failed. This implementation is not proven to resolve an iOS issue until live tests pass.
## Browser refresh and stream cancellation (0.31.5)
A browser may cancel an obsolete H3 stream when the user reloads or leaves a
page, sometimes before its HEADERS arrive. RFC 9114 `H3_REQUEST_CANCELLED`
(and a peer-originated `H3_NO_ERROR` reset) is not itself a server-side HTTP 400.
RustProxy therefore omits that fabricated 400 access-log entry and avoids
adding `H3_INTERNAL_ERROR` to a directly identified cancelled response stream.
Malformed or forbidden headers still yield a protocol-error 400.
This correction does **not** establish the cause of a blank document after F5.
If the new navigation fails, compare its actual document/asset statuses over H3
and H2, the configured reverse-proxy Host/Origin/forwarded-proto chain, and any
connection-level QUIC errors. An error record without HTTP method, path and
request headers cannot identify the browser document which failed. Capture
browser network traces and QUIC qlogs with timestamps for live acceptance.
Per-vhost `http2_enabled`/`http3_enabled` toggles also no longer rotate the global
HOT/COLD cache identity. Origin representation policy and cache invalidation are
unchanged; the frontend protocol remains part of live request routing.
## Verification gate
```bash
bash dev.sh check
cargo test --locked --test http3 -- --nocapture
cargo test --locked --test integrations --test listeners
bash dev.sh test
```
`tests/http3.rs` creates a real loopback UDP endpoint, a trusted generated test
certificate, a real origin and a Quinn+h3 client. It checks MISS/HIT, body preservation,
Server/diagnostic policy, connection capacity and ALPN HTTP/2/HTTP/1.1 compatibility.
The same integration test explicitly enables `websocket_rfc9220`, then opens an RFC 9220 Extended CONNECT stream and translates it
to a real HTTP/1.1 backend Upgrade, sends a masked WebSocket frame as H3 DATA, half-closes
the client send side and verifies byte-transparent DATA return plus tunnel metrics. Unit
regressions separately reject HTTP/1.1-only proof/framing headers on the H3 side.
It is provided but was NOT run in the packaging environment. The same-stack client
is a regression test, not independent interoperability evidence.
After applying examples/http3-vhost.json on a disposable test instance and starting
`bash dev.sh mock --port 9001`, use an independently built curl with HTTP3 support:
```bash
curl --version
curl --http3-only --cacert data/certs/default.pem -D - https://localhost:8443/cache
curl --http3-only --cacert data/certs/default.pem -D - https://localhost:8443/cache
curl --http2 --cacert data/certs/default.pem -D - https://localhost:8443/cache
curl --http1.1 --cacert data/certs/default.pem -D - https://localhost:8443/cache
```
Do not substitute `--http3` fallback or `-k` for strict protocol/certificate validation.
Also test independent clients, malformed-frame handling, stream exhaustion, slow
uploads/downloads, IPv6 wildcard destination metadata, cancellation, graceful drain,
certificate rotation, and memory/CPU at configured maxima before exposure.
No full RFC conformance or load-test result is asserted by this source package.
## Primary references
- HTTP/3 RFC 9114: https://www.rfc-editor.org/rfc/rfc9114.html
- QUIC RFC 9000: https://www.rfc-editor.org/rfc/rfc9000.html
- QPACK RFC 9204: https://www.rfc-editor.org/rfc/rfc9204.html
- Quinn: https://docs.rs/quinn/latest/quinn/
- RFC 8441 Extended CONNECT: https://www.rfc-editor.org/rfc/rfc8441.html
- RFC 9220 WebSocket over HTTP/3: https://www.rfc-editor.org/rfc/rfc9220.html
- hpx-h3 2.5.19: https://docs.rs/hpx-h3/2.5.19/h3/
+185 -295
View File
@@ -1,384 +1,274 @@
# Installing and updating RustProxy 0.29.1
# Installation, updates and first start
The scripts prepare a Linux/systemd installation. This source archive is **not a
verified production build**: Rust/Cargo, real CA operations, live systemd startup
and rollback against a real Rust database have not been exercised in the packaging
environment. Perform the acceptance steps below on an isolated server first.
Use maintained packages whenever possible. For custom installations, use the scripts included with this source version; do not combine parts of different releases.
## Official installation repositories
## Supported entry points
Use the project-maintained package repositories as the normal installation/update path:
| Installation | Recommended starting point |
|---|---|
| Linux DEB/RPM (RustProxy server) | https://rustproxy.linuxiarz.pl/ |
| Docker host discovery agent (independent DEB/RPM) | `sudo apt install rustproxy-docker-agent` or `sudo dnf install rustproxy-docker-agent`; see [Docker discovery](DOCKER_DISCOVERY.md) |
| OPNsense plugin | https://rustproxy-opn.linuxiarz.pl/ |
| Debian 13+ source bootstrap | `install-debian13.sh` from the project repository |
| Manual Linux/systemd install | Compile the application; use `install.sh` and `update.sh` |
| OCI container | Included `Dockerfile`; see [Docker / OCI container](#docker--oci-container) |
- **DEB/RPM:** `https://rustproxy.linuxiarz.pl/`
- **OPNsense plugin:** `https://rustproxy-opn.linuxiarz.pl/`
Package/plugin instructions and maintenance policies are maintained separately from the source archive. The optional Docker agent is packaged independently of RustProxy's main service for **Linux amd64/x86_64 only**, not FreeBSD pkg. On a Docker host, the agent can also be installed manually from `docker-agent/install.sh`; the installer and packages preserve existing tokens and TLS keys. See [Docker discovery](DOCKER_DISCOVERY.md) and [agent operations](../docker-agent/README.md).
Package-build metadata is intentionally maintained outside this source tree. The Debian source
bootstrap below remains supported for development, custom builds and environments where the
packaged path is not suitable.
## Debian 13+ source bootstrap
## Optional: Debian 13+ one-line source install/update
Review the remote script before execution. On a managed installation, `auto` selects install or update from `/opt/rustproxy/install.json`.
```bash
# auto: first install or managed update
# Initial install or existing managed update
wget -qO- https://git.linuxiarz.pl/gru/RustProxy/raw/branch/master/install-debian13.sh | bash
# force the update path
# Explicit managed update
wget -qO- https://git.linuxiarz.pl/gru/RustProxy/raw/branch/master/install-debian13.sh | bash -s -- update
```
The bootstrap installs build dependencies, installs Rust with rustup for an unprivileged build user,
clones `https://git.linuxiarz.pl/gru/RustProxy` and builds with `cargo build --release --locked`.
Its default `auto` mode calls `install.sh` only when no managed installation exists. When
`/opt/rustproxy/install.json` exists it calls `update.sh`, which uses the saved installation record,
backs up private state, switches the release, verifies health/version and rolls code/data/unit back
on failure. First install keeps administration on `127.0.0.1:9090` over HTTP, matching the normal
`install.sh` loopback-safe default. Updates preserve the saved admin/listener settings. For initial
remote access use an SSH tunnel, then enable Administration HTTPS before persisting a non-loopback bind.
Plain remote HTTP requires both an explicit non-loopback `RUSTPROXY_ADMIN_LISTEN` and
`RUSTPROXY_ALLOW_REMOTE_ADMIN=1`, and should be limited to a trusted private network.
The script installs dependencies, builds with `cargo build --release --locked` as an unprivileged build user, then uses the local deployment scripts. Run it only on Debian 13+ and with access to its required repositories. Installing from a moving `master` branch does **not** pin a specific audited release; use a reviewed commit when reproducibility matters.
Equivalent prerequisite commands are:
## Build from the source
The minimum Rust version is declared in `Cargo.toml` (`rust-version`); `rust-toolchain.toml` selects the `stable` channel. Use Cargo, a C compiler/linker, Bash and Python 3 (Python 3.11+ is advisable for project checks). Run as a normal build user:
```bash
apt install -y build-essential gcc git ca-certificates python3 python3-venv python3-pip pkg-config wget sudo certbot python3-certbot-dns-rfc2136 python3-certbot-dns-cloudflare python3-certbot-dns-ovh
wget -qO- --https-only --secure-protocol=TLSv1_2 https://sh.rustup.rs | sh
```
## Build as a normal user
Requirements: stable Rust (minimum declared 1.88), Cargo, a C compiler/linker,
Python 3.10+, Bash. Linux/systemd deployment also requires `systemctl`, `runuser`
and a usable non-login service account (created by the installer when missing).
The static TOML checker additionally needs Python 3.11+. No script downloads an
executable or installs a compiler as root.
```bash
cd RustProxy
bash dev.sh doctor
bash dev.sh check
bash dev.sh test
bash dev.sh release
```
The first build resolves and creates `Cargo.lock`; retain the real lockfile and
binary hash for repeatable subsequent builds. There is no fabricated lockfile or
precompiled binary in the archive. Set `--binary` explicitly when using a custom
`CARGO_TARGET_DIR`. Review and trust the source and built binary before installation.
## First installation
`install.sh` also downloads the direct `user-country-ipv4.csv` and `user-country-ipv6.csv`
assets from the `sapics/ip-location-db` latest release over HTTPS, validates their CSV
structure and writes them privately to `DATA_DIR/geoip`. Package/plugin installs may omit
that packaging-time download: after all listeners bind successfully, RustProxy starts an
enabled-by-default GeoIP worker, performs an immediate background refresh and then checks
every 24 hours by default. Country allow-only policies continue to fail closed until a valid
database is available. Settings can use separate IPv4/IPv6 CSV files or one native MaxMind
MMDB file; direct source URLs, optional per-file SHA-256 URLs, maximum file size and interval
are configurable under **Settings -> GeoIP updates**.
`dev.sh release` builds the application; it is not equivalent to a successful production rollout. Cargo dependencies must be available to the build environment; Swagger UI 5.33.1 is vendored in `third_party/swagger-ui-5.33.1/` and needs no download (set `SWAGGER_UI_DOWNLOAD_URL` or `SWAGGER_UI_ARCHIVE_SHA256` to build from an explicit archive instead). An example systemd unit is in `deploy/`.
For a first Linux/systemd installation:
```bash
bash install.sh --plan --binary target/release/rust-proxy
sudo bash install.sh --binary target/release/rust-proxy
```
Defaults: HTTP `0.0.0.0:80`, HTTPS `0.0.0.0:443`, administration
`127.0.0.1:9090`, code `/opt/rustproxy`, all private state `/var/lib/rustproxy`,
service account `rustproxy`. The initial page is a small informational response; no demonstration
backend or sample application is started. The first database has no configured
vhosts. Existing, nonempty data is refused rather than reset.
Use `--plan` to inspect changes first. The installer refuses existing unmanaged/nonempty state rather than overwriting it. Common options include `--prefix`, `--data-dir`, `--http`, `--https`, `--no-https`, `--admin`, `--allow-remote-admin` and `--no-start` (see `install.sh --help`).
The first install also writes `/etc/profile.d/rustproxy-path.sh`, which prepends the
managed `PREFIX/current` directory (normally `/opt/rustproxy/current`) to `PATH` for
new login shells. Open a new login shell, or source that profile explicitly, before
expecting `rust-proxy` to resolve without its absolute path.
## Defaults and administrator setup
HTTPS is enabled by default and starts with a **self-signed certificate**, not a
publicly trusted certificate. For an explicit HTTP-only installation use:
For the standard managed Linux installation:
```bash
sudo bash install.sh --binary target/release/rust-proxy --no-https
```
Assign the intended certificate in the panel before exposing applications. Existing
HTTP/3 defaults enable QUIC on TLS listeners: permit the corresponding UDP port
when desired, or explicitly disable H3 in Listeners. Review firewall rules yourself;
the installer does not change them. Public frontend TLS and administration TLS are
independent settings.
| Item | Default |
|---|---|
| HTTP frontend | `0.0.0.0:80` |
| HTTPS frontend | `0.0.0.0:443` with an initially self-signed certificate |
| Administration | `127.0.0.1:9090`, HTTP on loopback |
| Installation | `/opt/rustproxy/current/` |
| Private data | `/var/lib/rustproxy` |
| Service account | `rustproxy` |
Generate the one-time setup capability directly from the installed release CLI:
The first installation has no predefined administrator and no application Vhosts. Obtain the one-time setup token locally:
```bash
sudo -u rustproxy /opt/rustproxy/current/rust-proxy \
--data-dir /var/lib/rustproxy admin setup-token
```
This works before the first server start. After an administrator exists, setup
tokens remain invalid. You can alternatively initialize the account interactively:
Use the setup page, or initialize the account interactively using `admin init`. To access loopback administration from another machine, tunnel over SSH:
```bash
sudo -u rustproxy /opt/rustproxy/current/rust-proxy \
--data-dir /var/lib/rustproxy admin init
ssh -L 9090:127.0.0.1:9090 operator@server
```
For direct remote administration, first sign in on the default loopback listener and
open **Settings -> Administration**. Enable HTTPS, choose the application-managed
certificate or a certificate from the inventory, set the bind address (for example
`0.0.0.0:9090`) and optionally add DNS names accepted by Host/Origin validation.
Save and restart RustProxy. The persisted remote bind is accepted only while
administration TLS is enabled. Keep the listener behind a firewall/private network;
the certificate must be trusted by the browser if you want a clean HTTPS connection.
Then visit `http://127.0.0.1:9090` on your workstation. For a directly reachable administration listener, configure **Settings → Administration → HTTPS** and a suitable certificate, restrict the network, save and restart. Do not publish unauthenticated or plain-HTTP administration over the Internet. Public frontend HTTPS and administration HTTPS are separate.
An SSH tunnel remains available without exposing the listener directly:
### Listeners and networking
- Assign a trusted public certificate to the relevant hosts before exposing them.
- Configure DNS to reach the intended public listener; check IPv4/IPv6 and firewall rules.
- HTTP/3 needs UDP on the HTTPS port as well as TCP for normal HTTPS.
- Listener bindings supplied explicitly via service arguments/systemd are locked against panel changes. Network bind, administration TLS and some monitoring changes take effect after a restart.
- Ports below 1024 need the appropriate OS privileges/capability; use the installed service model rather than running the whole application as root.
## Linux DEB/RPM: read-only access to external paths
Linux packages and the maintained manual service template use `ProtectHome=read-only` rather than
`ProtectHome=true`. This makes document roots under `/home`, `/var/www` and
`/srv` readable if the service account has **ordinary Unix group or ACL
permissions**. `ProtectSystem=strict` remains enabled: the service cannot
modify these files merely because a group or ACL permits it. No systemd
drop-in or extra `ReadOnlyPaths=` entry is needed to **read** site content or
an external certificate. This also applies when paths are stored in the panel.
The panel generates Linux permission commands from the actual
paths you enter: below the two external certificate/key fields, and directly
below a Web Server's document root. The commands target the standard
`rustproxy` account and resolve symlink targets when run. Review them and run
them as an administrator on the server; the panel never executes them.
Install your distribution's `acl` package for `setfacl`. If you use a different
service identity, set **Service user** under **Options and help** to that account before
copying. This field changes only the commands, not the process identity.
No command can replace
the certificate trusted-owner checks or bypass systemd/SELinux/AppArmor
restrictions.
The service runs as the unprivileged `rustproxy` user. To use a dedicated web
group, give that group directory traversal and file read permissions on the
site, then add RustProxy to it:
```bash
ssh -L 9090:127.0.0.1:9090 operator@your-server
sudo usermod -aG web-readers rustproxy # use an existing narrow site group
sudo systemctl restart rustproxy # renew the service process's group list
```
Open `http://127.0.0.1:9090` on that workstation while the administration listener
is still using its default HTTP mode. No default administrator/password is added.
Plain-HTTP remote administration is possible with the explicit allow flag:
Alternatively, to expose only `/home/alice/public_html` (install `acl` to get
`setfacl`), grant traversal of the private home and read/traversal of the site:
```bash
sudo bash install.sh --binary target/release/rust-proxy \
--admin 0.0.0.0:9090 --allow-remote-admin
sudo setfacl -m u:rustproxy:--x /home/alice
sudo setfacl -R -m u:rustproxy:r-X /home/alice/public_html
sudo -u rustproxy test -r /home/alice/public_html/index.html && echo readable
sudo systemctl restart rustproxy
```
That plain-HTTP path has no transport confidentiality and should be limited to a trusted
private network. Prefer the built-in administration HTTPS mode for a direct remote
listener.
Use appropriate **default ACLs** or group ownership on directories where the
deployment process creates new files. Never expose the entire home directory
or use world-writable permissions to work around access errors. The
`sudo -u` check tests Unix permissions on the host, not the effective systemd
mount namespace.
Other installation switches: `--prefix`, `--data-dir`, `--user`, `--http`, `--https`,
`--no-https`, `--admin`, `--allow-remote-admin`, `--sha256` (expected binary digest), `--no-start`. Paths must be dedicated,
absolute, separate, non-nested and without spaces or symlink components.
`--plan` reads the supplied binary but changes nothing; it does not prove port
availability, compiler success or systemd readiness.
### Startup-owned listeners
When HTTP, HTTPS or administration listen addresses are supplied explicitly by systemd, CLI
arguments or the corresponding `RUST_PROXY_*_LISTEN` environment variables, RustProxy treats
those exact binds as process-owned. Settings shows them as locked and configuration updates
cannot remove, disable or rewrite them. Listeners not supplied at process start remain normal
editable desired-state entries.
## ACME prerequisites
Automatic renewal runs inside RustProxy, not a separately installed cron timer. RustProxy
prefers a private Python environment at `<data-dir>/acme-python`. When that environment is
missing or incomplete, the service creates it automatically and installs the pinned Certbot
core plus the Cloudflare, OVH and RFC2136 plugins with `pip`. The application never executes
`sudo` and never modifies distribution-owned Python site-packages.
The host should provide Python 3 with either the standard `venv` module or `pip`; RustProxy can
bootstrap `virtualenv` into its own data directory when a distribution splits `venv` into a
separate package. OpenSSL remains a local certificate-inspection fallback. If the private
environment cannot be created, RustProxy uses a runnable distribution `certbot` from `PATH`
when one is available.
If Python is absent (a possible layout on appliances such as OPNsense), or Python is present
but no local environment can be created and no system Certbot exists, the Certificates panel
shows ACME as unavailable. PEM upload and references to existing certificate files remain
available.
On the standard service installation the managed environment therefore lives at
`/var/lib/rustproxy/acme-python`. Do not run a second Certbot renewal timer over RustProxy's
`certs/acme` profiles. See [renewal](RENEWAL.md).
A source-tree preflight can still inspect a distribution-provided fallback environment with:
External PEM files have **additional application security checks**. The
certificate/key and every real ancestor directory must be owned by root or
the RustProxy service user, and ancestors cannot be group/world-writable;
private keys cannot be world-readable. For Certbot files at
`/etc/letsencrypt/live/example.com`, for example:
```bash
sudo -u rustproxy env PATH=/usr/local/bin:/usr/bin:/bin \
python3 scripts/runtime/acme_provider.py --doctor
sudo setfacl -m u:rustproxy:--x /etc/letsencrypt /etc/letsencrypt/live \
/etc/letsencrypt/live/example.com /etc/letsencrypt/archive \
/etc/letsencrypt/archive/example.com
sudo setfacl -m u:rustproxy:r-- "$(readlink -f /etc/letsencrypt/live/example.com/fullchain.pem)"
sudo setfacl -m u:rustproxy:r-- "$(readlink -f /etc/letsencrypt/live/example.com/privkey.pem)"
sudo systemctl reload rustproxy
```
The provider script is embedded in the RustProxy binary; this command is diagnostic only.
Apply the file ACL again when a renewal replaces the symlink's target, then
reload. Arbitrary user-owned parent directories (for example `/home/alice`)
are suitable for static websites but **not** for external keys under the
current trusted-path validation. Use root-owned certificate storage instead.
Keep all key access narrow and read-only.
### Using a separate system Certbot
After granting certificate read access, return to the form and use
**Read names from certificate**. The editor reads the public certificate's
DNS SAN entries into the legacy SNI field; this does not read the private key
or save the certificate. Manual name edits are preserved by background reads.
See [Certificates](CERTIFICATES.md#permission-commands-and-automatic-sni-names).
If another service already owns a certificate through the system Certbot, keep that lineage
outside RustProxy's managed ACME profiles and create/use a **manual** RustProxy certificate id.
To diagnose unresolved errors, run `systemctl cat rustproxy` and
`systemctl show rustproxy -p ProtectHome -p ProtectSystem -p User -p Group`,
check parent permissions with `namei -l /path/to/file`, and examine
`journalctl -u rustproxy`. A legacy whole-unit file in
`/etc/systemd/system/rustproxy.service` takes precedence over the packaged
unit and is **not** automatically removed by an upgrade. On SELinux/AppArmor
hosts, additional mandatory access-control policy may be required; POSIX ACLs
cannot bypass it. See [Web Server](WEB_SERVER.md) and
[Certificates](CERTIFICATES.md).
The preferred 0.28.2 workflow is to select **Existing server files / external paths** and then
**External files (absolute paths)** in the Certificates panel. For example:
**Write access is intentionally different.** An app that needs to upload or
modify files under an arbitrary document root needs a separately planned
writable location/policy; group membership alone does not bypass the read-only
systemd mount. Keep mutable data isolated from served files.
```text
Certificate: /etc/letsencrypt/live/example.com/fullchain.pem
Private key: /etc/letsencrypt/live/example.com/privkey.pem
```
## Optional systemd JSON configuration
RustProxy does not copy these files into its data directory. On every targeted reload it resolves
Certbot's `live/` symlinks again, validates the new certificate/key pair and atomically publishes a
new public TLS snapshot. A renewal wrapper can therefore call:
This is **optional**. Without `--config`, RustProxy keeps its active configuration in `/var/lib/rustproxy/config.sqlite3` (SQLite). With `--config`, panel/API saves and history restores write `/etc/rustproxy/config.json` atomically; SQLite still contains history, administrator state and audit entries. In **Settings → Manual config** you can see which mode and path are active.
1. First export your existing, validated **full configuration** (do not start with `{}` on an established host). On the running instance with administration reachable at `127.0.0.1:9090`, use a privileged shell with `set -o pipefail`:
```bash
curl --fail --show-error -X POST \
-H 'Authorization: Bearer TOKEN' \
-H 'X-RustProxy-Certificate-Key: certbot-main' \
-H 'Content-Type: application/json' \
--data '{}' \
https://proxy-admin.example/api/certificates/shared-cert/reload
sudo install -d -o root -g root -m 0700 /etc/rustproxy
sudo install -o root -g root -m 0600 /dev/null /etc/rustproxy/config.json
sudo -u rustproxy /usr/bin/rust-proxy --data-dir /var/lib/rustproxy \
--admin-listen 127.0.0.1:9090 config get | sudo tee /etc/rustproxy/config.json >/dev/null
```
The `rustproxy` service identity must be able to traverse the parent directories and read both
files. RustProxy 0.28.2 supports POSIX ACLs and does not require one exact chmod value. External
certificate/key files must not be group/world-writable, and an external private key must not be
world-readable. Named ACL read access for `rustproxy` is accepted; the operating system still
performs the final access check when RustProxy opens the file. Every parent directory in the
configured and resolved path must be owned by root or the RustProxy service user and must not be
group/world-writable (root-owned sticky ancestors such as `/tmp` are the narrow exception).
These external files are not included in a RustProxy backup.
If the installed binary is at `/opt/rustproxy/current/rust-proxy`, use that path in **both** commands and the override. The export requires an existing initialized administrator. You may instead use **Settings → Manual config → Export JSON**, then install that file securely with `sudo install -o root -g root -m 0600 exported.json /etc/rustproxy/config.json`.
The packaged systemd service keeps `/etc/letsencrypt` and `/etc/ssl` read-only. `ReadOnlyPaths=`
does **not** grant Unix permissions, so use narrow ACLs instead of making private keys globally
readable. For example:
2. Keep the directory and initial file owned by `root`, allow only the RustProxy service identity through **named POSIX ACLs** (install the `acl` package first, e.g. `sudo apt-get install acl` on Debian, to obtain `setfacl`/`getfacl`). The directory needs `rwx` for atomic create/rename; the original JSON needs `rw` for startup and backup tools. `root` retains ownership until the first atomic panel save, after which the new `0600` file is normally owned by `rustproxy`:
```bash
# Traversal only for the protected roots.
setfacl -m u:rustproxy:--x /etc/ssl/
setfacl -m u:rustproxy:--x /etc/letsencrypt/ /etc/letsencrypt/live/ /etc/letsencrypt/archive/
# Traversal only for this Certbot lineage.
setfacl -m u:rustproxy:--x /etc/letsencrypt/live/example.com/ /etc/letsencrypt/archive/example.com/
# Read only the certificate/key material RustProxy actually needs.
setfacl -m u:rustproxy:r-- /etc/letsencrypt/archive/example.com/fullchain*.pem \
/etc/letsencrypt/archive/example.com/privkey*.pem
# Optional default ACL for renewers that preserve inherited ACL masks.
setfacl -m d:u:rustproxy:r-- /etc/letsencrypt/archive/example.com/
# Robust renewal-hook form: re-grant read access to the current generation before API reload.
setfacl -m u:rustproxy:r-- \
"$(readlink -f /etc/letsencrypt/live/example.com/fullchain.pem)" \
"$(readlink -f /etc/letsencrypt/live/example.com/privkey.pem)"
sudo chown root:root /etc/rustproxy /etc/rustproxy/config.json
sudo chmod 0700 /etc/rustproxy
sudo chmod 0600 /etc/rustproxy/config.json
sudo setfacl -m u:rustproxy:rwx /etc/rustproxy
sudo setfacl -m u:rustproxy:rw- /etc/rustproxy/config.json
sudo getfacl /etc/rustproxy /etc/rustproxy/config.json
sudo -u rustproxy test -r /etc/rustproxy/config.json
sudo -u rustproxy test -w /etc/rustproxy
```
Because `/etc/letsencrypt/live/.../*.pem` are symlinks into `archive/`, access must cover the
resolved archive files. Some renewal tools recreate private keys as `0600` and may mask an
inherited default ACL; therefore the renewal wrapper should reapply the named file ACL before
calling the RustProxy reload API. Never grant `w` to `rustproxy` on the Certbot tree. Verify a deployment
with `sudo -u rustproxy test -r /etc/letsencrypt/live/example.com/privkey.pem && echo OK`.
Never make `/etc/rustproxy` world-writable. RustProxy checks that `--config` is an existing absolute **regular non-symlink** file. The parent directory must be on a filesystem that supports atomic rename. Restoring a file as root may require reapplying its named ACL; atomic saves generate a new owner-only `0600` file.
If you do not want the RustProxy service to read `/etc/letsencrypt`, keep the previous copy/deploy
workflow instead. A Certbot deploy hook can publish every successful renewal into RustProxy's
managed certificate directory:
3. Edit the systemd drop-in with `sudo systemctl edit rustproxy` and add the following example (the binary path must match your installation):
```ini
[Service]
ExecStart=
ExecStart=/usr/bin/rust-proxy --data-dir /var/lib/rustproxy --admin-listen 0.0.0.0:9090 serve --proxy-listen 0.0.0.0:80 --tls-listen 0.0.0.0:443 --allow-remote-admin --config /etc/rustproxy/config.json
ReadWritePaths=/etc/rustproxy
```
`ReadWritePaths=` is needed when the unit has `ProtectSystem=strict`; it does **not** grant Unix file permissions, hence the ACLs above. An existing `ExecReload` can remain; it reloads through the local control API. Keep the existing unit's sandboxing, state/log writable paths, network capabilities and runtime user. Do not expose the example's `0.0.0.0:9090` admin listener over plaintext on an untrusted network: enable administration HTTPS and strict network controls first, or use the safer `127.0.0.1:9090` binding without `--allow-remote-admin`.
4. Verify and restart during a maintenance window:
```bash
/opt/rustproxy/current/rust-proxy --data-dir /var/lib/rustproxy tls deploy --id shared-cert \
--cert /etc/letsencrypt/live/example/fullchain.pem \
--key /etc/letsencrypt/live/example/privkey.pem
sudo systemctl daemon-reload
sudo systemctl cat rustproxy
sudo systemd-analyze verify rustproxy.service
sudo systemctl restart rustproxy
sudo systemctl status rustproxy
sudo journalctl -u rustproxy -n 60 --no-pager
```
The deploy command reads the Certbot files locally and sends them to the authenticated loopback
`POST /api/certificates/deploy` endpoint. The running RustProxy service writes a private managed
generation and hot-publishes it for new public TLS/H2/H3 handshakes. Do not use `deploy` for a
certificate ID configured with external absolute paths; renew those files in place and call the
targeted reload endpoint instead. Administration TLS is startup-fixed and may still require a
service restart.
Confirm in **Settings → Manual config** that the source is **JSON file** and the path is `/etc/rustproxy/config.json`. Save a harmless edit and confirm that the file is rewritten and readable by `rustproxy`; check that a revision appears in SQLite. To import a deliberate external file edit without restarting, run `sudo systemctl reload rustproxy` (if the unit has `ExecReload`) or `rust-proxy --data-dir /var/lib/rustproxy --admin-listen 127.0.0.1:9090 config reload`. The file is not watched automatically. An incompatible external edit must fail validation rather than silently overwrite the live configuration.
Optional environment overrides go in a root-owned `/etc/rustproxy/environment`
(mode 0600); create the directory when needed. Restart after changing service
environment values. Provider secrets remain in private profile files, not this
file or the exported configuration. A systemd environment file is not a shell script.
For the alternate environment-variable method, set `RUST_PROXY_CONFIG=/etc/rustproxy/config.json` in the service's protected EnvironmentFile. Do not set **both** unrelated config paths. See [Configuration](CONFIGURATION.md) and the [deployment example](../deploy/README.md).
## Update an installation created by install.sh
## Docker / OCI container
Build and test the new release as a normal user first, then:
The included `Dockerfile` compiles RustProxy in a Rust build image and runs it on Debian as the unprivileged `rustproxy` account (UID 10001). The image contains Python, Certbot and DNS plugins for certificate automation. The main container does **not** need the Docker socket.
```bash
docker build -t rustproxy:latest .
docker volume create rustproxy-data
docker run -d --name rustproxy \
-p 8080:8080 \
-v rustproxy-data:/var/lib/rustproxy \
rustproxy:latest
docker exec rustproxy rust-proxy admin setup-token
```
Inside the image the HTTP frontend binds `0.0.0.0:8080` and administration binds `127.0.0.1:9090`, so publishing port 9090 alone does not expose the panel. For remote administration, enable administration HTTPS with a certificate and an explicit non-loopback listener first, and restrict access with firewall rules.
- Mount `/var/lib/rustproxy` persistently: it holds SQLite, private keys, certificate profiles and the optional COLD cache.
- Publish the HTTP/HTTPS TCP ports you use, and the matching **UDP** port when HTTP/3 is enabled.
- The image runs without root; map low host ports with port forwarding or deliberate capabilities.
- To discover containers on other hosts, install the separate [Docker agent](DOCKER_DISCOVERY.md).
- Upgrade by building a new image, keeping the volume and taking an encrypted backup first.
## Managed update
Test the new build first. Then execute:
```bash
bash update.sh --plan --binary target/release/rust-proxy
sudo bash update.sh --binary target/release/rust-proxy
```
Before stopping the service, the updater downloads the current direct `user-country`
IPv4/IPv6 CSV assets, validates their structure and compares their local SHA-256 with the
installed files. Unchanged files are left untouched; changed or missing assets are staged
and published only after the private-data backup exists. This intentionally avoids requiring
a separately moving publisher-checksum release. The manual updater and the in-process GeoIP
worker use the same `.geoip-update.lock`, so they cannot publish concurrently. Use
`--skip-geoip` only when intentionally accepting a stale/missing country database (for
example during an upstream outage).
Managed updates preserve the saved configuration, create a private backup, switch releases and attempt rollback on failed startup/health checks. **They interrupt traffic and are not a substitute for a separate off-host backup.** Keep your backup password and test a restore on a separate installation. Source-tree/unmanaged migrations require the dedicated stopped upgrade path: see [Migration](MIGRATION.md).
Use the original `--prefix` for a non-default installation. Other startup/account/
data settings are read from root-owned `install.json`; update does not silently
replace them with command-line defaults. Change intentional listener settings in
RustProxy, or maintain the unit separately when needed.
## ACME prerequisites
Update mode checks that `/etc/profile.d/rustproxy-path.sh` still matches the saved
installation prefix, but deliberately does not rewrite it. A missing or changed file
produces an explicit warning before service downtime; repair the profile separately
if direct `rust-proxy` shell invocation is required.
The managed certificate service can create a private Python/Certbot environment in the data directory; a usable system Certbot may be used as fallback. DNS-01 providers need credentials and appropriate permissions. HTTP-01 needs incoming public TCP `80`. On systems without an available Python/Certbot runtime, PEM import and external certificate references can still be used. See [Certificates](CERTIFICATES.md).
The updater verifies the supplied binary hash and runs only its `--version` as the
service account. Immediately before stopping a RustProxy 0.14.0+ service it creates a
private one-shot managed-update marker. During graceful shutdown the process forces hot
objects into the enabled cold tier even when ordinary `cold_on_shutdown=false`; the marker
is removed before backup. The managed unit allows up to 120 seconds for controlled stop.
If cold storage is disabled, RAM-only cache cannot survive the process exit. The updater
then stages an immutable release, stops the service and backs up the **entire** data directory. This includes SQLite and sidecars, certificates, account
keys, provider credentials, Certbot renewal configs and internal relative symlinks.
Code changes use an atomic `current` symlink switch. It then starts the new process
and requires systemd activity, minimal local `/healthz` readiness, and the actual
MainPID executable matching the staged, locally version-checked binary. The local
health probe follows the persisted administration bind and HTTP/HTTPS mode (including
the configured Host allowlist), rather than assuming the install-time plain-HTTP
address. Version is no longer exposed by the anonymous health endpoint.
The managed systemd unit is regenerated from the saved installation record and
`daemon-reload` is run before startup. The previous unit is included in the backup
and restored on rollback. Keep custom service policy outside this managed unit or
reapply it after an update.
## After installation
A failed start/health check restores the previous release, private data **and**
managed systemd unit. The reported error now includes the original update failure;
recent service status/journal output is emitted before rollback when available.
Failed-generation data is retained alongside the original data directory; the
backup is retained under `/opt/rustproxy/backups`. A failed rollback health check is
reported explicitly. Protect these directories: they contain private keys and
credentials. They are never automatically pruned. Plan enough free space for the
full backup, restored data, retained failed state and old/new executable releases.
During development, if an older saved vhost/settings document is intentionally
incompatible with the current format, use:
```bash
sudo bash update.sh --reset-config --binary target/release/rust-proxy
```
This happens only after the backup is created. It deletes the saved desired
configuration row so the new process creates current listener defaults; administrator
account data, certificates, ACME/provider files and other private state are retained.
Do not use this flag when the old vhost/settings configuration must be preserved.
After a failed update, **do not run `install.sh`**: rollback leaves the previous
installation installed. Fix the reported cause and rerun `update.sh` (optionally
with `--reset-config` for this development-only incompatible-config case).
An update interrupts traffic; zero-downtime deployment is not implemented. `--no-start`
leaves the service stopped and cannot certify a healthy installation. Backup failure
leaves the old code/data in place and attempts to restart a previously running service.
Keep an independent off-host backup; local rollback is not a disaster-recovery plan.
## Existing source-tree installations from earlier archives
Do not point a first installer at your old live data. To keep the current source-
tree deployment, stop RustProxy and use the retained source updater:
```bash
bash /path/to/new/RustProxy/update.sh --source-tree /path/to/old/RustProxy --stopped
```
This mode keeps the old `upgrade.sh` contract and does not install a systemd service
or build a binary. Back up **all** private data and ACME profiles separately before
starting it. See [migration](MIGRATION.md). To adopt the new service layout, make an
explicit offline migration with ownership, absolute Certbot paths and port settings
reviewed; do not merely move a Certbot account tree to another location.
## Server acceptance
Check actual non-root HTTP/TLS/QUIC binds, certificate assignment, a real renewal
validation (`Dry run`), backend failures, live traffic, Basic Auth over TLS, ACLs,
WebSocket enable/disable and location isolation. Verify the backup can be restored
on another isolated instance. Run `systemctl status rustproxy` and
`journalctl -u rustproxy` after installation/update. Only then decide on rollout.
Verify `systemctl status rustproxy` and `journalctl -u rustproxy` (managed systemd), the panel login, frontend certificates, first Vhost, real backend response, access policies, optional QUIC, current logs and a full backup/restore test. For error diagnosis, consult [Troubleshooting](TROUBLESHOOTING.md).
-165
View File
@@ -1,165 +0,0 @@
# RustProxy 0.4.0 integrations - historical implementation record
> Historical context. This document records the 0.4.0 integration milestone and is retained
> for traceability. Current behavior is documented in the functional/technical guides linked
> from [README.md](README.md).
This is source implementation, not a verified binary. Read TEST_REPORT.md before deployment.
The admin panel remains local; public frontend TLS does not make the admin listener remote-safe.
## Forwarded identity and the trust boundary
Every origin request now gets canonical `X-Forwarded-For`, `Forwarded`, `X-Real-IP`,
`X-Forwarded-Proto`, `X-Forwarded-Host`, `X-Forwarded-Port` and `X-Forwarded-URI`.
Without configured trusted proxies, incoming proxy headers are discarded. The socket
peer IP is used, so a client cannot supply an arbitrary address in X-Forwarded-For.
Both header formats are emitted; RFC 7239 IPv6 nodes are quoted and bracketed.
There is no ephemeral client port in the representation fingerprint.
When RustProxy is behind a real reverse proxy, Settings -> Forwarding accepts explicit
IPv4/IPv6 CIDRs (max 32). Do not enter application client networks as trusted proxies.
The last hop must sanitize incoming forwarding headers. `0.0.0.0/0` and `::/0` are rejected.
With a trusted socket peer, X-Forwarded-For is preferred; Forwarded's `for=` chain is the
fallback. The chain is walked right-to-left and anything before the nearest untrusted
hop is discarded. Malformed or oversized chains fall back to the actual peer.
`X-Forwarded-Proto` is honored only with the separate `trust_forwarded_proto` opt-in.
Authority/Host always comes from validated Host or HTTP/2/3 `:authority`, not X-Forwarded-Host.
ACLs and IP affinity use this centrally resolved client address. Listener selection
continues to use the REAL local destination socket and actual TLS state.
```json
"forwarding": {
"trusted_proxies": ["10.87.7.10/32"],
"trust_forwarded_proto": true
}
```
That address is illustrative, not a default. Leave the list empty for direct clients.
RFC 7239: https://www.rfc-editor.org/rfc/rfc7239.html
## Tinyauth per vhost
Open Vhosts -> Edit -> Authentication. Enable protection, enter the verification
server and the public login URL, allow a private verifier explicitly if needed,
and save. An origin URL automatically appends `/api/auth/nginx`; an explicit URL
path remains unchanged. No public `/tinyauth` proxy route needs to be created.
The following matches the supplied nginx-style setup:
```json
"tinyauth": {
"enabled": true,
"url": "http://10.87.7.99:3000/api/auth/nginx",
"login_url": "https://auth.linuxiarz.pl/login",
"allow_private": true,
"address_allowlist": ["10.87.7.99/32"],
"connect_timeout_seconds": 5,
"timeout_seconds": 10,
"request_headers": ["cookie", "authorization", "x-api-key", "user-agent", "accept"],
"response_headers": ["remote-user", "remote-email"],
"redirect_forbidden": true
}
```
Verification is an internal body-free GET, carrying canonical forwarding headers,
X-Original-URL and X-Original-Method. Original method, path and query remain available
to Tinyauth, while the upload body is sent only to the application after authorization.
The RustProxy admin session cookie is never passed to the verifier or application.
Only 2xx approves access. Selected Remote-* / safe X-* response headers become trusted
identity headers. Incoming Remote-* and configured identity headers are removed,
including after administrator request-header rules. Missing identity fields are not
filled from client values. Transport/framing/forwarding fields cannot be selected
as identity headers. Each direction supports at most 32 names; approved identity values are bounded
to 8 KiB in total.
401 redirects to a validated X-Tinyauth-Location on the configured login origin,
or constructs `login_url?redirect_uri=<percent-encoded original URL>`.
403 stays 403 unless the provider returns an allowed same-origin redirect and the
403-redirect option is enabled. Cross-origin locations are rejected. Verifier errors
fail closed (502/504), not open. Auth denial/error is not replaced by custom error pages.
Public login URLs require HTTPS; HTTP is allowed only for loopback test servers.
Additional CA PEM can extend trust for Tinyauth HTTPS verification; there is no
verify-off switch for the authentication verifier. The per-backend `tls_verify` option
does not apply to Tinyauth.
Configure Tinyauth itself to trust the ACTUAL RustProxy source IP, not 0.0.0.0/0.
Authorization runs before origin connection, static pages, WebSocket upgrade to the
origin and every cache lookup. Authenticated vhosts always BYPASS the internal shared
cache and emit `Cache-Control: private, no-store`, even when their cache toggle is on.
This intentionally also disables privacy-sensitive compression through the existing
predicate. Login/session validity is NOT cached. Revocation is checked on each new
HTTP request or WebSocket handshake; an already upgraded socket is not periodically
reauthenticated. There is no automatic replay of POST or failed upgrades.
Do not enable this protection on the Tinyauth login vhost itself (login loop).
Provider reference: https://tinyauth.app/docs/guides/nginx-proxy-manager/
Header reference: https://tinyauth.app/docs/reference/headers/
## Named listeners
Settings -> Listeners owns every proxy socket. Each endpoint has a stable ID, display
name, scope label, enabled state and a binding with numeric IP:port, TLS, HTTP/2 and
optional HTTP/3 parameters. Scope labels do not install firewall rules.
Vhosts reference endpoints only through `frontend.listener_ids`. Inline frontend
listener definitions and the old global listener-pair compatibility fields are not
accepted in 0.8.0. A named endpoint can be shared by multiple vhosts; same-socket
transport settings must agree. Disabled endpoints create no socket and grant no vhost
binding. Unknown/duplicate IDs and admin-port conflicts are rejected.
Listener transport changes require restart. The panel shows desired and actually bound
endpoints separately. A wildcard bind never widens a vhost's own destination-IP match.
## Certificate library
Certificates is an independent sidebar section. Import a PEM certificate/full chain
and PEM private key, give the entry an ID, specify domain names to validate the pair,
and save it to the inventory. In Vhost -> SSL certificates, select the saved entry.
Import does not add public SNI names by itself; active vhost assignment supplies names.
The advanced filename editor remains for CLI-managed existing pairs and direct SNI maps.
The library shows assignment and default status. ACME/Let's Encrypt and expiry renewal
are not implemented; the inventory/API boundary is ready for that future work.
Uploads: at most 128 KiB per file, key matching/name validation, immutable randomly
named private files, limited parallel imports and mode 600 on Unix. Config stores
filenames and IDs, never PEM private key bytes. File inputs are cleared after import.
Failure between staging and configuration publication may leave unused private files;
they are not automatically activated or deleted. Removing inventory metadata does not
delete key files. Back up the cert directory together with SQLite.
Existing referenced/default entries cannot be removed in the UI without unassigning them.
Certificate changes affect new handshakes; existing sessions continue normally.
The resolver is shared by listeners: one DNS name cannot use different certificates
on different destination IPs. Conflicts are rejected rather than silently chosen.
HTTP-only startup also prepares the private certificate directory, but creates no TLS
socket unless configured. No existing certificates are overwritten.
## Response policies and cache on extra frontends
Global and per-vhost `server_header` accept `preserve`, `hide`, `custom` plus `value`.
Omitted/null vhost policy inherits the global choice. Final policy applies to origin
responses, HITs, local pages and errors for inline/named TCP/TLS/H3 listeners.
Generic header rules run before this dedicated Server policy.
As of 0.18.8, `diagnostic_headers` exposes only the request-ID switch:
```json
"diagnostic_headers": {
"request_id": false
}
```
There is no revision response-header switch or special `X-Rust-Proxy-Revision` handling. If an upstream or ordinary response-header rule supplies that name, it behaves like any other non-protected response header.
Request IDs remain available internally/upstream and in logs. X-Mock-Origin is preserved and can
be removed with ordinary global or per-vhost response-header rules. An explicit vhost diagnostic
object overrides the request-ID choice; null inherits.
Cache keys now include the actual frontend IP:port and stable canonical forwarding
metadata. Empty HTTP/2/HTTP/3 GET bodies without an exact length are checked by peeking
at most eight frames, skipping empty DATA frames; nonempty data/trailers are
replayed to the origin without buffering the full body.
A stream with data/trailers bypasses cache. Reusing the same socket with a different
client source PORT no longer changes the forwarded representation.
Client IP still affects the conservative all-header fingerprint, so different clients
can have separate cache entries. Auth, cookies, no-store, private, upgrades and partial
bodies still bypass caching. Cookies created by a login or the sticky-session feature
are not a cache bug. Close/consume the entire response before expecting a stored HIT.
-55
View File
@@ -1,55 +0,0 @@
# Interactive live logs
RustProxy provides an operator CLI that follows structured request/error events from every HTTP vhost:
```bash
rust-proxy --livelogs
```
The command connects only to the loopback administration listener and creates a bounded local CLI session from the private RustProxy database. When `./data/config.sqlite3` is absent, the bare command discovers common installed layouts: `/var/lib/rustproxy` is preferred on Linux/non-BSD systems and `/var/db/rustproxy` on BSD/OPNsense-style installations, with the alternate convention checked as a fallback. An explicit `--data-dir` always wins and custom installations should pass the same path used by the service. `--admin-listen` can select a non-default loopback port.
## Administration HTTP/HTTPS transport
The CLI first probes the loopback administration `/healthz` endpoint over HTTP. An HTTP-only administration listener stays on HTTP; the `308 Permanent Redirect` emitted by a TLS-enabled administration listener switches the CLI to HTTPS before a CLI session cookie is sent. HTTPS is pinned to leaf certificates present in the private `<data-dir>/certs` directory, so a configured self-signed administration certificate does not require a system trust-store installation.
This fixes the previous reconnect loop where `--livelogs` treated the TLS listener's expected HTTP-to-HTTPS `308` response as a stream failure. The same local transport selection is used by CLI config and certificate API operations.
The live path is separate from configured file/syslog sinks and from the `/logs` stored-log explorer. Request processing publishes ordinary routed request records into a bounded Tokio broadcast channel without waiting for viewers. Low-level malformed HTTP/TLS/HTTP2/HTTP3 protocol failures introduced in 0.26.0 are intentionally persistent-sink-only and do not enter this Live Logs ring, so scanner noise cannot evict normal live request history. A slow viewer receives an explicit `gap` event with the number of entries lost from that viewer's live queue. The CLI also consults the retained history after connecting/reconnecting and de-duplicates entries by request id. Each request now carries a fresh 64-hex-character SHA-256 `request_id`; the same protected value is forwarded to the backend as `X-Request-ID` for correlation.
## User-Agent, country and Traffic analytics
Structured access entries expose `user_agent` and `country` as selectable source fields in addition to the standard entry. User-Agent capture is enabled by default and is bounded before it enters retained logging/analytics state. Country capture is disabled by default. Normally country resolution uses the local GeoIP database; for a vhost with verified Cloudflare client-IP mode, RustProxy first accepts a valid single `CF-IPCountry` value from a direct Cloudflare edge and falls back to GeoIP of the verified effective client IP. With country capture disabled, country resolution is kept off the normal request path unless a configured country access rule or country rate-limit already requires it; in those policy cases resolution still occurs for enforcement, but the country is not retained in access logs unless capture is enabled.
The authenticated **Traffic** workspace aggregates completed requests per vhost without database writes. The request path uses a bounded non-blocking queue, and high-cardinality dimensions use fixed-capacity approximate top tables. The page shows aggregate requests, 5xx errors, request/response bytes and mean duration plus top countries, User-Agents, effective client IPs, paths, routes, methods, status classes and cache outcomes. Donut charts summarize countries, User-Agents, client IPs, methods, status classes and cache outcomes while the bounded tables retain exact displayed labels/counts from the approximate TOP state. The lists are process-lifetime diagnostics, are not persisted across restart, and may drop analytics samples under overload rather than applying backpressure to proxy traffic. Country rankings are populated only while country capture is enabled. Client-IP rankings use the same verified effective client identity used by ACL/logging after trusted-proxy processing.
## Interactive commands
No regex language is required. Type commands while the stream is running:
```text
host api.example.com
vhost api
backend origin-a
route users
client 192.0.2.10
method GET
transport h2
cache MISS
status 5xx
path /api/
errors on
slow 250
detail on
filters
show 12345
pause
resume
reset
clear
quit
```
Use `*` to clear a scalar filter, for example `backend *`, and `slow off` / `errors off` for boolean filters. `show ID` prints the complete retained structured event for that request. `detail on` expands the continuously printed view with route/upstream/cache/transform/WebSocket/H3/byte metadata.
The server revalidates the streaming session every 10 seconds rather than writing session state for every request. Password changes, session revocation or expiry therefore terminate an existing stream within that bounded interval.
+54
View File
@@ -0,0 +1,54 @@
# Live logs, stored logs and error logging
RustProxy has several independent logging paths. Choose the one matching the question; enabling stored file logging is not required to view recent in-memory requests.
## Live Logs
The administration panel does not fetch `/api/logs` while Live Logs is closed. Opening or docking the Live Logs window starts the bounded live snapshot refresh; closing it stops that polling. Pausing or expanding the history buffer also suspends periodic `/api/logs` reads until live viewing resumes. The main `/api/events` status stream is kept independent by using `logs=false` in the panel.
Use **Logs → Live Logs** in the authenticated panel, or locally:
```bash
rust-proxy --data-dir /var/lib/rustproxy --livelogs
```
The CLI connects to the local administration listener and follows structured request events. It can filter by host, Vhost, backend, route, method, client, protocol, status, cache state and latency; use `filters`, `pause`, `resume`, `detail on` and `quit` in the interactive view. It adapts to a loopback administration HTTPS listener where configured.
For a published Docker application, **Docker vhosts → Live logs** (beside **Open app**) opens this same dialog filtered to the generated Vhost. It includes HTTP traffic for that publication and its aliases. It does **not** display Docker container stdout/stderr; the Docker agent remains inventory-only.
Live viewing is **bounded in memory**. Slow consumers may receive a gap/loss indication and are not allowed to block proxy traffic. Low-level malformed HTTP/TLS/H2/H3 protocol errors can be sent only to persistent sinks rather than appearing in the ordinary live request list. A request ID (`X-Request-ID`) helps correlate an application response with proxy records.
## Stored Logs
The authenticated **Logs → Stored logs** page reads the configured structured JSON/JSONL access logs, optional per-Vhost files and optional error log. These files can remain readable even when their current writer is disabled. The explorer supports paging, dynamic columns, sorting, record expansion/Raw JSON and repeated-value analysis.
Search uses a restricted case-insensitive query syntax, for example:
```text
status:>=500 AND path:/api/*
client_ip:192.0.2.* AND status:5xx
method:POST AND !country:Poland
error:"connection reset"
```
Plain words search stored values; quotes allow spaces, `!` negates a clause, `*` matches wildcards and numeric operators work on supported fields. **`OR` is not supported.** Searches and repeated-value statistics are bounded by server scan limits; do not interpret a truncated result as the entire historical archive.
## Error and protocol logs
RustProxy can persist warnings/errors and selected protocol failures separately from access traffic. Set output level, sink and optional retention/rotation under **Settings → Logging** and related error-log controls. Access logs may use the global structured file or per-Vhost locations. Log paths must be writable by the service identity, including when running inside a container/systemd sandbox.
The installed Linux deployment may configure `logrotate` for durable logs; check the installed policy rather than assuming all platforms rotate automatically. Logging is bounded/asynchronous where practical, and some records can be dropped under saturation; inspect sink drop/error counters in monitoring.
## Privacy and investigation
Logs can contain client IPs, URLs, application hostnames and timing metadata. Limit access and retention. RustProxy masks selected known credential fields, but avoid deliberately logging secrets or personal data in query parameters and custom headers. The administration API and stored-log search remain behind authentication.
For common symptoms:
- **502/503/504:** correlate request ID, Vhost and backend health with upstream timeout/connect/TLS errors.
- **403/405/429:** inspect ACL, method rule, country or rate limiting.
- **Unexpected cache result:** inspect optional `X-Cache` diagnostics and Cache dashboard alongside request logs.
- **Files empty:** enable the correct sink and check permissions, path and rotation state.
- **Protocol failures missing from Live Logs:** inspect configured persistent error/protocol logs instead.
Also see [Monitoring](MONITORING.md) and [Troubleshooting](TROUBLESHOOTING.md).
-139
View File
@@ -1,139 +0,0 @@
# Vhost management - historical 0.4-era implementation record
> Historical context. This document is retained for implementation traceability and must not
> be used as the current vhost/operator guide. Use [VHOSTS.md](VHOSTS.md),
> [CUSTOM_LOCATIONS.md](CUSTOM_LOCATIONS.md), [VHOST_ACCESS.md](VHOST_ACCESS.md) and
> [WEB_SERVER.md](WEB_SERVER.md) for current behavior.
This guide describes implemented source behavior. Read TEST_REPORT.md before
acceptance: the packaging environment did not compile or run the Rust backend.
## Compact operations
Vhosts opens a compact list with domains, bound sockets, backend count and live
health/latency. Edit opens a modal with Details, Backends, Health, SSL, Cache,
Security, Pages, Authentication and Advanced tabs. Existing source/ACL JSON remains available.
The structure is inspired by proxy-host managers, not a claim of full feature
parity with nginx-proxy-manager. Density is configured in Settings and stored only
as a browser preference. Logo and favicon have not been redesigned.
## Optional certificate upload
In Certificates, upload certificate/full chain PEM and an unencrypted private-key
PEM. The pair is cryptographically validated and written to private immutable files;
the form then saves inventory metadata through revision CAS. No vhost or DNS name
is required just to import a certificate. In a separate vhost save, choose its ID
in SSL; validation checks that it covers the host's configured DNS names.
The file inputs clear after the request. Failed/stale inventory saves can leave
unbound staged files; no key bytes are returned by the API or stored in the config.
Clear/inherit uses the global SNI/default behavior.
Limits: 128 KiB per file, 300 KiB JSON request, up to 16 supplied DNS names, two concurrent
upload workers, 64 configured certificate inventory entries and 128 staged/upload
pairs on disk. Canceled uploads remain staged; no automatic deletion of potentially
active keys is attempted. New uploads eventually stop at that limit. Perform
manual cleanup only after backup and confirmation that neither the saved
configuration nor the serving snapshot references those files, preferably stopped.
The configured-certificate backup does not collect unused staging files.
Certificates must cover the configured DNS names. This does not prove browser trust,
chain completeness, revocation status or remaining lifetime. ACME-issued certificates
are shown as managed/read-only inventory entries; they are renewed through the ACME
workflow rather than edited as uploaded custom PEM files. New issuance selects the
production CA environment by default but still requires explicit production confirmation;
staging remains available for testing. A certificate can be deleted only after it has
been unassigned and saved everywhere, including vhosts, redirects, 404 hosts, the
default TLS certificate and the administration panel. Encrypted-key password prompts
are not supported. The single SNI resolver rejects different selected certificates for
one DNS name on different IP bindings. Socket changes still require restart; changing
a certificate for an existing listener uses hot snapshot publication after Save.
Requests already in flight are not interrupted.
## Cache and response headers
Cache selectors distinguish inheritance, on and off. Blank numeric fields inherit.
TTL/object limits, query/path exclusions and optional diagnostics are configurable
per host. The memory/entry ceilings are shared globally; one host cannot increase
its object ceiling above the global limit. Explicit per-vhost enable is permitted
when the global cache is explicitly disabled. Cookie-affine pools and path cache=false still
bypass. Purge on a host clears its entries and invalidates its in-progress fills,
without rewriting configuration or evicting other hosts. Changing configuration
still performs a full invalidation.
Security includes visual rules for headers sent to the backend and to the client,
and per-vhost HSTS/nosniff/frame/referrer/CSP/permissions policies. Advanced JSON
edits update the visual rows. Global, vhost and path header rules run in order;
existing final Server/security/protocol safeguards retain precedence. In
particular, a hidden Server cannot be restored by an ordinary header rule. An
explicit per-vhost Server override can replace the inherited global Server policy.
## Custom pages
Global Settings / Pages supplies error defaults, direct-IP and unknown-host
responses. Vhost / Pages supplies an optional static response, a missing-path
fallback and error overrides. Modes are inheritance, built-in congratulations,
empty and administrator HTML (typed or loaded from an HTML file).
Supported error keys: 400, 401, 403, 404, 408, 413, 429, 500, 502, 503 and 504.
Keep the original status by leaving status unset; an explicit status is allowed
from 200, 204, 400, 401, 403, 404, 408, 410, 413, 429, 500, 502, 503 or 504. Only empty
mode permits 204. HTML is at most 32 KiB UTF-8 per page and the complete serialized
configuration remains bounded to 256 KiB. No CSS/image asset hosting or directory
serving is provided; use self-contained HTML or an upstream for multi-file sites.
The built-in congratulations text is English; replace it with your own HTML for
other languages. RustProxy-generated 503 and 504 failures use matching built-in dark
HTML pages when no administrator override is configured; vhost/global custom error
pages still take precedence. An origin's own 503/504 response is not rewritten unless
a custom error override is configured. Admin interface translations still come from
language packs.
Request behavior:
1. Validate authority/path and real local binding; check applicable host/path ACLs
and enabled Tinyauth authentication before serving any content.
2. Use the explicit direct-IP page for a literal IPv4/IPv6 authority, if enabled.
3. Otherwise use the matching vhost's static response, if configured.
4. If no path is selected, use that vhost's missing-path fallback; if there is no
matching vhost, use the global unknown-host fallback. Otherwise keep normal 404.
5. Forward normally when a route exists, then replace a configured error with its
vhost override or global default. Apply the normal response/security policy.
Static/fallback endpoints accept GET/HEAD; other methods get 405 with Allow.
HEAD has the selected response headers and no body. Default status for a static
vhost/direct-IP page is 200; a missing-path/unknown-host fallback retains 404 unless
explicitly changed. Error responses retain their original status by default.
Already-authored pages are not rewritten a second time by error rules. Replacement
drops old encoding, length, cookies and other representation metadata, while
preserving authentication challenges, Retry-After and Allow. Pages use no-store.
ACL denial always prevents upstream access, even when its 403 page is configured
to return 200. A success status for an authentication/server error can mislead
clients and monitoring. Prefer preserving original statuses. Direct-IP HTTPS
must still finish TLS; this HTTP feature cannot bypass certificate validation.
HTML is not previewed in the admin DOM. It is trusted administrator content and
may execute scripts when served to visitors. Never import untrusted HTML.
## Separate live-log window
Use Live logs in the toolbar or a host's Logs button. Choose modal or docked mode;
filters remain above a scrollable table. Filters combine vhost, method, status
family, cache/error outcome and text (host, path, method, vhost, backend). Pause
freezes the displayed bounded snapshot, not collection. Resume uses the latest
snapshot. Export contains exactly the selected rows. Closing/reopening keeps
filters; signing out clears viewer state.
The source is a 256-entry in-memory access ring refreshed through SSE/polling.
It is not an unbounded tail, durable file, guaranteed replay or audit store. The
vhost field comes from the actual selected frontend and remains available for
cache/static responses, without fabricating a backend request. Pool health data
are live observations, not settings; disabled checks are not proof of liveness.
## Additional 0.4.0 controls
The Authentication tab enables Tinyauth. Security includes inherited/per-vhost
Server and diagnostic header settings. Details selects named listeners shared
with other vhosts; inline listeners are not part of the current configuration model. Certificates are managed in
the separate inventory rather than uploading keys from each vhost editor.
The toast moves into the active dialog/top layer so its message and action remain
visible and interactive above the backdrop. See INTEGRATIONS_040.md for contracts.
-91
View File
@@ -1,91 +0,0 @@
# Long-running memory verification (RustProxy 0.30.0)
This procedure supplements static source checks. A synthetic Python model and a
local Python HTTP peer **do not execute RustProxy** and cannot establish its
production RSS, heap growth or absence of leaks.
## Audit coverage
| Area | Source-level finding | Action in the same 0.30.0 version |
| --- | --- | --- |
| Selective cache PURGE | Per-key generation metadata could retain every distinct purged key indefinitely after its object was removed | Reset stale per-key metadata at the existing global generation barrier; drop an abnormally large map allocation; regression for 512 different purges |
| HOT/COLD cache | Body/entry limits, bounded transient leases, limited cold queue, revalidation slots and background cleanup permits | Kept existing behavior |
| Log output (JSONL/file/Logstash/Loki) | Serialized the same entry again when Loki and a JSONL sink ran simultaneously; sink queues and per-vhost open files were already bounded | Reuse serialized JSONL content for Loki; keep redaction and sink-specific formatting |
| Historical log discovery | Prior audit already capped directory inventory while scanning, at 256 per-vhost and 64 gzip entries | Retained existing 0.30.0 limits |
| Traffic analytics | Top lists limited to 32 entries per dimension, at most 64 vhosts and a 2048-item input queue, but repeated labels incurred transient `String` allocations | Borrow existing labels, allocate only when admitting/replacing a top-list entry |
| Local directory listing | Two simultaneously live vectors for directory metadata and a copied HTML response | Filter directory metadata in place; move output bytes into response; keep all filenames and HEAD Content-Length behavior |
| TCP/UDP streams and HTTP/H2/H3 | Connection/session permits, finite queues, idle deadlines and completed task draining | No change |
| Upstream clients, DNS, health probes | DNS worker limit, timeouts, pool recycling, bounded health checks | No change |
| Compression, transforms, PHP-FPM | Streaming response paths, transform memory and concurrency budgets, FastCGI streaming | No change |
| Rate limits, ACME, Docker, background tasks | Limited client buckets with expiry and shrink, cancelled/limited background activities | No change |
This is **not** a proof that every runtime allocation is released. The directory
listing feature intentionally generates the complete visible directory as HTML;
very large directories may still need substantial temporary memory. A high RSS
after a load spike may also be allocator reuse instead of an active leak. Verify
with real-runtime measurements rather than assuming either explanation.
## Source/synthetic verification
```sh
./dev.sh check
./dev.sh test
# Perform separate real-runtime load and RSS measurements on staging.
```
The old Python cardinality model and protocol-peer load driver were removed
with `tools/checks/`. They were synthetic tests, not proof of RustProxy memory
bounds. `./dev.sh test` now runs only the Rust suite; runtime memory behavior
must still be verified by sustained staging traffic and RSS tracking.
## Real RustProxy staging traffic and RSS
On a Linux staging machine with Rust/Cargo installed, configure a vhost and a
known reachable backend/local Web Service. Enable whichever logging targets you
need to test (JSONL file, Logstash, Loki) in the normal dashboard. Start the
proxy and determine the **RustProxy** PID, not the load generator's PID.
```sh
./dev.sh check && ./dev.sh test
# In another terminal: run the configured instance, e.g. ./dev.sh run
pid=$(pgrep -n -x rust-proxy)
# Warm / repeated URL: cache HITs, persistent clients, normal JSON access logs.
python3 tools/dev/memory_load.py \
--url http://127.0.0.1:8080/ \
--host-header example.test \
--pid "$pid" --requests 400000 --concurrency 128 \
--cooldown 120 --output /tmp/rustproxy-memory-repeat.json
# High cardinality: many URL paths, cache misses, large analytics churn.
# Pick a staging path prefix accepted by your origin; status codes will be
# recorded even when the requested URLs return 404.
python3 tools/dev/memory_load.py \
--url http://127.0.0.1:8080/ \
--host-header example.test \
--pid "$pid" --requests 400000 --concurrency 128 \
--unique-paths 100000 --cooldown 120 \
--output /tmp/rustproxy-memory-distinct.json
```
Run at least three **successive** traffic/cooldown cycles on the same process.
Test separately with access JSONL only, JSONL+Logstash and JSONL+Loki using
reachable staging services; add streaming large response bodies, directory
listings, TCP/UDP and HTTP/3 using appropriate specialized clients. Send
controlled cache PURGE requests through your authenticated cache invalidation
API and ensure previously purged cache entries never reappear.
The tool samples `/proc/<pid>/status` (RSS, high-water mark, threads) and
`/proc/<pid>/fd` at a configurable interval. Output includes response codes,
requests/second, received bytes, time series and start/peak/end RSS. Check
steady-state RSS after each cooldown, open file descriptors, timeouts, dropped
logs, HIT/MISS behavior, and whether growth **continues** across periods of
similar load after caches and connections have stabilized. In addition, inspect
cache/transformation memory accounting and capture a Rust heap/allocation
profile on the actual instance if RSS rises repeatedly.
Do not run these load tests against an active production service without a
maintenance or capacity plan. The load driver does not create vhosts, backend
services, log collectors or a RustProxy executable, and it cannot reproduce
all TCP, UDP, H2 and H3 protocol behavior. None of these real-instance
acceptance steps were performed in the source-only audit environment.
+39 -511
View File
@@ -1,535 +1,63 @@
# Deployment and configuration migration history
# Upgrades and migration
## 0.32.3 -> 0.32.4 Preserve Host and Dashboard gutters
This guide covers upgrades that need operator action and the included Nginx Proxy Manager importer. For ordinary package upgrades, first follow [Installation and updates](INSTALL.md).
No SQL schema, saved vhost JSON, account preferences or widget IDs change.
`preserve_host: true` now works with `http1`, `auto`, `http2` and `h2` instead of
being rejected for the latter three. If an HTTPS backend is addressed by IP but
serves multiple vhosts, retaining the visitor Host also sets HTTP/2 `:authority`
to the public host, while TCP/DNS use the configured URL and optional
`tls_server_name` sets SNI/certificate identity. Keep a separate
`health_check.host` for HTTP probes that require the vhost name. The debugging
H1 preset remains optional, and original Overview widget preferences survive.
Check TLS verification, hostname/certificate coverage and Angie handling on a
real staging instance before deployment; the Rust compiler acceptance gate
is still open.
## Before changing versions
1. Record installed version, platform, current listener binds and external certificate paths.
2. Create a password-encrypted off-host backup and confirm its password is available; separately copy external cert/key and integration state.
3. Verify the target source and dependencies, compile and run tests on a staging copy when possible.
4. Read release notes for cache compatibility, configuration changes, API token permissions and listeners requiring restart.
5. Update with a maintenance window. Test production DNS, TLS, Vhost routes, access, cache behavior, PHP, Docker, Streams, logging and backup recovery before considering the upgrade complete.
## 0.32.2 -> 0.32.3 upstream protocol and Dashboard compatibility
A managed install uses `update.sh`; an unmanaged source-tree install must follow the supplied stopped upgrade path. Never overwrite an active database by extracting a ZIP over live private data. RustProxy normally applies forward SQLite schema migrations as needed; do not manually edit internal migration tables.
No database or backend JSON rewrite is required. In 0.32.2 an HTTPS origin
saved as `"protocol": "http2"` could negotiate HTTP/1.1 via TLS ALPN. The same
value **still** permits H2/H1 over TLS in 0.32.3 and still uses H2C on plaintext
HTTP. To enforce HTTP/2 only, explicitly select new `"protocol": "h2"`.
Existing `http1` and `auto` values, HTTP/1.1 WebSocket Upgrade, configured
keepalive and pool settings retain their meaning.
## Notable compatibility points
The dashboard registry grows to 24 IDs. Eight HTTP backend cards are optional,
hidden by default for all accounts until enabled; old ordering and hide/show
preferences survive. These use existing status data with no extra background
polling or schema migration. Verify live behavior and WebSocket negotiation on
a Rust-enabled staging environment before deployment.
| Change area | What to review |
|---|---|
| **Cache** | Operational Vhost saves aim to retain compatible HOT/COLD cache. Content-affecting changes invalidate a scope; older disk cache objects can require refilling. Validate actual behavior before rollout. |
| **Web Server** | `sendfile=auto` is an optional Linux cleartext HTTP/1.1 path; default is `off`. No existing service needs to enable it. |
| **0.32.x origin protocols** | Legacy backend `http2` remains compatible on TLS (H2/H1) and H2C for plain HTTP. New strict `h2` must be set explicitly. |
| **0.33.x Overview** | Dashboard presets/layouts are per administrator, independent from Vhost configuration; existing preferences should be retained and new widgets may remain hidden. |
| **Certificate API grants** | Older broad/reload-only credentials can exist after additive migration; replace them with explicit certificate-ID allowlists and least privilege. |
| **PHP-FPM** | Distinct applications with different PHP INI overrides need separate external FPM pools and a worker restart; RustProxy cannot isolate FPM workers itself. |
| **Legacy custom root location** | A former untouched synthetic `/` location may normalize into the main Vhost fallback; a deliberately customized `/` rule remains an override. |
| **External Certbot files** | File-backed certificate paths stay external; backups and restores do not include those bytes. Check symlink and permission changes after renewal. |
## 0.28.1 -> 0.28.2
Restore of configuration history changes **only configuration**, not a full application state snapshot. See [Backup](BACKUP.md) for disaster recovery.
SQLite schema 8 is upgraded automatically to schema 9. The additive migration creates `certificate_api_grants`. Existing Certificate API credentials become reload-only compatibility credentials covering all certificate IDs (`*`); rotate them in **Settings -> Certificate API** to replace the wildcard with explicit certificate IDs. New credentials require 1-32 explicit certificate IDs and are reload-only unless deploy permission is enabled.
## Nginx Proxy Manager (NPM) import
The saved proxy configuration document and COLD cache format are unchanged. External certificate paths keep their 0.28.1 semantics, but 0.28.2 validates the full configured/resolved parent chain and accepts POSIX ACL read access without requiring an exact chmod value. The packaged systemd service marks `/etc/letsencrypt` and `/etc/ssl` read-only; Unix permissions/ACLs still need to grant the `rustproxy` user the required traversal/read access.
The included `scripts/migration/migrate_npm.py` reads NPM's SQLite database and prepares RustProxy Vhosts. **It does not modify NPM or migrate its private TLS keys.** Keep `migrate_npm.py` and its two sibling helper modules together when running outside this source checkout.
## 0.26.1 -> 0.26.2
What can migrate: NPM proxy-host domain names, upstream scheme/host/port, basic enabled state, WebSocket and force-HTTPS policy, and supported frontend listener selection. The importer reports more complex access lists, custom Nginx locations/directives, caching, exploit rules and certificate details **for manual review**. Private/backend origin policy and upstream TLS verification must be explicitly checked after import.
No SQLite or saved-configuration migration is required. Version 0.26.2 changes only administration/log-explorer behavior and account-scoped view preferences. Existing JSONL and RAW log files remain readable, and old Stored Logs filter query parameters remain accepted by the API for compatibility even though the dashboard now uses one query field.
### Dry run first
## 0.26.0 -> 0.26.1
No SQLite or saved-configuration migration is required. Version 0.26.1 changes persistent protocol-error attribution and the Stored Logs UI/API normalization only. Existing JSONL files remain readable, Live Logs behavior is unchanged, and unresolved protocol errors continue to use `_unmatched.jsonl`.
## 0.25.7 -> 0.26.0
There is no SQLite schema or saved-configuration migration. Version 0.26.0 extends the existing structured access-log record with optional protocol-diagnostic fields and adds a read-only stored-log explorer. Existing logging settings remain valid and file logging remains disabled by default. Low-level malformed/TLS/protocol events are written only when at least one external structured-log sink is enabled; they do not enter the Live Logs memory ring. Existing JSONL access files and the configured error file can be browsed after upgrade without conversion.
The `/logs` explorer is authenticated administration UI. Its API derives source IDs from the active RustProxy logging configuration and does not accept filesystem paths. On Unix it refuses symlink targets when opening files for browsing.
## 0.25.6 -> 0.25.7
There is no SQLite schema or saved-configuration migration. Version 0.25.7 reuses the existing `admin_preferences` document to persist backend-table visible columns and sort order per administrator account; search text remains transient. It also adds vhost enable/disable UI actions, themed scrollbars and additional Prometheus/Grafana observability. Existing vhost `enabled` values and backend configuration remain unchanged until an administrator explicitly uses the new action.
## 0.25.5 -> 0.25.6
There is no SQLite schema or saved-configuration migration. Version 0.25.6 adds the already-exposed upstream client generation/rotation diagnostics to the live Backends table and keeps the 0.25.5 backend client recycling defaults unchanged.
## 0.25.4 -> 0.25.5
There is no SQLite schema or saved-configuration migration. Existing `cloudflare_client_ip` vhost settings remain opt-in. When enabled and the direct peer is a verified Cloudflare edge, RustProxy now prefers a valid `CF-IPCountry` value for country analytics/logging and country policy evaluation, falling back to the installed GeoIP database when that header is unavailable or invalid.
## 0.25.3 -> 0.25.4
There is no SQLite schema or saved-configuration migration. Schema 8 and the existing 512-row `configuration_history` retention remain unchanged. Version 0.25.4 adds an authenticated restore operation that validates a retained document and saves it as a new revision; existing history is not rewound or overwritten.
## 0.25.2 -> 0.25.3
SQLite schema 7 is upgraded automatically to schema 8. The migration adds `configuration_history`; on first open the current revision is seeded into history, and subsequent successful configuration saves retain the newest 512 revisions for the dashboard revision-history view. No manual SQL is required.
New vhost/location access fields are additive. Missing `method_policy`, location `acl_profile_id`, location country policy and location inline ACL fields retain the prior behavior. RustProxy-generated request IDs are now SHA-256 values and are forwarded to origins as protected `X-Request-ID`; custom request-header rules cannot replace that correlation identifier.
## 0.25.1 -> 0.25.2
There is no SQLite schema or saved-configuration migration. Full backup creation may now run while the service is active; restore still requires RustProxy to be stopped. Backup archive version 2 preserves validated internal relative symlinks such as Certbot `live/` entries pointing into the profile `archive/` tree. Existing archive-version-1 backups remain accepted for restore.
The administration server keeps the same strict Host/Origin/request-marker/CSRF checks. The browser shell only adds a bounded one-shot same-origin reload when a restored or resumed authenticated tab receives `auth.invalid_origin`.
## 0.25.0 -> 0.25.1
There is no SQLite schema or saved-configuration migration. 0.25.1 reorganizes the Traffic dashboard and source tree only; existing Web Services, vhosts, traffic settings, cache state and administrator data remain compatible. Browser asset URLs remain `/assets/app.js` and `/assets/vhosts.js` even though their source is now split into bounded modules.
When updating a source checkout by extracting the archive over an older tree, remove the obsolete source monoliths `src/admin/web/app.js` and `src/admin/web/vhosts.js` if they remain. Checkouts older than the Web Server module split may also retain obsolete `src/web_server.rs`; remove it as well. Do **not** remove `src/config.rs`, `src/docker.rs`, `src/runtime.rs`, `src/balance.rs`, `scripts/deploy/deploy.py`, `scripts/migration/migrate_npm.py` or `docker-agent/main.go`: these are intentional module/CLI entry points in 0.25.1. Build/cache artifacts such as `target/` and `__pycache__/` may be deleted safely, but `.env`, `data/`, databases and local certificates are runtime/operator state and must not be deleted as part of a source refresh.
## 0.24.10 -> 0.25.0
There is no SQLite schema migration for the first Web Server stage. `web_services` is an
additive collection inside the existing revisioned configuration document, and missing
vhost/location `source` fields continue to deserialize as Reverse Proxy. Existing 0.24.10
configurations therefore remain valid without manual conversion.
A Web Service must be created separately before a vhost/location can reference it. Document
root, index files and directory-listing settings belong to that reusable Web Service and are
not migrated into individual vhosts.
## Historical deployment changes
## 0.15.1 -> 0.16.0
There is no SQLite schema migration. Existing vhosts, backends, cache files, administrator accounts and sessions are retained.
Custom locations are now interpreted only as overrides. An untouched legacy synthetic `root` prefix `/` location with no custom backend/cache/ACL/header/rewrite/local-response behavior is normalized away and replaced by the internal main-vhost fallback. A deliberately customized `/` location remains a real override. No manual edit is required for the normal legacy `root` entry.
Publishing the administration listener through a vhost remains explicit-risk functionality. For reserved session-cookie passthrough the public request must be HTTPS, the external hostname must be listed in `administration.allowed_hosts`, the backend must directly address the configured administration listener by IP, and that backend must have `accept_local_listener_risk=true`. Ordinary upstreams continue to have `rustproxy_session` removed in both directions.
Access-log JSON may contain the additional fields `http_version`, `transport`, `websocket_bootstrap`, `websocket_phase`, `websocket_error`, `h3_reset_code` and `upstream_status`. Cache/system metrics add memory-breakdown gauges; existing consumers should ignore unknown fields as usual.
## 0.15.0 -> 0.15.1
There is no schema or configuration-format migration. This patch changes only administration UI language-pack placement, Custom locations editing/help/preview and dashboard visualizations. Existing vhosts, locations, cache settings and SQLite data remain unchanged.
## 0.14.0 -> 0.15.0
There is no new SQLite schema version in 0.15.0. Renewal observations remain in the existing
schema-v3 `certificate_renewals` table. The application no longer contains a runtime JSON importer.
When `upgrade.sh` / `scripts/deploy/upgrade.py` is used while RustProxy is stopped, an obsolete
`<data-dir>/certs/renewals.json` observation sidecar is validated as a regular file and removed as
the final source-upgrade step. It is not imported and no second legacy copy is retained. If the
source-upgrade transaction fails before that final removal, the old sidecar remains untouched.
If a site replaces source manually instead of using the stopped upgrader, the old sidecar is simply
ignored. Back it up and remove it. An empty renewal-observation table is safe: the ordinary scheduler
reconstructs non-secret status/timestamps on its first 5–10 minute startup check, while certificates,
keys, ACME account material and renewal profiles remain unchanged.
## 0.13.1 -> 0.13.2
Back up `config.sqlite3` and the configured certificate files before updating. On first
0.13.2 open, RustProxy applies the additive SQLite schema-v3 migration and creates
`certificate_renewals`; existing configuration, administrator accounts, sessions, audit
records and revisions are retained. The table stores only non-secret renewal observation
state (timestamps/status/message). If it is empty and the older private
`certs/renewals.json` exists, validated observations are imported once and the sidecar is
removed only after the database write succeeds. That one-time runtime compatibility path existed in
0.13.2/0.14.0 and is removed in 0.15.0; use the 0.15.0 stopped upgrader described above instead.
The managed `update.sh` path for 0.13.2+ also marks the stop as a software update so an
enabled cold tier receives remaining hot cache objects even when ordinary
`cold_on_shutdown=false`. This is a best-effort persistent handoff bounded by the managed
stop timeout; no cache persistence is possible when cold storage is disabled.
The raw JSON editor moved to **Settings -> Manual config**. `/configuration` remains a
browser compatibility alias, so old bookmarks do not require migration.
## 0.8.0 -> 0.8.1 cache behavior
The source default for `cache.enabled` changes to `true`. Existing saved configurations
that explicitly contain `enabled: false` remain disabled; the new default applies to new
or missing cache settings and is not a silent rewrite of an administrator choice.
`cache.max_object_bytes` may now be configured up to 1 GiB when `cache.max_bytes` is at
least as large. Browser conditional requests/no-cache refreshes now use conditional HIT
or backend revalidation instead of blanket BYPASS; `no-store` remains a hard exclusion.
- Existing vhosts deserialize new traffic-policy fields with conservative/default-off
behavior except `http2_enabled` and `http3_enabled`, which default true to preserve prior protocol behavior.
- The pre-production inline/global listener format is intentionally no longer readable.
Recreate development listener configuration under `listeners.endpoints` and bind each
vhost with `frontend.listener_ids`. No automatic vhost migration is performed.
- Existing backend TLS settings are preserved. Only newly created panel backend rows use
the 0.13.0 default `tls_verify:false`; enable verification explicitly for origins that require certificate identity checks.
- First installs fetch verified GeoIP country files. Existing installations upgraded with
`update.sh` keep their data directory; install the two verified files under `data/geoip`
before enabling country rules if the installation predates 0.8.0.
No database schema migration is introduced. The release changes deployment defaults
and control-plane startup behavior:
- `admin setup-token` can create the one-time setup token before first server start.
- New managed systemd installations default to `0.0.0.0:80` and `0.0.0.0:443`.
Existing installations keep the saved service unit during `update.sh`; update does
not silently add port 443.
- Wildcard administration is opt-in only. Direct server startup requires
`--admin-listen 0.0.0.0:9090 serve --allow-remote-admin`.
The installer uses the equivalent `--admin` and `--allow-remote-admin` options.
This is plain HTTP; keep it behind a firewall/private network or use a tunnel.
Back up private state before updating and run the real Rust check/test suite on the
target build host.
---
# 0.7.1 -> 0.7.2 build fix
This is a distinct source release. It removes the absent-SWAGGER-environment panic;
it does not migrate the database, alter vhosts or reset any account/certificate.
Back up private data as usual and update the complete source tree, not just build.rs.
The patch changes Cargo dependencies and adds a required build helper, so copying
only the Rust file would leave an inconsistent package.
The existing `dev.sh` Python 3.10+ requirement now also applies to direct Cargo
builds. No `.cargo/config.toml` or manual `SWAGGER_UI_DOWNLOAD_URL` export is required.
The default pinned tag is fetched on the first build; a reviewed local tag archive
remains supported for restricted build hosts. See [API_ACCESS.md](API_ACCESS.md).
No default to an older Swagger distribution is introduced.
Use the existing stopped-source `update.sh --source-tree` workflow or build a new
binary and use the managed-service updater as documented in [INSTALL.md](INSTALL.md).
`dev.sh` backs up an older Cargo.lock before regenerating it; do not fake the new
lockfile by replacing its root version text. A valid new lockfile is not included.
Rebuild/restart after successful tests; the old process cannot load embedded changes.
There is no need to clear application data or certificates, or to run `cargo clean`
just to remove this panic. Test the real admin session and `/docs` after building.
---
# 0.7.0 -> 0.7.1 access changes
No database/configuration schema migration is introduced by this patch. Back up
existing data/certificates and use the existing install/update instructions.
The reported release is source-only; compile and test before installing a binary.
Operational JS and full translation packs are now session-protected. Old tabs
should be reloaded after upgrading. Automation reading `/api/auth/status.version`
or `/healthz.version` must use authenticated `/api/status` instead. Dedicated
Prometheus scraping retains its own CIDR/token policy and remains independent of
browser sessions. No reverse-proxy vhost auth policy is relaxed by this patch.
In 0.7.1 the Swagger pin depended on `.cargo/config.toml`. That requirement is
removed in 0.7.2 (see the build notes above). Source-tree updates still back up and
replace that managed directory; retain any unrelated local Cargo settings from
your private backup. Read [API_ACCESS.md](API_ACCESS.md) for current build inputs.
Run `python3 tools/audit/admin_audit.py --url http://127.0.0.1:9090` after deployment.
It prints only status checks, no response bodies or credentials.
---
# Updating, backup and recovery
## 0.7.0: renewal and service deployment
See [INSTALL.md](INSTALL.md) for separate installer/updater commands and complete
code-plus-data rollback. Older source deployments retain `update.sh --source-tree`.
Back up the entire private state, including Certbot profiles, before any update.
Do not overlay a new binary onto a live, untested database without a recovery plan.
Managed 0.6 certificate issuer metadata defaults to automatic renewal in 0.7.
The scheduler starts after a 5-10 minute delay and reuses existing consented
profiles. Imported PEM without issuer metadata is not scheduled. Review auto-renew
flags, service PATH, DNS permissions and HTTP-01 reachability before starting.
The new process does not implicitly switch staging certificates to production.
Missing WebSocket flags preserve existing enabled behavior. New UI-authored vhosts
default to disabled. Existing backend members remain in the normal pool; new
dedicated location origins default to `pool_eligible:false`. Basic Auth is opt-in,
requires actual TLS by default and combines with (rather than bypasses) IP ACLs.
Normal dev/server startup no longer starts the demonstration origin. Existing
sample vhosts are not silently deleted: remove or replace them deliberately, or
explicitly enable the protocol fixture in an isolated old test installation. New
databases show a neutral informational landing page with no sample backend.
New fields are not a promise of backward parser compatibility. Downgrade by
restoring both old code and the pre-upgrade private-data snapshot. No verified Rust
build is supplied; run check/test and server acceptance before rollout.
## 0.6.0: historical vhost migration note
0.6.0 originally shipped a one-way importer from the pre-vhost development format.
That importer is removed in 0.8.0. Current builds accept only `vhosts`, named
`listeners.endpoints` and `frontend.listener_ids`. Development databases containing
top-level route/backend documents or inline frontend listeners must be recreated or
manually rewritten before starting 0.8.0. No automatic compatibility conversion runs.
The dedicated Prometheus listener remains opt-in/disabled by default. Settings ->
Prometheus controls it. A restart is required for enable/bind/token-variable changes.
Certificate provider metadata remains additive and secrets remain outside exported
configuration.
## 0.5.0 behavior changes
Back up the database and configured PEM pairs before upgrading. The managed
updater replaces the full source tree, not live data. Build and test on staging.
No verified binary or fabricated Cargo.lock is supplied.
TLS bindings with omitted `http3` now enable UDP with defaults; older versions
omitted disabled options when serializing, so review ALL TLS bindings. To retain
TCP-only operation, explicitly save `http3: null` on the named TLS listener before
restarting the new process. Shared/wildcard
IP-port bindings must agree on all protocol flags and H3 limits. A UDP bind failure
is not silently ignored. HTTP/1.1 and optional HTTP/2 remain on TCP.
`diagnostic_headers.hide_mock_origin` was removed and is no longer accepted or
exported; use explicit response-header rules if needed. X-Mock-Origin is a normal upstream field; remove it with ordinary
response-header rules when desired. Request ID remains off by default. The removed 0.17 `diagnostic_headers.revision` field is no longer part of the configuration schema. Custom Server values
are NOT automatically rewritten to the new binary version.
Cache contents are process-local. Reserved admin-cookie removal now occurs before
cache eligibility; application cookies still bypass. Domain counters reset on
configuration publication. See CACHE_DIAGNOSTICS.md for explaining BYPASS.
ACME/RFC2136 is optional and adds external Python/Certbot/plugin requirements.
No DNS update or CA request happens simply by upgrading. Issuance is explicit,
staging-first and does not activate a certificate without import. Current encrypted full backup includes the durable ACME profile/account state and
Secret Store material required for DNS-provider renewal; the historical `dev.sh backup`
helper does not. Use `rust-proxy backup create` for current installations. Automatic
renewal behavior is documented in RENEWAL.md.
## Reported auth module compilation failure
0.0.1 used `src/auth.rs`. 0.0.2 moved the implementation into `src/auth/mod.rs` and
`src/auth/store.rs`. Overlay extraction can retain both layouts and cause Rust
E0761. Missing `Store::session_at`, `admin_record`, `end_session` and
`revoke_all_sessions` messages can follow because the implementation module did not
load. The 0.0.2 archive itself did not contain both auth layouts.
0.4.0 retains an explicit `#[path = "auth/mod.rs"]` declaration, including for direct
Cargo invocations. Preflight also detects mixed source trees. It recognizes the
exact original auth file by SHA-256 and moves it to
`.upgrade-backup/legacy-auth/auth.rs.0.0.1`. Unknown custom source is left untouched
and the development script stops with instructions. Never delete custom files just
to silence a compiler error.
Locate the NPM SQLite database (often `/data/database.sqlite` inside its container). Back up its live consistent database before reading it; SQLite WAL may contain uncheckpointed changes.
```bash
bash dev.sh doctor
bash dev.sh repair
python3 scripts/migration/migrate_npm.py \
--database /path/to/npm/database.sqlite \
--rustproxy https://rustproxy.example.net:9090
```
These commands do not require Rust. Repair is deliberately limited; a safe whole
source-tree update is preferable to extracting individual files over a checkout.
The default invocation creates `npm-migration-plan.json` with planned additions/skips; no write happens without `--apply`. Enter admin credentials through the prompt or secure environment, and use a trusted server certificate. The optional `--ca-file` supports a private CA; `--insecure-admin-tls` is a deliberate local/test exception, not a recommended setting for ordinary production automation.
## Recommended source update
Review each conflicting hostname, missing HTTPS listener/certificate, custom Nginx directive and backend policy. Explicit listener selection is possible with `--http-listener` and `--https-listener`.
Extract the new archive beside, not inside, the installed project. On installations
that provide the current encrypted backup command, stop RustProxy and create the
backup outside the data directory before replacing source files:
### Apply after reviewing the plan
```bash
rust-proxy --data-dir /home/mg/RustProxy/data backup create \
--output /home/mg/RustProxy-backup-before-upgrade.rpb
unzip RustProxy-0.4.0.zip -d /home/mg/rustproxy-040-staging
cd /home/mg/rustproxy-040-staging/RustProxy
python3 scripts/migration/migrate_npm.py \
--database /path/to/npm/database.sqlite \
--rustproxy https://rustproxy.example.net:9090 \
--apply
```
`tools/legacy/backup.py` is a legacy release-test helper and is not a complete Secret
Store backup. For current backup/restore behavior see `SECRET_STORE_BACKUP.md`.
The migration writes through RustProxy's **revisioned administration API**, so it should not silently overwrite a newer config save. Test each imported domain and assign actual RustProxy certificates manually. Keep NPM available for rollback until the imported configuration is verified.
A missing database means there is no database to back up. Do not initialize an empty
replacement accidentally. For data stored elsewhere, use the actual directory.
## Unsupported or very old configurations
Stop RustProxy using its terminal or service manager. Then:
```bash
bash upgrade.sh /home/mg/RustProxy --stopped
# External data directory: also pass --data-dir /actual/private/data
cd /home/mg/RustProxy
bash dev.sh test
bash dev.sh
```
The updater checks a process lock on POSIX, requires the explicit `--stopped`
acknowledgement and rejects nested source/target paths and managed symlinks.
It preserves `.env`, `data`, `.git`, `target` and unmanaged files. Source, scripts,
tests, examples and documentation are replaced as whole managed paths. Old managed
files remain in `.upgrade-backup/before-0.4.0-<identifier>`. An interrupted copy
exception triggers source rollback. Abrupt power loss is not a transactional
filesystem update; inspect the backup and source tree before restarting.
The updater does not evaluate the old `.env` or discover arbitrary external data
paths. Pass `--data-dir` when necessary, and stop every process yourself before
changing source. It does not perform a database migration or backup automatically.
Custom source edits are backed up, not merged.
## Database compatibility
RustProxy 0.13.2 advances the SQLite schema from version 2 to **version 3** with one
additive `certificate_renewals` table for non-secret ACME observation state. The existing
0.0.1-to-0.0.2 auth migration still runs when needed. Configuration-document additions
remain defaulted/non-destructive. Existing accounts, policies, revisions and configuration
remain intact. Starting with 0.15.0 the application reads renewal observations only from this table;
historical JSON observation state is handled, when present, by the stopped source-upgrade remove
described above rather than by the running service.
The generated default certificate is created on first TLS-enabled startup, in
`<data-dir>/certs`. Its key is never overwritten by startup. A partial or invalid
pair causes a clear startup error; it is not silently discarded. Demo settings
never overwrite an existing configuration, including an empty existing one.
## Backup contents and restore
Current RustProxy releases use the password-encrypted full-backup format documented
in `SECRET_STORE_BACKUP.md`. The supported interface is `rust-proxy backup create`
and CLI-only `rust-proxy backup restore`; it includes the encrypted Secret Store,
key material required to recover it, certificates and other durable application
state while excluding cache, logs and runtime files.
The historical `./dev.sh backup` / `tools/legacy/backup.py` snapshot helper described by
older release notes is not sufficient after Secret Store migration and is retained
only for offline compatibility/release tests.
## Downgrade
Older versions reject unknown configuration fields such as `tls`, `protocol` or
`tls_ca_pem`. Restoring old source alone is therefore not a safe downgrade after
saving new configuration. Restore the matching pre-upgrade database and environment
from a verified backup, with the server stopped. Do not edit password hashes or
schema versions manually.
## 0.0.6 -> 0.1.0 document and source changes
No new SQL schema version is introduced. Old documents default to inherited
listeners, disabled cache/compression and preserve-Server behavior. New optional
route cache policy does not change existing route behavior. Existing account,
revision, TLS and ACL state is retained.
Managed paths now include build.rs, langs, deploy and make_zip.py. Extra *.json
language packs from the installed langs directory are copied into the new tree
when the new distribution does not contain the same filename. Shipped packs are
updated, with old bytes preserved in the source backup. Invalid custom packs can
correctly block a build; review their JSON syntax and metadata before building. No symlinked
language pack is followed. The updater never copies a database from the package.
The old lockfile is preserved in the managed source backup and a new dependency
graph is generated by Cargo on first build. No 0.1.0 resolved Cargo.lock is included
because Cargo was unavailable here. Keep the actual newly resolved file afterward.
Saving any 0.1.0 document serializes its new fields. Older binaries use strict
unknown-field rejection, so rolling back only the source/binary may fail startup.
Restore the pre-upgrade database/certificates into a new private directory and keep
the newer data separately. Do not directly edit live SQLite to remove fields.
A saved invalid listener address or missing low-port permission can prevent
startup. The online config CLI cannot repair an offline server. Keep a pre-change
backup and use the documented stopped-server restore path; remove persisted
listener overrides through Settings while the service is still running before
changing deployment assumptions. The cache itself is RAM only and is not backed up.
## 0.1.0 -> 0.2.0: compression, vhosts and pool behavior
The `SizeAbove` u16/u64 mismatch is fixed by replacing that predicate with a custom
u64 implementation. Do not use `try_into().unwrap()` or truncate the setting to
work around the old source: configured thresholds above 65535 must remain valid.
This source change removes that call, but is not a successful compiler result;
run `bash dev.sh check` and `bash dev.sh test` on a Rust-enabled staging machine.
SQL schema version stays 2; accounts, ACLs, certificates and revisions are retained.
New `vhosts` defaults empty; named TLS listeners default to H2/H3 capability when those fields are omitted. Legacy routes
continue on the global listeners and are never silently migrated or exposed on
additional IPs. The editor's explicit migration action converts one unambiguous
route group in a single revision and keeps the migrated route group's existing health behavior.
New vhosts default to TCP health checks and remain unavailable until
`rise` checks succeed. Plan the correct path/status/timeouts before migration.
Health/counter/affinity observations are ephemeral. They survive compatible edits,
not restarts; new/changed/re-enabled origins require new checks. Cache and idle
upstream pools are not carried across config publication. Admitted exchanges finish
with their old snapshot and are not replayed on a different backend.
HTTPS origins previously configured `http2` now retain HTTP/1.1 ALPN compatibility.
Plaintext `http2` is still strict h2c. Per-listener IP/port/TLS/HTTP2 changes require
restart. During an address move, hot routing can deny the previous destination
before the new socket is bound. Plan a maintenance window, verify assigned IPs,
privileges/certificates and retain an offline-restorable backup.
After saving any 0.2.0 document, 0.1.0 can reject new fields even without a vhost,
because `https_http2` is serialized. Downgrade requires matching pre-0.2.0 data,
source and environment. Never remove fields by editing a live SQLite database.
The archive contains no fabricated lockfile, executable, database or private key.
Generate and retain the real dependency graph before acceptance/deployment.
## 0.2.0 -> 0.3.0
The reported `json_internal!` recursion error is addressed by splitting
`status_payload` into small JSON values and inserting feature flags through a map.
Do not overlay only this function: this release also adds the `query` Axum feature,
`src/pages.rs`, `management.js`, new configuration fields and matching UI assets.
The earlier u64 compression predicate remains in place. Compilation has not been
executed in the packaging environment; this is not a successful-build assertion.
Existing database documents deserialize with empty/default `pages`, vhost `cache`
and `certificate_id` fields. No SQL schema migration is added. Per-vhost certificate
selection rewrites the effective SNI map in a snapshot, not the files. Upload is a
staging action: Save must publish a validated configuration before traffic uses it.
Canceling the editor leaves an unbound pair, not an active certificate change.
Use the complete backup/whole-source update procedure above. The existing backup
copies configured keys and fallback files. Unbound upload staging files are not in
the configuration and therefore are not part of that configured-certificate backup.
Retain them separately only when needed. Never delete a filename still referenced
by the configuration or currently serving snapshot; stop the process before manual
cleanup and preserve a backup of the full private certificate directory.
New defaults do not activate custom pages or vhost cache overrides. An explicitly
disabled global cache can still be overridden by one vhost; the global byte/entry/object
limits still bound it. Current releases rotate cache compatibility only for configuration
changes that can alter cached/origin semantics; unrelated control-plane publication preserves
reusable objects. The dedicated per-vhost Purge action remains selective.
Downgrade requires restoring the earlier database/configuration together with its
matching source: 0.2.0 rejects the new fields. Do not merely relabel a new document.
The source archive contains no fabricated lockfile. Resolve and retain Cargo.lock
on the first compiler-enabled build and run all tests before restarting production.
## 0.3.0 -> 0.4.0
Use the full-source update above, not an overlay of a few Rust files. This revision
adds Quinn/h3 dependencies, new modules and a new embedded integrations.js asset.
No successful compiler run or dependency resolution is claimed. Generate and retain
a real Cargo.lock, then run check and all tests before acceptance.
SQL schema stays 2. Proxy sockets are persisted only in `listeners.endpoints`; a new
database is seeded from startup CLI listener addresses. Existing development documents
using the removed listener format are rejected rather than migrated. Tinyauth and
vhost Server policy continue to use their normal defaults. New diagnostic defaults remove x-request-id from client responses unless it is enabled explicitly where needed. Access logs retain request IDs. The old revision diagnostic field/header behavior was removed in 0.18.2. Forwarding headers are
rebuilt from actual peers unless known proxy CIDRs are explicitly configured.
Reference named listener IDs per vhost instead of duplicating bindings. There is no
inline/global compatibility conversion path in 0.8.0. Scope names have no firewall meaning. Changed sockets, ALPN or H3 limits
require restart; opening UDP is necessary for H3. Saving intent does not prove
that an OS bind or UDP firewall rule will succeed.
The certificate directory/fallback is prepared even with HTTP-only startup, so
library uploads can be staged before enabling TLS. The generated fallback is
self-signed development material and does not automatically open an HTTPS socket.
Import the PEM pair in Certificates, save inventory, then select its ID in SSL.
Unbound staged files are not automatically backed up; preserve private files needed
for rollback and never remove any currently referenced certificate/key.
Tinyauth-protected vhosts bypass shared cache regardless of cache toggles. Failures
deny access instead of serving cached/static content. Configure its verifier URL,
public login URL and explicit private-origin permission before enabling it on an
existing service. Test login, refusal, logout/revocation and WebSockets on staging.
The examples are complete replacement documents for disposable test databases,
not merge patches. Saving a 0.4 document serializes new fields; older binaries may
reject them. Downgrade requires matching pre-upgrade database, certificates,
environment and source, with the process stopped. The source backup alone is not
a database/configuration rollback.
Do not assume arbitrary old Nginx config files, plugin-specific JSON, unmanaged data directory layouts or incompatible backup formats migrate automatically. Follow the original release notes or restore on an isolated older version first, then upgrade in controlled steps. Use the current API schema rather than undocumented JSON keys. For problems see [Troubleshooting](TROUBLESHOOTING.md).
-37
View File
@@ -1,37 +0,0 @@
# Fake Tinyauth protocol peer - local development only
This is NOT authentication. Any client can mint the allow cookie. The fixture binds
only to 127.0.0.1 and must never protect a production application.
```bash
bash dev.sh mock --port 9001 --name origin-a --ttl 30
# Separate terminal:
bash dev.sh mock-auth --port 9300
```
In a disposable vhost's Authentication tab, set:
- verifier: http://127.0.0.1:9300 (adds /api/auth/nginx)
- login: http://127.0.0.1:9300/login
- explicitly allow private verifier IPs
- forwarded identities: remote-user and remote-email
Use your actual frontend address/hostname. Example for a local plain-HTTP vhost:
```bash
# Denied -> 302 to configured login, no application request:
curl -i -H 'Host: app.localhost' http://127.0.0.2:8181/inspect
# Allowed -> app receives mock-user / mock@example.test; cache remains BYPASS:
curl -i -H 'Host: app.localhost' -H 'Cookie: mock_auth=allow' http://127.0.0.2:8181/inspect
# Force authentication failure even with an allow cookie:
curl -X POST -H 'Content-Type: application/json' -d '{"status":503}' http://127.0.0.1:9300/control
# Simulate slow verifier; choose less than or greater than the vhost total timeout:
curl -X POST -H 'Content-Type: application/json' -d '{"delay_ms":12000}' http://127.0.0.1:9300/control
# Restore default deny/allow behavior:
curl -X POST -H 'Content-Type: application/json' -d '{"reset":true}' http://127.0.0.1:9300/control
curl http://127.0.0.1:9300/state
```
/control replaces status/delay/redirect behavior (omitted values reset to defaults).
Optional location sets X-Tinyauth-Location for redirect-policy tests. /state exposes
only the last 16 metadata records, not Authorization, cookies or query strings.
Real Rust-router auth tests are in tests/integrations.rs.
-116
View File
@@ -1,116 +0,0 @@
# Python-only mock origin
Run independently of Cargo/Rust, in a separate terminal:
```bash
bash dev.sh mock --port 9001 --name origin-a --ttl 30
```
This runs until Ctrl+C. A second origin for balancing uses another terminal:
```bash
bash dev.sh mock --port 9002 --name origin-b --ttl 30
```
The helper binds 127.0.0.1 by default. It accepts IPv4 addresses only and refuses
non-loopback bindings without --allow-remote. Do not expose it publicly:
/control deliberately has no authentication. It has 32 connection workers,
15-second socket timeout, a 2 MiB maximum response and a 10-second delay ceiling.
It is a development fixture, not a secure production website.
## Configure a test vhost
After building and starting RustProxy, create a vhost with domain mock.localhost,
frontend 127.0.0.1:8080, backend http://127.0.0.1:9001, private backend permission,
HTTP health path /health, and Cache enabled with X-Cache diagnostics. Leave sticky
cookies off to test shared cache. Wait for its backend to become healthy.
`examples/mock-vhost.json` is a complete ready-to-apply document for a disposable
installation. It explicitly enables the per-vhost cache (the 0.8.1 global default is
also enabled), adds a custom 503 page, and serves the congratulations page on direct-IP access. Applying
it replaces all existing routes/vhosts; it is not a merge patch. Review first:
```bash
bash dev.sh config validate examples/mock-vhost.json
bash dev.sh config apply examples/mock-vhost.json
```
Do not replace your real configuration for this demonstration. Use the panel to
add equivalent settings to an existing project instead.
## Verify cache, not just the origin
Requests must go through the proxy with the right Host:
```bash
curl -si -H 'Host: mock.localhost' http://127.0.0.1:8080/cache
curl -si -H 'Host: mock.localhost' http://127.0.0.1:8080/cache
```
After the first complete response, expect MISS then HIT with an identical nonce
while fresh. Origin JSON always generates a new nonce and timestamp; a cache hit
therefore makes reuse visible. Health probes can advance the sequence too, so
compare nonce/body, not an assumption that sequence increases by exactly one.
GET is the clearest fill test because it consumes the complete body. HEAD is also
cacheable in 0.11.5 and shares the GET representation. Only response `Vary` fields
create request-header representation variants; unrelated request headers and the
selected backend-pool member no longer fragment the same Host + URL object.
Use the host's Purge action and request again to expect a new MISS. Waiting past
TTL also refreshes. To compare with a non-cacheable route:
```bash
curl -si -H 'Host: mock.localhost' http://127.0.0.1:8080/nocache
curl -si -H 'Host: mock.localhost' http://127.0.0.1:8080/nocache
```
Bodies should change and must not become HIT. A diagnostic MISS means lookup was
attempted, not that the response was stored: no-store/private/Set-Cookie may reject
storage after origin headers arrive. BYPASS indicates a request-side exclusion.
## Endpoints
| Path | Behavior |
|---|---|
| / | Small HTML page with links |
| /cache?ttl=30 | Random JSON; public max-age; Vary: Accept-Language |
| /nocache | Random JSON; no-store |
| /private | Private JSON; cannot enter shared cache |
| /cookie | Sets a cookie; must not enter shared cache |
| /large?size=131072 | Compressible bytes; public freshness; useful above 65535 bytes |
| /slow?ms=800 | Delayed no-store response |
| /health | 200/503 according to controlled health; delay is also applied |
| /state | Current health, delay, sequence and origin ID |
| /status/401, /status/403, /status/404, /status/503 | Origin error responses for page tests; 401 includes a challenge |
| POST /control | JSON with healthy:boolean and/or delay_ms:0..10000 |
Compression needs enabled proxy compression and an appropriate Accept-Encoding.
The origin itself does not compress. Inspect both response headers and decoded body;
not all curl builds support Brotli or Zstandard. Example:
```bash
curl -si --compressed -H 'Host: mock.localhost' \
'http://127.0.0.1:8080/large?size=131072'
```
## Failover, latency and recovery
Control the origin directly, not through the proxy, especially after it is marked
down. Configure the health path /health (the default root page always returns 200).
```bash
curl -s -H 'Content-Type: application/json' -d '{"healthy":false}' http://127.0.0.1:9001/control
curl -s -H 'Content-Type: application/json' -d '{"healthy":true,"delay_ms":1200}' http://127.0.0.1:9001/control
curl -s -H 'Content-Type: application/json' -d '{"healthy":true,"delay_ms":0}' http://127.0.0.1:9001/control
```
Watch health/RTT in the vhost list/editor and dashboard. Fall/rise thresholds and
probe interval delay transitions intentionally. Add origin-b as a second backend
for failover and balancing tests. Random origin data alone does not prove that the
Rust scheduler, compression or cache is correct; run the Rust acceptance tests.
## Verification scope
The mock server is a development fixture, not a proxy test. Exercise its endpoints
directly with curl when needed, then run the RustProxy suite separately with
`bash dev.sh test` on a host that has Cargo.
+172
View File
@@ -0,0 +1,172 @@
# Monitoring, metrics and health
RustProxy has operational views in the authenticated panel and an optional standalone Prometheus exporter. They measure different things: dashboard sampling, process-lifetime counters and stored logs are not interchangeable.
## What to watch
| View | Main information | Interpretation |
|---|---|---|
| **Overview** | Active connections, request rate, errors, process CPU/RAM, protocols, backend state, optional charts | Dashboard samples are not long-term billing data. |
| **Vhosts → Backends** | Member health, weights, H1/H2 modes, current work, last observed upstream response | A `last_status` from normal proxy traffic is not an active health probe result. |
| **Web Server** | Requests/status classes, static/PHP traffic, response-header time, response bytes, PHP concurrency permits | Response header duration excludes streamed body transfer. PHP permits are not FPM child processes. |
| **Traffic** | Per-Vhost totals and top countries/IPs/paths/methods/agents/cache results | Bounded in-memory analytics, approximate top lists and possible sample loss under heavy load. |
| **Cache** | HOT/COLD occupancy, hit/miss/bypass, object metadata, selected invalidation | HTTP `Age` differs from cache residence age. |
| **Docker / Streams** | Agent status, dynamic Vhost health, TCP/UDP sessions and traffic | An agent can be waiting for first observation; Streams are non-HTTP. |
| **Logs** | Live requests, stored JSONL/error/protocol records | A log sink can be disabled while live memory view remains available. |
Vhost and Docker Vhost live request-rate charts use a **recent-traffic adaptive Y scale** so isolated high spikes do not flatten ordinary request activity. Values above the chart range touch the top edge, while the peak text remains the genuine largest retained sample; hover the legend to inspect the plotted range. This visualization does not change lifetime request counters or Prometheus/JSON metrics. See [Vhosts](VHOSTS.md) and [Docker discovery](DOCKER_DISCOVERY.md).
Overview allows account-specific card layout and history controls; see [Dashboard](DASHBOARD.md). GeoIP/country analytics depend on the installed/update database and whether country capture is enabled.
**Settings → System information** shows the operating-system/hardware snapshot and **Statistics source**. The old dedicated process-information block is no longer displayed there; this is a panel presentation change only. Process CPU/RAM and related measurements continue to be available in the Overview and authenticated metrics APIs where supported. **Settings → Appearance → Data refresh** stores the per-account refresh intervals; switching off automatic refresh does not disable manual refresh.
## Prometheus exporter
The exporter is **disabled by default** and uses a separate HTTP listener, normally `127.0.0.1:9191`, at `/metrics`. It is not protected by the admin browser cookie; it has its **own** client-network and bearer-token policy. In **Settings → Prometheus** enable the endpoint and configure:
Use the separate **Prometheus token** controls in Settings to generate, rotate or revoke a 32+ character Bearer credential. Its clear value appears only once; copy it directly into your monitoring secrets store. The database keeps only its hash, and rotation/revocation takes effect without reprinting the previous secret. The older environment-variable credential remains supported as a fallback *only until* a generated credential or explicit revocation entry exists. Setting a new token does not reconfigure the exporter listener or its enable switch.
- `listen`, `path`, `allowed_cidrs` and an optional environment-variable **name** `bearer_token_env` (the secret value is supplied separately to the service).
- `allow_trusted_network_without_token` only for a deliberately restricted monitoring subnet. `/0` CIDRs are not accepted in this mode.
- Collector switches for process/system, Vhosts, traffic, backends, Docker, cache domains, Streams and logging.
A non-loopback exporter requires a valid generated or environment Bearer token at scrape time, or an explicit token-free trusted-network exception; otherwise requests return 401. The exporter uses plain HTTP; keep it on loopback, trusted network or a protected TLS tunnel. `Forwarded`/`X-Forwarded-For` headers cannot grant exporter access because network restrictions apply to the transport peer.
For legacy environment-token authentication, provide a 32–4096-character printable non-whitespace ASCII token in a **service environment variable** and set `bearer_token_env` to its name, e.g. `RUST_PROXY_METRICS_TOKEN`. The generated-token workflow above avoids this environment variable. Changing exporter activation/bind/token-variable usually needs restart; some collector/CIDR settings apply live. The exact allowed GET path must not have a query string.
Example scrape configuration, after choosing the correct private target:
```yaml
scrape_configs:
- job_name: rustproxy
static_configs:
- targets: ['127.0.0.1:9191']
```
Certificate expiry is exported for every installed certificate, including manually uploaded ones: `rust_proxy_certificate_not_after_timestamp_seconds{certificate="…"}` and `rust_proxy_certificate_days_remaining{certificate="…"}` (negative after expiry). Values are read from the certificate files and refreshed at most every five minutes. Example alert:
```yaml
- alert: RustProxyCertificateExpiresSoon
expr: rust_proxy_certificate_days_remaining < 14
for: 1h
```
When using a bearer token, configure Prometheus `authorization` or `bearer_token_file` securely rather than embedding a clear token in an exported dashboard. Review `grafana/README.md` and `grafana/rustproxy-overview.json` for chart examples; `nagios/README.md` contains external check scripts.
## JSON metrics API for external dashboards
RustProxy also offers a **plain, read-only JSON snapshot**. It does not use SSE,
WebSockets or persistent subscriptions: clients poll using ordinary `GET` (for
example, every 2–5 seconds).
| Endpoint | Where | Authentication |
|---|---|---|
| `GET /api/metrics/json` | Administration listener (default `127.0.0.1:9090`) | Admin session or a **General API** `ro`/`rw` Bearer token from **Settings → Security**, restricted by allowed CIDRs |
| `GET /api/metrics/summary` | Administration listener, compact calculated metrics only | **Same** admin session/General API Bearer credentials and CIDR policy |
| `GET /metrics.json` | Dedicated exporter (default `127.0.0.1:9191`, **only when Prometheus is enabled**) | Same allowed CIDRs and exporter Bearer token as `/metrics`; not an admin/API token |
| `GET /metrics.summary.json` | Dedicated exporter, compact calculated metrics only | **Same** exporter Bearer token/CIDR policy, if enabled |
When the exporter `path` differs from `/metrics`, the JSON routes append
`.json` and `.summary.json` to that path (e.g. `/private/stats` →
`/private/stats.json` and `/private/stats.summary.json`). Exporter routes
reject query strings. Every JSON response uses `application/json` and
`Cache-Control: no-store`; do not expose these endpoints publicly.
For a dedicated integration, issue a General API token with **read-only**
permission and a narrow `allowed_cidrs` set. Example from a permitted client:
```sh
curl -fsS -H "Authorization: Bearer $RUSTPROXY_API_TOKEN" \
http://127.0.0.1:9090/api/metrics/json | jq '{vhosts: .vhosts | {total,enabled,disabled}, requests: .requests.total, cpu: .system.cpu_percent, ram_bytes: .system.process_rss_bytes}'
```
### Summary: ready for dashboard cards
For the shortest response, poll **`/api/metrics/summary`** (or the standalone
exporter's `.summary.json` sibling) instead of loading every Prometheus sample.
The full `/api/metrics/json` includes the **same calculated object** at `summary`.
The response groups all values into `vhosts`, `backends`, `traffic`, `system`,
`cache`, `web_server`, `streams` and `capacity`, plus a collection timestamp.
```sh
curl -fsS -H "Authorization: Bearer $RUSTPROXY_API_TOKEN" \
http://127.0.0.1:9090/api/metrics/summary | \
jq '{working_vhosts: .vhosts.serving_estimate, backends: .backends.total, \
backend_up: .backends.up, rps: .traffic.requests_per_second, \
traffic_mb: .traffic.megabytes_total, cpu: .system.cpu_percent, \
ram_percent: .system.memory_percent}'
```
- `vhosts.enabled` is the configured on/off flag. `vhosts.serving_estimate`
counts enabled vhosts using a ready backend pool or an embedded Web Server.
It is **not** an HTTP probe or guarantee of reachability; redirect hosts,
not-found hosts and Docker assignments are counted separately. Only enabled
vhosts with a compiled pool are included in `backend_ready` / `backend_not_ready`.
- `backends.total` counts **live compiled pool members including Docker**.
`configured_static` counts saved static members. `up`, `down`, `checking`,
`draining`, `disabled`, `unknown`, `pools_ready` and `active_exchanges`
are independently calculated; `traffic_bytes_per_second` sums moving
backend rates and **does not** represent all public HTTP traffic.
- `traffic.requests_total`, `bytes_in_total`, `bytes_out_total` and `bytes_total`
are cumulative process counters; `megabytes_total` uses decimal MB (10⁶ B).
`requests_per_second`, `errors_per_second`, `bytes_in_per_second`,
`bytes_out_per_second` and `bytes_per_second` are **rolling poll interval
rates** computed on the server from a shared bounded sample (minimum 1 s,
maximum 30 s between usable samples). An initial poll, restart, counter reset
or gap longer than 30 s yields `null` until a second valid poll. Very rapid
simultaneous polls reuse the latest completed interval. These are not
guaranteed exact one-second rolling averages; poll every **2–5 seconds**.
`requests_per_second_average` is a separate **uptime average**.
- `traffic.error_percent` is lifetime errors divided by completed requests;
`cache.hit_percent` is lifetime HOT/COLD hit ratio from cache metrics.
Zero-denominator percentages are `null` unless a cache ratio has a defined
default. The `traffic.network_rx_bytes_per_second` and
`network_tx_bytes_per_second` fields are **host** interface rates, not
RustProxy-exclusive transfer; backend rate is separate.
- `system.cpu_percent`, `process_cpu_percent`, `memory_percent`, RAM and RSS
reflect the last OS sample (`system.sampled_at_ms`). Unsupported values can
be `null`. `web_server.requests_total` adds observed Web Server responses,
while `streams.active_sessions` adds observed Layer-4 sessions.
The JSON is organized for simple chart consumption:
- `summary` contains the small, calculated metrics object described above.
- `vhosts` includes `total`, `enabled`, `disabled`, backend readiness and an array of per-Vhost counters.
- `requests` includes process-lifetime totals, active exchanges, errors, status
classes, byte counters, latency, active connections and H1/H2/H3/WebSockets.
- `system` includes sampled host/process CPU, memory, swap, process I/O/FDs, network interfaces and throughput.
- `backends`, `web_server`, `streams`, `docker`, `cache`, `traffic`, `transform`,
`logging` and `capacity` contain their corresponding operational summaries.
- `prometheus.types` and `prometheus.samples` include **every numeric sample**
exported by the existing Prometheus text renderer, with label pairs as JSON
objects. Histogram buckets/counts/sums are included. The normal Prometheus
collector switches still control this detailed section.
Example field access:
```js
const snapshot = await (await fetch('/api/metrics/json')).json();
const vhostsEnabled = snapshot.vhosts.enabled;
const requestTotal = snapshot.requests.total;
const cpuPercent = snapshot.system.cpu_percent; // can be null before sampling
const samples = snapshot.prometheus.samples.rust_proxy_vhost_requests_total ?? [];
// samples[0] -> { labels: { vhost: 'site1' }, value: 123 }
```
`collected_at_ms` is the JSON snapshot time; `system.sampled_at_ms` is the
last OS collector sample time (the OS collector runs in the background).
Raw counters represent cumulative totals, **not instantaneous RPS**. Use the
server-computed interval rates from `summary` or calculate
`(new_total - old_total) / elapsed_seconds` from raw counters in the client;
reset the baseline after counter decreases, process restart or config reset.
Absent/unsupported OS values are `null`; non-finite Prometheus values are also
represented as `null`. Traffic breakdowns are approximate and can lose samples
under load. The JSON excludes raw request paths, client IP addresses and raw
User-Agent values, matching the bounded-label privacy model of Prometheus.
## Metric privacy and limits
Prometheus series use bounded configured labels; arbitrary request URLs, raw client IPs, credentials, raw User-Agents and query strings are intentionally not exposed as unbounded labels. Dashboard traffic top lists can still contain sensitive operational metadata, so keep the panel authenticated.
Metrics reset on process replacement, and some per-Vhost/backend records can reset when identity or config changes. Distinguish counters, rates and gauges; watch for traffic sampling gaps rather than interpreting every missing point as zero. Before deployment, verify collector reachability, firewall rules, CPU/RAM overhead and alert thresholds against real traffic.
-24
View File
@@ -1,24 +0,0 @@
# Local-response / 404 hosts
RustProxy 0.22.3 has a dedicated **404 hosts** page for domains that should be
owned by RustProxy but must not contact a backend.
Each entry has the same frontend model as vhosts/redirects:
- one or more hostnames,
- named listener selection,
- optional TLS certificate,
- independent HTTP/2 and HTTP/3 switches.
The response can be:
- the built-in `RustProxy is running` placeholder,
- an application-style built-in status page for 401, 403, 404, 429, 503 or 504,
- an empty/status-only response,
- bounded custom HTML.
The status is selected rather than typed manually. Application-style pages expose only the bundled 401/403/404/429/503/504 templates. Placeholder, empty and custom-HTML modes expose the supported application error set: 400/401/403/404/408/413/429/500/502/503/504. HTTP/2 and HTTP/3 toggles are shown beside the listener controls because they apply to the selected frontend listeners. Host/listener ownership is
validated against vhosts and redirect hosts, so two active configuration objects
cannot claim the same frontend binding.
All 404-host responses remain local and never open an upstream connection. They are not stored in RustProxy cache. Before the response leaves RustProxy, cache policy/validator headers and RustProxy `X-Cache*` diagnostics are removed, so 404 hosts do not advertise cache behavior on the wire. The built-in `RustProxy is running` placeholder also intentionally omits optional RustProxy server, diagnostic, security, compression and backend headers.
-120
View File
@@ -1,120 +0,0 @@
# Nginx Proxy Manager migration
RustProxy ships `scripts/migration/migrate_npm.py` for a one-time, dry-run-first migration of
Nginx Proxy Manager (NPM) proxy hosts from its SQLite database.
The tool reads NPM only. It does not modify the NPM database and it never imports
Let's Encrypt/private-key material. It logs in to the RustProxy administration API,
reads the current revision, merges generated vhosts into the existing configuration,
and writes only when `--apply` is supplied.
`migrate_npm.py` is the CLI entry point and requires the sibling modules
`scripts/migration/migrate_npm_core.py` and `scripts/migration/migrate_npm_api.py`. They use only the
Python standard library, so no `pip` packages are required. If the migrator is copied
or mounted outside the RustProxy source tree, keep all three files in the same directory.
## What is migrated
For each NPM `proxy_host` row:
- `domain_names` -> RustProxy vhost hostnames, including leading `*.` wildcards;
- `forward_scheme`, `forward_host`, `forward_port` -> one RustProxy backend;
- `allow_websocket_upgrade` -> vhost WebSocket policy;
- `enabled` -> vhost enabled state;
- `http2_support` -> vhost HTTP/2 policy;
- NPM hosts with a certificate/forced SSL -> RustProxy TLS listener. Certificate
files are not copied: the application-managed RustProxy default certificate is
used unless `--certificate-id` names an existing RustProxy inventory certificate;
- `ssl_forced` -> RustProxy `force_ssl=true`. When HTTP and HTTPS listeners exist,
both are attached so cleartext requests can receive RustProxy's `308` redirect to
HTTPS. If no TLS listener exists, an SSL-forced NPM host is skipped instead of being
imported with weaker semantics.
Migrated backends use `allow_private=true` because NPM commonly proxies to LAN or
container origins. The migration tool follows RustProxy 0.22.1 and imports backend `tls_verify=false` unless you later enable verification explicitly.
Use `--npm-compatible-backend-tls` when an NPM HTTPS origin depends on
the common Nginx behavior of not verifying the upstream certificate; review that
exception after migration.
The following are reported for manual review rather than guessed: NPM access lists,
advanced Nginx snippets, custom locations, NPM cache settings and `block_exploits`.
Existing RustProxy hostname ownership is never silently replaced; conflicting NPM
rows are skipped and shown in the report.
## SQLite location
The standard NPM SQLite path inside the container is `/data/database.sqlite`. On a
Docker host with a bind mount, pass the corresponding host path. For a named volume,
`docker volume inspect` shows its mountpoint.
Read the live mounted database rather than copying only `database.sqlite` from a
running WAL-mode instance; copying without its WAL file can omit recent changes.
## Dry run
Set the password in the environment or omit it and use the interactive prompt:
```bash
export RUSTPROXY_USERNAME=admin
export RUSTPROXY_PASSWORD='...'
python3 scripts/migration/migrate_npm.py \
--database /path/to/npm/data/database.sqlite \
--rustproxy https://rustproxy.example.net:9090
```
The default run is read-only. It creates `npm-migration-plan.json` with mode `0600`
and prints every imported/skipped NPM row plus manual-review warnings.
If the administration panel intentionally uses the application self-signed
certificate and the script connects directly to it, add:
```bash
--insecure-admin-tls
```
Prefer `--ca-file /path/to/ca.pem` or a hostname with a trusted certificate where
available.
By default the first enabled HTTP listener and first enabled TLS listener are used.
For `ssl_forced` hosts both are selected when available. `--force-https` applies the
same Force SSL policy to every imported host. Select explicit listener IDs when needed:
```bash
--http-listener public-http --https-listener public-https
```
## Apply
After reviewing `npm-migration-plan.json`:
```bash
python3 scripts/migration/migrate_npm.py \
--database /path/to/npm/data/database.sqlite \
--rustproxy https://rustproxy.example.net:9090 \
--apply
```
The write uses the revision returned by `GET /api/config`; a concurrent RustProxy
configuration change causes the API compare-and-swap to fail rather than overwrite a
newer revision.
## Running from a helper container
If the Docker host does not have Python 3, mount the NPM data read-only and mount the
RustProxy source/script into a temporary Python container. For example, from the
RustProxy directory:
```bash
docker run --rm -it \
--network host \
-v /path/to/npm/data:/npm:ro \
-v "$PWD/scripts/migration:/rustproxy-migration:ro" \
python:3.13-alpine \
python /rustproxy-migration/migrate_npm.py \
--database /npm/database.sqlite \
--rustproxy https://127.0.0.1:9090 \
--insecure-admin-tls
```
Use `--apply` only after the dry-run plan is reviewed.
+113
View File
@@ -0,0 +1,113 @@
# Performance
RustProxy runs on Tokio and Hyper with an asynchronous, streaming request path. This guide describes how performance is measured, where RustProxy stands compared with a similarly configured Nginx, and which settings matter in production.
## What is measured
Results are only comparable when both servers do the same work. A stock Nginx installation does very little per request, while RustProxy always records request statistics, traffic analytics and access logs and adds forwarding headers. The comparison therefore uses an Nginx configuration with a similar feature set:
| Feature | Nginx | RustProxy |
|---|---|---|
| Access log | `log_format` with timing fields, 64 KiB buffer | Built-in access log and live statistics (default) |
| Forwarding headers | `Host`, `X-Forwarded-For`, `X-Forwarded-Proto`, `X-Real-IP` | `Forwarded`, `X-Forwarded-*`, `X-Real-IP`, `X-Request-Id` (default) |
| Response headers | `X-Request-ID`, `X-Content-Type-Options` | Request ID and default security headers |
| Upstream keep-alive | `keepalive 64`, HTTP/1.1 | Connection pool (default) |
| Cache | `proxy_cache` with `proxy_cache_lock` | HOT memory cache with request collapsing |
| Compression | `gzip on` | gzip/Brotli/Zstandard negotiation (default) |
| Static files | `root`, `etag on` | Web Service with validators (default) |
## Method
- Machine: 2 vCPU virtual machine, Linux 6.18, loopback networking.
- Each server is pinned to one CPU core (`worker_processes 1` for Nginx, `taskset` for RustProxy). The load generator and the origin share the second core.
- Load: `wrk -t1 -c50 -d8s`, HTTP/1.1 keep-alive, two or three runs per scenario, average reported.
- Origin: Nginx serving static files with `Cache-Control: public, max-age=600`.
- Nginx 1.24 and RustProxy release builds (`lto = "thin"`, `codegen-units = 1`).
## Results
Requests per second on one CPU core (higher is better):
| Scenario | Nginx | RustProxy 0.35.0 | RustProxy 0.35.2 | 0.35.2 vs 0.35.0 |
|---|---:|---:|---:|---:|
| Reverse proxy, 1 KiB response | 61,400 | 14,700 | 15,100 | +3% |
| Reverse proxy, 100 KiB response | 10,600 | 14,500 | 15,500 | +7% |
| Cache hit, 1 KiB response | 53,300 | 22,500 | 25,000 | +11% |
| Web Server, 1 KiB file | 85,000 | 15,200 | 26,000 | +71% |
| Web Server, 100 KiB file | 25,900 | 9,200 | 16,900 | +84% |
Reading the results:
- **Large proxied responses:** RustProxy streams the body directly between connections and is faster than Nginx in its default buffering mode, which spools larger upstream responses to temporary files.
- **Small requests:** Nginx remains ahead, because its fixed per-request cost is lower. In RustProxy this cost is dominated by per-request accounting (live statistics, traffic analytics, request IDs and logs) and by the asynchronous runtime. For most deployments it is far below the capacity of the backends behind the proxy.
- **Static files:** since 0.35.1 RustProxy reads files directly on the request task instead of a background thread pool, which nearly doubles static throughput.
- **Why small requests and cache hits trail Nginx:** each proxied request crosses the loopback network twice (client and backend), and on a VM about half of the CPU time goes to the kernel for those writes and the wake-ups of the other side. Most of the remaining time is RustProxy's per-request work that Nginx does not do in the same way: request IDs, live per-Vhost statistics, traffic analytics and structured access-log entries, plus the asynchronous runtime. A cache hit saves the backend hop but still pays for this accounting, so its gap is similar. No single function dominates the profile; closing the gap further would require architectural changes rather than tuning.
- **CPU per request:** at the 1 KiB proxy scenario RustProxy uses about 70 µs of CPU time per request on the test machine, Nginx about 17 µs. A single core therefore sustains well over 10,000 proxied requests per second.
Throughput scales with additional CPU cores; RustProxy uses all available cores automatically.
## Memory allocator
Release builds use the operating system's allocator (glibc on most Linux distributions). An alternative allocator, [mimalloc](https://github.com/microsoft/mimalloc), can be compiled in as an optional feature:
```bash
cargo build --release --features mimalloc
```
It is off by default because of how it behaves with a large HOT cache. Measured on the test machine with a 300 MiB HOT cache under constant turnover (random objects of 1–260 KiB, continuous evictions):
| Build | Process memory (RSS) | Throughput under cache turnover |
|---|---:|---:|
| System allocator (default) | 614 MiB | 5,700 req/s |
| mimalloc | 760 MiB (+24%) | 20,300 req/s (3.5×) |
| mimalloc with `MIMALLOC_PURGE_DELAY=0` | 720 MiB (+17%) | 18,600 req/s |
In the standard scenarios from the results table (proxy, cache hits, static files) the two allocators performed within measurement noise. On a production server with a 600 MiB HOT cache the same difference showed as about 800 MiB versus 1.1–1.2 GiB of process memory.
Choose mimalloc only when all of these apply:
- the HOT cache holds many objects that are replaced constantly (high eviction rate on the **Cache** page);
- throughput under that turnover matters more than memory;
- the server has at least 30% RAM headroom above the configured HOT cache size.
Otherwise keep the default. The system allocator returns memory from evicted cache entries to the operating system shortly after eviction, so process memory stays close to the configured cache size.
**Since 0.35.4** the default build returns freed memory to the operating system at most four times per second (plus the regular cache maintenance pass) instead of after every eviction. Most of the system allocator's slowdown under turnover came from these repeated trims, so the gap to mimalloc is now much smaller. Measured on the same 2 vCPU machine, one core, 256 MiB HOT cache filled with 100 KiB objects and every request a new URL:
| Build | RSS after load | Throughput under cache turnover |
|---|---:|---:|
| 0.35.3, system allocator | 595 MiB | 2,900 req/s |
| 0.35.4, system allocator | 639 MiB (+7%) | 7,000 req/s (2.4×) |
The mimalloc rows above were measured with 0.35.2 and a different object mix; re-measure on your own workload before choosing the optional build.
### Where the memory goes
Process memory is always larger than the configured HOT cache. The HOT size already includes RustProxy's estimate of per-object bookkeeping (index, keys, headers), but not allocator rounding and fragmentation, connection and TLS buffers, the runtime, logs or GeoIP data. Enable the optional **Memory breakdown** Overview card to see the split for your workload: if the "Outside cache" trend stays flat while the cache is full, the difference is overhead or fragmentation; a steady rise points to memory that is not released. For reference, Varnish documents about 1 KiB of per-object overhead outside its storage size and notes that total use can reach about twice the configured storage.
## Production tuning
| Setting | Recommendation |
|---|---|
| Cache | Enable the HOT cache for cacheable content; hits avoid the backend completely. |
| Compression | Keep it enabled for text content. Use precompressed `.br`/`.gz` files for static sites. |
| Upstream protocol | Prefer HTTP/1.1 keep-alive or HTTP/2 to backends; avoid backends that close every connection. |
| Health checks | Use intervals of several seconds; frequent probes consume capacity on large installations. |
| Logging | Keep stored log files on fast local storage and use retention limits on busy servers. |
| Limits | `--max-connections` (default 512) and `--max-inflight` (default 256) cap concurrency. Raise them on busy servers with enough memory. |
| `sendfile` | Optional for large cleartext static files on Linux. On the test machine it gave no measurable gain, so keep `off` unless your own measurements show otherwise. |
| Memory allocator | Keep the default system allocator. See [Memory allocator](#memory-allocator) for when the optional `mimalloc` build is worth its extra memory. |
| TLS | TLS 1.3 with session resumption keeps handshake cost low; HTTP/2 or HTTP/3 reduce connection setup for browsers. |
## Reproducing the test
1. Start an origin (any static HTTP server) with a 1 KiB and a 100 KiB file.
2. Configure RustProxy with one Vhost pointing to the origin and one Web Service serving the same directory.
3. Configure Nginx with the equivalent features from the table above.
4. Pin each server to the same number of cores and run `wrk` from a different core:
```bash
taskset -c 1 wrk -t1 -c50 -d8s -H "Host: proxy.example" http://127.0.0.1:8080/1k.txt
```
Always compare matched configurations on the same machine, and repeat each run. Results on physical hardware and real networks differ from loopback measurements.
+40 -403
View File
@@ -1,417 +1,54 @@
# PHP-FPM / FastCGI for the Web Server
# PHP-FPM with Web Services
**Separate pools for distinct Web Services:** Reusing the same PHP-FPM socket
with different `PHP_ADMIN_VALUE` maps can leak `open_basedir` restrictions into
a different virtual host. See the 0.29.7 guide
[PHP_FPM_ISOLATION.md](PHP_FPM_ISOLATION.md) for remediation and tests.
RustProxy's Web Server supports **FastCGI** forwarding to an existing **PHP-FPM** pool for configured script extensions. RustProxy does not include a PHP interpreter or run the PHP application itself.
RustProxy 0.25 can execute configured PHP extensions through a long-running PHP-FPM pool while preserving the reusable Web Service architecture.
## Before enabling PHP
## Architecture
Install and start PHP-FPM independently, configure the application files and create a **dedicated pool/socket for each application or security domain**. Set the correct Unix user/group, socket permissions, PHP version and pool-level configuration. Restrict a TCP FPM endpoint to a trusted private interface; never expose FastCGI directly to the Internet.
In **Web Server → PHP-FPM profiles** create a reusable profile, or set PHP options in one Web Service. Select a socket such as `unix:/run/php/app.sock` or a reachable TCP endpoint (use the syntax accepted by the editor); enable PHP, choose extensions (default `.php`) and set request/connect timeouts and concurrency. Attach the profile to a Web Service and publish that service using a Vhost.
## Important settings
| Setting | Effect |
|---|---|
| **Endpoint** | Unix socket or private TCP address of a real PHP-FPM service. |
| **Extensions** | Which files are eligible for PHP execution. Confirm front-controller routes and direct PHP paths. |
| **`try_files` / PATH_INFO** | Resolve PHP front-controller or `script.php/...` paths, where supported, without accidentally executing an unintended file. |
| **Connect/request timeouts** | Bound waiting for FPM connection and application execution. Defaults in source include 1 s connect and 30 s request. |
| **Concurrent requests** | Local RustProxy FastCGI request limit (default 64), **not** the number of FPM worker processes. |
| **Authorization forwarding** | Optional preservation of eligible `Authorization` information (default enabled). Review application expectations. |
| **Custom FastCGI parameters** | Static application-specific settings. Reserved transport/identity parameters cannot be overridden arbitrarily. |
| **PHP values** | `PHP_VALUE` and `PHP_ADMIN_VALUE` options for the external FPM workers. |
| **Body/header limits** | Bound upload size, response headers and stderr exposed in logs. |
## Pool isolation is mandatory for mixed applications
PHP-FPM workers can retain `PHP_VALUE` / `PHP_ADMIN_VALUE` behavior across requests. **Two applications with different INI/admin settings must not share one FPM worker pool**, even when they have different RustProxy Vhosts. RustProxy may warn about conflicting shared endpoints but cannot isolate PHP workers inside FPM.
Example separation:
```text
Request
-> existing listener / vhost / location / ACL / auth
-> Web Server -> Web Service
|- static file
`- configured PHP extension -> FastCGI -> PHP-FPM
-> existing error/header/logging/metrics/transport pipeline
Web Service: blog -> unix:/run/php/php_blog.sock
Web Service: webmail -> unix:/run/php/php_webmail.sock
```
Vhosts and locations still reference only a Web Service. Each Web Service may
use local PHP settings or reference a shared `php_fpm_profiles` entry from the
normal revisioned configuration; its local settings are retained on detach.
Give each pool access only to its own application's files; configure `open_basedir` and other hardening at FPM pool level where possible. Restart the affected FPM pools after policy changes to retire existing workers.
### Shared PHP-FPM profiles (0.29.4)
## WordPress and Roundcube
In **Web Server → Edit Web Service → PHP-FPM**, select **Local configuration**
or a named profile. **Manage PHP-FPM profiles** opens a modal with an expandable
editor for every profile and an **Add profile** action. A profile contains the
same PHP execution, FastCGI and PHP_VALUE/PHP_ADMIN_VALUE settings as local
configuration. Changes to a shared profile affect all attached services after
validation and apply. In-use profiles cannot be deleted.
For **WordPress**, point the document root to the website directory, enable PHP for `.php`, use the appropriate front-controller (`index.php`) and verify that `wp-config.php` exists and is readable only where needed. A redirect to `setup-config.php` can indicate filesystem/FPM/open_basedir misconfiguration rather than proxy cache.
Configuration example:
For **Roundcube**, use its published web root and a separate FPM pool with the application's expected file access. Some Roundcube static script URLs use `PATH_INFO`; enable/test handling that resolves `static.php/...` without turning arbitrary strings into executable script names.
```json
{
"php_fpm_profiles": [{
"id": "php82", "name": "PHP 8.2 FPM",
"php": {"enabled": true, "endpoint": "unix:/run/php/php8.2-fpm.sock"}
}],
"web_services": [{
"id": "site", "name": "Site", "document_root": "/var/www/site",
"php_profile_id": "php82"
}]
}
```
These are routing examples; presets do not install either application or choose a safe OS-specific pool automatically.
Unspecified PHP fields inherit the safe serde defaults. A service without
`php_profile_id` uses its own `php` object as in earlier versions.
## Diagnostics
## Where to configure each PHP setting (0.29.7)
- `502/503/504`: check FPM health, Unix socket group permissions, endpoint path, capacity and configured timeouts.
- PHP errors/blank page: check FPM stderr, application logs and effective INI values.
- Incorrect site appears to inherit another site's `open_basedir`: use distinct FPM pools and restart them.
- Compressed PHP content truncated: verify current application response/framing and try the same request without compression; review FastCGI and response logs.
- Cache behavior differs from static files: PHP may ignore validators or send `private` / `Set-Cookie`, preventing shared cache storage.
Both editors expose the **same fields and validation**:
1. **Web Server → Web Service → PHP-FPM → Local configuration:** set values on one service (`web_services[].php`). Select **Local configuration** in the profile selector first.
2. **Web Server → PHP-FPM profiles → Add/Edit profile:** set shared values on a profile (`php_fpm_profiles[].php`), then attach it in a Web Service's PHP-FPM tab (`php_profile_id`). Editing the profile changes every attached service. Per-service `php` values remain saved for later detach but do **not** override an attached profile.
3. **Actual PHP-FPM pool config** (`php-fpm.d/*.conf` or distribution equivalent): the pool must exist and listen on the configured socket. Isolate restrictive `open_basedir`/`php_admin_value` settings in *separate pools and sockets*, not by alternating request-time `PHP_ADMIN_VALUE` on shared workers. RustProxy does not launch PHP-FPM.
| UI field (JSON property) | What to enter / sample | Notes |
| --- | --- | --- |
| Enable PHP-FPM / FastCGI (`enabled`) | Checked for PHP apps | Off means PHP is not executed. Never serve PHP source. |
| PHP-FPM socket (`endpoint`) | `unix:/run/php/php_wordpress.sock`, `tcp:127.0.0.1:9000`, `tcp:[::1]:9000` | Only local Unix sockets or TCP loopback **IP**, not hostnames. Socket ownership and permissions must allow RustProxy access. |
| PHP extensions (`extensions`) | `php, phtml` | Comma-separated, no leading dots; at most 16. Only listed extensions are executable. |
| Enable PHP PATH_INFO splitting (`path_info`) | Checked for Roundcube `/static.php/skins/...` | Splits an existing PHP script path and its trailing URL; off for ordinary WordPress unless needed. |
| Forward Authorization (`pass_authorization`) | Checked when API / Basic / Bearer auth is used | Sends request `Authorization` to `HTTP_AUTHORIZATION` in FastCGI. |
| Connection timeout (`connect_timeout_ms`) | `1000` = 1 second | Integer 50–60000 milliseconds. |
| Request timeout (`request_timeout_ms`) | `30000` = 30 seconds | Integer 100–3600000 milliseconds for the full FastCGI request. |
| Max simultaneous PHP requests (`max_concurrent_requests`) | `64` | Integer 1–4096 per Web Service; excess is rejected with HTTP 503. |
| Request-body limit (`max_request_body_bytes`) | `67108864` = 64 MiB; `0` = no additional limit | Integer 0–8589934592 **bytes**, independent of PHP `post_max_size`. |
| Response-header limit (`max_response_header_bytes`) | `65536` = 64 KiB | Integer 4096–1048576 bytes. |
| PHP-FPM STDERR log limit (`stderr_log_bytes`) | `4096`; `0` = disabled | Integer 0–65536 bytes collected per request. |
| Custom FastCGI parameters (`custom_params`) | `APP_ENV = production` | One static `NAME = value` per line. Names A-Z/0-9/underscore, beginning with a letter. No core CGI, HTTP_* or PHP control params. |
| PHP INI overrides (`php_values` / `PHP_VALUE`) | `memory_limit = 256M` | One php.ini `directive = value` per line. PHP decides which directives can change at this scope. |
| Administrator PHP INI overrides (`php_admin_values` / `PHP_ADMIN_VALUE`) | `open_basedir = /var/www/webmail/:/tmp/` | Trusted operator only; for security isolation prefer dedicated PHP-FPM pool configuration. Do not define the same directive in both INI maps. |
**Syntax in the three multiline fields is not JSON:** use `key = value`, a separate line per entry; **do not** wrap it in `PHP_VALUE=...` or `php_admin_value[...]` inside the RustProxy editor. Those latter directives belong to the *native PHP-FPM pool configuration*, not the RustProxy FastCGI request fields. Empty values and control characters are rejected. Up to 64 entries per map, maximum 8192 bytes per value and 128 KiB combined.
### Ready-to-use WordPress and Roundcube examples
**WordPress — Web Service `/var/www/linuxiarz.pl/`, dedicated FPM socket:**
```text
PHP enabled: checked
PHP PATH_INFO: unchecked
Endpoint: unix:/run/php/php_wordpress.sock
PHP extensions: php
PHP_VALUE:
memory_limit = 256M
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 60
PHP_ADMIN_VALUE: leave empty (or set security restrictions in WordPress's own FPM pool)
```
Use `index.php` in **Index files**, plus `try_files` lines `$uri`, `$uri/`, `/index.php?$query_string` in **Routing**. The WordPress pool must be allowed to read its `wp-config.php`, `wp-includes` and `wp-content` directories.
**Roundcube — Web Service `/var/www/webmail/public_html`, separate FPM socket:**
```text
PHP enabled: checked
PHP PATH_INFO: checked
Endpoint: unix:/run/php/php_roundcube.sock
PHP extensions: php
PHP_VALUE:
memory_limit = 256M
upload_max_filesize = 64M
post_max_size = 64M
PHP_ADMIN_VALUE: prefer the native Roundcube PHP-FPM pool instead; e.g.:
open_basedir = /var/www/webmail/:/usr/lib/php/:/tmp/:/etc/enigma
```
Add additional permitted external paths only if Roundcube's actual dependencies need them. Roundcube assets such as `/static.php/skins/elastic/...` require PATH_INFO splitting. Restart/recycle workers after separating pools; using a different RustProxy profile but the **same socket** does not isolate the worker state. See [pool isolation](PHP_FPM_ISOLATION.md).
### FastCGI vs php.ini examples
```text
# Custom FastCGI parameters — application-specific static environment:
APP_ENV = production
APP_DEBUG = 0
# PHP_VALUE — regular PHP-level options:
memory_limit = 256M
max_execution_time = 60
error_reporting = E_ALL
log_errors = On
display_errors = Off
date.timezone = Europe/Warsaw
# PHP_ADMIN_VALUE — privileged operator-level options:
open_basedir = /var/www/webmail/:/usr/lib/php/:/tmp/
```
The `#` comments above are explanations separating fields; **do not paste them** into the corresponding form inputs. Boolean INI directives commonly accept `On`/`Off`, size directives can accept `K`/`M`/`G` and timeouts use seconds. The actual PHP/FPM version determines supported directives and permitted scopes.
## Runtime model
RustProxy implements the FastCGI protocol directly. It does not spawn `php`, `php-cgi` or shell processes for individual requests.
Supported endpoint syntax:
```text
unix:/run/php/php-fpm.sock
tcp:127.0.0.1:9000
tcp:[::1]:9000
```
Use the `unix:` prefix for a Unix-domain socket. Use `tcp:` followed by a literal loopback IP address and port for TCP. Hostnames and non-loopback addresses are rejected. Unix sockets are preferred for a local PHP-FPM installation; exposing FastCGI directly to an untrusted network is not supported by this configuration.
## Web Service configuration
```json
{
"index": ["index.html", "index.php"],
"try_files": ["$uri", "$uri/", "/index.php?$query_string"],
"php": {
"enabled": true,
"extensions": ["php"],
"endpoint": "unix:/run/php/php-fpm.sock",
"connect_timeout_ms": 1000,
"request_timeout_ms": 30000,
"max_concurrent_requests": 64,
"pass_authorization": true,
"max_request_body_bytes": 0,
"max_response_header_bytes": 65536,
"stderr_log_bytes": 4096,
"custom_params": {
"APP_ENV": "production"
},
"php_values": {
"memory_limit": "256M",
"upload_max_filesize": "64M",
"post_max_size": "64M"
}
},
"symlink_policy": "deny"
}
```
The normal ordered `index` list handles `index.php`; there is no separate PHP-index option.
### Additional PHP/FastCGI options
- `path_info`: default-off secure script/path splitting. When enabled, `/index.php/foo/bar` may resolve the existing `index.php` script and pass `/foo/bar` as `PATH_INFO`.
- `pass_authorization`: forwards the inbound `Authorization` header as `HTTP_AUTHORIZATION`. Disable it when the application does not need that header.
- `max_request_body_bytes`: streaming request-body limit enforced before/during FastCGI forwarding. `0` means no additional RustProxy-side limit. A request exceeding the configured value receives `413 Payload Too Large`.
- `max_response_header_bytes`: maximum bytes accepted before the CGI response-header terminator. The allowed range is 4096..1048576 bytes.
- `stderr_log_bytes`: maximum PHP-FPM stderr bytes retained for one diagnostic request entry. `0` disables stderr capture; maximum 65536.
- `custom_params`: trusted static FastCGI parameters supplied by the operator.
- `php_values`: php.ini-style overrides encoded into one validated FastCGI `PHP_VALUE` parameter.
### Custom FastCGI parameter syntax
In the administration UI use one entry per line:
```text
APP_ENV = production
APP_DEBUG = 0
TENANT = main
```
Names are normalized to uppercase and are limited to letters, digits and `_` (the first character must be a letter). Values are static: there is no request-variable interpolation in this field. RustProxy rejects core CGI parameters, `HTTP_*`, `PHP_VALUE` and `PHP_ADMIN_VALUE`, so custom values cannot replace `SCRIPT_FILENAME`, routing data, HTTP-derived fields or PHP control parameters. Up to 64 custom FastCGI parameters and 64 PHP INI overrides are accepted, with at most 128 KiB of combined custom parameter/value data.
### PHP INI (`PHP_VALUE`) syntax
Use one `directive = value` entry per line:
```text
memory_limit = 256M
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 30
```
RustProxy validates the lines and combines them with newline separators into the FastCGI `PHP_VALUE` parameter. PHP-FPM/PHP still decides whether a directive is changeable at this scope. `PHP_ADMIN_VALUE` is available separately through the validated administrator INI overrides for trusted PHP-FPM endpoints. Control characters/newlines inside names or values are rejected.
These custom parameters are stored as normal Web Service configuration. Do not put secrets there unless normal configuration storage is an acceptable location for them.
## `try_files` front controller
A typical application can use:
```text
$uri
$uri/
/index.php?$query_string
```
Existing static files/directories are used first. The final path becomes the fallback and can therefore reach `index.php`. This resolution remains inside the already-selected Web Service and does not introduce a second vhost router.
## Roundcube 1.7 static.php / PATH_INFO (0.29.5)
Roundcube serves CSS, JavaScript and images through URLs such as
`/static.php/skins/elastic/styles/styles.min.css?s=123`. These paths are
**PHP-FPM requests to the existing** `public_html/static.php` **script**, not
static filesystem lookups. Configure the selected Web Service (or attached
shared PHP-FPM profile) with `php.enabled: true`, `php.path_info: true`, and
`php.extensions: ["php"]`. Point its root to the Roundcube `public_html` tree.
You can keep the normal Web Service `try_files` (`$uri`, `$uri/`,
`/index.php?$query_string`). RustProxy 0.29.5 resolves a valid PHP script plus
PATH_INFO after security checks and rewrites, **before** falling back through
`try_files`. No regex rewrite or extra Web Service is required for these URLs
when the main vhost selects this Web Service.
For `/static.php/skins/elastic/images/favicon.ico?s=123`, FastCGI receives:
```text
SCRIPT_NAME=/static.php
SCRIPT_FILENAME=<document_root>/static.php
PATH_INFO=/skins/elastic/images/favicon.ico
REQUEST_URI=/static.php/skins/elastic/images/favicon.ico?s=123
QUERY_STRING=s=123
```
PHP-FPM still needs filesystem access to `static.php` and the Roundcube
installation outside `public_html` that script reads. The PHP `PATH_INFO`
option is explicit and remains off by default for unrelated services.
## Bounded execution
Each compiled Web Service owns a bounded PHP concurrency semaphore. `max_concurrent_requests` limits simultaneous FastCGI requests. RustProxy does not create an unbounded wait queue: if no execution slot is immediately available it returns `503 Service Unavailable` with `Retry-After`.
Connection establishment and complete FastCGI request execution have independent configured time limits. A connect failure produces a gateway error; a request timeout produces a gateway timeout. The outer normal RustProxy error-page processing remains able to replace these responses.
Request bodies are split into bounded FastCGI records rather than collected into one large buffer. When `max_request_body_bytes` is non-zero, the limit is checked both from `Content-Length` when available and while streaming chunked/unknown-length bodies. PHP-FPM stdout is parsed and delivered through a bounded response channel so application output can stream to the client.
## Secure script selection
Only configured extensions are executable. Before PHP is selected, RustProxy resolves the request/`try_files`/index path using the Web Server filesystem confinement rules and verifies that it is a regular file below the document root.
When PHP handling was selected:
- PHP source is never returned as a fallback;
- an unavailable PHP-FPM endpoint produces 502/504 behavior rather than source disclosure;
- encoded traversal/separators and `..` remain rejected;
- the current implementation requires `symlink_policy=deny`.
The last restriction is deliberate: PHP-FPM opens `SCRIPT_FILENAME` independently, so RustProxy does not claim an `inside_root` symlink guarantee that the external process cannot enforce atomically.
## FastCGI parameters
RustProxy supplies a controlled CGI/FastCGI parameter set including:
- `REQUEST_METHOD`
- `SCRIPT_FILENAME`
- `SCRIPT_NAME`
- `PATH_INFO` when `path_info` is enabled and a trailing path was securely split from an existing PHP script
- `QUERY_STRING`
- `REQUEST_URI`
- `DOCUMENT_ROOT`
- `SERVER_PROTOCOL`
- `SERVER_NAME`
- `SERVER_PORT`
- `REMOTE_ADDR`
- `REMOTE_PORT` when available
- `HTTPS`
- `GATEWAY_INTERFACE`
- `SERVER_SOFTWARE`
- `REDIRECT_STATUS`
- `CONTENT_TYPE`
- `CONTENT_LENGTH`
- selected request headers as `HTTP_*`
Request headers cannot become FastCGI `PHP_VALUE` or `PHP_ADMIN_VALUE` parameters. Operator-defined `custom_params` are allowed only through the validated Web Service configuration and cannot override core CGI/HTTP-derived fields. Operator-defined `php_values` are the only path that creates `PHP_VALUE`; `PHP_ADMIN_VALUE` is never generated.
An inbound `Proxy` header is **never** translated into FastCGI `HTTP_PROXY` (httpoxy mitigation), regardless of the optional Web Service hardening switch. The **Block common attack probes** option additionally blocks high-confidence sensitive/probe paths before PHP-FPM dispatch. FastCGI output cannot control downstream `Content-Length`, transfer framing, connection headers or `Proxy-*` transport headers; RustProxy owns downstream response framing.
`PATH_INFO` splitting is optional and disabled by default. When enabled, RustProxy searches for an existing configured PHP script prefix inside the document root, keeps `SCRIPT_FILENAME`/`SCRIPT_NAME` on that script, and supplies only the remaining suffix as `PATH_INFO`. `REQUEST_URI` remains the original request target.
## Response parsing
The FastCGI stdout parser understands standard CGI response headers, including `Status`. Hop-by-hop transport headers are stripped before returning the response through RustProxy.
For `HEAD`, PHP-FPM can still execute normally but the client response body is empty. GET/POST/PUT and other methods can reach a PHP script once routing resolves to a configured executable extension.
PHP stderr is bounded by `stderr_log_bytes` and recorded as a RustProxy `WARN` diagnostic. It goes to the configured error file when enabled, or the console/systemd journal otherwise; it is not sent to the client as page content. CGI response headers are separately bounded by `max_response_header_bytes`.
## Caching
PHP output does not use static file validators or the static-file OS representation path. Application response headers and the normal vhost/location response-header pipeline control client/intermediary caching.
The Web Service static `cache_rules` apply to local static file responses, not
to PHP stdout. When global/vhost caching is enabled, explicitly cacheable
PHP-FPM responses may enter the shared RustProxy cache; responses with
`Set-Cookie`, `Cache-Control: private` or `no-store` cannot be stored. In
particular, do not enable forced caching of authenticated WordPress pages
without auditing the origin response policy. HTTP/2 and HTTP/3 `:authority`
requests populate the `HTTP_HOST` FastCGI parameter, retaining the original
host/port from the incoming request when provided.
## PHP installation
RustProxy does not bundle PHP. Install and maintain PHP-FPM using the operating system/package ecosystem and enable only the extensions required by the application.
Operational requirements:
- a running PHP-FPM 8.x service supported by the operator's platform;
- a dedicated Unix socket or loopback TCP listener reachable by the RustProxy service account;
- document-root permissions usable by both RustProxy and the PHP-FPM worker identity as intended;
- correct socket owner/group/mode when using Unix sockets;
- PHP-FPM limits such as `pm.max_children`, application execution timeout and upload/body limits sized consistently with the RustProxy concurrency limit.
Extensions such as PDO drivers, mbstring, intl, gd, curl or opcache are application dependencies, not RustProxy dependencies.
## Example PHP-FPM pool concept
The exact paths/users depend on the operating system. For production, use a **separate PHP-FPM worker identity per site/application**. Never run PHP-FPM as `root` or as the `rustproxy` service account. A compromised CMS/plugin then must not inherit access to RustProxy's SQLite database, Secret Store, certificate private keys or backups.
A local pool can expose a socket such as:
```ini
user = site_example
group = site_example
listen = /run/php/rustproxy-example.sock
listen.owner = www-data
listen.group = rustproxy
listen.mode = 0660
```
The exact socket owner may differ by distribution; the important properties are that the PHP worker user is **not** `rustproxy`, and only RustProxy plus the pool manager can reach the socket. Configure the Web Service endpoint as:
```text
unix:/run/php/rustproxy-example.sock
```
Keep `/var/lib/rustproxy` mode/private ownership inaccessible to every PHP pool. Prefer separate writable upload/data directories from executable PHP source, and prevent the application from creating executable PHP files in upload areas. A CMS-level arbitrary file upload or vulnerable plugin can otherwise become PHP code execution even when the reverse proxy itself is correct.
Do not expose a PHP-FPM TCP listener to the public network.
## Verification targets
The implementation includes source/unit coverage for:
- PHP front-controller resolution through `try_files`;
- request body delivery to a FastCGI fixture;
- FastCGI response parsing;
- connection failure returning a gateway error without PHP source disclosure;
- bounded endpoint/time/concurrency configuration validation.
Release-host acceptance should additionally exercise a real PHP-FPM runtime over Unix socket and loopback TCP and verify ACL/auth/custom errors/headers plus H1/H2/H3 client transports.
## Per-Web-Service PHP administrative INI overrides (0.29.2)
`php.php_admin_values` is a validated operator-managed string map sent in a single
`PHP_ADMIN_VALUE` FastCGI parameter. Example:
```json
"php": {
"enabled": true,
"path_info": true,
"endpoint": "unix:/run/php/php-fpm-mail.sock",
"php_admin_values": {
"open_basedir": "/var/www/webmail/:/tmp/",
"display_errors": "Off"
}
}
```
Use separate Web Services for distinct per-location PHP settings, then assign those
services to the corresponding Custom Locations. Each Web Service can refer to the
same trusted PHP-FPM socket. The setting does **not** isolate worker process
identities or file permissions. Never expose PHP-FPM FastCGI to untrusted clients.
HTTP headers cannot supply `PHP_ADMIN_VALUE`, and `php_values` must not repeat
an admin directive. Control characters and excessive values are rejected.
Prometheus metrics use only configured Web Service IDs, never script paths,
URLs, PHP values, or dynamic user labels. `rust_proxy_php_requests_total` and
`rust_proxy_php_responses_total{class=...}` count responses after headers have
been received (including proxy-generated failures); the cumulative header
latency metric intentionally excludes streamed body time.
The same snapshot also exports `rust_proxy_web_server_requests_total`,
`rust_proxy_web_server_responses_total{class=...}` and
`rust_proxy_web_server_response_header_duration_microseconds_total` for all
Web Server requests (PHP and non-PHP).
Inspect **Web Server → Live metrics**, [Logs](LOGS.md) and the application's own FPM logs. The proxy's PHP request semaphore is not a replacement for monitoring external FPM worker capacity.
-31
View File
@@ -1,31 +0,0 @@
# Dedicated PHP-FPM pools for distinct Web Services
PHP-FPM may retain request-supplied `PHP_VALUE` and `PHP_ADMIN_VALUE` directives in long-lived workers. When two applications use one PHP-FPM socket but different INI overrides, a setting such as `open_basedir` can be inherited by the next request, even when RustProxy routes the request to the correct Web Service. **RustProxy cannot provide process-level isolation inside a shared external PHP-FPM worker.**
## Affected operator configuration (0.29.6)
- WordPress Web Service `blog-linuxiarz-pl` uses document root `/var/www/linuxiarz.pl/` and endpoint `unix:/run/php/php_rustproxy.sock`, without an `open_basedir` override.
- Roundcube Web Service `webmail-linuxiarz-pl` uses document root `/var/www/webmail/public_html` and the **same** endpoint. Its FastCGI `PHP_ADMIN_VALUE` includes `open_basedir=/var/www/webmail/:/usr/lib/php/:/tmp/:/etc/enigma:...`.
- WordPress PHP-FPM stderr confirms the Roundcube `open_basedir` at the WordPress file check. As a result, `wp-config.php` is not visible and WordPress redirects to `/wp-admin/setup-config.php` before triggering fatal errors. This is **not** a cache MISS defect.
## Required operator remediation
1. Back up the current FPM configuration. Provision separate, access-restricted PHP-FPM pools under the PHP-FPM service manager used by your system (NixOS configuration when applicable). Use distinct Unix sockets, for example `/run/php/php_wordpress.sock` and `/run/php/php_roundcube.sock`. Ensure RustProxy's runtime user may connect to both. Do **not** expose raw FastCGI publicly.
2. Configure pool-level `php_admin_value[open_basedir]` for **Roundcube only**, with exactly the filesystem paths it requires, or keep its validated override in RustProxy on a dedicated pool. Keep WordPress in its own pool with appropriate policy. Pool-level directives are preferable to request-supplied administrator directives for security-sensitive settings.
3. In **Web Server > Web Services** set `blog-linuxiarz-pl > PHP-FPM endpoint` to `unix:/run/php/php_wordpress.sock` and `webmail-linuxiarz-pl` to `unix:/run/php/php_roundcube.sock`. Shared PHP-FPM profiles must also refer to the corresponding correct endpoint.
4. Apply PHP-FPM configuration and **restart** the affected PHP-FPM service/pools to retire workers with leaked directives. Save the RustProxy Web Service changes. Check that both web apps respond and that private files remain blocked.
5. Repeat requests in alternating order from both vhosts and inspect PHP-FPM warnings. Suggested tests: `curl -I https://blog.linuxiarz.pl/`, `curl -I https://blog.linuxiarz.pl/wp-login.php`, `curl -I https://webmail.linuxiarz.pl/`. The WordPress home page must no longer redirect to `/wp-admin/setup-config.php` due to `open_basedir`.
If WordPress still redirects to setup-config.php with a **dedicated** pool, check whether `wp-config.php` really exists and is readable by the pool user, the configured pool's own php_admin_value restrictions, and database credentials. Do not fix the issue by using unrestricted `open_basedir=/` globally or by disabling protections on Roundcube.
## New 0.29.7 protection
During configuration compilation, RustProxy emits a warning identifying pairs of Web Services sharing an endpoint with unequal `PHP_VALUE`/`PHP_ADMIN_VALUE` maps. For backward compatibility, the existing configuration remains loadable; the warning **does not** automatically rewrite operator FPM configuration or claim that sharing becomes safe.
## Per-backend health
For an internal 404 response origin, leave `pool_eligible: false`, set `health_check_enabled: false`, and continue routing a named `internal` location to its `backend_id`. If a backend does require separate probes, use an optional per-member `health_check` object (same shape as vhost `load_balancing.health_check`).
## HIT-only cache diagnostic headers
Enable global `cache.expose_only_hits: true`, optionally override each vhost at `vhosts[].cache.expose_only_hits`. HIT-only mode filters RustProxy diagnostic cache headers for MISS/BYPASS without altering internal hit/miss metrics or upstream cache eligibility. `Via` is a separate proxy-header option and can be disabled using `cache.via_header: null`.
-241
View File
@@ -1,241 +0,0 @@
# Dedicated Prometheus endpoint
RustProxy exposes Prometheus text exposition 0.0.4 on a dedicated read-only
listener. It is independent from administrator browser sessions and does not
export raw client IPs, request paths, query strings, full User-Agent strings, cookies,
credentials, backend URLs or ACME account secrets.
## Configuration
```json
{
"prometheus": {
"enabled": false,
"listen": "127.0.0.1:9191",
"path": "/metrics",
"allowed_cidrs": ["127.0.0.0/8", "::1/128"],
"bearer_token_env": null,
"allow_trusted_network_without_token": false,
"include_system": true,
"include_vhosts": true,
"include_traffic": true,
"include_backends": true,
"include_docker_vhosts": true,
"include_cache_domains": true,
"include_streams": true,
"include_logging": true
}
}
```
The listener is plain HTTP. Keep it on loopback, a private monitoring network or
behind a protected tunnel. `allowed_cidrs` is always evaluated against the actual
transport peer (`PeerAddr`); `Forwarded` and `X-Forwarded-For` never grant metrics
access.
### Bearer token
Set `bearer_token_env` to an environment-variable name such as
`RUST_PROXY_METRICS_TOKEN`. RustProxy stores only the variable name. The value must
contain 32..4096 printable non-whitespace ASCII characters and must be present at
startup.
### Trusted network without a token
`allow_trusted_network_without_token=true` explicitly allows a peer already matched
by `allowed_cidrs` to scrape without a bearer token. This can be used for a private
Prometheus VLAN. An explicit IPv4/IPv6 `/0` is rejected in this mode. Keep the CIDR
list narrow; enabling this option is an authentication decision, not a forwarded-IP
feature.
A non-loopback exporter requires either a bearer-token variable or this explicit
trusted-network bypass. Changing enabled/listen/token-variable requires restart;
path, CIDRs, collector switches and the trusted-network bypass apply live.
Only `GET` on the exact configured path without a query string is accepted. A
wrong path is `404`, wrong method `405`, disallowed peer `403`, invalid/missing
required bearer `401`, and exporter concurrency exhaustion `429`.
## Web Server and PHP-FPM metrics (0.33.0)
Prometheus already exports core Web Server/PHP response counters by configured
`web_service` ID. The 0.33.0 additions below are enabled with the existing
Prometheus exporter (no new listener, credentials, options or open routes).
| Family | Type | Labels | Meaning |
|---|---|---|---|
| `rust_proxy_web_server_requests_total` | counter | `web_service` | Header-ready WWW responses, including PHP |
| `rust_proxy_web_server_responses_total` | counter | `web_service`, `class` | HTTP 2xx/3xx/4xx/5xx |
| `rust_proxy_web_server_response_header_duration_microseconds_total` | counter | `web_service` | Cumulative time until headers |
| `rust_proxy_web_server_requests_active` | gauge | `web_service` | Requests still awaiting headers |
| `rust_proxy_web_server_request_kind_total` | counter | `web_service`, `kind` | `static`, `php`, `other` handler |
| `rust_proxy_web_server_methods_total` | counter | `web_service`, `method` | `GET`, `POST`, `PUT`, `DELETE`, `other` |
| `rust_proxy_web_server_status_code_total` | counter | `web_service`, `status` | Selected 200/206/304/403/404/500/502/503 |
| `rust_proxy_web_server_response_body_bytes_total` | counter | `web_service` | Body bytes delivered before final compression |
| `rust_proxy_web_server_compression_total` | counter | `web_service`, `encoding` | Downstream gzip/br/zstd/deflate operations |
| `rust_proxy_php_requests_total` | counter | `web_service` | PHP response headers processed |
| `rust_proxy_php_responses_total` | counter | `web_service`, `class` | PHP response status classes |
| `rust_proxy_php_response_header_duration_microseconds_total` | counter | `web_service` | Cumulative PHP time until headers |
| `rust_proxy_php_response_body_bytes_total` | counter | `web_service` | PHP body bytes emitted before compression |
| `rust_proxy_php_concurrency_available` | gauge | `web_service` | Free RustProxy PHP request permits |
| `rust_proxy_php_concurrency_limit` | gauge | `web_service` | Configured concurrent PHP request limit |
| `rust_proxy_php_concurrency_in_use` | gauge | `web_service` | Permits currently held by PHP requests |
| `rust_proxy_php_failures_total` | counter | `web_service`, `reason` | Fixed FastCGI/PHP failure classes |
PHP `reason` is one of `capacity`, `connect_timeout`, `connect_error`,
`request_timeout`, `request_io`, `response_timeout`, `response_io`,
`invalid_response`, `body_io`. PHP concurrency is the admission semaphore in
RustProxy, not external PHP worker health or PHP-FPM child count. Status-code
labels are deliberately restricted to a stable, useful subset; arbitrary code,
request paths, IP addresses and untrusted method strings are not labels.
The exported byte counters are measured **before the final dynamic response
encoder**, so treat them as payload sizes rather than measured network transfer.
Precompressed static file delivery is not counted in the downstream compression
family. All time-to-headers metrics exclude streamed transfer duration.
Useful Grafana/PromQL examples:
```promql
sum(rate(rust_proxy_web_server_requests_total[5m]))
sum by (class) (rate(rust_proxy_web_server_responses_total[5m]))
sum by (kind) (rate(rust_proxy_web_server_request_kind_total[5m]))
100 * sum(rust_proxy_php_concurrency_in_use) / clamp_min(sum(rust_proxy_php_concurrency_limit), 1)
1e-3 * sum(rate(rust_proxy_web_server_response_header_duration_microseconds_total[5m])) / clamp_min(sum(rate(rust_proxy_web_server_requests_total[5m])), 1e-9)
sum by (reason) (rate(rust_proxy_php_failures_total[5m]))
```
Import `grafana/rustproxy-overview.json` and use the **Web service** variable
for the new Web Server, PHP-FPM, per-service and correlation sections.
## Coverage audit in 0.26.0
The exporter represents all scalar fields currently sampled by `diagnostics::View` when available. The per-interface `network_interfaces` list added in 0.18.6 is intentionally dashboard-only so kernel-created interface names cannot create an uncontrolled Prometheus label set. RustProxy also exports the bounded operational data already available from traffic metrics, cache, vhosts, backend pools, streams, logging, GeoIP and ACME renewal observations.
| Area | Exported diagnostics |
|---|---|
| Global traffic | requests/completions/active/rejects/errors, status classes, request/response bytes, exchange lifetime, accepted/active/rejected connections |
| Protocols | HTTP/1.1, HTTP/2 and HTTP/3 requests, H2/H3 connections/rejects, TLS connections/handshake failures, WebSocket tunnels/bytes/errors/lifetime, compression counters |
| Admin | request/status counters and time to control-plane response headers |
| Process/host | diagnostic source, runtime platform info, sampled timestamp, system/process CPU, logical CPUs, RAM total/used, system swap total/used, system uptime, RSS total/anon/file/shmem, virtual memory/limit, VmData, process swap, FDs/limit, threads, process start time, load 1/5/15, network totals/rates, process disk I/O, cgroup memory current/max |
| GeoIP | loaded state plus IPv4/IPv6 range counts |
| Cache | hot entries/bytes, global hit ratio and counters, cold entries/bytes and movement/error counters, fill bytes, cold queue bytes/items, active cold read/write bytes, combined transient bytes, configured-domain hot/cold occupancy, hit ratio and decision data |
| Vhosts | desired enable/domain/path/cache settings, per-vhost traffic/status/error/byte counters and cumulative exchange-duration histogram |
| Traffic analytics | independent live collector (`include_traffic`): capture-state gauges, per-vhost request/error/body-byte/duration totals; approximate bounded top countries; fixed User-Agent families; fixed HTTP method families; status classes; cache outcomes; serving source (`web_server`, `reverse_proxy`, `local_response`, `unresolved`); and currently configured route IDs |
| Built-in Web Server | configured Web Service count/info, PHP and directory-listing flags, vhost/route source metadata, plus actual request traffic through the shared vhost counters and Traffic serving-source metrics |
| Redirect/404 hosts | desired enable state, domain/rule counts and local-response status |
| Backends | pool readiness/policy info, bounded backend status, health/drain/backup, active work and configured request-concurrency/queue limits, requests/errors/bytes, weight, separate active/passive failure state, active/passive successes and health-failure totals, unconditional HTTP-5xx/timeout/connect/I/O/slow-response observation counters, DNS/TLS/header-timeout/read-timeout/send-timeout diagnostics, capacity rejection/queue-timeout counters, retry attempt/success totals, upstream-client generation/current-generation requests/generation age/rotation totals, plus `passive_http_5xx_total`, `passive_timeouts_total`, `passive_connect_errors_total`, `passive_io_errors_total` and `passive_slow_total` aliases, health transitions, last status, current/EWMA latency, last-probe/failure/ejection/recovery timestamps and traffic rate for ordinary vhosts |
| Docker vhosts | separate `rust_proxy_docker_vhost_*` and `rust_proxy_docker_backend_*` families: configured agents/assignments/domains, vhost traffic/status/duration plus backend readiness, health, requests/errors/bytes, latency/EWMA, separate active/passive failure state, unconditional observation counters and matching passive-health aliases, retry attempt/success totals, upstream-client generation/current-generation requests/generation age/rotation totals, transitions and probe results |
| Streams | desired enable state, active/session/reject/error counters, bytes/packets and max connections |
| Logging | structured sink queue capacity, sent/error/drop counters plus the global live-log contention drop counter |
| TLS/ACME | configured certificate count, auto-renew flag, bounded issuer metadata, latest ACME job state, renewal check/expiry timestamps, failure count and status |
| Inventory | listeners/protocol flags, vhost/redirect/404/stream/ACL counts, request capacity and available permits, config revision and restart-required state |
Values unavailable on the running OS are omitted rather than fabricated. Series
labels are bounded by configured IDs/domains, finite states, fixed protocol/status/UA/method/source
families, or the finite two-letter country space. Per-request URLs, raw client IPs, full
User-Agent values and arbitrary Host input are never labels.
This is complete for **currently implemented bounded diagnostic views**. It does
not intentionally expose secrets, raw configuration documents, access-log entries
or unbounded per-request values merely to make them visible to Prometheus.
## Prometheus examples
Loopback/private CIDR scrape without a token when the explicit trusted option is
used:
```yaml
scrape_configs:
- job_name: rustproxy
scrape_interval: 15s
metrics_path: /metrics
static_configs:
- targets: ['127.0.0.1:9191']
```
With bearer authentication:
```yaml
authorization:
type: Bearer
credentials_file: /etc/prometheus/secrets/rustproxy-token
```
Vhost rate:
```promql
sum by (vhost) (rate(rust_proxy_vhost_requests_total[5m]))
```
Web Server traffic rate:
```promql
sum by (vhost) (rate(rust_proxy_traffic_source_requests_total{source="web_server"}[5m]))
```
Top country snapshot from bounded Traffic analytics:
```promql
sum by (country) (rust_proxy_traffic_country_requests)
```
Country series are populated when **Settings -> Logging -> Capture country** is enabled; User-Agent family series follow **Capture User-Agent**. The exporter never turns those request-path captures on by itself.
Current capture state is exposed as `rust_proxy_traffic_country_capture_enabled` and `rust_proxy_traffic_user_agent_capture_enabled`. Configured-route snapshots use `rust_proxy_traffic_route_requests`; raw URL paths are never exported as labels.
Fixed User-Agent families:
```promql
sum by (family) (rust_proxy_traffic_user_agent_family_requests_total)
```
Vhost p95 exchange lifetime:
```promql
histogram_quantile(0.95,
sum by (vhost, le) (rate(rust_proxy_vhost_exchange_duration_seconds_bucket[5m])))
```
Docker-vhost request rate (kept separate from ordinary vhosts):
```promql
sum by (agent, vhost, service) (rate(rust_proxy_docker_vhost_requests_total[5m]))
```
Docker backend availability and latency:
```promql
rust_proxy_docker_backend_up
rust_proxy_docker_backend_ewma_latency_seconds
```
Backend client lifecycle and retry diagnostics:
```promql
rust_proxy_backend_upstream_client_generation_requests
rust_proxy_backend_upstream_client_generation_age_seconds
rate(rust_proxy_backend_upstream_client_rotations_total[5m])
rate(rust_proxy_backend_retry_attempts_total[5m])
rate(rust_proxy_backend_retry_successes_total[5m])
```
The same fields are exported for Docker-discovered backends under the `rust_proxy_docker_backend_*` prefix.
Certificate expiry window:
```promql
rust_proxy_certificate_renewal_not_after_timestamp_seconds - time()
```
The supplied dashboard is `grafana/rustproxy-overview.json`; Debian packages also
install it under `/usr/share/rustproxy/grafana/`. Its dedicated **Docker vhosts** row
uses only the separate Docker metric families and provides Docker-agent/service
filters so multi-agent installations can be inspected independently from ordinary
vhosts/backends.
Reference: https://prometheus.io/docs/instrumenting/exposition_formats/
## Stream pool metrics (0.32.0)
The existing global `include_streams` controls all Stream Prometheus families. Each configured Stream may set nullable `metrics_enabled` (default true); each backend may set its own nullable `metrics_enabled` (inherit from Stream). Detailed backend counters are emitted only when both flags are enabled. Basic per-Stream counters, backend active/drain and numeric health-state gauges remain observable with global Stream export enabled; routing and health monitoring never depend on these flags. Backend series use only configured `stream`, `backend` and `protocol` labels, never client IP or arbitrary UDP payload. `rust_proxy_stream_backend_health_state` encodes unverified=0, checking=1, healthy=2, unhealthy=3, disabled=4. Optional detailed families include bytes/packets, assignments, errors/rejects, sessions and health successes/failures. All counters reset on process startup.
For v0.32.0 Stream pools, backend-level `metrics_enabled` overrides the Stream default; missing/null values inherit it. Detailed backend accounting defaults off, including for legacy `upstream` configurations. Global `include_streams` independently gates all Stream Prometheus export.
+49 -59
View File
@@ -1,77 +1,67 @@
# RustProxy documentation index
# RustProxy documentation
This is the canonical index for the current project documentation. The authenticated administration documentation page at `/docs` embeds these Markdown files in its **Reader** tab and keeps OpenAPI/Swagger in a separate **API** tab.
Guides for operating RustProxy: installation, publishing services, security, certificates, monitoring and automation. The same pages are available in the administration panel under **`/docs → Reader`**, next to the interactive **API** reference.
## Project and release
- Project website and packages: [rustproxy.linuxiarz.pl](https://rustproxy.linuxiarz.pl/)
- Source code: [git.linuxiarz.pl/gru/RustProxy](https://git.linuxiarz.pl/gru/RustProxy)
- License: [GNU AGPL v3.0 or later](../LICENSE)
- [Project README](../README.md) - product overview, installation entry points and current release notes.
- [ROADMAP](../ROADMAP.md) - planned and completed release scope.
- [CHANGELOG](../CHANGELOG.md) - user/operator-visible changes by release.
- [TODO](TODO.md) - open work only; completed work is not retained as TODO.
## Overview
## Functional documentation
- [Feature list](FEATURES.md) — everything RustProxy can do, grouped by area.
- [Screenshots](SCREENSHOTS.md) — a tour of the administration panel.
- [Performance](PERFORMANCE.md) — benchmark method, comparison with Nginx and tuning.
- [WEB_SERVER](WEB_SERVER.md) - reusable Web Services, static-file behavior, ranges/validators and document-root security.
- [PHP_FPM](PHP_FPM.md) - implemented FastCGI/PHP-FPM integration, security model and runtime requirements.
- [PHP_FPM_ISOLATION](PHP_FPM_ISOLATION.md) - separate PHP-FPM pools to prevent cross-vhost INI settings leakage.
- [VHOSTS](VHOSTS.md) - vhosts and backend/source behavior.
- [CUSTOM_LOCATIONS](CUSTOM_LOCATIONS.md) - location matching, rewrites and local responses.
- [VHOST_ACCESS](VHOST_ACCESS.md) - access rules applied to vhosts and locations.
- [CACHE](CACHE.md) - cache behavior and policy.
- [TRANSFORM](TRANSFORM.md) - response transformation.
- [STREAMS](STREAMS.md) - stream proxying.
- [REDIRECTS](REDIRECTS.md) - redirect hosts and rules.
- [NOT_FOUND_HOSTS](NOT_FOUND_HOSTS.md) - local 404 host handling.
- [DOCKER_DISCOVERY](DOCKER_DISCOVERY.md) - Docker discovery and generated vhosts.
- [ACME_PROVIDERS](ACME_PROVIDERS.md) and [RENEWAL](RENEWAL.md) - certificate automation.
- [LIVE_LOGS](LIVE_LOGS.md), [FILE_LOGS](FILE_LOGS.md) and [ERROR_LOGGING](ERROR_LOGGING.md) - independent in-memory live request viewing, bounded on-disk log exploration, protocol-error persistence and optional error-file logging.
## Getting started
## Administration and operations
- [Installation and updates](INSTALL.md) — packages, source build, Docker image, first administrator, systemd and updates.
- [Configuration](CONFIGURATION.md) — SQLite or JSON configuration source, configuration map and changes that need a restart.
- [Troubleshooting](TROUBLESHOOTING.md) — symptom-based checks and a rollout checklist.
- [INSTALL](INSTALL.md) - installation and managed updates.
- [SETTINGS](SETTINGS.md) - listeners, administration and response defaults.
- [API](API.md) - administration API overview.
- [API_ACCESS](API_ACCESS.md) - API authentication, CSRF and `/docs` security model.
- [SECRET_STORE_BACKUP](SECRET_STORE_BACKUP.md) - encrypted secret storage and backup/restore.
- [PROMETHEUS](PROMETHEUS.md) - monitoring endpoint and metrics.
- [CONTAINER](CONTAINER.md) - container deployment.
- [NPM_MIGRATION](NPM_MIGRATION.md) and [MIGRATION](MIGRATION.md) - migration tooling and historical migration notes.
## Publishing services
## Development and testing
- [Vhosts and backends](VHOSTS.md) — domains, listeners, backends, balancing, health checks and redirects.
- [Custom locations](CUSTOM_LOCATIONS.md) — path routing, rewrites, local responses and error fallbacks.
- [Web Server](WEB_SERVER.md) — static sites, downloads, `try_files`, rewrites, `.htaccess` subset and error pages.
- [PHP-FPM](PHP_FPM.md) — PHP profiles, pool isolation and application examples.
- [HTTP transport and TLS](TRANSPORT.md) — HTTPS, HTTP/2, HTTP/3, WebSockets, backend protocols and post-quantum key exchange.
- [TCP/UDP Streams](STREAMS.md) — non-HTTP forwarding.
- [MOCK_SERVER](MOCK_SERVER.md) - local Python mock origin used by development/integration checks.
- [MOCK_AUTH](MOCK_AUTH.md) - local fake Tinyauth peer for development only.
- [MEMORY_STRESS](MEMORY_STRESS.md) - sustained-memory audit, offline load simulations, real RustProxy staging stress and RSS measurement procedure.
## Security and access
## Technical documentation
- [Access and authentication](ACCESS.md) — ACLs, countries, rate limits, Basic Auth, Forward Auth and OIDC.
- [Certificates](CERTIFICATES.md) — PEM import, ACME HTTP-01/DNS-01, external Certbot files and automatic renewal.
- [Security model](SECURITY.md) — trust boundaries and deployment recommendations.
- [ARCHITECTURE](ARCHITECTURE.md) - process and request architecture.
- [SECURITY](SECURITY.md) - current security model and limitations.
- [HTTP3](HTTP3.md) - HTTP/3 transport behavior.
- [TRANSPORT](TRANSPORT.md) - transport background/contract notes.
- [CACHE_DIAGNOSTICS](CACHE_DIAGNOSTICS.md) - cache diagnostics and interpretation.
- [REFERENCES](REFERENCES.md) - implementation references.
## Performance and delivery
- [Cache](CACHE.md) — HOT (memory) and COLD (disk) cache, freshness, `Cache-Status` and invalidation.
- [Response delivery](RESPONSE_DELIVERY.md) — compression, transformations and response headers.
The pre-cleanup developer-level changelog is retained as [CHANGELOG_TECHNICAL_ARCHIVE](CHANGELOG_TECHNICAL_ARCHIVE.md) when low-level historical detail is needed. The root `CHANGELOG.md` remains the operator-facing release record.
Completed roadmap history is retained separately as [ROADMAP_HISTORY](ROADMAP_HISTORY.md); the root `ROADMAP.md` contains the current release scope and future direction.
## Operations
## Historical or version-labelled context
- [Dashboard](DASHBOARD.md) — Overview cards, layouts, presets and configuration history.
- [Monitoring](MONITORING.md) — Prometheus exporter, JSON metrics, Grafana and Nagios.
- [Logs](LOGS.md) — live, stored and error logs.
- [Backup and recovery](BACKUP.md) — encrypted backups, restore and the Secret Store.
- [Migration](MIGRATION.md) — upgrades and Nginx Proxy Manager import.
- [Docker discovery](DOCKER_DISCOVERY.md) — Docker agent and generated Vhosts.
- [Administration API](API.md) — authentication, tokens, endpoints, lightweight polling views and configuration sections.
### Retained audits and historical context
## Integrations
- [SECURITY_AUDIT](SECURITY_AUDIT.md) and [SECURITY_AUDIT_0.31.0](SECURITY_AUDIT_0.31.0.md) - current security audit material and the 0.31.0 audit.
- [CHANGELOG_TECHNICAL_ARCHIVE](CHANGELOG_TECHNICAL_ARCHIVE.md) and [ROADMAP_HISTORY](ROADMAP_HISTORY.md) - retained long-form technical history and completed milestones.
- [ACME_RFC2136](ACME_RFC2136.md) - historical DNS provider details.
- [INTEGRATIONS_040](INTEGRATIONS_040.md) and [MANAGEMENT](MANAGEMENT.md) - retained design and integration notes.
- [TEST_REPORT](TEST_REPORT.md) - verification history and release gates.
| Integration | Purpose | Guide |
|---|---|---|
| Docker agent | Discover containers on Docker hosts and publish them as Vhosts | [Docker discovery](DOCKER_DISCOVERY.md) |
| Tinyauth, Authelia, authentik | Forward Auth for selected Vhosts | [Access](ACCESS.md) |
| OIDC (Authelia, authentik, Pocket ID, others) | Central login across unrelated domains | [Access](ACCESS.md) |
| ACME / Certbot | Certificates via HTTP-01 or DNS-01 (Cloudflare, OVH, RFC 2136) | [Certificates](CERTIFICATES.md) |
| Prometheus and Grafana | Metrics scraping and a ready dashboard | [Monitoring](MONITORING.md) |
| Nagios | Service checks for API, backends, latency and queues | [Monitoring](MONITORING.md) |
| Nginx Proxy Manager | Import of existing proxy hosts | [Migration](MIGRATION.md) |
| OPNsense | Certificate deployment from the os-acme-client plugin | `integrations/opnsense-acme/README.md` |
| MikroTik RouterOS | OCI image build for RouterOS containers | `integrations/routeros-oci-build/README.md` |
The old per-version implementation notes and audits removed from this trimmed source archive are no longer required to build the application or use the documentation Reader. Only registered documents are embedded at compile time.
## Release notes
## Maintenance rules
- Put user/operator-visible release changes in `CHANGELOG.md`.
- Put future release scope in `ROADMAP.md` and unresolved work in `docs/TODO.md`.
- Keep detailed implementation evidence in technical documents or version audits, not in CHANGELOG.
- When functionality changes, update the functional document, this index, Reader coverage and cross-links in the same change.
- Remove obsolete or duplicated documents when they no longer provide historical or technical value; retain useful audits with an explicit historical label.
See the [changelog](../CHANGELOG.md).
-66
View File
@@ -1,66 +0,0 @@
# Redirect hosts
RustProxy 0.13.0 adds first-class redirect hosts on the **Redirects** dashboard page. A redirect host owns one or more source hostnames and named HTTP/HTTPS listeners and answers locally without opening a backend connection.
Each host contains ordered redirect rules. The longest matching path wins. Rules can match `http`, `https` or either scheme and can use exact or path-prefix matching. Targets can be:
- `url`: an absolute `http://` or `https://` URL,
- `host`: a hostname/authority with the target scheme set to preserve, HTTP or HTTPS,
- `path`: an absolute path on the same request authority.
Supported status codes are 301, 302, 303, 307 and 308. Use 307/308 when the client must preserve the HTTP method. Path handling can append the complete source path, strip the matched prefix before appending, or replace the path entirely. Source query parameters can be preserved or dropped. `cache_seconds=0` emits `Cache-Control: no-store`; a positive value emits a bounded public `max-age`.
Redirect hosts use the same named listener inventory and certificate library as vhosts. The dashboard uses the same listener-selection component for both editors. A new redirect preselects the standard enabled HTTP :80 and HTTPS :443 listeners together when available, while existing bindings are preserved. The same hostname cannot overlap a vhost or another redirect host on the same socket. Global host ACL policy still runs before a redirect response. Redirect rules never make DNS/TCP/TLS connections to the target; the client follows the resulting `Location` header.
Configuration lives under `redirects` and normal dashboard saves use `PUT /api/config/redirects`.
## Example
```json
{
"redirects": [
{
"id": "legacy-site",
"name": "Legacy site redirects",
"frontend": {
"hosts": ["old.example.com"],
"listener_ids": ["http", "https"]
},
"certificate_id": "old-example",
"rules": [
{
"id": "application",
"source_scheme": "any",
"match_type": "prefix",
"path_prefix": "/app",
"target_type": "host",
"target": "app.example.com",
"target_scheme": "https",
"path_mode": "strip_prefix",
"query_mode": "preserve",
"status_code": 308,
"cache_seconds": 300
},
{
"id": "fallback",
"match_type": "prefix",
"path_prefix": "/",
"target_type": "url",
"target": "https://www.example.com/",
"path_mode": "replace",
"query_mode": "drop",
"status_code": 302,
"cache_seconds": 0
}
]
}
]
}
```
Rules are evaluated by longest path first; exact matching wins over prefix matching at the same path. A disabled redirect host reserves its hostname/listener ownership just like a disabled vhost, so another frontend cannot silently take over that traffic.
## Hostname editor
The Redirect source-domain field uses the same chip editor as Vhosts: add one hostname/wildcard at a time, remove individual chips, and keep listener selection in the shared frontend control.
-65
View File
@@ -1,65 +0,0 @@
# Implementation references
Primary specifications and library documentation consulted while implementing the
transport code. These references are not evidence that this source package was built
or tested successfully against a resolved dependency graph.
## HTTP and WebSocket
- HTTP semantics and intermediary header handling: https://www.rfc-editor.org/rfc/rfc9110
- HTTP/1.1 messaging: https://www.rfc-editor.org/rfc/rfc9112
- HTTP/2: https://www.rfc-editor.org/rfc/rfc9113
- WebSocket handshake and framing: https://www.rfc-editor.org/rfc/rfc6455
- Extended CONNECT bootstrap semantics: https://www.rfc-editor.org/rfc/rfc8441
- WebSocket over HTTP/3: https://www.rfc-editor.org/rfc/rfc9220
- hpx-h3 2.5.19: https://docs.rs/hpx-h3/2.5.19/h3/
- Hyper HTTP/2 server builder: https://docs.rs/hyper/latest/hyper/server/conn/http2/struct.Builder.html
- Hyper upgrades: https://docs.rs/hyper/latest/hyper/upgrade/index.html
- hyper-util legacy client: https://docs.rs/hyper-util/latest/hyper_util/client/legacy/struct.Client.html
- Resolver interface: https://docs.rs/hyper-util/latest/hyper_util/client/legacy/connect/dns/index.html
- Tokio bounded-buffer bidirectional copying: https://docs.rs/tokio/latest/tokio/io/fn.copy_bidirectional.html
## TLS
- HTTPS connector builder: https://docs.rs/hyper-rustls/latest/hyper_rustls/struct.HttpsConnectorBuilder.html
- rustls signing key/certificate validation: https://docs.rs/rustls/latest/rustls/sign/struct.CertifiedKey.html
- rustls server certificate resolver: https://docs.rs/rustls/latest/rustls/server/trait.ResolvesServerCert.html
- SNI certificate bindings: https://docs.rs/rustls/latest/rustls/server/struct.ResolvesServerCertUsingSni.html
- Development certificate generation: https://docs.rs/rcgen/0.13.2/rcgen/fn.generate_simple_self_signed.html
## Persistence and security
- SQLx SQLite connection model: https://docs.rs/sqlx/latest/sqlx/struct.SqliteConnection.html
- SQLite online backup API: https://www.sqlite.org/backup.html
- OWASP SSRF guidance: https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html
- OWASP password storage: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
- Prometheus instrumentation practices: https://prometheus.io/docs/practices/instrumentation/
The next Docker stages must use actual published port bindings and reconciliation,
not infer exposure from EXPOSE alone. Start with the official engine networking and
events documentation, and record the API versions actually tested in that release.
## Cache and compression
- HTTP caching semantics and Age/freshness: https://www.rfc-editor.org/rfc/rfc9111
- Cache-Status response field: https://www.rfc-editor.org/rfc/rfc9211
- tower-http streaming compression: https://docs.rs/tower-http/latest/tower_http/compression/
- Compression implementation API: https://docs.rs/crate/tower-http/latest/source/src/compression/service.rs
- Compression predicates: https://docs.rs/tower-http/latest/tower_http/compression/predicate/
References describe upstream APIs/specifications, not successful compilation of the
unresolved 0.2.0 dependency graph. Consult the exact Cargo.lock when generated.
## Vhosts, keepalive and negotiation
- Client pool timers, idle limits and HTTP/2 pings: https://docs.rs/hyper-util/latest/hyper_util/client/legacy/struct.Builder.html
- DNS Name parsing interface: https://docs.rs/hyper-util/latest/hyper_util/client/legacy/connect/dns/struct.Name.html
- TLS ALPN server configuration: https://docs.rs/rustls/latest/rustls/server/struct.ServerConfig.html
- HTTP strict transport security: https://www.rfc-editor.org/rfc/rfc6797
The library documentation is reference material, not proof that a particular future
resolved graph compiles. The reported tower-http 0.6.11 `SizeAbove::new(u16)` call
has been removed entirely; the custom predicate uses the generic Predicate API.
- Generic compression predicate API: https://docs.rs/tower-http/latest/tower_http/compression/trait.Predicate.html
-165
View File
@@ -1,165 +0,0 @@
# Automatic certificate renewal
## Scope and defaults
RustProxy 0.7.0 renews **configured managed certificates** whose `issuer` metadata
identifies an existing HTTP-01, Cloudflare, OVH or RFC2136 Certbot profile. Issuance
and initial library import are still explicit, staging-first operations. Importing
a PEM pair alone does not create a managed lineage or authorize CA requests.
For managed certificates, `issuer.auto_renew` defaults to `true`, including saved
0.6 metadata lacking that field. Explicit `false` opts out. Therefore upgrading
an already configured managed certificate can enable scheduled CA checks; review
this before starting the new binary. Missing profiles fail closed with an error;
there is no silent recreation, provider switch or production issuance.
Certificates shows an independent enable/disable action, Check now and Dry run,
last check, next check, expiry and result. Expiry is populated after a worker check;
it is not a pre-existing inventory of every manually imported certificate. Renewal
is not a configurable new ACME account lifecycle or revocation feature.
## Schedule and recovery
Saved renewal observations are loaded from SQLite **before administration listeners are
exposed**, so last check, next check, expiry and result are available immediately after a
restart without pressing **Check now**. RustProxy then waits 5-10 minutes before the first
automatic CA check, avoiding a renewal burst during startup. Successful checks are scheduled
approximately every 12 hours with up to 30 minutes of additional jitter. Only one issue/renew
worker runs at once. Failures back off from one to six hours; a missing/invalid profile is
checked again after one hour. Observation rows contain non-secret status/timestamps only and
are stored only in `config.sqlite3`. RustProxy 0.15.0 no longer reads or imports historical JSON
observation sidecars at runtime. When SQLite has no observation row, the normal scheduler safely
reconstructs status on its next automatic check; this does not replace or regenerate certificate,
key, account or renewal-profile material. A manual dry run does not postpone a real due check. The
service must be running; this is not an independent timer that works while RustProxy is stopped.
The helper runs `certbot renew --cert-name rustproxy` in the original isolated
lineage, reusing saved authenticator/account/credential settings. It does not use
`--force-renewal`, change the CA or run directory hooks. Do not add shell hooks
to the private Certbot renewal configuration: hooks already saved by an operator
are still part of Certbot's trusted configuration. Certbot decides when the
certificate needs renewal. A successful exit can mean nothing was due: actual
certificate and key bytes are compared with the currently installed generation.
For changed material, the helper verifies validity times using OpenSSL and creates
private immutable, content-addressed staging files. Rust validates the certificate/
key match and requested domain coverage, then validates a complete new snapshot
including current vhost assignments. Only a successful revisioned SQLite write
publishes that snapshot. New TCP and QUIC TLS handshakes use the new certificate;
existing connections are not restarted. A simultaneous manual replacement/removal
or automatic-renewal disable wins over an older automatic worker result.
Worker failure, invalid PEM/SANs, expiry regression or configuration conflict leaves
the previously active generation intact. If Certbot succeeded but publication did
not, the next check compares files again and can retry the same staged generation.
Unchanged checks do not write a new configuration revision. Changed certificate publications
use the ordinary revisioned configuration path, but certificate inventory changes are not part
of cache compatibility and therefore do not discard otherwise compatible hot/cold objects.
Private profile/file checks, single-worker and per-profile locks, bounded subprocess
timeouts and process-group cancellation constrain work. Original certificate files
and Certbot archives are retained, not automatically deleted. Immutable renewal
staging is limited to 1024 keys; initial issuance staging remains limited to 128.
Plan monitored disk space and controlled offline cleanup after verified backups;
there is no automatic certificate garbage collector or external expiry alert yet.
## Provider-specific prerequisites
HTTP-01 still requires public TCP port 80 to reach an active plaintext RustProxy
listener for every name. The reserved challenge handler exposes only bounded tokens
for the current worker's domains, before application auth/ACL/cache. It is not a
public file browser. Wildcard certificates use DNS-01, not HTTP-01.
Cloudflare, OVH and RFC2136 need their original DNS permissions, credentials and
propagation settings. Secrets are stored in private files, not returned by the
renew endpoint or included in desired configuration. They are not encrypted at
rest. The helper, Certbot, plugins and OpenSSL must be usable by the service user.
Keep absolute profile paths stable; moving the Certbot directory requires explicit
migration of its stored paths. A system-wide timer should not also manage these
RustProxy-isolated profiles.
For a certificate intentionally owned by a separate system Certbot instead of this managed
renewal subsystem, 0.28.2 can reference the Certbot lineage directly. Configure a manual
certificate id with both absolute paths, for example
`/etc/letsencrypt/live/example.com/fullchain.pem` and
`/etc/letsencrypt/live/example.com/privkey.pem`. RustProxy resolves the `live/` symlinks again on
every targeted reload, so a renewal wrapper only needs to call:
```bash
curl --fail --show-error -X POST \
-H 'Authorization: Bearer TOKEN' \
-H 'X-RustProxy-Certificate-Key: certbot-main' \
-H 'Content-Type: application/json' \
--data '{}' \
https://proxy-admin.example/api/certificates/shared-cert/reload
```
The service identity must be able to traverse the Certbot directories and read the files. RustProxy
0.28.2 accepts named POSIX ACL read access and does not require an exact `0600`/`0640` mode.
External certificate/key files must not be group/world-writable, and private keys must not be
world-readable. Parent directories must be root- or RustProxy-owned and not group/world-writable
(apart from root-owned sticky ancestors such as `/tmp`). The referenced files are not copied into
RustProxy backups.
For a normal Certbot lineage, grant traversal without directory listing and read only the archive
files. A default ACL is useful, but a renewer may create a new private key with mode `0600` and
mask inherited ACL permissions. The reliable hook therefore reapplies the named read ACL to the
newly resolved generation before calling the reload API:
```bash
setfacl -m u:rustproxy:--x /etc/letsencrypt/ /etc/letsencrypt/live/ /etc/letsencrypt/archive/
setfacl -m u:rustproxy:--x /etc/letsencrypt/live/example.com/ /etc/letsencrypt/archive/example.com/
setfacl -m u:rustproxy:r-- /etc/letsencrypt/archive/example.com/fullchain*.pem \
/etc/letsencrypt/archive/example.com/privkey*.pem
setfacl -m d:u:rustproxy:r-- /etc/letsencrypt/archive/example.com/
# Run these two resolved-file grants after renewal, before the API reload.
setfacl -m u:rustproxy:r-- \
"$(readlink -f /etc/letsencrypt/live/example.com/fullchain.pem)" \
"$(readlink -f /etc/letsencrypt/live/example.com/privkey.pem)"
```
For external material below `/etc/ssl`, traversal of the protected root can be granted separately:
```bash
setfacl -m u:rustproxy:--x /etc/ssl/
```
If direct read access to the Certbot tree is undesirable, use the copy/deploy workflow instead:
```bash
/opt/rustproxy/current/rust-proxy --data-dir /var/lib/rustproxy tls deploy --id shared-cert \
--cert /etc/letsencrypt/live/example/fullchain.pem \
--key /etc/letsencrypt/live/example/privkey.pem
```
That command resolves the Certbot `live/` symlinks locally, validates and stages a managed pair,
then publishes the existing id. Do not use deploy for an id configured with external absolute
paths, and do not point either external-manual workflow at a certificate carrying RustProxy
`issuer` metadata; one lineage must have one owner.
Staging certificates are not trusted by browsers. A Dry run checks renewal
validation but does not deploy the staging result. Only an explicit production
issuance creates a production profile; renewal never upgrades staging implicitly.
## API
All routes below require the existing local administrator session and CSRF checks.
- `GET /api/acme`: current job, provider names, nominal interval and renewal states.
- `POST /api/acme/renew`: `{"certificate_id":"site","dry_run":false}`; returns 202
and a job. Only identity/options, no API credential resubmission.
- Certificate opt-out is a revisioned config update setting
`tls.certificates[].issuer.auto_renew` to `false`.
`--dry-run` is **not** a claim of live CA validation in this release's test report:
packaging tests used a simulated Certbot. Run a real staging/production-lineage
validation on your server. Full runtime scheduler/publication acceptance is pending.
## Primary references
Certbot renewal behavior and flags:
https://eff-certbot.readthedocs.io/en/stable/using.html#renewing-certificates
https://eff-certbot.readthedocs.io/en/stable/man/certbot.html
Challenge types: https://letsencrypt.org/docs/challenge-types/
+47
View File
@@ -0,0 +1,47 @@
# Compression, transformations and response headers
RustProxy can compress eligible responses and optionally transform supported static content before caching or delivery. These controls are **separate**: compression changes transfer encoding; transformation changes representation bytes. Always verify content and response headers with a real client after enabling either feature.
## Response compression
Open **Settings → Response / Compression** to configure the global codec ceiling. Vhosts and Web Services may further restrict enabled codecs; a local override cannot enable a codec forbidden globally.
| Setting | Current default / behavior |
|---|---|
| `gzip` | Enabled globally |
| `br` (Brotli) | Enabled globally |
| `zstd` | Enabled globally; used only when the client accepts it |
| `deflate` | Disabled globally |
| `min_bytes` | 1,024 bytes by default, configurable from 32 to 1,048,576 |
RustProxy negotiates encoding against the request's `Accept-Encoding` values and quality weights. An eligible reply with a preexisting `Content-Encoding` is **not compressed again**. Compression is skipped for range responses, HEAD/204/304, upgrades, event streams, `Cache-Control: no-transform` and other ineligible cases. Unknown-length streams are handled without buffering the whole body merely to compress it.
The Web Server can additionally use precompressed `.br`/`.gz` file sidecars when eligible. Sidecar delivery is distinct from live response compression. Do not manually force a `Content-Encoding` header that does not match the actual body.
## Static response transformations
Under **Settings → Transform**, the engine is disabled by default. Even when enabled globally, **each Vhost must opt in**. The default globally enabled transform types, once the engine is turned on, are JavaScript and CSS. HTML, SVG and JSON transformations are individually optional. A Vhost may override the allowed types and extensions within global restrictions. In **Vhosts → Transform**, the optional custom extension list sits immediately below its override checkbox rather than next to the transform-format selectors. The list remains read-only until its override is enabled.
The available resource controls include:
- `max_body_bytes` — 2 MiB default per eligible response (1 KiB–32 MiB accepted).
- `memory_budget_bytes` — 128 MiB default process-wide budget (1 MiB–2 GiB accepted).
- `max_inflight` — 32 default concurrent transformation slots (1–1024 accepted).
- Presets in the panel can set suggested limits; the stored numeric settings determine actual limits.
Transformation is a bounded operation for eligible, supported representations, **not a general rewrite engine**. Unsupported MIME types, larger or unknown-size bodies, non-transformable responses and resource-saturated periods are skipped or handled without changing the original body. Transforming JS/CSS/HTML can break applications that rely on specific syntax or content, so test before enabling site-wide.
Transformations can run on eligible reverse-proxy and Web Server responses. When a transformed response is safely cacheable, the cache stores a representation compatible with its validators and later per-client encoding. Changing transform rules may invalidate affected cached content. See [Cache](CACHE.md).
## Custom and security headers
Configure global response headers, Vhost-specific policies and Web Server/location headers at the appropriate scope. Header rules can set, append or remove allowed names. Certain routing, forwarding, framing, cache diagnostics and security-sensitive fields are protected or subject to extra validation. Review especially HSTS, CSP, CORS and application-specific cookies before using a broad rule.
For controlled rollout:
1. Enable only the codec/types required, then test with and without `Accept-Encoding` and with ordinary browsers.
2. Compare response size, actual decoding, `Vary`, cache status and content hash for sensitive assets.
3. Check the **Transform** and **Cache** dashboards, response logs and browser console for errors.
4. Disable the particular Vhost or transform type if output changes unexpectedly; investigate before re-enabling.
The Transform dashboard provides examined/transformed/skipped/failed counts, saved bytes and resource-limit observations. These are runtime diagnostics, not a guarantee that every asset was transformed or that end-user page loads improved.
-714
View File
@@ -1,714 +0,0 @@
# Historical roadmap archive
> Archived during the RustProxy 0.25 documentation cleanup. This file preserves completed historical roadmap entries. The active roadmap is [`../ROADMAP.md`](../ROADMAP.md); unresolved work is tracked in [`TODO.md`](TODO.md).
# RustProxy roadmap
## 0.25.0
Current direction: 0.24.10 -> 0.25.0
- [x] Add independent reusable Web Service configuration with name, document root, index list and directory-listing switch.
- [x] Add Web Service CRUD through the dashboard/API and persist it through the existing revisioned configuration store.
- [x] Add `Reverse Proxy` / `Web Server` source selection to vhost fallback and custom locations without storing Web Server filesystem settings in vhosts.
- [x] Allow one Web Service to be referenced by multiple vhosts and one vhost to mix Web Server and Reverse Proxy locations.
- [x] Add `GET`, `HEAD`, MIME, `Content-Length`, streaming, index lookup, directory slash redirect and optional directory listing.
- [x] Add metadata-based `ETag`/`Last-Modified`, conditional `304`, single `Range`/`If-Range`, `206` and `416`.
- [x] Enforce decoded-path validation and document-root confinement; Unix traversal uses `openat` + `O_NOFOLLOW` and does not serve symlinks.
- [x] Keep Web Server inside the existing routing, ACL/auth, custom-header/error-page/security and H1/H2/H3 response pipeline; do not create separate routing/error/cache subsystems.
- [x] Add unit/source-contract coverage, OpenAPI/UI language coverage and Web Server documentation.
- [x] Clean 0.25 README/Docs/CHANGELOG release material so current user-facing changes are separated from historical audits.
- [x] Add an authenticated `/docs` Markdown Reader with a separate API/OpenAPI tab, canonical docs index, 0.25 specification audit and open-work TODO.
## 0.24.10
Current direction: 0.24.9 -> 0.24.10
- [x] Make Error File a first-class revisioned configuration section and persist enabled/path/format across database reopen.
- [x] Remove the redundant Error File help sentence while retaining JSON/RAW controls and live apply.
- [x] Keep response-time history permanently expanded and add an accessible information popover matching Process.
- [x] Extend UI/API/source regressions and update release identity to 0.24.10.
## 0.24.9
Current direction: 0.24.8 -> 0.24.9
- [x] Add the encrypted Secret Store and migrate ACME Cloudflare/OVH/RFC2136 credentials away from persistent plaintext files.
- [x] Add password-encrypted full backup/restore with manifest validation, integrity checks, staging and rollback-safe activation.
- [x] Add the optional bounded asynchronous ERROR file sink with JSON/RAW formats, shared secret redaction and external logrotate integration.
- [x] Extend offline source/release contracts and update release identity to 0.24.9.
## 0.24.8
Current direction: 0.24.7 -> 0.24.8
- [x] Expand Cache RAM overview history with HOT usage/limit and transient total/fill/COLD queue/COLD I/O series.
- [x] Add COLD storage occupancy history without mixing disk bytes into process RAM.
- [x] Move the RSS/cache accounting caveat behind an accessible information control.
- [x] Extend offline UI/release contracts and update release identity to 0.24.8.
- [ ] Run compiler-enabled `./dev.sh check` / `./dev.sh test` and browser/live-cache acceptance on the release host.
## 0.24.7
Current direction: 0.24.6 -> 0.24.7
- [x] Make the transform temporary-memory budget configurable and persisted with the normal SQLite-backed transform settings.
- [x] Add a process-wide maximum concurrent transformation limit with fail-open pressure behavior.
- [x] Add persisted RAM saver / Recommended / Performance / Uber performance resource presets while keeping transform types/extensions independent.
- [x] Add UI budget safety warnings and manual-edit -> Custom preset behavior.
- [x] Expose live transform RAM/concurrency, process/cache RAM accounting and COLD disk occupancy in the dashboard.
- [x] Clean externally reported cache/CSS compiler warnings without lint suppression.
- [x] Rotate cache compatibility identity for global transform representation/eligibility changes but not resource-only capacity metadata.
- [x] Extend OpenAPI, SQLite persistence, memory-contract and release/source regressions and update release identity to 0.24.7.
## 0.24.6
Current direction: 0.24.5 -> 0.24.6
- [x] Persist selected HOT/COLD cache preset intent in the SQLite-backed configuration and restore it in Settings.
- [x] Add a Recommended COLD policy preset without overriding disk capacity/path choices.
- [x] Bound transform aggregation/decompression/concurrency memory and remove avoidable JSON/HTML/CSS allocation churn.
- [x] Bound stale revalidation, proactive refresh scheduling, COLD cleanup selection/concurrency and delete fallback tasks.
- [x] Audit the remaining long-lived/background workers for queue/task cardinality and cancellation ownership.
- [x] Add Rust-independent memory contract regression checks and update release identity to 0.24.6.
## 0.24.5
Current direction: 0.24.4 -> 0.24.5
- [x] Resolve unknown-length, actually empty HTTP/3 bodies before HTTP/1.1 upstream framing.
- [x] Preserve streaming for non-empty H3 bodies and declared `Content-Length` requests.
- [x] Add source-level regression coverage for DELETE and other body-capable methods.
- [x] Bound HOT by allocation-aware admission, enforce `max_object_bytes` during fill, and add a separately bounded transient/COLD-queue memory model.
- [x] Preserve HOT -> COLD ownership semantics while charging demoted payloads until persistence drops them; expose logical/allocated/transient telemetry.
- [x] Make global cache disable authoritative over vhost overrides and remove the artificial 1 GiB HOT/object ceiling.
- [x] Add host/cgroup-aware conservative/recommended/performance cache presets and non-blocking memory/headroom warnings in Settings.
- [x] Update source/package/UI/OpenAPI identity and offline release checks to 0.24.5.
## 0.24.4
Current direction: 0.24.3 -> 0.24.4
- [x] Make the Linux THP mitigation process-local with `PR_SET_THP_DISABLE` before Tokio worker creation.
- [x] Preserve the existing glibc arena limit and allocator-trim behavior.
- [x] Add source/release regression coverage for THP opt-out ordering and release identity.
## 0.24.3
Current direction: 0.24.2 -> 0.24.3
- [x] Reduce Linux/glibc arena retention by adding `MALLOC_ARENA_MAX=2` to managed systemd units.
- [x] Remove redundant identity-body and JavaScript/CSS transformation allocations; release successful-transform temporaries earlier and trim large glibc transform churn at a bounded cadence.
- [x] Move HTTP/3 enablement to listener editing while keeping HTTP/3 tuning under HTTP connections.
- [x] Rename Proxy Process to Process and hide unsupported platform diagnostics dynamically.
- [x] Remove the stale unreferenced per-BSD parser source from the release archive.
- [x] Update source/package/UI/OpenAPI identity and Rust-independent release gates to 0.24.3.
## 0.24.2
Current direction: 0.24.1 -> 0.24.2
- [x] Bump package/source/UI/OpenAPI release identity to 0.24.2 without functional changes.
## 0.24.1
Current direction: 0.24.0 -> 0.24.1
- [x] Replace the duplicated BSD statistics implementation with shared cross-platform library APIs.
- [x] Preserve Linux-specific `/proc`/cgroup diagnostics as additive compatibility fields.
- [x] Populate additional portable system/process metrics exposed by the selected libraries where available.
- [x] Expose low-risk runtime metadata through the authenticated status API, including the resolved data/database path.
- [x] Make CLI installed-data discovery use Linux and BSD/OPNsense conventions without hardcoding the path in the browser.
- [x] Move Logging into Settings and retain legacy `/logging` navigation compatibility.
- [x] Fix the release-host compiler error in portable uptime sampling and remove the stale H2 import warning.
- [x] Remove artificial hostname-count ceilings from certificate bindings/issuance and hostname-list configuration while retaining syntax, payload and resource-safety bounds.
- [x] Update source/package/UI/OpenAPI identity and source-side regression contracts to 0.24.1.
- [ ] Generate and commit a resolver-produced `Cargo.lock`, then run compiler-enabled `cargo fmt --all`, `cargo check --locked --all-targets` and `cargo test --locked --all-targets` on the release host.
The 0.24.1-era notes are summarized in the retained [technical changelog archive](CHANGELOG_TECHNICAL_ARCHIVE.md).
## 0.24.0
Current direction: 0.23.3 -> 0.24.0
- [x] Graceful H2 idle retirement with activity/stream recheck, GOAWAY and bounded drain.
- [x] Independent H2 maximum connection lifetime and drain timeout settings.
- [x] Settings API/UI/OpenAPI contracts for all three H2 lifecycle limits.
- [x] Extract system statistics into platform backends without changing Linux metric semantics.
- [x] Add FreeBSD/OpenBSD/NetBSD native best-effort sampling and explicit unsupported/null metrics.
- [x] Surface platform in Proxy Process and Prometheus metadata.
- [x] Update source/package/UI identity and source-side regression contracts to 0.24.0.
The 0.24.0-era notes are summarized in the retained [technical changelog archive](CHANGELOG_TECHNICAL_ARCHIVE.md).
## 0.23.3
Current direction: 0.23.2 -> 0.23.3
- [x] Add opt-in per-vhost legacy WebSocket compatibility without changing ordinary HTTP behavior.
- [x] Force legacy-mode upstream WebSocket handshakes to HTTP/1.1 and force legacy WebSocket `Host` to the configured backend authority and align `Origin` with it.
- [x] Tolerate old `101` response framing while keeping the WebSocket accept proof and subprotocol validation strict.
- [x] Add dashboard/OpenAPI/language-pack controls and source regressions.
- [x] Update package/source identity and Rust-independent release gates to 0.23.3.
The 0.23.3-era notes are summarized in the retained [technical changelog archive](CHANGELOG_TECHNICAL_ARCHIVE.md).
## 0.23.2
Current direction: 0.23.1 -> 0.23.2
- [x] Let Cache and Transformation live charts retain 60, 120, 300 or 600 samples without changing the 3-second polling cadence.
- [x] Persist each chart history-window choice locally in the browser and keep history memory bounded.
- [x] Fix local CLI administration transport detection so HTTPS administration no longer traps `--livelogs` in a 308 reconnect loop.
- [x] Pin local CLI HTTPS to certificates from the private RustProxy certificate directory and reuse the transport for config/TLS CLI calls.
- [x] Update package/source identity and Rust-independent release gates to 0.23.2.
The 0.23.2-era notes are summarized in the retained [technical changelog archive](CHANGELOG_TECHNICAL_ARCHIVE.md).
## 0.23.1
Current direction: 0.23.0 -> 0.23.1
- [x] Add default-collapsed, full-width live activity charts to the Cache and Transform workspaces.
- [x] Plot selected-scope Cache HIT/MISS/BYPASS rates from runtime counters without adding proxy-path work.
- [x] Plot selected-scope Transform applied/cache-HIT/skipped/error rates from the existing runtime metrics endpoint.
- [x] Reuse the dashboard SVG/chart styling, live 3-second refresh and language-pack model.
- [x] Update package/source identity and Rust-independent release gates to 0.23.1.
The 0.23.1-era notes are summarized in the retained [technical changelog archive](CHANGELOG_TECHNICAL_ARCHIVE.md).
## 0.23.0
Current direction: 0.22.3 -> 0.23.0
- [x] Add scoped external certificate API authentication with Key ID + Bearer Token, hash-only storage, rotation/revoke/status and last-used metadata.
- [x] Restrict the external credential to certificate deploy/reload and require HTTPS outside loopback.
- [x] Add Settings → Certificate API UX and preserve direct production binary commands for local deploy/reload.
- [x] Add `rust-proxy --livelogs` with a bounded live stream, interactive structured filters, detailed request inspection and explicit lag reporting.
- [x] Split Settings → Cache into General, Resilience, Cold cache and Static & diagnostics sub-tabs without changing the atomic cache settings document.
The 0.23.0-era notes are summarized in the retained [technical changelog archive](CHANGELOG_TECHNICAL_ARCHIVE.md).
## 0.22.3
Current direction: 0.22.2 -> 0.22.3
- [x] Add HTTP/1.1 `Upgrade: h2c` on opt-in cleartext H2 listeners while preserving prior-knowledge h2c.
- [x] Validate Upgrade/HTTP2-Settings and reject malformed or body-bearing bootstrap requests before `101`.
- [x] Reconstruct the initiating H1 request as HTTP/2 stream 1 and retain the normal Hyper H2 path for later streams.
- [x] Add dedicated certificate hot reload without mutating desired configuration or revision.
- [x] Add external certificate deployment for Certbot/manual issuers with immutable generation staging and atomic multi-vhost switch by certificate id.
- [x] Add API/CLI/panel support plus audit/OpenAPI/documentation for certificate reload/deploy.
The 0.22.3-era notes are summarized in the retained [technical changelog archive](CHANGELOG_TECHNICAL_ARCHIVE.md).
## 0.22.2
Current direction: 0.22.1 -> 0.22.2
- [x] Add replay-safe failover retry with bounded policy and observability.
- [x] Correct explicit HTTP/2 upstream request versioning and retain h2c prior-knowledge regression coverage.
- [x] Add per-backend maximum concurrent proxy exchanges with bounded queue timeout / 503 behavior and metrics.
- [x] Add detailed upstream failure classification plus last failure and health ejection/recovery timestamps.
- [x] Consolidate backend operations data in the existing dashboard table: health, active/limit, requests/errors, latency, traffic, last failure and last recovery.
- [x] Parse and expose public-certificate issuer/SAN/validity/remaining-days/expiry status plus renewal status/last attempt/error.
The 0.22.2-era notes are summarized in the retained [technical changelog archive](CHANGELOG_TECHNICAL_ARCHIVE.md).
## 0.22.1
Current direction: 0.22.0 -> 0.22.1
- [x] Move selective PURGE/BAN and active BAN management into dedicated Cache modals with inline operation feedback.
- [x] Add per-vhost RFC 8441 HTTP/2 WebSocket Extended CONNECT beside RFC 9220.
- [x] Bridge H2 RFC 8441 WebSockets to H1 upstreams and preserve Extended CONNECT to explicit H2 upstreams.
- [x] Add opt-in cleartext HTTP/2 prior-knowledge (`h2c`) listeners.
- [x] Add stable active-tunnel inventory, per-tunnel kill, operator reason and audit logging.
- [x] Split WebSocket tunnel statistics by H1 / H2 RFC 8441 / H3 RFC 9220 on the main dashboard and Prometheus.
- [x] Preserve the 0.22.0 cache feature set and `Accept-Ranges` policy.
- [x] Update 0.22.1 source/package identity and Rust-independent release gates.
The 0.22.1-era notes are summarized in the retained [technical changelog archive](CHANGELOG_TECHNICAL_ARCHIVE.md).
## 0.21.0
Current direction: 0.20.0 -> 0.21.0
- [x] Shard Cold Cache storage and add bounded asynchronous GC plus selective cleanup.
- [x] Add RFC 5861 stale windows, request `max-stale`, correct request `max-age=N`, and correct revalidation-directive semantics.
- [x] Add the extensible decode -> transform -> canonical cache -> encode stage with transformed lengths/validators, strict `no-transform`, HEAD/fill safety and canonical `Vary: Accept-Encoding` handling.
- [x] Add global, per-vhost and Docker-discovery transformation controls and administration UI/API coverage.
- [x] Make Passive Backend Health explicit opt-in for ordinary and Docker vhosts while preserving independent active health checks and unconditional observability.
- [x] Keep route cache override tri-state and expose effective route/cache/transform diagnostics.
- [x] Close 0.21.0 with the current conservative JS/CSS transformers; defer parser-backed replacements to a later development cycle.
- [x] Finish release polish: backend timing header rename, human-readable uptime, transformed Live traffic series, Docker Passive Backend Health layout fix, warning cleanup and cache module split.
- [x] Keep package/source identity and OpenAPI at 0.21.0 and package the completed feature scope as the 0.21.0 release source.
The 0.21.0-era notes are summarized in the retained [technical changelog archive](CHANGELOG_TECHNICAL_ARCHIVE.md).
## 0.20.0
Current direction: 0.19.4 -> 0.20.0
- [x] Split Certificates into Installed / Renewal / Generate tabs.
- [x] Move generation/import into the tabbed workspace and keep existing certificate actions compatible.
- [x] Improve ACME/renewal status readability and display exact Certbot log locations on failures.
- [x] Update package/source identity, OpenAPI and release checks to 0.20.0.
## 0.19.4
Current direction: 0.19.3 -> 0.19.4
- [x] Normalize Docker-vhost Aliases field geometry with neighboring inputs and remove the extra inherited label spacing.
- [x] Retain `0.0 KiB/s` idle and `<0.1 KiB/s` positive low-rate backend traffic formatting with release regression coverage.
- [x] Add Cache domain-table filtering by resolved cache-enabled state without changing aggregate cache statistics.
- [x] Update package/source identity, OpenAPI and release checks to 0.19.4.
## 0.19.3
Current direction: 0.19.2 -> 0.19.3
- [x] Preserve the exact panel and Settings tab through session expiry/re-login without shell flicker.
- [x] Canonicalize `/configuration` into persistent Settings tab routing.
- [x] Add shared save-button busy state and field-focused validation feedback.
- [x] Preserve certificate import file selections after failed saves.
- [x] Add per-Settings-tab unsaved-change indicators, including Manual config.
- [x] Add persisted dark/light administrator theme selection and consolidate theme-sensitive CSS tokens.
- [x] Keep Live Logs column selection compact with standard checkboxes and no internal scrollbars.
- [x] Add a fail-soft embedded-config release checker with hourly default cadence and CLI/environment overrides.
- [x] Keep the release checker compiler-compatible by mapping boxed HTTP body collection failures explicitly into `anyhow::Error`.
- [x] Reduce the footer to API latency/dropped logs and fix the last broad Live Logs checkbox sizing override.
- [x] Update package/source identity, OpenAPI and release checks to 0.19.3.
## 0.19.2
Current direction: 0.19.1 -> 0.19.2
- [x] Restore administration-backend local-listener risk detection for startup-locked listeners and loopback aliases; make vhost discard restore the persisted draft baseline.
- [x] Use descriptive status names for default responses.
- [x] Add an on-demand 4096-entry live log buffer that lasts until the log window closes.
- [x] Align inline checkboxes and reduce Docker-vhost alias editor height.
- [x] Render idle backend traffic as `0.0 KiB/s` and positive sub-0.1 KiB/s traffic as `<0.1 KiB/s`.
- [x] Remove cache diagnostics/validators from locally generated placeholder/default pages and enforce `no-store`.
- [x] Allow safe vhost-ID rename with collision protection and operator warning.
- [x] Update package/source identity, OpenAPI and release checks to 0.19.2.
## 0.19.1
Current direction: 0.19.0 -> 0.19.1
- [x] Repair Default responses section serialization/server routing.
- [x] Isolate cache objects by frontend scheme.
- [x] Keep a 256-entry live log window with 4096 retained entries available on demand.
- [x] Polish backend traffic units, checkbox/column UI and Docker-vhost Advanced layout/alias spacing.
- [x] Remove stale screenshots and index historical/version-labelled documentation.
- [x] Restore source-package test files/fixtures required by the release gates.
- [x] Update package/source identity, OpenAPI and release checks to 0.19.1.
## 0.19.0
Current direction: 0.18.8 -> 0.19.0
- [x] Persist per-admin Live Logs column visibility in SQLite without affecting JSON export or proxy configuration revisions.
- [x] Remove the duplicate top-right version badge.
- [x] Carry the legacy X.509 `tls_verify=false` compatibility fix and expanded upstream diagnostics into the release.
- [x] Add schema-v4 migration, administration API coverage and update package/source identity to 0.19.0.
## 0.18.8
Current direction: 0.18.7 -> 0.18.8
- [x] Use hostname-style chips for Docker-vhost aliases.
- [x] Add per-app HTTP-to-HTTPS redirect, WebSocket and cache switches for Docker vhosts.
- [x] Add an agent-level default ACL-profile selector without removing per-app ACL overrides/inline rules.
- [x] Remove the compression-codec explainer from Overview.
- [x] Update package/source identity, API schema and release checks to 0.18.8.
## 0.18.7
Current direction: 0.18.6 -> 0.18.7
- [x] Keep Response time history collapsed by default without stopping its live samples.
- [x] Make per-interface RX/TX charts visually consistent with the aggregate network history.
- [x] Fix expanded Docker-vhost route/ACL field alignment from the 0.18.6 UI.
- [x] Update package/source identity and release checks to 0.18.7.
## 0.18.6
Current direction: 0.18.5 -> 0.18.6
- [x] Add network-throughput diagnostics split by non-loopback host interface.
- [x] Re-audit compression counters and add q-weighted negotiation coverage for all four supported codecs.
- [x] Polish Docker-agent settings alignment and spacing around listener selection / Add agent.
- [x] Update package/source identity and release checks to 0.18.6.
## 0.18.5
Current direction: 0.18.4 -> 0.18.5
- [x] Make compression counters track the codec actually applied by RustProxy and add independent per-codec regression coverage.
- [x] Split the Overview request-rate chart into total, HTTP/1.1, HTTP/2 and HTTP/3 series.
- [x] Add a proxied-app launcher to Docker vhosts and show their domains beside generated backend-pool IDs.
- [x] Remove the global Open proxy shortcut.
- [x] Make Compact density clearly tighter than Dense.
- [x] Give proxy timeout controls explicit safe defaults instead of blank inherited values.
- [x] Replace Docker-agent listener CSV entry with the named-listener picker and tighten the agent status-row UX.
- [x] Update package/source identity and release checks to 0.18.5.
## 0.18.4
Current direction: 0.18.3 -> 0.18.4
- [x] Make TCP the default backend health-check mode without rewriting existing explicit HTTP/off configuration.
- [x] Make Docker vhosts summary-first so large container inventories do not render every routing/ACL/timeout/health control at once.
- [x] Simplify Settings -> Security with progressive disclosure for credential and host-policy editing.
- [x] Move Administration audit to a dedicated on-demand modal.
- [x] Update package/source identity and release checks to 0.18.4.
## 0.18.3
Current direction: 0.18.2 -> 0.18.3
- [x] Export Docker vhosts/backends under dedicated Prometheus metric families rather than ordinary vhost/backend families.
- [x] Add a separate live Docker-vhosts metric-group switch in Prometheus settings.
- [x] Cover Docker vhost traffic/duration and backend health/latency/probe/runtime observations with bounded labels.
- [x] Add regression checks for ordinary-vs-Docker metric isolation.
- [x] Add configurable separate access-log files per vhost and preserve bounded asynchronous sink behavior.
- [x] Lock startup-owned HTTP/HTTPS/admin binds while keeping non-startup listeners editable.
- [x] Polish compact UI, 404 response statuses, redirect summaries, global TLS cipher visibility and managed-certificate lifecycle UX.
- [x] Default WebSocket proxying to HTTP/1.1 Upgrade and make RFC 9220 HTTP/3 Extended CONNECT an explicit experimental vhost opt-in.
## 0.18.2
Current direction: 0.18.1 -> 0.18.2
- [x] Serve the standalone Docker agent over HTTPS by default using a persistent self-signed ECDSA certificate.
- [x] Add SHA-256 certificate pinning for self-signed Docker agents while retaining normal public-CA validation when no pin is configured.
- [x] Add per-service Docker backend health-check settings backed by the normal bounded backend health machinery.
- [x] Show Docker backend health/runtime diagnostics and agent TLS/observation state in the Docker vhosts UX.
## 0.18.1
Current direction: 0.18.0 -> 0.18.1
- [x] Add a standalone, token-authenticated Docker discovery agent that reports only running containers with non-loopback published TCP ports.
- [x] Add multi-agent endpoint/token/base-domain/listener/default-IP settings and keep observed Docker inventory separate from desired persisted assignments.
- [x] Detect multiple usable Docker-host addresses and preserve every public Docker bind for each discovered service.
- [x] Add a Docker vhosts panel with short names, arbitrary FQDNs, aliases and per-service backend-IP selection; reconcile assignments into dynamic ephemeral vhosts without revision writes.
- [x] Move the cache Live toggle to the right edge of the cache action toolbar.
- [x] Remove all dedicated `X-Rust-Proxy-Revision` handling; it is no longer generated, stripped, protected or migrated.
- [x] Replace production password recovery help with the installed `rust-proxy admin reset-password` command.
## 0.17.0
Current direction: 0.16.1 -> 0.17.0
- [x] Add Debian/systemd/logrotate packaging and local APT-repository generation.
- [x] Export all currently implemented bounded process/OS diagnostics and extend safe Prometheus coverage for backend, GeoIP, certificate renewal, stream and logging state.
- [x] Add explicit trusted-CIDR Prometheus scraping without a token and preserve actual-peer-only ACL evaluation.
- [x] Ship a Grafana operations dashboard.
- [x] Add dedicated certificate/listener-aware local-response / 404 hosts and a dashboard editor.
- [x] Keep the built-in welcome placeholder `no-store` and strip optional response metadata.
- [x] Match Redirect source-domain editing UX to Vhost hostname chips.
## 0.16.1
Current direction: 0.16.0 -> 0.16.1
- [x] Keep revision CAS as an internal concurrency mechanism but stop showing revision numbers in successful configuration-save feedback.
- [x] Preserve all 0.16.0 proxy/cache/H3/admin/UX behavior and bump release identity to 0.16.1.
## 0.16.0
- [x] Keep the main vhost backend as an internal fallback separate from Custom locations, with legacy synthetic-root normalization.
- [x] Preserve the reserved administration session cookie only for an explicitly allowed, TLS-protected self-admin reverse-proxy route; ordinary upstreams remain isolated.
- [x] Complete backend WebSocket takeover before HTTP/3 Extended CONNECT success and add phase/error/reset/upstream diagnostics to access logs.
- [x] Split process-memory and cache-memory reporting into RSS categories plus hot/transient fill/cold-queue/cold-I/O gauges while retaining the existing allocator-release paths.
## 0.15.1
Current direction: 0.15.0 -> 0.15.1
- [x] Repair EN/PL Custom locations/help translations and reject misplaced language-pack keys in release checks.
- [x] Rework visual header-rule editing and add a live effective-behavior preview to each location.
- [x] Expand in-panel location documentation with structured sections and common examples.
- [x] Add combined HIT/MISS/BYPASS activity and Proxy process CPU/RSS charts.
## 0.15.0
Current direction: 0.14.0 -> 0.15.0
- [x] Add Nginx-style Custom location matching, rewrite and local-response controls with inline documentation and a visual header-rule builder.
- [x] Add reusable Nagios/Icinga API checks for control-plane latency, backend availability/latency and dropped-work counters.
- [x] Move per-vhost timing/Cloudflare diagnostics to the main frontend configuration and add source IP to live logs.
- [x] Add dashboard history charts for RAM cache, network throughput and response latency plus compact API/footer diagnostics.
- [x] Add separate idle-age and total-hot-residency cold-cache policies.
- [x] Remove the runtime renewal-observation JSON importer and quarantine obsolete state only during a stopped source upgrade.
- [x] Accept repeated literal path separators by normalizing them for routing/ACL decisions while preserving the original backend target.
## 0.14.0
Current direction: 0.13.2 -> 0.14.0
- [x] Accept explicitly allowlisted browser-facing administration DNS authorities behind a trusted reverse proxy without requiring the internal admin port.
- [x] Keep direct IP/localhost administration access on the configured admin port and derive same-origin/cookie security from trusted external proxy metadata.
- [x] Implement RFC 9220 WebSocket Extended CONNECT over HTTP/3 without a side-channel transport or HTTP/1.1 bootstrap fields on the client-facing H3 request.
- [x] Move the H3 stack to `hpx-h3 2.5.19` with integrated Quinn transport and advertise Extended CONNECT support.
- [x] Rename the requested dashboard sections/actions in EN/PL and update release contracts/documentation.
## 0.13.2
Current direction: 0.13.1 -> 0.13.2
- [x] Honor a valid administrator session on direct panel refresh and revalidate stale browser-history login documents.
- [x] Force hot-cache handoff to enabled cold storage during managed updates and provide a longer controlled-stop budget.
- [x] Store automatic-renewal observations in SQLite schema v3 and restore them before the administration listener starts, with one-time legacy JSON import.
- [x] Move the raw configuration document editor under **Settings -> Manual config** while preserving `/configuration` as a compatibility alias.
- [x] Update documentation, OpenAPI, language help, offline regressions and release identity.
## 0.13.1
Current direction: 0.13.0 -> 0.13.1
- [x] Extend administrator browser sessions to a configurable absolute 7–30 day lifetime, default 30 days.
- [x] Keep password change, logout and revoke-all as immediate session invalidation boundaries.
- [x] Allow deliberate local-listener backends, including port 9090, only behind explicit per-backend risk acceptance while retaining private/special-address policy.
- [x] Add panel confirmation, OpenAPI fields, EN/PL strings, documentation and regressions.
## 0.13.0
Current direction: 0.12.1 -> 0.13.0
- [x] Add first-class redirect hosts and dashboard navigation.
- [x] Support scheme/path matching, URL/host/path targets, 301/302/303/307/308, path/query and redirect-cache controls.
- [x] Share named listeners and certificate inventory with redirects while rejecting conflicting frontend ownership.
- [x] Default backend HTTPS `tls_verify` to false when omitted; keep explicit opt-in verification.
- [x] Compact certificate inventory cards, render DNS SANs as vhost-style badges and inspect legacy/manual public PEM files when stored coverage metadata is absent.
- [x] Update OpenAPI, docs, translations, regressions and release identity.
## 0.12.1
Current direction: 0.12.0 -> 0.12.1
- [x] Split ordinary configuration writes into revisioned section endpoints and retain atomic full-document publication for intentional multi-section changes.
- [x] Stop unrelated control-plane saves from rotating cache compatibility identity.
- [x] Preserve dashboard subpage routing across browser refresh/back-forward navigation.
- [x] Add independently selectable cold-cache idle/LRU/shutdown persistence policies.
- [x] Flush hot cache objects to cold storage during graceful shutdown and release RAM after bulk demotion where the allocator supports it.
- [x] Expose certificate DNS SubjectAltName coverage separately from configured SNI bindings.
- [x] Improve HTTPS backend TLS validation guidance while preserving secure-by-default omitted configuration and the explicit debug preset.
- [x] Update OpenAPI, regression checks and release identity to 0.12.1.
- [ ] Compile and execute the Rust test suite on a Rust 1.88+ staging host.
## 0.12.0
Current direction: 0.11.5 -> 0.12.0
- [x] Move Layer-4 TCP/UDP stream management out of Settings into a dedicated main-navigation page directly below Vhosts.
- [x] Give Streams the vhost-style searchable card + modal workflow with explicit active-versus-desired restart state.
- [x] Add fixed-cardinality per-stream connection/session, reject, error, byte and UDP packet telemetry without database work on the data path.
- [x] Add browser-session RX/TX rate charts derived from monotonic backend stream counters.
- [x] Restyle named listener bind addresses with the same chip interaction used for vhost hostnames.
- [x] Extend offline source/release regression gates for Streams navigation, authenticated assets, UI contracts and L4 metrics source invariants.
- [x] Stop successful unsafe methods from globally purging otherwise unrelated cached GET objects.
- [x] Use Host + URL as the logical primary cache identity and response `Vary` for representation variants instead of hashing unrelated request headers/backend members.
- [x] Share cache objects across backend-pool members and GET/HEAD requests.
- [x] Preserve resident age through 304 revalidation and hot/cold movement; persist the additional cold metadata in format 2.
- [x] Add variant-schema safety and cache regression coverage for the new behavior.
- [x] Make Force SSL require a real HTTP ingress and choose port 443 deterministically when several TLS ports are selected.
- [x] Add a graphical ACL-profile rule editor and resilient bulk-rule import with exact line errors.
- [x] Hide RustProxy cache diagnostics when a vhost explicitly disables cache.
- [x] Distinguish disabled vhost pools from unhealthy backends in the dashboard.
- [x] Add per-vhost client-IP forwarding control without weakening RustProxy ACL/GeoIP/rate-limit/Tinyauth identity.
- [x] Add a compact vhost search/filter for large installations.
- [x] Keep the checked-in Cargo lockfile stable and correct the Quinn manifest version to the resolved release.
- [x] Make the standard JSONL log directory writable under the hardened systemd service.
- [x] Accept HTTP/2/H3 `:authority` on the HTTPS administration panel without requiring a duplicate legacy Host header.
- [x] Reject ambiguous requests where legacy Host conflicts with protocol-native authority, while preserving strict HTTP/1.x origin-server semantics.
- [x] Add administration H2 authority and `/healthz` regression tests.
- [x] Add per-vhost Force SSL with an HTTP 308 redirect to the configured TLS frontend while preserving path/query.
- [x] Preserve NPM `ssl_forced` in the SQLite migrator as RustProxy Force SSL with usable HTTP+HTTPS listener bindings.
- [x] Fix managed update/rollback health checks for persisted administration HTTPS and changed admin bind/Host policy.
- [x] Add a dry-run-first SQLite migration tool for Nginx Proxy Manager proxy-host names and backends, with generic RustProxy TLS certificate fallback and manual-review warnings for unsupported NPM features.
- [x] Add offline migration-planner and HTTPS health-target regression coverage.
- [x] Add reusable ACL Profiles with country allow/block and IP/CIDR policy, attachable per vhost while retaining cumulative inline vhost and route ACL controls.
- [x] Add chip-style hostname editing and leading `*.` wildcard domains with exact-host and longest-wildcard precedence.
- [x] Add compact browser-session request-rate charts to vhost cards with progressive lifetime compaction.
- [x] Add the built-in HTTP 401 error page and treat benign post-upgrade WebSocket closes as normal tunnel completion.
- [x] Redirect plaintext requests arriving on the HTTPS administration socket and accept valid DNS Host values when no explicit admin hostname allowlist is configured.
- [x] Allow one named frontend listener policy to bind up to 16 explicit local addresses while forbidding wildcard binds in multi-address mode.
- [x] Align the Logging format builder with the Vhost configuration modal and reposition its field-configuration action.
- [x] Make source ZIP generation deterministic and verify reproducibility in release smoke tests.
- [x] Add bounded hot RAM and optional persistent cold disk cache tiers with asynchronous demotion/promotion, restart recovery, integrity metadata and separate hot/cold statistics.
- [x] Add cold-tier settings for path, disk/object budgets and idle demotion time while preserving global hot RAM limits.
- [x] Add structured-log format presets plus a visual field selector/renamer with JSON preview and advanced raw mapping fallback.
- [x] Normalize Logging sink cards to the shared application card styling instead of release-specific fieldset CSS.
- [x] Add baseline multi-stage Docker/OCI packaging with an unprivileged runtime user and persistent data volume.
- [x] Document browser hard-reload cache revalidation semantics and expose the exact request revalidation reasons in the cache inspector.
- [x] Redesign vhost cards around listener/protocol/certificate/cache identity plus live pool health, requests, active exchanges, errors, latency and traffic rate.
- [x] Add bounded structured JSON access-log sinks for JSONL files, Logstash TCP and Loki HTTP(S), with configurable field mapping/static fields and sink counters.
- [x] Expand cache object inspection with body bytes, resident bytes, overhead and aggregate stored-object memory counters.
- [x] Add optional `X-Cache-Resident-Age` and expose resident age for stored HITs.
- [x] Add administration HTTPS using the application-managed default certificate or a selected certificate, with restart-aware persisted bind address and hostname allow list.
- [x] Permit non-loopback administration binding when HTTPS is enabled; keep plain remote HTTP behind the legacy explicit risk gate.
- [x] Add global TLS protocol/cipher policy plus per-vhost TLS 1.2/1.3 and cipher overrides for TCP TLS. HTTP/3 vhosts inherit the global TLS 1.3 policy.
- [x] Make Settings tabs fill the available width and add dedicated Administration/TLS policy pages plus a separate Logging navigation page.
- [x] Enable shared cache by default while keeping explicit vhost/route disable controls.
- [x] Implement fresh conditional HIT for `If-None-Match` / `If-Modified-Since`.
- [x] Implement backend validator revalidation and cached-body reuse on backend 304.
- [x] Treat request/response `no-cache` as validation semantics rather than no-store; preserve `no-store` as hard exclusion.
- [x] Retain stale validators for bounded revalidation without stale serving.
- [x] Raise maximum object configuration to 1 GiB and remove eager object-sized fill allocation.
- [x] Add diagnostics, real-binary smoke scenarios, unit regressions and 0.8.1 release documentation.
- [x] Per-vhost H2/H3 frontend opt-outs next to named-listener selection, capability-gated by Settings listeners.
- [x] Global static-cookie cache defaults for common assets, visible global/per-vhost controls and response-side privacy guards.
- [x] Listener editor uses page scrolling without a nested scrollbar.
- [x] Per-vhost fixed-window request rate limiting with burst allowance.
- [x] Per-country rate overrides and block/allow-only country access rules backed by verified install-time GeoIP data.
- [x] Cloudflare client IP mode gated by direct-peer Cloudflare CIDRs.
- [x] Per-vhost H2/H3 frontend opt-outs and Server-Timing diagnostics.
- [x] Named listener selection in vhosts; listening IP/ports managed in Settings.
- [x] Health-check `off` disables active and passive classification.
- [x] HTTPS-by-IP backend debugging defaults and warnings.
- [x] Global static-cookie cache defaults plus per-vhost inheritance/overrides with response privacy guards.
- [x] Corrected cache Age calculation.
- [x] Updated deployment regressions, docs and 0.8.0 release identity.
- [x] Release CLI can generate the one-time setup token before first server start.
- [x] Explicit two-flag risk gate for wildcard remote administration binding.
- [x] Numeric Host validation for the wildcard panel; DNS hostnames remain rejected.
- [x] Linux/systemd install defaults enable 80/443; explicit `--no-https` opt-out.
- [x] Updated deployment regressions, docs and 0.7.3 release identity.
- [ ] Build/check/test the Rust binary and exercise remote-panel/systemd behavior live.
- [x] Explicit release identity, without overwriting the 0.7.1 artifact.
- [x] Swagger asset default independent of environment presence/Cargo config lookup.
- [x] Embedded assets prepared by our build, without an adapter's old-version fallback.
- [x] Offline local ZIP, bounded member validation, optional SHA-256 and safe caching.
- [x] Executable build-helper regressions and updated build/migration instructions.
- [ ] Build/check/test real Rust and render the actual upstream Swagger distribution.
- [x] Per-location visual proxy-request and client-response header rules.
- [x] Minimal anonymous login document and filtered extensible language bundle.
- [x] Exact default-deny admin route/method policy including private assets and docs.
- [x] Protected OpenAPI/Swagger, cookie session and CSRF-confirmed mutations.
- [x] Generic public discovery/errors and cross-tab UI revocation cleanup.
- [x] Source audit, access probe and regression tests with explicit execution limits.
- [ ] Compile and run router/proxy tests, inspect real Swagger rendering, and run the
anonymous probe against the built release before production acceptance.
- [x] Scheduled renewal for HTTP-01, Cloudflare, OVH and RFC2136 managed profiles.
- [x] Validated revisioned hot certificate publication with unchanged/failure safety.
- [x] Per-certificate opt-out, manual check, dry run, next/last/expiry observations.
- [x] Separate install/update scripts; isolated service, full data backup and rollback.
- [x] Vhost Basic Auth with bounded Argon2id checks and optional credential forwarding.
- [x] Per-vhost WebSocket toggle and graphical cumulative CIDR ACL editing.
- [x] Visual prefix locations with pool exclusion for dedicated origins.
- [x] Neutral welcome page, opt-in protocol fixture, shared square modal/Settings style.
- [x] Independent proxy and local administrator control plane; no Docker dependency.
- [x] SQLite documents, revision CAS, immutable snapshots, audit and private backups.
- [x] Accounts, bounded sessions, setup capability, origin/CSRF protections.
- [x] Host/path routes, aliases, explicit private-origin permission and CIDR ACLs.
- [x] Streaming bodies and full-exchange limits; bounded logs, SSE and Prometheus.
- [x] Public TLS/SNI, verified HTTPS upstreams, guarded DNS, HTTP/2 and WebSocket source.
- [x] Whole-source upgrade, legacy auth-module repair and rollback on copy failure.
- [x] Persist public HTTP/HTTPS bind addresses and ports, including 80/443 presets.
- [x] Distinguish active listeners, desired listeners and startup defaults; show restart pending.
- [x] Allow remote origins on ordinary 80/443 without allowing local listener loops.
- [x] RAM cache with TTL/Age, entry/object/byte limits, LRU eviction and route bypass.
- [x] Bounded streaming fills; do not publish partial, failed, oversized or trailer bodies.
- [x] Privacy exclusions, conservative Vary identity and ACL-before-cache ordering.
- [x] Optional X-Cache, X-Cache-Hits and Cache-Status; counters and authenticated purge.
- [x] Invalidate old fill generations on purge, config publication and successful unsafe methods.
- [x] Streaming gzip, Brotli, Zstandard and deflate negotiation with privacy/no-transform safeguards.
- [x] Preserve/hide/custom Server policy; client-response and upstream-request header editors.
- [x] Denser responsive dashboard; original logo retained.
- [x] OS/process CPU, RAM/RSS, load, descriptors, threads, network, cgroup and cache diagnostics.
- [x] Compile-time language discovery from langs/*.json; metadata, fallback and RTL.
- [x] Extra language files survive managed source updates.
- [x] Log filtering by host/path/cache outcome and bounded JSON export.
- [x] Unit and real-binary integration test source; 49 simulated-API frontend checks.
- [x] Example unprivileged systemd service with low-port capability.
- [x] Remove reported compression threshold type mismatch without narrowing or panic conversion.
- [x] Explicit frontend IP/port/TLS ownership inside a vhost; actual-destination isolation.
- [x] Multiple backend members, weights, backup and drain modes.
- [x] Weighted round-robin, least active exchanges, least traffic, adaptive and IP hash.
- [x] Connection keepalive/reuse limits and timers, not a Keepalived/VRRP integration.
- [x] Bounded TCP/HTTP health checks, slow-response exclusion, fall/rise and recovery.
- [x] Opaque cookie/IP affinity; compatible edit continuity, safe cache bypass.
- [x] Full-exchange backend leases; no automatic replay of failed writes.
- [x] Per-vhost response/security headers, TLS-only HSTS and explicit preload policy.
- [x] HTTP/2 TLS preference retains HTTP/1.1 for clients and HTTPS origins.
- [x] Vhost editor, atomic legacy migration, health/traffic/latency dashboard and API metrics.
- [x] All new user-facing text in discovered language packs; logo unchanged.
- [x] Rust regression and real-binary acceptance test source; 63 simulated-API UI checks.
- [x] Complete source update, examples, migration documentation and evidence report.
- [x] Split oversized status JSON expansion instead of masking it with a larger recursion limit.
- [x] Optional per-vhost certificate upload/selection with bounded private staging and validated SNI mappings.
- [x] Independent vhost cache behavior, inherited defaults and shared resident/object ceilings.
- [x] **Selective cache invalidation by vhost**, promoted from Cache evolution; invalidate pending fills too.
- [x] Compact manager list and tabbed vhost dialog, visual header rules and live health/latency.
- [x] Dashboard density presets and top alignment, without redesigning the logo.
- [x] Separate modal/docked live logs with persistent controls, vhost filters, pause and scoped export.
- [x] Global/vhost error pages, maintenance/static response and direct-IP/unknown-host behavior.
- [x] Security shortcut within Settings; new labels remain in discoverable language packs.
- [x] Python-only random-data mock origin for cache, health, delay, compression and error-page development.
- [x] Regression test source for new runtime policies; executed frontend/helper checks in TEST_REPORT.md.
- [x] Canonical Forwarded/X-Forwarded-For/X-Real-IP with explicit bounded proxy trust; keep destination-IP routing independent.
- [x] Per-vhost Tinyauth auth subrequest, sanitized identity headers, restricted login redirects and fail-closed handling before cache/static/upgrades.
- [x] Separate certificate inventory tab with upload, per-vhost selection and metadata-only read API; leave ACME as future work.
- [x] Named multiple-IP listeners, descriptive scope, reusable vhost references and explicit legacy-route permission.
- [x] HTTP/3 UDP adapter using Quinn/h3 with TLS 1.3, same SNI/policy pipeline, Alt-Svc, requested limits and bounded drain; retain TCP H2/H1.
- [x] Request ID/revision off by default, independent per-vhost controls, optional mock-origin removal and effective per-vhost Server policy.
- [x] Actual frontend socket in cache identity; streaming-safe empty request detection for H2/H3.
- [x] Toast above active modal backdrop, including browser top-layer/inertness handling.
- [x] Python fake-auth peer plus Rust regression sources for auth, headers, extra-frontend cache, listeners and real QUIC/TCP.
- [x] Dedicated bounded cache panel: resident objects/bytes, per-domain effectiveness, TTL/Age/hits, reasons and vhost filters.
- [x] Remove the reserved administrator cookie before cache eligibility; preserve application-cookie privacy.
- [x] Handle empty terminating DATA frames and expose fixed X-Cache-Reason / Cache-Status detail diagnostics.
- [x] Replace special mock-origin hiding with explicit revision controls; keep general header rules.
- [x] Default HTTP/3 for TLS while persisting explicit opt-out and TCP fallbacks.
- [x] Diagnose overlapping sockets with addresses, ownership, protocol/limit differences and remedies.
- [x] Classify unavailable/invalid/timed-out upstreams as 503/502/504 respectively.
- [x] Basic Let's Encrypt DNS-01/RFC2136 job and library-import workflow through Certbot; private TSIG/account state and staging-first consent.
- [x] Offline adapter tests, browser fixtures and Rust regression sources; unchanged brand assets.
- [x] Vhosts-only runtime/UI and one-way legacy import preserving path/backend and IP ownership.
- [x] HTTP-01 active-job/domain-scoped challenge serving with private webroot storage.
- [x] Certbot Cloudflare and OVH API DNS-01 adapters alongside RFC2136.
- [x] Unified provider/import dialog, masked credentials, explicit consent and issuer metadata.
- [x] Dedicated opt-in Prometheus endpoint, scrape CIDRs and environment-token authentication.
- [x] Application/admin/listener/vhost/backend/cache/system collectors and exchange histograms.
- [x] Functional Settings tabs with preserved dirty fields, keyboard navigation and no Security sidebar.
- [x] Offline provider regressions, simulated-API UI checks and unexecuted Rust acceptance tests.
- [ ] Validate real HTTP-01, Cloudflare/OVH/RFC2136 DNS updates, CA issuance, cancellation cleanup and certificate hot-deployment.
- [x] Add the managed renewal scheduler and audited validated deployment source.
- [ ] Verify real renewal/publication and add external expiry alerts, revocation/account lifecycle.
- [ ] Resolve and retain Cargo.lock; run check, test and all real-binary suites.
- [ ] Verify real Tinyauth integration, revocation, trusted forwarding and independent HTTP/3 client interoperability.
- [ ] Run an independent RFC 9114/QUIC conformance suite, malformed-stream/load tests and actual TLS/UDP production deployment checks.
- [ ] Execute vhost isolation/weighted balancing/health/recovery/keepalive/affinity acceptance against a real Rust binary.
- [ ] Verify compression output with independent Brotli/Zstandard decoders.
- [ ] Run real TLS/HTTP2/WebSocket/cache concurrency, cancellation and graceful-drain tests.
- [ ] Benchmark HIT/MISS/streaming RSS and CPU under slow clients and origin failures.
- [ ] Fuzz malformed HTTP/cache directives and language-pack validation.
- [ ] Exercise real non-root 80/443 bindings and systemd service confinement.
- [ ] Verify native browser HTTP/CSP/cookie enforcement (navigation blocked in this environment).
- [ ] Validate the dedicated scrape endpoint/token with a real Prometheus server and production network policy.
- [x] X-Cache-Resident-Age - implemented in 0.9.0
## Optional module follow-ups
### Docker discovery
Implemented in 0.18.1 through the standalone `docker-agent/` provider boundary.
Future work can add provider-side filters, mTLS and DNS-provider automation; the core remains useful without Docker and never opens the Docker socket itself.
### Cache evolution
Conditional revalidation is implemented. Stale-while-revalidate/stale-if-error, request collapsing,
tag-based invalidation and richer per-route policy remain open. Persistent hot/cold storage is implemented in 0.10.0.
Selective invalidation by vhost is implemented in 0.3.0; the domain/object inspector in 0.5.0; tags are not. No VCL engine,
distributed cache, ESI or Varnish-protocol compatibility is implemented.
### Operations
Container/OCI packaging is implemented in 0.10.0. Certificate alerting/revocation and account lifecycle, reproducible releases,
performance baselines and cross-platform diagnostics remain open. Remote HTTPS administration is implemented in 0.9.0;
loopback remains the default and plain remote HTTP remains explicitly risk-gated.
+73
View File
@@ -0,0 +1,73 @@
# Screenshots
A tour of the RustProxy administration panel. The panel runs on the separate administration listener (by default `http://127.0.0.1:9090`) and is available in English and Polish, with light and dark themes.
## Sign-in
Local administrator sign-in. The first account is created with a one-time setup token; there is no default password.
![Sign-in](images/login.png)
## Overview dashboard
Live requests per second, bandwidth, status codes, cache hit ratio, protocol mix, system resources and backend health. Cards can be rearranged, resized, hidden and saved as presets.
![Overview dashboard](images/overview.png)
## Vhosts
Every published domain with its listeners, backends, health, request rate and security options. The editor groups frontend, backends, balancing, security, cache and custom locations.
![Vhost list](images/vhosts.png)
![Vhost editor](images/vhost-editor.png)
## Web Server
Reusable Web Services for static sites and PHP applications, with presets, delivery options and generated filesystem permission commands.
![Web Server](images/web-server.png)
## Certificates
Certificate inventory with expiry, covered hostnames, assigned Vhosts and managed ACME renewal.
![Certificates](images/certificates.png)
## Traffic
Per-Vhost traffic analytics: top paths, clients, user agents, countries, methods and status codes.
![Traffic analytics](images/traffic.png)
## Cache
HOT and COLD cache usage, hit ratio, stored objects and purge/ban tools.
![Cache](images/cache.png)
## Logs
Live and stored request logs with filters, search syntax and record details.
![Logs](images/logs.png)
## Settings
Listeners, TLS policy, security, logging, cache, compression, monitoring, administration and appearance.
![Settings](images/settings.png)
## Documentation and API
The built-in Reader with all guides, and an interactive OpenAPI reference powered by Swagger UI. Both work offline and use the administrator session.
![Documentation Reader](images/documentation.png)
![API reference](images/api.png)
## About
Version, author, license summary and project links.
![About](images/about.png)
-59
View File
@@ -1,59 +0,0 @@
# Secret Store and encrypted full backup/restore
RustProxy keeps application credentials in a central encrypted Secret Store. Provider and ACME secrets are not stored as plaintext configuration values in SQLite.
## Secret Store
Secret records live in the `secret_store` SQLite table and contain an identifier, provider/type, encryption version, nonce, ciphertext, metadata and timestamps. Values are encrypted with AES-256-GCM using a random local data key stored at:
```text
<data-dir>/secrets/master.key
```
The key file is created with mode `0600`; its directory is private. The application loads it during normal startup, so normal operation does not require an interactive password.
Existing ACME DNS credential files are migrated on startup for Cloudflare, OVH and RFC2136. After successful import, the legacy plaintext credential file is removed. Certbot-compatible credential files are recreated only as short-lived private files around the external operation and removed afterwards.
Do not copy only `config.sqlite3` as a Secret Store backup: the local key material is also required. Use the encrypted full-backup command below.
## Create a full backup
`backup create` may run while RustProxy is serving traffic. If the service is stopped, the CLI takes the data-directory lock; if the service is running, it creates a consistent SQLite snapshot and stages durable filesystem state without taking ownership of that lock. The output file must be outside the RustProxy data directory and must not already exist.
```bash
rust-proxy --data-dir /var/lib/rustproxy backup create \
--output /private/rustproxy-backup.rpb
```
When run from a TTY, RustProxy asks for the backup password without putting it in process arguments or shell history. Press Enter at the password prompt to generate a strong random password. A generated password is printed exactly once and is required for restore.
For non-interactive `backup create`, RustProxy generates the password automatically and prints it once to standard output. Protect that output as a secret.
The encrypted backup contains the durable application state needed for recovery, including:
- a consistent SQLite snapshot,
- configuration and Secret Store records,
- backup-wrapped Secret Store key material,
- certificates and private keys,
- ACME state and durable provider data,
- other durable RustProxy data accepted by the backup manifest,
- validated internal relative symlinks, including Certbot `live/` links into the same profile's `archive/` tree.
It excludes rebuildable/runtime data such as cache, logs, temporary files, sockets, PID/lock files, SQLite WAL/SHM sidecars and the managed Certbot Python environment (`acme-python`; the `acme_python` spelling is excluded as well). Certificate/ACME durable state inside the RustProxy data directory remains included; the virtual environment is rebuilt when needed. Manual certificates configured with absolute external paths (for example `/etc/letsencrypt/live/...`) are intentionally **not** copied into the backup: only the configured path remains in SQLite, so the external files or mounts must be restored separately. Symlinks inside the backed-up RustProxy data tree are accepted only when their target is relative and resolves inside that staged tree; absolute, escaping or dangling links fail backup/restore. Restore recreates accepted in-tree links as links rather than dereferencing them.
The outer container derives a backup key with Argon2id and encrypts authenticated chunks with AES-256-GCM. The backup password is independent from the local Secret Store key.
## Restore
Restore is CLI-only, requires RustProxy to be stopped, and requires an interactive TTY for the password:
```bash
rust-proxy --data-dir /var/lib/rustproxy backup restore \
/private/rustproxy-backup.rpb
```
Before replacing the destination, RustProxy verifies the encrypted container, password/authentication tags, manifest, checksums, backup format, RustProxy/schema compatibility, restored SQLite database and decryptability of all Secret Store records.
Files are first restored into a private staging directory. Only after validation succeeds is the staged data directory atomically activated; activation failure attempts to roll back to the previous directory. Sensitive restored files use restrictive permissions.
A lost backup password cannot be recovered by RustProxy.
+32 -209
View File
@@ -1,225 +1,48 @@
# Security model and limitations
# Security model and deployment boundaries
This is a development source release with no executed Rust integration results in
the packaging environment. Do not expose it as a production security boundary until
the actual build, protocol tests, deployment review and an appropriate security
assessment have passed.
RustProxy provides several security controls, but it is not a complete Web Application Firewall, PHP sandbox or network firewall. Safe deployment depends on frontend TLS, trusted origin connectivity, application updates, least-privilege operating-system access and release testing.
## Administration
## Administration and private state
Administration binds to loopback HTTP by default. Settings can enable a dedicated
administration HTTPS listener, choose the application-managed certificate or a
certificate from the inventory, persist a non-loopback bind and allow explicit DNS
Host/Origin names. Remote persisted binding is accepted only with administration TLS.
The normal installer, one-line bootstrap and reference service keep the plain-HTTP admin listener
on loopback by default. An explicit startup `--allow-remote-admin` can expose plain HTTP on a
non-loopback address; that path should remain behind a trusted firewall/private network boundary and
should be replaced by Administration HTTPS for direct remote use.
Passwords use salted Argon2id; password work has admission limits and runs off the
network I/O path. Setup requires local filesystem authority or the one-time random
capability. No default administrator/password is created.
- Keep the separate admin listener on **loopback** unless explicitly configuring trusted administration HTTPS and restricted reachability. Use an SSH tunnel for simple remote management.
- Create the first administrator locally. There are **no default passwords**. Protect CLI recovery operations, sessions, session revocation and API token creation.
- Keep the SQLite database, Secret Store encryption material, certificate keys, `.env`, ACME profiles, external credentials and backups readable only by trusted operators. `.env` and service configuration may be trusted executable/input material.
- Never expose stored logs, OpenAPI, detailed status, full config or backup files anonymously. Keep public Vhosts away from the administration listener except in a carefully reviewed explicit-risk topology.
Sessions have HttpOnly/SameSite=Strict cookies, idle and absolute expiries and
credential-generation revocation. Mutations require scheme-aware Host/Origin checks,
the request marker and a CSRF token. Local CLI reset changes the hash and invalidates
all existing sessions without editing SQLite manually. Administration HTTPS marks the
session cookie Secure; HTTP cannot. The admin certificate and bind changes are
restart-required. A dedicated metrics credential is separate from browser sessions.
## Origin and request trust
The data directory and SQLite files are private. The local OS user with data-directory
access is a recovery authority and can create short-lived CLI sessions. Protect that
account, filesystem and backup copies; browser authentication does not defend against
a compromised process owner. `.env` is trusted shell code, never untrusted input.
- Enforce IP/country/DDNS policies, rate limits, HTTP method rules and ordinary Vhost request **Content-Type** deny rules **before** connecting to backends. A Custom location cannot undo a Vhost-level denial. A Content-Type rule is based on the declared request header; a client can omit or mislabel it, so it does not substitute for application or PHP security patches.
- Private and local backend addresses require deliberate exceptions. Routing to the local administration listener can expose secrets or create routing loops.
- HTTPS to a backend is not proof of identity unless upstream certificate/hostname verification is enabled; current backend defaults do **not** enable verification automatically.
- Honor forwarding identity headers only from correctly configured trusted peers. A client can forge arbitrary `X-Forwarded-*` and User-Agent headers.
- Restrict Basic Auth to HTTPS and protect Tinyauth endpoints. Authentication traffic should never be stored in public shared cache.
## Backend and routing boundary
### Global HTTP security policy
Only configured HTTP/HTTPS origins may be dialed. Absolute-form client targets and
CONNECT cannot turn the proxy into an unrestricted forward proxy. ACL is evaluated
before new TCP/TLS connections and before reuse of an existing upstream connection.
Host and route restrictions combine; route allow cannot override host deny.
Forwarded headers are rebuilt from socket identity by default. An explicit
trusted-proxy CIDR policy can accept a bounded existing chain and derive the client
IP for ACL/affinity; see INTEGRATIONS_040.md. Routing still uses the actual local
destination, never any forwarded address or host.
**Settings → Rules & headers → Global block / allow** is a shared first gate for public HTTP listeners. Countries, User-Agents, IP/CIDR (including allow-only DNS), methods, declared request Content-Types and an optional shared rate limit can be configured there. These rules run before vhost routing, redirects and cache invalidation and do not replace Basic Auth, Tinyauth, application controls or OS firewalling. The country and IP decisions are both required to permit a request; a country deny cannot be bypassed by an IP allow and vice versa. Country mode **allow** is fail-closed for an unknown country; **block** mode is fail-open for unknown GeoIP data. Resolve identity from a trusted source and keep GeoIP updates healthy.
Private/loopback destinations require explicit approval. DNS answers pass the same
policy and optional per-backend CIDR restriction; an unsafe answer fails the entire
resolution. Validated results are used without a second DNS lookup. Special-address
blocks include common metadata/link-local targets and normalize mapped IPv6.
Listener self-target restrictions prevent simple loops and control-plane access by
default. A privileged administrator can deliberately set `accept_local_listener_risk=true`
on one backend; private/loopback destinations additionally require `allow_private=true`.
This is intended for consciously accepted local-listener use (including port 9090) and
can create a request loop or expose administration through a public vhost. It does not
relax metadata/link-local/special-address filtering or CIDR restrictions. Keep network
egress and frontend ACLs as defense in depth.
The **Security headers** subtab controls an HTTPS HSTS policy and optional global nosniff, frame protection, referrer, CSP and permissions policies. They are a common baseline; per-vhost values can set matching headers, but an unset field does not remove a global value. Rejecting only declared Content-Type is not a WAF, and changing HSTS/CSP may break sites. Changes do not apply to TCP/UDP Streams or the private administration listener.
No backend `Location` redirect is followed internally. HTTPS transport encryption remains
available, but backend trust/hostname verification is off by default. Set `tls_verify: true`
for origins where certificate-chain and hostname authentication is required. With
verification disabled, a network attacker able to intercept that route may impersonate
the origin. Address/DNS restrictions still apply; the local-listener exception exists only
when explicitly accepted as above. Operator-supplied backend configuration is privileged
configuration, not a field that an unauthenticated caller can edit.
## Content and cache privacy
## TLS and tunnels
- Shared cache must refuse private/no-store content and unsafe authenticated representations. Test behavior with cookies, `Authorization`, `Set-Cookie`, `Vary` and conditional requests.
- HTTP BAN/PURGE is disabled by default and must use a narrow allowlist if enabled. Never grant untrusted clients cross-site invalidation rights.
- Rewrites/transforms and custom headers can change semantics; verify that CSP, HSTS, CORS, validators and `Cache-Control` remain appropriate.
Private key files are size-bounded, regular files, owner-only on POSIX and opened
without following final-component symlinks. Named files cannot escape the certificate
directory by using path separators. Keep the entire directory tree private as well.
Keys and certificate name coverage are validated before publication. Generated
self-signed certificates are development material, not automatic public trust.
## Web Server and PHP
Existing TLS sessions/tunnels are not revoked by changing a certificate or ACL.
Policies apply to new requests and handshakes. Restart/stop the relevant process for
immediate termination until a scoped administrative close operation is implemented.
- Put credentials, source secrets and backups **outside document roots**. Directory listing and symlink relaxation are disabled by default; change only when deliberate.
- Run PHP-FPM as a separate controlled service. Give unrelated PHP applications distinct FPM pools to prevent cross-application INI/worker leakage.
- Treat `.htaccess` support as a restricted compatibility feature, not arbitrary Apache configuration. RustProxy hardening rules supplement but do not replace application security updates.
WebSocket payloads remain opaque byte streams. The proxy validates upgrade metadata,
not application-level authorization or message semantics. Frame/message limits on a
backend remain the backend's responsibility. Proxy buffers, tunnel/connection counts
and inactivity timers bound proxy-side resource usage.
## Integrations and transport
Web Server PHP-FPM integration never forwards the client `Proxy` header as CGI `HTTP_PROXY`
(httpoxy protection), regardless of optional Web Service hardening. FastCGI response framing and
hop-by-hop headers are discarded and RustProxy owns downstream framing. PHP-FPM endpoints are
limited to Unix sockets or loopback TCP. Optional custom FastCGI parameters are validated static
operator values and cannot replace core CGI, `HTTP_*`, `PHP_VALUE` or `PHP_ADMIN_VALUE`; php.ini
overrides use a dedicated validated `PHP_VALUE` map. Configurable request-body, response-header
and stderr bounds keep the PHP connector resource usage explicit.
- Docker agents have read-only inventory endpoints but their local Docker socket permission is highly privileged. Restrict the host account, port, Bearer token and pinned certificate.
- Prometheus has its own endpoint/network credential policy and no admin browser session. Keep it private or behind a secure tunnel.
- TCP/UDP Streams do **not** inherit HTTP ACL/auth/cache/TLS termination. Protect protocol endpoints separately.
- HTTP/3 needs UDP and should be tested with firewall, NAT and TLS behavior on the actual deployment.
- Managed ACME and external certificate reload integrations require least-privilege DNS/file/token access.
## Information and resource controls
Access logging does not collect credential headers, query values, cookies, bodies
or WebSocket payloads. Hostnames and paths are logged; never put secrets in them. Do not enable third-party verbose tracing or application logging without
reviewing their output separately. Access rings, viewer slots, auth work, updates,
DNS workers and request/connection admission are bounded. A slow log viewer does not
create an unbounded delivery queue.
Backups include TLS private keys and credential/session state. Restore matching
SQLite and certificate data together, then revoke restored sessions. Never commit
`.env`, the data directory, backups or generated certificates to a public repository.
## Not a completed security feature
Docker socket access controls, service-account metrics access,
2FA, roles, provider secret storage, certificate
expiry automation, a full special-address registry policy and independent fuzz/load
assessment remain roadmap work. Feature availability is not evidence of a completed
security audit or a performance guarantee.
## Cache, response transforms and ports (0.1.0)
Cache is enabled by default in 0.8.1 and ACLs precede lookup. Cookie (outside the
configured static-asset exception), Authorization, Range, unsafe preconditions and
request/response `no-store` remain hard shared-cache exclusions. Conditional validators
can be satisfied from a fresh object or revalidated against the origin. Response
`no-cache` with a usable validator may be retained only for mandatory validation before reuse. Origin privacy is
checked before custom header removal, and generated request IDs are not cached. Bounded
fill permits, object/entry/byte limits and generation checks protect cancellation,
update and purge paths. Purge is authenticated, CSRF-protected and audited, not a
public PURGE/BAN capability.
Compression is opt-in, with authenticated/private/no-transform exclusions. Response
rules cannot overwrite protected framing/identity fields. For compatibility with 0.26.6,
custom `Age` rules remain accepted. Response-side `Age` is deferred to the final wire stage,
after RustProxy's cache freshness/revalidation logic, so it cannot alter stored age, TTL or
cache eligibility. Server hiding is cosmetic and
must not be considered an access-control or vulnerability mitigation substitute.
Standard public 80/443 ports are supported without globally forbidding remote
origins using those ports. Local listener targets remain guarded. Review SETTINGS.md
for interface-enumeration and restart semantics. A Linux service can grant only
CAP_NET_BIND_SERVICE to a dedicated unprivileged account; the application does not
acquire capabilities itself. Public listener controls do not implicitly expose the separate admin
API. The provided systemd unit is an unexecuted example, not an audit result.
Language packs are trusted build inputs with size/type/metadata bounds. The browser
renders strings as text and falls back per key. Translations are not executable
plugins, and no directory browsing or runtime arbitrary file loading is exposed.
## 0.2.0 frontend and pool boundaries
An explicit vhost binding is checked using the accepted stream's real local IP,
port and TLS state. Host, SNI and forwarded headers cannot assert a different local
address. A shared wildcard TCP listener does not grant every vhost access on every
accepted IP. Legacy routing does not inherit additional frontend sockets. Conflicting
hostname ownership is rejected, including disabled declarations.
Binding a private IP is not an authorization mechanism or firewall. NAT, port
forwarding or another proxy can make that destination reachable. Configure network
restrictions and application authentication as well; no trusted-proxy identity or
Keepalived/VRRP service is introduced. New unavailable IP bindings fail startup.
Health probes share literal/DNS/allowlist/loop protections, including actual old
listeners and desired new listeners, and cannot follow redirects to arbitrary
origins. They are bounded and do not use user cookies or credentials. Probes can
still hit an operator-selected GET path; select a side-effect-free health endpoint.
Affinity uses opaque random tokens but is not authentication, CSRF protection or
session replication. Tokens survive compatible edits, not process restarts. Cookie
mode bypasses cache; the dedicated cookie is stripped before origin forwarding and
cannot be overridden by an origin Set-Cookie. No failed request, especially a write,
is replayed on another member to obtain a successful response.
Dedicated per-vhost security headers also cover matching-host denials/errors and
cache hits. HSTS is removed on plaintext; includeSubDomains/preload remain explicit
opt-ins. Preload directives do not enroll a domain in a browser list. Arbitrary CSP
and Permissions-Policy values are encoding-validated, not semantically audited.
Old configurations retain origin headers unless an explicit policy overrides them.
## Certificate uploads and custom pages (0.3.0)
Certificate upload is an authenticated local-control-plane mutation protected by
Origin and CSRF validation, body/per-file bounds and two-worker admission. PEM pairs
are parsed, key-matched and checked against supplied DNS names. Private immutable
files use generated simple names, mode 600 and the existing no-symlink/directory
lock protections. Public API responses/config/audit contain metadata, not key PEM.
JavaScript clears file controls after use, but does not claim secure erasure of
browser memory. Only configuration publication activates an inventory selection.
No new listener or public certificate-upload endpoint is created.
Custom HTML is fully trusted administrator-authored content served on the chosen
public vhost. It may contain executable HTML/scripts there; it is never previewed
or inserted as HTML in the admin DOM. Do not upload untrusted markup. The fixed
welcome page interpolates only the software version, not Host, URL or user input.
HTML is bounded to 32 KiB per page and by the overall 256 KiB configuration limit.
Responses use no-store; old representation encoding/length/cookies are discarded
when replacing an origin error. Authentication challenges, Allow and Retry-After
are retained, then administrator response/security rules are applied.
ACLs run before static/fallback/direct-IP pages. Even a custom status 200 for a
blocked request never authorizes an upstream fetch. Such overrides can confuse
clients and monitoring; retaining the original 401/403/5xx status is recommended.
Direct-IP HTTPS still needs a completed TLS handshake and a suitable certificate;
an HTTP welcome page cannot remove a TLS certificate-name error.
## Integration boundaries in 0.4.0
Tinyauth runs after host/path ACLs and before any cached/static/origin response or
WebSocket upgrade. Incoming identity headers are stripped, including after generic
header rules, and only configured fields from a successful verifier are copied.
The verifier gets no request body or admin session cookie. Its DNS/private-network
and TLS checks match the origin safety boundary. Authentication failure cannot be
turned into a 200 by custom pages. Denials and protected responses are no-store;
there is no auth-result or protected-content shared cache. Existing upgraded
sessions are not reauthenticated continuously.
Only configured login-origin redirects are accepted. Do not expose the fake-auth
helper or treat its test cookie as an actual login. H3 uses the same routing,
auth, request limits and response policies; 0-RTT is disabled. Transport resource
limits do not replace rate limiting, firewalls or independent security/conformance
testing. H3 is uncompiled/unexecuted in this packaging environment.
Diagnostic response fields are hidden by default, but origin secrets still need
explicit response rules. In 0.5.0 the dedicated controls expose request ID and
revision independently; the obsolete hide_mock_origin field is not accepted. X-Mock-Origin can be
removed using ordinary response-header rules. Scope labels on listeners never
enforce network restrictions. See ACME_RFC2136.md for private TSIG/account storage
and the separate backup requirement for manual DNS-01 issuance.
## User-Agent blocklists
RustProxy can apply an optional global User-Agent blocklist and an additional per-vhost blocklist. Matching is case-insensitive; plain text matches a substring and `*` is a wildcard. Policies are bounded and disabled by default. They are evaluated before opening an upstream; a match returns HTTP 403. Requests without a User-Agent are not denied by this rule.
User-Agent is client-controlled and easy to spoof, so this is an operational noise/blocking control rather than an authentication or identity boundary. Keep ACLs, authentication and rate limits as the authoritative controls.
## Release and operational validation
Before exposing a new version at an Internet edge, test it on the target OS with your own configuration: TLS/H2/H3/WebSocket handling, access rules and cache privacy, backup/restore, logging and failure recovery. Keep monitored off-host backups and a rollback plan.
-78
View File
@@ -1,78 +0,0 @@
# Limited administration exposure audit - 0.7.1
## Scope and evidence
Reviewed the supplied 0.7.0 source and this patch's admin router/middleware,
authentication endpoints, sessions, API assets, dashboard login/logout, Swagger
integration, proxy header rule precedence and deployment readiness consumer.
This is a manual source review plus static/browser-script regression tests. It
is **not** a penetration test of the user's installed binary or a complete audit
of proxy/TLS/QUIC/ACME/operating-system/dependency security. No Rust compiler was
available; runtime enforcement and complete vendor rendering remain unverified.
The reported unauthenticated operational JSON disclosure was not reproduced on
an installed server. In the input source, `/api/config`, status, logs, metrics,
backend/cache and certificate endpoints already passed through a session guard.
The concrete findings were public version/mode metadata, unnecessarily public
operational HTML/JS/full language assets, broad path-only method exceptions and
incomplete browser-state minimization. Do not infer that a user's older build,
reverse-proxy front or a different exposed endpoint had the same behavior.
## Findings and source changes
| Observation | 0.7.1 change | Verification limit |
|---|---|---|
| `/healthz` disclosed version; auth discovery disclosed version/mode/password policy | Health reduced to one status field; auth discovery to setup-required boolean | Source/contract tests; actual router tests supplied, not run |
| Full operational HTML/modules/language bundle could be fetched anonymously | Root selects minimal login or authenticated document; full modules/bundle are private | Login fetch/DOM checks use mocked fetch, not real HTTP |
| Public route exemptions were not a strict method/path matrix | Explicit allowlist; private and unknown requests denied before JSON/query parsing | Six additional async Rust test functions supplied, unexecuted |
| Anonymous JSON rejection could include extractor input detail | Login/setup map parser failures to a fixed message, preserving 400/413/415 status semantics | Source and unexecuted router tests |
| Mutation marker/CSRF duplicate values were not explicitly rejected | Require one exact Origin, marker and session-bound CSRF value | Existing guards retained; added unexecuted duplicate-header tests |
| Stale DOM/form values can outlive a visible logout screen | Scrub inputs/state, notify other tabs, reload root; docs clear schema/results on session change | Chromium script tests with simulated location/storage |
| New API docs would otherwise create another disclosure surface | Protect document, schema and every necessary asset with existing guard; no wildcard asset route | Route/schema contracts; vendor-renderer acceptance outstanding |
| Interactive docs can carry credentials into requests | Fixed same-origin schema, per-action session verification, no external validator/persisted auth, writes opt-in + CSRF | Custom interceptor tests use a vendor stub; server guard is the authority |
| Updater used public version in `/healthz` | Locally verify systemd MainPID executable against staged release after minimal readiness | Temporary-file and simulated-systemd tests; real deployment not run |
## Deliberate public minimum
Anonymous login assets, favicon/shared CSS, setup-needed boolean, minimal health
status, and guarded login/one-time setup POSTs remain. These are necessary for
sign-in, initial administration and local readiness. Static CSS may contain UI
selectors; it contains no runtime configuration or secrets. Invalid Host/Origin
still returns generic 403 instead of bypassing the boundary.
Login/setup POST success is allowed to create/return session information **only
after credential/capability validation**. No API Bearer token or setup token was
added as an alternative authority for operational reads. Setup remains one-time,
local-capability-based; password checking/rate limiting/session invalidation were
not weakened or replaced by JavaScript-only checks.
## Separate Prometheus listener
Admin `/metrics` and `/api/metrics` require the browser/admin session. The optional
standalone Prometheus listener remains independent, disabled by default, with
configured source CIDRs and its existing bearer-token policy. Non-loopback export
requires a token by the existing Rust validation. A loopback exporter can be
intentionally configured without a browser login. Do not treat it as an overlooked
admin API exception, and do not place it on an untrusted network without reviewing
its token, CIDRs and protected transport. This patch does not verify its runtime.
## Outstanding acceptance and operational caveats
- Build and execute `dev.sh check` / `dev.sh test`; run `admin_audit.py` against
the actual upgraded service. Compare 401/no-store responses without cookies.
- Open real `/docs` in a normal browser; verify rendering, actual cookie+Origin+CSRF,
logout, CSP console output and failure after revocation. Vendor assets were not
downloaded/rendered here, and build dependency compatibility is unverified.
- Test real install/update restart/rollback; local MainPID and `/proc` verification
now fail closed when the expected executable cannot be established.
- Retain loopback-only administration and use a protected tunnel. Cookie/session
security does not protect a compromised local account, malicious browser extension,
an administrator sharing exported configuration, or every possible web attack.
- No RBAC/read-only administrator role, general API token scopes, dependency CVE
scan, fuzzing or proof of vulnerability absence is delivered by this small patch.
- Review a real resolved Cargo.lock and the pinned Swagger artifact before shipping
binaries. Source review is not a substitute for release acceptance.
For the exact exposure matrix and test/build commands, read [API_ACCESS.md](API_ACCESS.md).
The review uses OWASP REST/session guidance as a checklist, not a certification:
https://cheatsheetseries.owasp.org/cheatsheets/REST_Security_Cheat_Sheet.html
-148
View File
@@ -1,148 +0,0 @@
# RustProxy 0.31.0 - API, panel and HTTP cache-maintenance security review
Scope: source snapshot supplied for the v0.31.0 maintenance follow-up, reviewed
2026-10-10. This is a **code/contract review with offline regressions**, not a
penetration test or proof of production safety. The supplied archive has no
`.git` directory; the review does not establish parity with remote `master`.
## Interfaces and trust boundaries
| Surface | Exposure | Authentication and mutation rules | Assessment |
|---|---|---|---|
| Public administration bootstrap (`/`, login JS/CSS, `/api/auth/status`, setup/login POST) | Login/public administration listener | Host/origin validation; request marker for unsafe bootstrap actions; one-time setup capability and login throttling | Source-reviewed; live browser flow pending |
| Private panel HTML, JS, documentation, OpenAPI | Administration listener | Administrator session; `Cache-Control: no-store`; CSP, frame and MIME protections | Source and anonymous-response test reviewed; runtime pending |
| `/api/status`, `/api/system-info`, `/api/traffic`, `/api/cache`, logs, config, preferences, audit | Administration listener | Administrator cookie session; sensitive reads not on anonymous allowlist | Source and regression tests added; runtime pending |
| `/api/events`, live-log SSE | Administration listener | Session on subscribe and repeated validity checks; bounded viewers; heartbeat/disconnect cleanup | Source-reviewed; reconnect/load pending |
| State-changing admin APIs (including cache/config/preferences) | Administration listener | Valid session, Origin/Host, `X-RustProxy-Request: 1`, CSRF token; audit on cache commands | Source and regression tests added; runtime pending |
| Certificate API (`/api/certificates/{id}/reload`, deploy) | Administration listener | Narrow-scoped Key ID + Bearer grant; no general read access; HTTPS required remotely; origin validation when supplied | Source-reviewed; token rotation/revocation live tests pending |
| Public `BAN`/`PURGE` | Configured public vhost listener | **No admin session**: separate explicit IP/CIDR allowlist (empty by default), matched listener/vhost/ACL and optional rate limit | Hardened below; reverse/HTTP tests pending |
| Separate Prometheus metrics exporter | Distinct optional listener | Configured IP CIDRs and bearer-token policy; not the admin session | Unchanged; deployment exposure audit pending |
## Findings addressed in this follow-up
1. **E0716 / compilation defect (high functional impact).** `error()` accepts
`&'static str`; a reference to `format!(...)` was a temporary. Invalid
selector errors now log the detailed reason server-side and return a fixed,
non-sensitive message with status 422. This removes the reported lifetime
violation; the Rust compiler must still verify the entire crate.
2. **Cache protocol-scope confusion (medium).** Path-only `BAN`/`PURGE`
selectors could affect representations of the same host/path across both
HTTP and HTTPS. Targets are now built with the trusted external scheme,
validated to `http`/`https`, and the matched Host/vhost. IPv6 literal Hosts
are bracketed for the URL parser. Other tenants, schemes and hosts remain
outside the selector.
3. **Unbounded blocking maintenance work (medium availability risk).** Cache
invalidation may scan hot/cold entries. Both public HTTP and authenticated
admin cache purge/selective invalidation share two maintenance slots and
execute scans with `spawn_blocking`. Bursts receive 429 instead of piling
up unbounded blocking work on Tokio core threads. The permit stays with the
worker after a client disconnects. This is a concurrency bound, **not** a
per-client rate quota or a hard time limit for disk work.
4. **Ambiguous maintenance input (medium).** Duplicate TTL/confirmation or
`Content-Length` headers, transfer-encoded bodies, nonempty bodies and
upgrade requests are rejected; `Content-Length: 0` remains compatible with
CLI clients. TTL is limited to 1..604800 s. Broad `BAN /` requires the
explicit confirmation header; `PURGE` cannot use BAN-only controls.
5. **Audit ordering (low/medium).** Removal of an administrator-created BAN
rule now records the audit entry before mutating state. Unavailable audit
storage blocks the mutation rather than allowing an unaudited removal.
6. **Operator safety/usability (low).** An EN/PL help popover beside the HTTP
allowlist gives exact `curl` syntax, IP-only authentication semantics and
reverse-proxy identity requirements. No new global config variable is added.
## Existing protections checked in source
- Admin router's exact anonymous method/path allowlist contains only the
expected authentication bootstrap APIs; private docs and assets remain
session-protected. Request guard rejects duplicate Host/Origin and checks
browser `Sec-Fetch-Site` cross-site signals.
- Mutation guard uses an Origin/Host match, per-session CSRF comparison and
`X-RustProxy-Request` marker; cookie sessions are HttpOnly/SameSite=Strict
and use Secure under HTTPS/trusted forwarded protocol.
- Secure response policy includes CSP, `X-Frame-Options: DENY`,
`X-Content-Type-Options: nosniff`, referrer restrictions and no-store for
private material. A static scan checked 27 first-party browser JS files for
direct unsafe HTML injection and dynamic `eval`/`Function` sinks; this scan
**cannot establish that the entire DOM is XSS-free**.
- IP trust comes from the transport peer or a validated forwarding chain only
when the peer is configured trusted. An untrusted client-supplied XFF does
not independently grant BAN/PURGE permission. The cache allowlist rejects
`/0`, invalid CIDRs, duplicates and more than 32 entries. Requests are never
forwarded upstream.
- Certificate API tokens have explicit operation/ID grants and cannot use the
general admin read API; Bearer grant operations are intentionally separate
from cookie + CSRF authentication.
## HTTP maintenance CLI (allowlisted client only)
```sh
# One URL, including the query string: 200 with JSON result.
curl -i -X PURGE 'https://site.example/assets/app.js?v=3'
# Lazily ban previously cached objects under this path: 202 with JSON result.
curl -i -X BAN 'https://site.example/assets/'
# Entire host: explicit opt-in confirmation header.
curl -i -X BAN 'https://site.example/' -H 'X-RustProxy-Confirm-Wide: 1'
# Optional BAN expiration (30 seconds).
curl -i -X BAN 'https://site.example/assets/' -H 'X-RustProxy-Ban-Ttl: 30'
```
Use **Settings -> Cache -> HTTP BAN / PURGE IP allowlist** to grant the
caller IP/CIDR. This HTTP interface does not accept admin cookies/tokens as an
alternative to the allowlist. The domain, Host header, external scheme and
listener must select the intended vhost. Behind a reverse proxy, forward the
non-standard methods untouched and **replace**, rather than append untrusted,
XFF/Forwarded headers at the edge. Never add the entire reverse-proxy subnet
as a maintenance client merely because it is trusted for forwarding.
## Release security test plan (requires compiled RustProxy)
1. On the deployment host, run `cargo fmt --all -- --check`,
`./dev.sh check`, `./dev.sh test`, and `./dev.sh release`. Address any
compiler/regression errors before installation. `cargo audit` or an
equivalent advisory scan requires an up-to-date advisory DB and was not run
in this offline environment.
2. Start a disposable instance with a populated cache under both HTTP and
HTTPS for the same vhost/URL. Confirm `PURGE` returns 200 and drops the
exact query variant only; `BAN` returns 202 and invalidates only **older**
entries on the URL prefix. Confirm the *other* scheme and vhost stay hot.
3. From a denied IP, expect 403 on `BAN` and `PURGE`. With an empty allowlist,
all clients must be denied. Send duplicate TTL/confirmation/Content-Length,
TE/body and `BAN /` without confirmation: expect 400. On a trusted-proxy
hop with missing real client identity, expect 403. Test both IPv4 and IPv6.
4. From an allowed IP, send 10 concurrent broad maintenance requests; confirm
that only two workers run simultaneously and excess return 429. Verify
server CPU/IO and audit entries while the cold tier is populated.
5. Send GET requests without admin cookies to `/api/config`, `/api/status`,
`/api/system-info`, `/api/events`, `/api/logs`, `/docs/openapi.json` and
`/assets/app.js`; expect 401 and protective headers. With valid cookie but
missing/bad Origin, marker or CSRF on PUT/POST/DELETE, expect 403. Test
logout and password/session invalidation, live SSE reconnect/expiry.
6. Test the Certificate API with no grant, a valid certificate-specific grant,
revoked token, HTTP remote peer, and misuse against `/api/config`/logs.
Check errors for token disclosure and audit coverage.
7. Test actual browser startup, CSP console, output escaping and form save
with malicious HTML strings; inspect reverse-proxy Host/Origin forwarding.
Prefer HTTPS administration with a strict `allowed_hosts` list.
## Residual risk / non-claims
- **Not executed in this environment:** Cargo format/check/tests/release,
actual Rust listener requests, HTTP/2/3, fuzzing, penetration tests,
session revocation under load, proxy deployment configuration, dependency
CVE scanning and long-running cold-cache resource measurements.
- Proxy trust configuration is security-critical. A misconfigured edge that
accepts forged forwarded client addresses can let external users impersonate
an allowlisted IP. Restrict direct listener reachability and sanitize headers.
- A public IP allowlist is not user authentication; anyone using an allowed
IP/network can issue destructive maintenance commands. Use narrow CIDRs and
optionally enforce additional controls on the edge (mTLS, firewall).
- The two-worker concurrency bound mitigates bursts, not all denial-of-service
scenarios: configured cache size and storage latency still determine scan
cost. Per-IP quotas are not provided here.
- Proxy protocol path, certificate verification, arbitrary upstream code,
all application content and third-party packages are **not certified safe**
by this audit. Production acceptance remains open until the runtime test
plan is completed.
-674
View File
@@ -1,674 +0,0 @@
# Listener and response settings
## Administration appearance preferences
**Settings -> Appearance** contains dashboard-only preferences. They are stored per administrator in the existing `admin_preferences` SQLite document and do not create a proxy configuration revision. The preferences are theme (`dark`/`light`), language (`auto` or a language-pack id), global interface scale (70-125%), timestamp format (`24h`/`12h`), chart style (`default`, `soft`, `technical`, `high_contrast`, `neon`, `monochrome`, `warm` or `cool`), button style (`normal` or `compact`), table/log density (`normal`, `compact`, `ultra`), dashboard density (`normal`, `compact`, `ultra`), Overview card order and Overview card visibility. `auto` language follows the browser language when a matching pack exists and otherwise falls back to the configured default pack.
The scale applies to the complete authenticated administration surface, including navigation, workspace content, dialogs, overlays and the draft dock. Text fields, selects, textareas, checkboxes and radios also receive a small proportional control-size adjustment on top of global zoom so field proportions track the selected scale more visibly. Button style is independent: **Normal** keeps the refreshed control sizing and **Compact** restores the smaller source-style sizing, including generated entity actions and tab buttons inside dialogs. Table/log and dashboard density are additional layout controls and do not change the whole-interface scale. The dashboard editor can hide/show and reorder the telemetry cards at the top of Overview; this changes presentation only, not metric collection. Stream card explanations appear on hover and keyboard focus instead of expanding the card list. Stream cards default hidden but, once switched on, remain present even when no Stream is active (neutral no-data state). The selected time format is shared by logs, backend/runtime diagnostics, audit/session timestamps, certificate views and configuration-history timestamps. Chart presets change colors, surfaces, grid and line presentation only; they do not change sampled data, and the palette selector exposes the main colors for each preset. Each Appearance subsection has its own reset action, and **Reset appearance** restores all documented Appearance defaults without touching unrelated account preferences. A small local cache is retained only to avoid a theme/scale flash before authentication, while the authenticated SQLite preference remains authoritative.
The **Corner style** choice applies to standard dashboard/metric cards, vhost
cards, entities, notices, backend protocol badges and inputs independently from
color themes; the default preset retains the original appearance. On narrow
screens the Overview uses two small metric tiles per row rather than one.
Status refreshes update values without resetting widget order or scroll.
The Vhosts list shows one upstream protocol badge per configured reverse-proxy
backend: the configured mode (H1, Auto, H2 compatible or H2 only) plus the last
HTTP/1.1 or HTTP/2 response version observed by RustProxy while proxying a
request. An unknown or stale observation (over five minutes old) appears as `?`,
including in Auto mode; no new probe is issued, and parallel connections can
use different negotiated versions. Hover/focus displays the observation note
and aggregate response-version counters. These are authenticated live-status
observations, not the configured frontend HTTP version.
Unfinished Web Server, Vhost, Redirect and Stream editors remain session-only drafts. Their bottom-right dock uses square corners, can collapse to a single title bar, shows the age of each suspended draft and does not persist draft form values to SQLite.
The Settings page includes a local search field that filters category tabs using translated category labels and page content. Filtering never rebuilds settings forms, so unsaved values remain in the DOM. Category navigation remains sticky on desktop and marks categories containing unsaved forms.
Unfinished Web Server, Vhost, Redirect and Stream forms use a session-only draft dock. Closing a dirty editor minimizes its exact browser state to the bottom-right dock so another editor can be opened independently. The dock follows the square-corner panel styling of the dashboard and intentionally avoids rounded/glass presentation. Drafts are intentionally not written to SQLite because form state can contain credentials or other secrets; they are discarded on logout/session reset.
## Certificate API credentials (0.31.9)
Open **Settings → Certificate API** (`/settings?tab=certificate-api`) to manage external certificate automation credentials. The credential list now has **View settings**, **Edit settings**, **Regenerate token** and **Revoke** actions. The preview shows Key ID, non-secret comment, allowed certificate IDs, reload-only/deploy scope and creation/last-use times. Existing bearer token values cannot be viewed: only hashes are stored.
**Edit settings** loads the current credential into a form with fixed Key ID. You may update its comment, list of 1–32 explicit certificate IDs, and **Also allow deploy** permission. **Save settings** changes only this metadata; the current secret stays valid and is never shown or replaced. Legacy `*` certificate grants must be replaced with explicit IDs before saving an edit.
**Regenerate token** requires confirmation. It generates a new random bearer token, preserves the Key ID/comment/permissions/allowed IDs, immediately invalidates the old token and reveals the new token only once. Copy it into the external automation before closing the page. Regeneration updates the token's creation/rotation timestamp and clears last-used metadata. The same existing session + Origin + CSRF checks protect both operations; bearer-only external clients cannot read or change credentials.
The existing **Generate / rotate token** form still creates new Key IDs (and retains its previous ability to rotate a Key ID by supplying it). Use **Edit settings** to modify settings without rotating, and **Regenerate token** to rotate without changing any settings. See [API](API.md) and [access control](API_ACCESS.md).
## Compact Overview card controls (0.32.6 development branch)
**Settings → Appearance → Dashboard layout** now puts Dashboard density on a
compact row and gives the Overview card controls the full width of the section.
The card selector is a single horizontally scrollable strip by default, with
left/right browsing buttons and accessible keyboard focus. **Show all cards**
expands the same 62 visibility/reorder controls into a wrapped grid. Use the
existing visual layout editor for drag-and-drop arrangement. Reordering and
visibility still save through `/api/preferences` per account; this UI-only
change does not alter server polling, proxy routing or backend metrics.
### Overview controls and density (0.32.7 development follow-up)
**Refresh now**, **Arrange Overview**, **Unlock layout**, **Visual Overview editor** and **Show all cards** inherit the same Normal/Compact button size rules as other buttons. Responsive placement still adapts to narrow screens, but no independent font-size, padding or height override applies. Dashboard density more clearly adjusts numeric tile sizing, inline chart height and the padding/title size of non-table Overview panels; the common tile gutters are unchanged. Button style and dashboard density remain independent per-account preferences.
### Overview header actions (0.32.8)
On the Overview page, **Traffic, in focus.** remains on the left and actions
align to the right on the same row when space permits. Buttons wrap to the right
on very narrow displays instead of being compressed into equal-width columns.
The existing Normal/Compact button preferences, editor confirmation actions,
layout locking and account-specific saved card layout are unchanged.
## Refresh preferences (0.31.0)
**Settings -> Appearance -> Data refresh** saves an additional `refresh` object in the existing `admin_preferences` document for the signed-in account. It does not modify proxy configuration or create any migration. Older documents, including partially populated `refresh` objects, use defaults. Browser and server enforce exact allowed values:
| Preference | Default | Allowed seconds |
|---|---:|---|
| `refresh.live_enabled` | `true` | `true`, `false` |
| `refresh.overview_seconds` | 3 | 1, 2, 3, 5, 10 |
| `refresh.traffic_seconds` | 5 | 0, 3, 5, 10, 30 |
| `refresh.cache_seconds` | 5 | 0, 3, 5, 10, 30 |
| `refresh.system_info_seconds` | 60 | 0, 15, 30, 60, 300 |
`live_enabled=false` disables automatic SSE **and all polling**, including system information. `0` disables only that source's automatic polling. Overview and System information have **Refresh now** buttons; Traffic and Cache retain their own buttons, filters, charts and pagination. The per-view Auto checkboxes for Traffic/Cache/Transform and the Docker live switch remain independent opt-outs, and none changes another administrator's preferences. **Reset section** restores refresh defaults without changing theme or unrelated preferences.
The main `/api/events?interval=N` connection (allowlisted 1/2/3/5/10, default 3 seconds) carries compact live status, cached CPU/RSS/aggregate and per-interface network chart samples from the server's existing background OS collector, plus the existing bounded log snapshot. The server checks the session on every event and retains its heartbeat, viewer cap and normal disconnect cleanup. On an SSE error the browser polls `/api/status?view=live` and `/api/logs` until snapshots resume, not concurrently during healthy streaming. The old full `/api/status` response is kept for API clients and initial diagnostics. Process/System detail values still update only after the separate authenticated `/api/system-info` read at `refresh.system_info_seconds`, while CPU/RSS and network **charts** follow the Overview live cadence (3 seconds by default). The `/api/system-info` endpoint reuses cached diagnostics and ACME metadata, never runs diagnostic subprocesses per request, and is fetched automatically only for Overview or the relevant Settings pages. A hidden browser tab pauses the channel and all automatic polling; returning revalidates its session before normal operation. All interval changes take effect immediately, without reloading or changing any server-global cadence.
## System information (0.30.1)
**Settings -> System information** (`/settings?tab=system-info`) is a read-only view of the authenticated `/api/system-info` endpoint (and the initial, backwards-compatible full `/api/status` response). It reports the OS name/kernel on Linux when available, executable architecture, logical CPU count, CPU and RAM telemetry, host/process uptime, RustProxy version, process RSS, thread and file-descriptor counts, and the existing statistics source and data/database locations. Values not supported by the host/platform display as unavailable, never as zero.
ACME diagnostics formerly shown in About and under Administration are now on this tab: mode/state, certificate environment path, and the active Python/pip/Certbot versions. Versions are probed by the ACME runtime setup with bounded subprocess output/timeouts and published from stored runtime status. They are not queried at every page refresh or status API request. Failed/unavailable probes leave the version empty. No new persistent settings or permissions are introduced.
## Rules and headers (0.31.7)
**Settings -> Rules & headers** (`/settings?tab=policies`) now groups the existing **Global User-Agent blocklist**, **Global custom headers** and **Client response** forms as persistent sub-tabs, following the Cache settings layout. There is no migration: each form continues to update its previous configuration section. **Settings -> Security** retains administrative hostname, Origin, session and password policies.
**Vhosts -> Security -> Traffic policy** also supports optional `rate_limit.user_agent_limits` in each vhost, for example:
```json
{
"rate_limit": {
"enabled": true, "requests": 120, "window_seconds": 60, "burst": 30,
"user_agent_limits": [
{"pattern": "*scanner*", "requests": 10, "window_seconds": 60, "burst": 0},
{"pattern": "BadBot", "requests": 2, "window_seconds": 60, "burst": 0}
]
}
}
```
The first case-insensitive match wins. A plain pattern searches for a substring and `*` matches any sequence. A matching User-Agent uses its **own bucket for the vhost, rule and client IP** and overrides that request's standard/country throttle; nonmatching requests continue using existing rate rules. Rules also work with `enabled=false` for the base limiter. The User-Agent header is untrusted, so avoid treating these limits as authentication or identity enforcement.
The **Revision history** detail shows only changed leaf fields, scrolling locally when there are many. **Full JSON comparison** is directly below Restore in the action group and opens a separate standard dialog. ACL profile *Attached to* details are collapsed by default. Listener HTTP/2 and HTTP/3 controls have additional spacing.
## Configuration and update semantics
GET `/api/config` returns a revisioned envelope. PUT the entire document with
`expected_revision`. Defaults apply when fields are omitted. Unknown fields are
rejected; failed validation/persistence leaves the old snapshot active. Existing
backend/route/TLS/ACL data must be retained when changing settings. The panel does
this and preserves dirty settings forms across status/language refreshes.
```json
{
"listeners": {
"endpoints": [
{"id":"http", "name":"HTTP", "scope":"public", "enabled":true,
"binding":{"address":"0.0.0.0:80", "tls":false, "http2":false, "http3":null}},
{"id":"https", "name":"HTTPS", "scope":"public", "enabled":true,
"binding":{"address":"0.0.0.0:443", "tls":true, "http2":true,
"http2_settings":{"idle_timeout_seconds":300, "max_connection_lifetime_seconds":0, "drain_timeout_seconds":10}}}
]
},
"server_header": {"mode":"custom", "value":"RustProxy"},
"compression": {"gzip":true, "br":true, "zstd":true, "deflate":false, "min_bytes":1024},
"headers": {
"request": [{"op":"set", "name":"x-proxy-name", "value":"RustProxy"}],
"response": [
{"op":"remove", "name":"x-powered-by"},
{"op":"set", "name":"x-served-by", "value":"RustProxy"}
]
}
}
```
This is a settings fragment, not a replacement for the existing routing/vhost document.
A complete opt-in example is in `examples/cache-dashboard.json`.
The dashboard exposes the complete raw JSON editor under **Settings -> Manual config**.
The same card exposes **Revision history**, which reads the newest 512 retained configuration revisions from the configuration-history table introduced in SQLite schema 8 (current database schema 9) and groups them by month/day. Selecting a revision shows timestamp, actor and only the changed fields against the previous retained revision; the separate Full JSON comparison dialog contains complete JSON documents. An older retained revision can be restored from the same dialog; RustProxy validates it through the normal configuration update path and saves it as a new revision, preserving the prior history.
The historical `/configuration` browser route is retained only as a compatibility alias and
opens that Settings block; it is no longer a primary-navigation item. Prefer the structured
section editors for routine changes so unrelated sections are not republished.
## GeoIP updates
**Settings -> GeoIP updates** controls the background country-database refresh and is enabled by default. The worker starts only after all server sockets have bound successfully, attempts the first refresh immediately, and then refreshes every 24 hours by default. Sources are direct HTTPS file URLs rather than coupled release/checksum base URLs. Publisher SHA-256 verification is optional per file; every downloaded payload is still size-bounded, streamed to a private staging file and parsed as the selected database format before publication.
Two formats are supported. **CSV ranges** uses separate IPv4 and IPv6 files and defaults to the direct `sapics/ip-location-db` `user-country-ipv4.csv` and `user-country-ipv6.csv` assets. **MaxMind DB (MMDB)** uses one `country.mmdb` file for both address families and defaults to the combined sapics `user-country.mmdb` asset. Standard MaxMind country databases are read from `country.iso_code`, then `registered_country.iso_code`; custom country MMDBs may expose top-level `country_code` or a root two-letter country string. To manage a private MMDB outside the updater, place it at `<data-dir>/geoip/country.mmdb`, select MMDB, disable automatic updates and leave the MMDB source URL empty; **Download now** then validates/reloads the local file instead of fetching it.
When a checksum URL is empty, RustProxy deliberately skips publisher-checksum comparison. It still computes a local SHA-256 for the validated download and compares it with the installed file, so unchanged content is not republished or hot-reloaded. This avoids coupling a moving `latest` data URL to a separately moving checksum release. If a checksum URL is supplied, a mismatch rejects the staged file. The configured maximum is 1 MiB..256 MiB and the interval is 60 seconds..31 days. Failures are warnings only; proxy traffic continues and the worker retries. Manual UI refresh, the background worker and managed upgrades share `.geoip-update.lock`. After publication the `ArcSwap` database is replaced atomically: requests already holding a snapshot may finish on it, while subsequent country ACL/rate-limit/logging lookups and status/metrics reads use the new database without a restart.
```json
{
"geoip_updates": {
"enabled": true,
"format": "csv_pair",
"ipv4_url": "https://github.com/sapics/ip-location-db/releases/download/latest/user-country-ipv4.csv",
"ipv6_url": "https://github.com/sapics/ip-location-db/releases/download/latest/user-country-ipv6.csv",
"mmdb_url": "https://github.com/sapics/ip-location-db/releases/download/latest/user-country.mmdb",
"ipv4_checksum_url": null,
"ipv6_checksum_url": null,
"mmdb_checksum_url": null,
"max_bytes": 268435456,
"interval_seconds": 86400
}
}
```
## Administration HTTPS
`administration` is separate from public proxy listeners. It defaults to the startup
loopback address and plain HTTP. Persisted address/certificate changes are validated
immediately but take effect after process restart. A non-loopback persisted bind is
accepted only when `administration.tls` is true. Plain remote HTTP remains available only when process startup explicitly allows it with
`--allow-remote-admin`.
```json
{
"administration": {
"listen": "0.0.0.0:9090",
"tls": true,
"certificate_id": "admin-example",
"allowed_hosts": ["admin.example.com"],
"public_origin": null,
"session_days": 30,
"panel_compression": {"zstd": true, "br": true, "gzip": true}
}
}
```
### Fixed public origin for a reverse-proxied panel (0.31.4)
For a **panel** reached through a trusted reverse proxy, set the optional
`administration.public_origin` in **Settings → Administration** to the exact
browser-facing HTTPS origin (for example `https://panel.example.test`).
**Do not duplicate that hostname in `administration.allowed_hosts`.** Its DNS
hostname is implicitly included in the runtime panel Host allowlist. **Restart
RustProxy**; this is a restart-bound access-control setting. Omit it (`null`) for
direct administration or when forwarded headers are canonical. This setting
is not a wildcard, does not accept HTTP or a URL path, and requires a DNS
hostname rather than an IP address. SQLite schema 9 remains unchanged.
The advanced **Additional allowed DNS hostnames** field controls independent,
extra hostnames only. Older configurations containing both values are
accepted; on editing/saving the redundant duplicate is omitted from the
persisted list. If `public_origin` changes or is cleared, its old hostname is
no longer implicitly allowed (unless explicitly retained as an additional host).
Example for the default **private loopback** administration listener behind an
HTTPS-terminating proxy:
```json
{
"administration": {
"allowed_hosts": [],
"public_origin": "https://panel.example.test"
}
}
```
The last hop must connect from loopback or an existing explicitly trusted proxy
CIDR. If it is not loopback, configure **only the actual proxy peer CIDR** under
Forwarding → Trusted proxies; be aware the general trusted-proxy setting also
applies to public traffic and must never include untrusted clients. The
administration listener should not be reachable directly from external clients.
Do **not** grant trust to public networks to work around Origin failures.
The pinned URL, not an element of `X-Forwarded-Proto`, defines the panel
scheme/authority. A trusted, bounded proxy chain `https, http` (HTTPS on the
browser side, HTTP on an internal hop) is permitted in this explicit mode.
An `http` first hop, malformed/too-long protocol chain, duplicate/conflicting
`X-Forwarded-Host`, different `Origin`, `Sec-Fetch-Site: cross-site`, and missing
CSRF protection for state-changing requests still result in HTTP 403. The
public proxy/vhost data plane is **not changed**, except that an already opted-in
self-admin vhost can recognize the pinned hostname for its reserved panel session
cookie; other upstreams continue to strip it. Credential-bearing certificate
automation retains its original TLS/forwarding checks.
Prefer fixing the proxy's forwarding headers rather than configuring the pin
when practical. For a TLS-only public entry, the closest trusted hop should
**replace**, not append, forwarded headers using known values, for example:
```nginx
proxy_set_header Host panel.example.test;
proxy_set_header X-Forwarded-Host panel.example.test;
proxy_set_header X-Forwarded-Proto https;
```
Only use that `https` setting on an entry that actually verifies external TLS;
never trust an arbitrary inbound `X-Forwarded-Proto`. If multiple intermediate
proxy hops must append protocol values, the pinned mode provides an explicit
server-side comparison target without accepting a client-supplied origin as
authoritative. Confirm deployment using a real mobile browser and the safe
`administration origin rejected` logs before production rollout.
**Administration → Panel content compression** (next to the admin listener/TLS settings)
controls Zstd, Brotli and gzip for the authenticated HTML document, JavaScript,
CSS and language bundles. Each browser request is negotiated using
`Accept-Encoding` quality values: the highest positive `q` wins, and ties
prefer **Zstd → Brotli → gzip**. Codecs can be enabled independently; the
identity representation is returned when none is acceptable or all are off.
The source is encoded only once per embedded asset and shared across visitors.
Settings persist in the existing revisioned administration document, without
additional CLI flags or a separate environment/config variable, and take effect
without restart. Each compressed response includes `Content-Encoding`,
`Vary: Accept-Encoding` and the representation-specific `Content-Length`.
The response compression of proxied websites remains independent; API responses,
SSE and WebSocket streams are not affected.
**Reverse proxy:** an upstream may remove or replace `Accept-Encoding`. RustProxy
negotiates from the header it actually receives, not the original browser request.
A reverse proxy normally passes through already encoded responses instead of
recompressing them; if the reverse should exclusively manage encoding, configure
it to strip `Accept-Encoding` on requests upstream, or turn the panel codecs off.
This provides compatible HTTP negotiation but does not guarantee identical
compression with every reverse proxy configuration.
`certificate_id: null` uses `data/certs/default.pem` and `default.key`, generated by
the application. A selected inventory certificate is loaded from the same private
certificate directory. `session_days` controls the absolute browser-session lifetime
and must be 7..30 (default 30). It applies without restart; changing listener/TLS/certificate
settings still requires restart. Session cookies are HttpOnly/SameSite=Strict and receive
the `Secure` attribute on HTTPS. When HTTPS is enabled, accidental plain HTTP received on the same administration
port receives a permanent redirect to the same authority/path over `https://`; the
plaintext path contains no administration handlers. An empty `allowed_hosts` accepts
valid DNS Host values on the configured HTTPS port as well as valid numeric destination
addresses. Once `allowed_hosts` contains entries it becomes a strict DNS allowlist for
browser-facing DNS authorities. An explicitly listed hostname may arrive from a trusted
reverse proxy on an external port such as 443 even when the private admin socket is 9090.
Direct IP/`localhost` access is still accepted only on the actual configured admin port.
For HTTPS termination in front of a plain local admin socket, the trusted proxy must pass
its external Host and scheme; `X-Forwarded-Host` is accepted only from loopback/configured
trusted proxy peers and `X-Forwarded-Proto` additionally follows the forwarded-proto trust
policy. Origin/CSRF comparison and the session cookie Secure flag use that external origin.
Use a firewall and normal network policy even with HTTPS.
## Structured access logging
The **Settings -> Logging** page is split into **Outputs**, **JSON format** and **Error log** subsections, using the same in-card sub-tab layout and keyboard interaction as Cache. Outputs controls optional external access-log sinks while the existing bounded live ring remains available to the panel. Sink I/O is performed by a bounded background queue; request processing only performs a non-blocking enqueue. Supported destinations:
- JSONL file append, either one combined file such as `/var/log/rustproxy/access.jsonl` or
one file per resolved vhost under a configurable directory (default `/var/log/rustproxy/vhosts`).
Unmatched traffic uses `_unmatched.jsonl`. The managed systemd service provisions
`/var/log/rustproxy` for the service account and keeps that directory writable under
`ProtectSystem=strict`. Other custom paths still need matching service permissions.
- Logstash TCP using `host:port` (IP or DNS). Each event is one JSON line.
- Loki HTTP(S) push. Optional bearer credentials are read from an environment variable
named by `bearer_token_env`; the secret itself is not stored in configuration.
`logging.format.fields` maps output field names to standard event fields; an empty map
emits the complete standard event. The panel provides Standard, Nginx-style, ECS-style
and Minimal presets plus a visual field selector/renamer with JSON preview. Advanced raw
mapping remains available for aliases not representable by the simple builder. Supported mapped sources include `vhost`, `route`, `cache_effective_enabled`, `transform_effective_enabled`, `backend`, request/response metadata and cache outcome fields. `static_fields` adds constant string fields. For low-level `protocol_error` events, RustProxy always preserves the required diagnostic envelope (`event`, error kind/detail, time, client/local/TLS/protocol fields, status and outcome) even with a custom mapping, so malformed-traffic records remain searchable in the built-in Logs explorer. Loki labels are separately configurable.
`/api/status.logging.sinks` reports queue capacity, dropped events, successfully delivered
events and sink errors.
## Hot and cold cache tiers
`cache.max_bytes` and `cache.max_entries` bound the HOT RAM tier. `max_bytes` is charged from retained body backing allocation plus entry metadata rather than only payload length. `cache.transient_max_bytes` separately bounds in-progress fills and full bodies moving between HOT and COLD; `cache.cold_queue_max_bytes` additionally caps queued demotions in bytes, and `cache.hot_eviction_max_objects` bounds one LRU admission attempt. The source defaults are 256 MiB HOT, 16 MiB maximum object, 64 MiB transient and 32 MiB COLD queue. The Settings panel calculates conservative/recommended/performance presets from the lower of host RAM and a finite cgroup memory limit without changing the COLD disk budget. It also warns when HOT + transient is a large fraction of effective RAM, when the additional configured cache budget is large compared with current host/cgroup headroom, when the COLD queue exceeds transient RAM, or when the object ceiling cannot fit in transient RAM. Automatic presets are disabled below 256 MiB effective RAM. On a 2 GiB effective-memory VM the recommended profile selects approximately 400 MiB HOT, 96 MiB transient, 16 MiB maximum object, 48 MiB COLD queue, 6400 HOT entries and a 64-object LRU admission limit. The optional persistent
cold tier is controlled by `cold_enabled`, `cold_path`, `cold_max_bytes`,
`cold_max_entries`, `cold_gc_interval_seconds`, `cold_gc_max_delete`,
`cold_after_seconds`, `cold_on_lru`, `cold_on_idle` and `cold_on_shutdown`.
`cold_gc_interval_seconds` defaults to 60 and `cold_gc_max_delete` to 256. Pressure GC
starts at roughly 90% of the configured byte/entry budget and cleans toward roughly 80%
without deleting more than the configured per-pass cap. `cold_path` is relative to the RustProxy data directory and cannot
contain parent traversal or an absolute path. The two demotion switches independently
select LRU-pressure and idle objects; shutdown flush is a best-effort direct write during
a graceful restart. Demotions use a small bounded queue and release hot accounting before
disk persistence so cold storage does not retain an unnecessary second body buffer in RAM.
Cold metadata is readable JSON and bodies are raw `.body` files; reads are size/SHA-256
checked before promotion to RAM. Since 0.21.0 objects are sharded by the first two SHA-256
hex characters (`cache/cold/00/...` through `ff/...`), with shard directories created on
first write. The previous flat Cold Cache is intentionally not migrated. Purge operations
clear both in-memory indexes and schedule disk cleanup; selective cleanup uses the same
GC/eviction path and runs asynchronously.
Version 0.22 adds foreground fill collapsing (`collapse_enabled`,
`collapse_wait_timeout_ms`, `collapse_timeout_fallback`), detailed stale-if-error policy
(`stale_if_error_timeout`, `stale_if_error_connection`, `stale_if_error_dns`,
`stale_if_error_tls`, `stale_if_error_statuses`) and opt-in proactive refresh
(`proactive_refresh_enabled`, `proactive_refresh_before_expiry_seconds`,
`proactive_refresh_percent`, `proactive_refresh_max_inflight`). Defaults preserve the
previous stale-if-error behavior; proactive refresh is disabled by default. Selective
PURGE/BAN is available in the authenticated API/panel, and HTTP BAN/PURGE has an optional, separately stored CIDR allowlist (`cache.http_invalidation_allowed_cidrs`, empty by default). See
[CACHE.md](CACHE.md) for request-collapse, Range and invalidation semantics.
The adjacent **info (`i`) popover** in Settings → Cache shows ready-to-copy
`curl -X PURGE` and `curl -X BAN` examples in English and Polish, including
the explicit `BAN /` confirmation. These public methods authorize by the
source IP/CIDR only; they do not accept a browser admin session as a bypass.
See [0.31.0 security audit](SECURITY_AUDIT_0.31.0.md) for reverse-proxy trust and tests.
## Static-response transformation
`transform` is a formal identity-response stage between the upstream response and shared
cache storage. `transform.enabled` is the global kill switch, but every vhost must also
set its own `transform.enabled=true`; vhosts stay disabled by default even when the global
engine is enabled. Global defaults enable JavaScript and CSS transformation and leave
HTML, SVG and JSON off. `extensions` controls extension-based detection and
`max_body_bytes` (1 KiB..32 MiB, 2 MiB default) bounds aggregation/decoding.
The pipeline is `origin -> decode supported Content-Encoding -> transform -> cache
identity representation -> client compression`. JavaScript/CSS can be enabled normally;
HTML minification, SVG optimization and JSON compaction are independent optional formats.
Recognition uses safe configured extensions and known Content-Type values. Binary/image
formats outside those explicit transform types are not transformed.
Origin `Cache-Control: no-transform` always disables transformation. When bytes change,
RustProxy removes representation-specific length/digest/encoding metadata and creates a
new strong `rustproxy-*` ETag for the transformed representation. The original origin
ETag/Last-Modified are kept separately and used for origin revalidation, so a proxy ETag
is never sent upstream as though it validated the original object. The transformed
identity body is what enters cache, preventing repeated minification on every HIT.
## Global TLS protocol and cipher policy
`tls.policy` is the global baseline inherited by every TLS host. It controls TLS 1.2,
TLS 1.3 and optional rustls cipher-suite names. An empty `cipher_suites` list uses the
ring provider defaults. Settings shows both the currently effective cipher suites and
the complete selectable suite inventory. At least one protocol version and
one compatible effective suite are required. This policy is also used by administration
HTTPS. HTTP/3 requires TLS 1.3 and a TLS 1.3 suite. Vhosts can narrow TCP TLS with
`tls_policy`; when a vhost has HTTP/3 enabled on a selected H3-capable listener it must
match the global policy because QUIC TLS is shared per UDP listener. Available suite names are exposed in authenticated status for
the Settings editor.
## Listeners: saved intent, not hot rebinding
When an HTTP, HTTPS or administration address was explicitly supplied by systemd/CLI
(or the corresponding service environment variable), that exact startup bind is a
process contract: the panel shows it as locked and configuration updates cannot remove,
disable or change it. If an address was not explicitly supplied at startup, listener
entries remain freely addable/removable/editable subject to normal validation.
`listeners.endpoints` is the only persisted frontend socket configuration. Each
endpoint has a stable id/name, an informational scope label, an enabled flag and one
primary binding containing IP:port, TLS, HTTP/2 plus its optional lifecycle settings, and optional HTTP/3 settings. Optional
`additional_addresses` reuse that same transport policy on up to 15 further addresses.
When more than one address is configured every address must be explicit; `0.0.0.0` and
`::` are rejected so a multi-IP selection cannot silently mean every interface. IP/port
and transport capability belong here, not inside a vhost. IPv6 uses `[address]:port`.
A vhost references endpoints through `frontend.listener_ids`. Listener changes are
restart-required because they alter TCP/UDP sockets or TLS ALPN capability. The
listener editor owns the HTTP/2/H2C and HTTP/3 enable/disable switches. An H2/H2C row
links to **HTTP connections -> HTTP/2** for idle, absolute-lifetime and graceful-drain
limits; an enabled H3 row links to **HTTP connections -> HTTP/3** for advertisement,
connection, stream and idle-timeout limits. Both tuning pages are listener-scoped and
do not own protocol enablement. Vhost H2/H3 switches only narrow the protocols already
enabled by the selected endpoints; they never create a protocol that Settings disabled.
The administration listener is independent and cannot share a frontend port.
Bindings must be valid local unicast/wildcard addresses; conflicting overlapping
endpoints are rejected rather than silently widened.
## Server and other client headers
`server_header.mode` is `preserve`, `hide`, or `custom`. Fresh installations default to `custom` with the exact value `RustProxy`; existing saved installations retain their configured policy. Custom values are
nonempty, bounded to 256 bytes and validated against header injection. Preserve
means retain the result of origin/global/route rules; it does not add a missing
header. Hide/custom take final precedence, including proxy-generated errors.
Global and route `headers.response` apply to client responses; `headers.request`
apply only upstream. Each scope/direction supports up to 32 ordered set/append/remove
rules. Remove has no value. Global rules precede route rules. Multiple Set-Cookie
values remain distinct. Admin-session cookie names are never forwarded or set by
public proxy rules. Transport, framing and trusted forwarding/identity fields are
protected. Cache diagnostic fields are owned by the cache controls, not generic rules.
Global response rules also cover generated errors; route-specific rules apply only
after an origin route response exists. Client policy changes use hot configuration
publication and invalidate the RAM cache. In-flight exchanges retain their old
snapshot; no mixed-version configuration is applied midway through a response.
## Compression
`zstd`, Brotli and gzip default ON; deflate defaults OFF. `min_bytes` defaults to 1024
(valid 32..1048576). The streaming middleware keeps its default content-type safety
predicate plus the RustProxy minimum-size predicate. Unknown-length eligible bodies can
be streamed without aggregating the complete response merely to discover its size.
RustProxy parses `Accept-Encoding` quality weights before invoking the encoder. Client
`q` values win. When two or more enabled codecs have equal quality, the deterministic
server preference is `zstd -> br -> gzip -> deflate`. An existing `Content-Encoding` is
always passed through and never recompressed. `no-transform`, request ranges/Content-Range,
SSE, upgrades, HEAD/1xx/204/304 and explicitly skipped internal responses remain excluded.
`Cookie`, `Authorization`, `Set-Cookie`, `Cache-Control: private` and `no-store` do not by
themselves disable compression; cacheability and dynamic compression are separate policies.
Applications that place secrets and attacker-controlled reflection in the same compressed
response can disable compression at the vhost or Web Service scope when their threat model
requires BREACH-style mitigation.
The global Response panel selects available codecs and the minimum size. Each ordinary
vhost and Web Server service may inherit, enable or disable dynamic compression and independently
inherit/enable/disable zstd, Brotli, gzip and deflate. Docker vhosts intentionally expose only one
Compression checkbox: when enabled they use the globally enabled codec set and priority, with no
per-Docker-vhost codec selectors. Global codec switches remain the hard ceiling: a local `true`
override cannot enable a codec disabled in the global Response panel. `min_bytes` remains global.
`Vary: Accept-Encoding` is added when compression is negotiated; content length is removed
for the transformed body. Strong ETags are weakened, and identity-body content digests and
`Accept-Ranges` are removed. Encoders use their fastest preset; there is no panel
compression-quality slider in this release.
## Diagnostics and units
Samples run off the traffic path roughly every two seconds. CPU and network rates
need two successful samples. Host CPU is aggregate; process CPU uses 100% per busy
logical CPU and can exceed 100%. Neither is normalized to a container CPU quota.
Host memory is MemTotal minus MemAvailable. RSS/FDs/threads and process I/O come
from procfs. Network rates sum non-loopback interfaces in the process network
namespace, not just RustProxy traffic. Load average is OS scope, not latency.
The process's cgroup v2 memory files are read at the standard mount location when
available. Linux keeps the existing `/proc`/cgroup metric names, units and behavior.
FreeBSD/OpenBSD/NetBSD use native system interfaces where the same metric is available;
unknown or unsupported values remain null rather than being fabricated. Snapshot timestamps and sample age are shown;
cache counters and proxy request metrics are separate from OS resource metrics.
The dashboard history is browser-local and resets on reload/sign-out. No time-series
database, disk utilization chart or Windows/macOS OS sampler is implemented.
## HTTP/2 compatibility and per-vhost settings
HTTP/2 lifecycle settings are listener-scoped under `listeners.endpoints[].binding.http2_settings`:
- `idle_timeout_seconds` (default 300) retires only a truly unused H2 connection; active streams and current frame I/O postpone it.
- `max_connection_lifetime_seconds` (default 0) is an independent absolute lifetime; `0` disables this limit.
- `drain_timeout_seconds` (default 10) bounds how long accepted streams may finish after retirement begins.
Idle and lifetime retirement both send GOAWAY, stop accepting new streams, drain already accepted streams and then close TCP. RFC 8441 WebSocket/H2 Extended CONNECT streams are counted as active until their response body/tunnel ends. The same settings apply to TLS H2 and cleartext H2C on that named listener.
For backward compatibility, the top-level `http2` object is still accepted as the fallback for an H2/H2C listener that has no explicit `binding.http2_settings`. The structured UI writes explicit per-listener settings; operators should treat the top-level object and `PUT /api/config/http2` as legacy compatibility surfaces rather than the preferred configuration path.
A named TLS listener with `binding.http2: true` advertises H2 capability and always
retains HTTP/1.1 fallback. `binding.http2: false` makes that listener HTTP/1.1-only.
Cleartext H2 uses `binding.h2c: true`. Changing listener transport capability or H2 lifecycle settings requires restart.
A vhost can additionally set `http2_enabled: false`. RustProxy reads TLS ClientHello
and SNI before committing to ALPN, so a vhost that disables H2 gets HTTP/1.1-only
ALPN even while another hostname on the same listener keeps H2. `http3_enabled` is
the equivalent per-vhost H3 opt-out. The panel enables these switches only when a
selected listener provides the protocol. No failed application request is replayed
to downgrade.
Vhosts have their own backend pool, health/keepalive/affinity controls, ACL and
security/custom response headers. See VHOSTS.md. Global Server policy still takes
final precedence. Per-vhost compression can filter individual globally enabled codecs.
The reported `SizeAbove(u16)` compilation error is removed by a custom u64 predicate,
not a cast or panic-producing conversion. Compression thresholds retain the full
32..1048576-byte setting range. Known body length/Content-Length is compared exactly;
unknown streaming lengths are eligible without buffering the full body. Actual
compression execution still requires the compiler-enabled acceptance suite.
## Management in 0.3.0
Settings contains a direct Security shortcut (accounts, sessions and host ACLs)
and a vhost shortcut (SSL, headers, cache and pages). Compact is the default density;
Dense and Comfortable change spacing/control sizes without browser zoom or logo
redesign. The preference is local to the browser, not a global proxy setting.
Global Pages configures error overrides, direct-IP and unknown-host responses.
See MANAGEMENT.md for inheritance, allowed statuses and security implications.
Access logs have moved out of the dashboard into a separate modal/docked window.
Filters stay above the scrollable rows; counts and exports use the same selection.
## Named listeners, forwarding and diagnostics (0.4.0)
As of 0.8.0, the vhost editor no longer asks for a free-form listener IP. Configure
listener IP/port/protocol once in this Settings section, then select named endpoints in
the vhost. The pre-production inline/global listener format is no longer accepted. This separates
socket intent from host routing and avoids duplicating listening IP configuration across
vhosts.
`listeners.endpoints` is the only proxy-listener configuration. Each record has id,
name, scope, enabled and a binding with address, tls, http2, optional http2_settings, h2c and optional http3. Scope
is only a label. A vhost uses `frontend.listener_ids`; every reference must exist.
Conflicting protocols on shared sockets are rejected.
Changes to effective transport plans need restart, including H2 lifecycle and H3 limits. The UI
shows saved named entries and actual bound transports separately.
`forwarding` controls trusted CIDRs and forwarded-proto acceptance.
`diagnostic_headers` exposes only `request_id` (false initially). Vhosts can override the diagnostic
policy and Server policy; absent/null means inherit. Runtime/cache/static/error
responses pass through the same final header policy.
See INTEGRATIONS_040.md and HTTP3.md for complete JSON, limits and security details.
## 0.5.0 changes (override earlier baseline examples)
A named TLS listener with omitted http3 uses default H3 limits. Explicit null is
persisted as disabled. Overlapping named sockets must match protocols AND H3 limits;
the error identifies owners and remedies. A changed binding requires restart, including
UDP protocol/limit changes. Check UDP port availability before upgrading.
The diagnostic control is `request_id` (false by default). The removed hide_mock_origin field is not accepted; use normal response-header removal
for X-Mock-Origin. Existing custom Server values remain unchanged.
Cache decision/object inspection is under Cache, separate from Overview. Basic
manual certificate issuance is under Certificates; TSIG is never a configuration
field. See CACHE_DIAGNOSTICS.md and ACME_RFC2136.md for exact scopes and limits.
## 0.6 navigation and exporter
The Settings page now has eight switching cards: Listeners, Response, Cache,
Headers, Forwarding, Pages, Prometheus and Security. Unsaved values remain in
place while switching; switching alone never writes configuration. Security is
not a separate sidebar item. Use arrow keys/Home/End in the tab bar.
The Prometheus card controls enabled, bind IP/port, URL path, client CIDRs,
bearer-token environment variable name and optional collectors, including the bounded
Traffic analytics collector. Only the variable name is exported; supply the actual secret to the service separately. See
PROMETHEUS.md for immediate revocation, restart rules and scrape configuration.
## Stream Dashboard cards (0.32.0)
Under **Settings → Appearance → Dashboard layout**, the existing Show/Hide and Up/Down card controls now include Stream overview, traffic, sessions and backend health. They are opt-in and account-scoped, saved through the existing `/api/preferences` flow. Legacy 12-card orders migrate in memory with all four Stream cards hidden. They use the existing status/SSE stream and stop computing their charts while hidden; no backend monitoring or listener settings are changed when toggled. An explicitly enabled Stream card stays on Overview and shows a neutral no-data state when no Stream is active. Overview details and chart legends stay visible inside the cards (without hover tooltips). Appearance Stream card descriptions use browser-native `title` hints on hover, with the same localized descriptions available to assistive technology through `aria-description` on the visibility buttons; they no longer add floating boxes or horizontal scrolling.
## Full Overview layout and additional metrics (0.32.3)
Open **Overview -> Arrange cards visually** (or **Settings -> Appearance -> Dashboard layout -> Visual layout editor**) to change **all 49** dashboard blocks: 30 previous tiles, 12 new optional charts, three large live charts, three diagnostic panels and the backend table. Drag tiles/sections, use arrow buttons for keyboard access, or hide and re-enable individual blocks. Large sections retain their own original charts, controls and live data after being moved. The responsive six-column desktop view scales to narrower widths. Visibility and order are account-scoped; they never alter backend behavior.
**Default view** loads the factory order and visibility into the editor **without saving**; **Save layout** applies it. **Reset layout** asks for confirmation, then persists the same default order and visibility in one preferences write. **Cancel** preserves the current layout. All 12 added charts are hidden until explicitly enabled and use a maximum of 30 status samples without additional HTTP polling. They cover process CPU/RSS/file descriptors, connections, WebSocket tunnels, TLS handshake failure and request-error rates, cache HIT share and HOT RAM usage, incoming HTTP/2 request share, mean response duration, and unhealthy backends. Unknown and first-delta measurements show `-`; response time is a mean, not a percentile, and HTTP/2 share is **frontend** traffic rather than negotiated upstream ALPN.
## HTTP backend Overview cards (0.32.3)
**Settings -> Appearance -> Dashboard layout** now contains fourteen opt-in
HTTP origin cards: **Backend health**, **Active backend requests**, **Backend
errors**, **Health probe RTT**, **Backend traffic**, **Configured backend
modes**, **Backend request rate**, **Backend capacity**, **HTTP 5xx**, **Backend
timeouts**, **Upstream connection errors**, **Backend retries**, **Backend
queue pressure**, and **Backend connection pools**. All are hidden by default on new and migrated administrator profiles;
card visibility and ordering use existing per-account `/api/preferences` fields.
They do not affect routing, health checks, upstream keep-alive or listeners.
The cards reuse `backend_pools` already included in the live status payload:
- Health separates up/down/checking and draining/disabled members. Active
requests is a sum of in-flight pool member exchanges, not open TCP sockets.
- Errors show lifetime member error counts and a sampled errors/s graph. Traffic
uses the positive differences of per-member in/out byte counters; resets and
newly added backend members cannot generate an artificial rate spike.
- Health probe RTT is the mean last observed healthy probe time, **not** client
end-to-end latency. It shows no data when health probes are unavailable.
- Configured backend modes distinguish saved `http1`, `auto`, compatible `http2`
and strict `h2` values, and count H2C cleartext members. This is **not an observed
H1/H2 negotiation rate**: TLS ALPN can select H1 for Auto and compatible HTTP/2.
- Backend request rate uses started-request counter deltas, with per-member
baselines after a restart or config change. Backend capacity compares active
requests against finite per-member concurrency limits; unlimited members are
excluded and a neutral state is shown when no such limit is configured.
The six additional diagnostics use pre-existing `backend_pools.members` counters:
HTTP 5xx responses, all timeout classes, combined connect/DNS/TLS/I/O failures,
retry attempts and successes from the existing safe retry policy, queue timeouts
and capacity rejections, and Hyper client-generation rotations and age. Rotations
are **not TLS handshake counts**. Rates are based on per-member positive counter
differences; newly added or reset members cannot create arbitrary spikes.
To arrange the actual Overview cards visually, click **Arrange cards visually**
on Overview or **Visual layout editor** in Settings -> Appearance -> Dashboard
layout. The modal displays copies of the existing telemetry cards; drag to
reorder, or use the move arrows for keyboard-only input. Hide/show tiles in
the modal and select **Save layout** once to persist order and visibility in
the existing `dashboard_widgets` and `dashboard_hidden_widgets` preferences.
Cancel/Escape discards the draft. The existing Appearance visibility and move
buttons remain available. No extra API endpoint, refresh loop, or database
migration is needed.
TLS ALPN selects H2 or H1 only when opening a **new TLS connection**, not for
each request. Hyper already pools reusable connections per compiled backend;
subsequent requests share the negotiated connection (H2 multiplexing when
possible). Existing bounded pool recycling (`pool_recycle_requests` and
`pool_recycle_seconds`), configured idle timeouts, and reopening failed
connections naturally re-evaluate TLS capabilities. A backend-global protocol
cache or scheduled H2 probe would be unsafe for origins with multiple DNS/peer
capabilities and would add network work. No traffic replay/downgrade on
post-request errors was added; strict H2, H2C, H1 and WebSocket separation
remain intact.
The sample charts are browser-local, bounded and reset on a changed config
revision, logout or source unavailability. They are not computed while hidden.
No additional polling, SQLite schema migration or new administrator endpoint
is introduced. Existing Stream widgets remain independent.
## Dynamic DNS / DDNS client ACL (0.32.5)
In **ACL profiles**, add an **Allow** rule and select **DNS / DDNS** instead of
IP/CIDR. Enter the hostname without a URL, port or wildcard, for example:
```text
allow myoffice.example.ddns.net
```
Apply the profile to the desired vhost. Use the **Allow exceptions** preset
(default deny) to admit only resolved addresses (and any other explicitly
allowed IP/country exceptions). Host policies and inline vhost/location ACLs
accept the same rule format. A/AAAA records are refreshed every 60 seconds,
with immediate refresh on published configuration changes. No request-path DNS
lookup is performed. The initial state and failed, empty, oversized, or expired
DNS results **do not authorize any client IP** for that DNS rule. Only
**allow** rules may contain DNS names; `deny example.com` is rejected.
Limits: 128 distinct names across active ACLs, 32 returned IPs/name, four-
second lookup timeout and eight parallel DNS lookups. Successful answers expire
after 180 seconds if the refresh task stalls. DNS changes may take up to the
next poll interval to take effect. The chosen DDNS provider controls which IPs
receive access; always trust the provider and protect its credentials. Existing
IP/CIDR ACLs and country rules are unchanged. No new admin access path or
backend origin DNS exceptions are created by this feature.
+19 -116
View File
@@ -1,127 +1,30 @@
# TCP/UDP Stream pools (v0.32.0)
# TCP and UDP Streams
Layer-4 forwarding runs on dedicated TCP/UDP listeners and never traverses HTTP Vhosts, ACL, cache, headers, compression, TLS termination or authentication. The existing **Streams** page provides a card/modal editor and per-Stream RX/TX chart. Changes to Stream listeners and backend pool configuration remain **desired state** until the next controlled RustProxy restart; saving Appearance preferences does not restart the proxy.
The **Streams** workspace forwards non-HTTP TCP or UDP traffic through dedicated sockets. It is separate from Vhost HTTP routing. Suitable examples include selected private TCP application ports and UDP protocols that need basic forwarding rather than HTTP features.
## Compatible configuration
## Configure a Stream
The legacy document still works without modification:
1. Open **Streams → Add** and choose `TCP` or `UDP`, a unique name/ID and enabled state.
2. Set the listening IP/port and an upstream address, or configure a backend pool.
3. If using several backends, select weights, balancing, health checks and draining appropriately.
4. Configure connection/session limit, TCP connect timeout and idle timeout.
5. Save, restart RustProxy if the listener change requires it, then test with a real client and inspect Stream metrics.
```json
{"streams":[{"id":"ssh","name":"SSH relay","enabled":true,"protocol":"tcp","listen":"0.0.0.0:2222","upstream":"10.0.0.20:22","connect_timeout_seconds":5,"idle_timeout_seconds":300,"max_connections":512}]}
```
The source model supports a legacy single-upstream configuration or a backend list (up to the validated per-host limit), with pool-based load balancing and health observations. Do not assume HTTP health-check response codes can be applied to a UDP-only application: use the probe options actually supported by the Stream type.
Without `backends`, `upstream` is treated as one enabled backend with ID `primary`, weight 1, no active health probe and detailed metrics disabled by default (opt in explicitly). The legacy field is retained in serialized configuration and is set to the first backend address when a pool is explicitly configured. Existing listeners continue to require restart on change.
The backend editor groups connection/capacity settings separately from active health-check options. When **Health checks** is Off, probe tuning fields are hidden; selecting TCP connect/UDP probe reveals them without erasing their values. Hidden fields are excluded from browser validation.
A TCP pool can be configured as follows (the same backend fields apply to UDP):
## Behavior and boundaries
```json
{
"id":"ssh", "name":"SSH pool", "enabled":true, "protocol":"tcp",
"listen":"0.0.0.0:2222", "upstream":"10.0.0.20:22",
"balance":"round_robin", "metrics_enabled":true,
"connect_timeout_seconds":5, "idle_timeout_seconds":300, "max_connections":512,
"backends":[
{"id":"node-a","name":"Server A","address":"10.0.0.20:22","enabled":true,"drain":false,"weight":2,"max_sessions":256,"metrics_enabled":true,"health":{"mode":"tcp_connect","interval_seconds":10,"timeout_ms":1000,"rise":2,"fall":2}},
{"id":"node-b","name":"Server B","address":"10.0.0.21:22","enabled":true,"drain":false,"weight":1,"max_sessions":256,"metrics_enabled":false,"health":{"mode":"tcp_connect","interval_seconds":10,"timeout_ms":1000,"rise":2,"fall":2}}
]
}
```
- **TCP** forwards a bidirectional byte stream. The proxy does not interpret application messages or terminate application TLS.
- **UDP** maintains bounded per-client forwarding sessions; datagrams and inactivity timeouts behave differently from TCP connections.
- **Not available:** HTTP Vhost routing, Host/path matching, HTTP cookies, WebSocket logic, shared HTTP cache, HTTP Basic/Tinyauth, HTTP header rules or frontend HTTP/3/TLS termination. Protect these endpoints with firewall rules and protocol-specific backend authentication/encryption.
- Listeners and backend IP/ports must be concrete, valid and nonconflicting with other RustProxy/admin/exporter sockets. Resource limits and network policy still apply.
`balance` is `round_robin` (default), `least_connections` (TCP), or `least_sessions` (UDP). The least-load variants use an active-assignment-to-weight ratio. Each new TCP connection selects one backend for its entire connection; a UDP client session selects once and remains pinned for its lifetime (idle timeout). `drain: true`, `enabled: false`, `Unhealthy` state, or a reached `max_sessions` prevents new assignments. In-flight TCP relays and UDP sessions retain their selected backend; a controlled process restart still follows the existing shutdown grace period and may end in-flight sessions.
## Status and testing
## Health monitoring
Stream views/Overview include sessions, connections, traffic, rejected/errors and backend health/capacity when enabled. Counter reset or no-data state after service restart is normal.
Each backend has independent `enabled`, `health` and `metrics_enabled` controls. Disabled and draining backends can continue independent health monitoring if probes are explicitly configured; it does not get new sessions. Probe concurrency is globally limited to 16, at most one health task per enabled/configured backend, with lifecycle tied to the listener cancellation token. Timeouts, probe intervals and rise/fall thresholds are bounded by validation. TCP checks use a timed TCP connect without application payload.
The source package includes `scripts/start_stream_backends.sh`, `scripts/test_stream_tcp.py` and `scripts/test_stream_udp.py` for isolated test networks. Their mock selftests alone do **not** establish that RustProxy forwarding works; run end-to-end client traffic through the configured Stream listener.
UDP checks cannot use a local UDP `connect()` as evidence that the remote service exists. For a validated active UDP probe define both `payload_hex` and an exact, nonempty `expect_hex` response (up to 128 bytes each), e.g.:
```json
{"mode":"udp_probe","interval_seconds":10,"timeout_ms":1000,"rise":2,"fall":2,"payload_hex":"50494e47","expect_hex":"504f4e47"}
```
**Do not enable UDP probing without the service owner's approval.** Unknown application protocols must leave checks `off`; `udp_probe` without an expected reply is `Unverified`, sends no datagrams and does not report Healthy. A backend without active checks is `Unverified` (routing still allowed), while probed backends show `Checking`, `Healthy`, `Unhealthy` or `Disabled`; hysteresis prevents state flapping. An Unhealthy backend stops receiving new sessions, but no ongoing TCP connection is reassigned. If the pool has no eligible backend, new sessions or datagrams are rejected and the legacy per-Stream `rejected` counter advances.
## Telemetry and Dashboard
The existing `/api/status` response and `/api/status?view=live` SSE/fallback add a bounded `backends` array under `streams.metrics[stream_id]`. It includes stable configured IDs, status, active sessions, health counts/last errors and (if enabled) optional completed/created/assignment, bytes, packets and error/reject metrics. `bytes_in` remains client → upstream, `bytes_out` upstream → client. A UDP datagram rejected before a session is established remains counted in the original per-Stream incoming counters, not backend delivered-byte counters.
In the editor, **Stream → Detailed metrics → Default (disabled)** means the absence of a Stream override resolves to `false` (it does **not** inherit from Prometheus). At the backend level, **Use Stream setting** resolves to that Stream's value; backend **Enabled**/**Disabled** override it. Prometheus retains `include_streams` as the separate global export gate. `metrics_enabled` defaults to `false` for detailed per-backend accounting (no new overhead on legacy streams). Stream-level `true` enables the default for its backends; backend-level `true`/`false` explicitly overrides the Stream value, while `null` inherits. This choice never changes session routing or health checks. Basic per-Stream counters and backend active gauges stay available. Backend label cardinality is bounded to IDs declared in config, never client IPs. History is a limited browser-memory window only; metrics are not persisted as periodic SQLite samples.
Under **Settings → Appearance → Dashboard layout**, toggle/reorder Stream overview, traffic, sessions and backend-health cards alongside existing telemetry cards. All Stream cards default hidden for existing accounts, including migrated 0.31.x layouts. An explicitly enabled card stays visible even when no Stream is configured, enabled or running; it displays a neutral **No stream data** state in that case rather than ignoring the Appearance setting. They use the existing API status/SSE/fallback and one dashboard refresh scheduler. Hiding a card avoids its chart computations, and changing visibility does not alter Stream runtime, existing widgets or global Prometheus settings. Widget descriptions in Appearance use native `title` tooltips on hover and accessible button descriptions (`aria-description`) instead of floating elements that expand the layout. On the main Overview page, secondary values and RX/TX chart legend are always inline under the metric/graph (not tooltips), with the original six-column desktop layout and taller tiles for readability.
## Safety and operational limits
At most 128 Stream hosts and 32 backends per host, weights 1–16, 1–65535 stream/session concurrency and optional per-backend session caps. Validated concrete backend IPs and nonzero ports are required. Duplicate backend IDs, local listener loops, conflicting transport health modes and invalid probes are rejected before configuration is published. UDP client queues are bounded at 64 datagrams. The existing listener restart indicator remains authoritative, and no new public administration routes or polling tasks are added.
## Standalone TCP and UDP forwarding tests
The ready-to-use launcher and individual TCP/UDP test clients are all in `scripts/`.
Start four loopback-only mock backends (TCP 9101/9102; UDP 9201/9202):
```bash
./scripts/start_stream_backends.sh
# Optional, only on an isolated test network: ./scripts/start_stream_backends.sh 0.0.0.0
```
Point your corresponding Stream backend pools at these ports, restart RustProxy
(the Stream configuration's existing restart requirement), then run:
```bash
python3 scripts/test_stream_tcp.py client --port 2222 --sessions 32 --messages 5 --min-backends 2
python3 scripts/test_stream_udp.py client --port 5353 --sessions 32 --messages 5 --min-backends 2
```
Stop the launcher with `Ctrl+C`. Its four children are stopped together. If you
prefer launching backend peers separately or need different ports, use the examples below.
These are **black-box clients for a real running RustProxy** plus small, intentionally
loopback-only mock services. Do not aim them at live/production backends. Use
separate terminals for each mock, and configure the Stream editor with these
backend addresses. **Restart RustProxy after modifying Stream backends** (the
existing Stream lifecycle still applies).
TCP example (Stream listener `127.0.0.1:2222`, backend pool `127.0.0.1:9121` and
`127.0.0.1:9122`):
```bash
# Terminal 1
python3 scripts/test_stream_tcp.py server --port 9121 --backend-id node-a
# Terminal 2
python3 scripts/test_stream_tcp.py server --port 9122 --backend-id node-b
# Terminal 3 - after enabling the TCP Stream listener in RustProxy
python3 scripts/test_stream_tcp.py client --port 2222 --sessions 32 --messages 5 --min-backends 2
```
UDP example (Stream listener `127.0.0.1:5353`, pool `127.0.0.1:9131` and
`127.0.0.1:9132`):
```bash
# Terminal 1
python3 scripts/test_stream_udp.py server --port 9131 --backend-id node-a
# Terminal 2
python3 scripts/test_stream_udp.py server --port 9132 --backend-id node-b
# Terminal 3 - after enabling the UDP Stream listener in RustProxy
python3 scripts/test_stream_udp.py client --port 5353 --sessions 32 --messages 5 --min-backends 2
```
Each client verifies exact round-trip payloads and logs per-backend session
assignments. TCP verifies the selected backend stays unchanged through several
messages on one connection; UDP verifies the same over datagrams from one
client socket. Use `--min-backends 1` for a single backend, drain, or failover
case. Compare the reported assignments with Stream metrics and Prometheus.
`--parallel`, `--sessions`, `--messages`, and `--timeout` control test load.
UDP mock services also respond to the **explicit** health probe `PING`/`PONG`
(`payload_hex: 50494e47`, `expect_hex: 504f4e47`); do not apply this payload
arbitrarily to real application servers.
To check the scripts themselves *without RustProxy*, run:
```bash
python3 scripts/test_stream_tcp.py selftest
python3 scripts/test_stream_udp.py selftest
```
Selftests only exercise the Python mock peer/harness; they do not verify the
RustProxy forwarding path, balance algorithms or real health check lifecycle.
Run the `client` cases against your compiled proxy and manually repeat after
disabling, draining, or stopping one mock backend to verify failover behavior.
If clients cannot connect, inspect listen IP/port, host firewall, TCP vs UDP selection, routing to the chosen backend, backend health and stream logs. See [Security](SECURITY.md) and [Monitoring](MONITORING.md).
View File
Whitespace-only changes.
-19
View File
@@ -1,19 +0,0 @@
# RustProxy TODO
This file contains open work that remains after the current RustProxy 0.25 Web Server implementation.
## Web Server follow-up
- Multipart byte-range responses if required; current single-range/If-Range support remains the default scope.
- Named locations only if they can reuse the existing vhost/location router rather than introduce a second router.
- Optional PHP `PATH_INFO` script/path splitting after a confinement-safe rule is defined.
- Additional FastCGI observability (bounded active/error/latency counters) without per-script labels.
- A race-resistant non-Unix filesystem confinement implementation before enabling the Web Server on non-Unix targets.
## Release acceptance
- Run `./dev.sh fmt`, `./dev.sh check` and `./dev.sh test` on a host with the stable Rust toolchain.
- Run live static-file acceptance over HTTP/1.1, HTTP/2 and HTTP/3, including ACL/auth/custom-error/header behavior.
- Run a real PHP-FPM acceptance matrix over a Unix socket and loopback TCP, including `index.php`, front-controller `try_files`, POST bodies, timeouts and source-disclosure failure tests.
Completed release work belongs in `CHANGELOG.md`; planned version work belongs in `ROADMAP.md`; implementation/audit evidence belongs in the matching audit document.
-55
View File
@@ -1,55 +0,0 @@
# Static-response transformation
RustProxy 0.22.3 retains static transformation between origin decoding and shared-cache
storage. A transformed identity representation is cached once and downstream compression
is selected later for each client. Per-vhost and Docker-discovery transform opt-in remain
disabled by default even when the global engine is available.
## Web Server origins (0.29.4)
Static-file and PHP-FPM responses selected through Web Server vhost sources
use the same optional vhost transform stage as reverse-proxy backends.
The normal rules still apply: supported MIME/extension and response encoding,
known bounded body size, memory/concurrency limits, and `no-transform`
exclusions. Successful transformed representations are eligible for shared
cache storage under the original/derived cache privacy policy. Downstream
compression is still applied separately.
## Administration dashboard
The main **Transform** workspace reads `GET /api/transform` and exposes in-memory runtime
statistics globally or per vhost:
- responses examined, transformed, skipped/fail-open and failed;
- transformed cache HITs (served without running the transformer again);
- original, transformed and saved bytes before client compression;
- average transform time;
- applied transforms by JavaScript/CSS/HTML/SVG/JSON;
- bounded skip/fail-open reason counters;
- current process-wide temporary-memory reservation versus the configured budget;
- current active transform slots versus the configured concurrency limit;
- a per-vhost effectiveness table.
Counters reset on process restart. They are diagnostic telemetry, not persisted cache
metadata. Resource usage is process-wide even when a vhost counter scope is selected. The
reservation is the transform limiter's conservative buffer estimate, not a second RSS
measurement. The Cache workspace separately marks stored transformed objects.
## Cache diagnostics
When `cache.expose_cache_status=true`, transformed cached representations are identified
with the RFC 9211 field plus an extension parameter:
```http
Cache-Status: RustProxy; hit; ttl=556488; detail="cache_hit"; transformed
```
The field remains `Cache-Status`, not `X-Cache-Status`. HTTP/2 and HTTP/3 normalize field
names to lowercase on the wire. Origin-provided `Cache-Status` and `X-Cache-Status` values
are removed before RustProxy emits its own diagnostics.
## Release status
The dashboard/telemetry does not change the existing v0.22.3 release gate. JavaScript and
CSS still require mature parser-backed minifiers, and the complete Rust compiler/runtime
acceptance gate must pass before release acceptance.
+62 -190
View File
@@ -1,223 +1,95 @@
# Transport contract
# HTTP transport, TLS, HTTP/2 and HTTP/3
This document describes the implementation contract of 0.22.3. It is not a record
of successful Rust execution; see TEST_REPORT.md for verification status.
RustProxy distinguishes **frontend** client protocols from the **upstream** protocol used to contact an application. Selecting HTTP/3 on a listener does not require an HTTP/3 backend.
## Protocol matrix
## Frontend listeners
| Connection | Implemented mode |
|---|---|
| Client to cleartext listener | HTTP/1.1 plus opt-in HTTP/2 prior knowledge and HTTP/1.1 `Upgrade: h2c`; WebSocket Upgrade on H1 and RFC 8441 Extended CONNECT on H2 when enabled per vhost |
| Client to TLS listener | HTTP/1.1 or HTTP/2 through ALPN; WebSocket uses H1 Upgrade by default or opt-in RFC 8441 Extended CONNECT on H2 |
| Client to HTTP/3 TLS listener | HTTP/3 over QUIC; RFC 9220 WebSocket Extended CONNECT only when a bound vhost explicitly opts in |
| Proxy to HTTP backend, `http1` | HTTP/1.1 |
| Proxy to HTTPS backend, `http1` | TLS with HTTP/1.1 |
| Proxy to HTTP backend, `http2` | HTTP/2 prior knowledge; the backend must support it |
| Proxy to HTTPS backend, `http2` | Prefer HTTP/2 through ALPN, retain HTTP/1.1 |
| Proxy to HTTPS backend, `auto` | ALPN HTTP/2 or HTTP/1.1 |
| Proxy to HTTP backend, `auto` | HTTP/1.1; no automatic h2c upgrade |
| Proxy to a WebSocket backend | HTTP/1.1 Upgrade over HTTP or HTTPS |
- **HTTP/1.1:** default plaintext HTTP and standard Upgrade-based WebSocket support where enabled.
- **HTTPS / HTTP/2:** TLS listeners may negotiate HTTP/2 and HTTP/1.1. Settings include H2 idle/connection-drain controls.
- **HTTP/3 (QUIC):** optional UDP transport on an enabled TLS frontend, using the listener certificate and supported H3 settings. The host firewall/NAT must pass UDP on the actual HTTPS port, not only TCP/443. Browsers can fall back to H2/H1 when H3 is unavailable.
- **H2C:** explicit cleartext HTTP/2 capability only where the listener or upstream policy enables it; it is not ordinary browser HTTPS.
Cleartext downstream HTTP/2 is an explicit listener opt-in. The same listener accepts both
the HTTP/2 prior-knowledge preface and a standards-oriented body-free HTTP/1.1 `Upgrade: h2c`
bootstrap. Upgrade requests must include `Connection: Upgrade, HTTP2-Settings`, exactly one
`Upgrade: h2c` and one valid base64url `HTTP2-Settings` field. RustProxy sends `101`, waits for
the client's normal HTTP/2 connection preface and initial SETTINGS frame, then reconstructs the
accepted HTTP/1.1 request as HTTP/2 stream 1 before handing later streams to Hyper's ordinary H2
state machine. Body-bearing or malformed upgrade requests are rejected before `101`. HTTP/1.1
remains available on the same cleartext listener.
Transport settings attach to **named listeners**, and a Vhost must select the appropriate listener IDs. Addresses locked by CLI/systemd startup cannot be rewritten from the panel. Changing a listener bind or startup-owned TLS option may require a restart.
Enabling WebSocket on a vhost keeps the HTTP/1.1 Upgrade path by default. `websocket_rfc8441`
and `websocket_rfc9220` are independent per-vhost opt-ins for HTTP/2 and HTTP/3 Extended
CONNECT with `:protocol=websocket`. HTTP/2 listeners advertise ENABLE_CONNECT_PROTOCOL when
serving H2; QUIC advertises it when required by the HTTP/3 transport. Routing still rejects
Extended CONNECT on vhosts that did not opt in. Ordinary CONNECT and other Extended CONNECT
protocols remain rejected because RustProxy is not an open forward proxy. RFC 8441 may bridge
to an HTTP/1.1 WebSocket backend or use a backend explicitly configured for HTTP/2.
### Saving HTTP/3 settings
Since 0.32.4, `preserve_host: true` is supported with `http1`, `auto`, `http2`
and strict `h2`. H1 carries the public Host; on H2 the outgoing `:authority`
follows that Host. The *network* destination and DNS/ACL policy stay pinned to
the configured backend URL. TLS SNI and certificate validation independently use
`tls_server_name` when supplied or the configured origin otherwise.
The client hostname also remains available via `X-Forwarded-Host`. HTTP health
probes are separate H1 requests and may need an explicit `health_check.host`.
- **Immediately after a successful panel save:** H2/H1 TLS responses use the saved `alt_svc_max_age` (`Alt-Svc`), provided a matching UDP listener was actually bound at startup. Origin `Alt-Svc` never passes through. The value `0` publishes `ma=0`.
- **Disabling H3 on a Vhost or an existing listener:** new H1/H2 responses publish `Alt-Svc: clear` to remove the browser's cached alternative service. A browser may keep an established QUIC connection until it closes; `clear` does not forcibly terminate it.
- **For new QUIC connections only:** updates to `max_concurrent_bidistream`, `max_concurrent_unistream`, `max_idle_timeout` and reductions to `max_concurrent_connections` are applied without restart. Existing QUIC connections keep their negotiated limits. If no existing UDP socket matches the saved listener address, no H3 is advertised or accepted on the old address.
- **Restart needed:** UDP listener enable/disable, changing IP/port/TLS properties, UDP socket buffer sizes, changes to H1/H2 listener settings and increasing `max_concurrent_connections` above the startup bound. The live listener status continues to show `restart_required` for these cases. After a restart, the new connection cap can take effect.
For ordinary proxy traffic, an explicit `protocol=http2` backend is forwarded with an
HTTP/2 request version. Cleartext `http://` uses h2 prior knowledge. HTTPS keeps the
existing ALPN policy and offers HTTP/2 with HTTP/1.1 compatibility; RustProxy does not
replay a failed TLS request merely to downgrade protocols.
### QUIC UDP socket buffers
Balanced routes may perform bounded failover retry only for body-free `GET`, `HEAD` and
`OPTIONS`. Defaults allow one retry on connection failure, response-header timeout or
`502/503/504`, always selecting another currently healthy backend. Path-pinned routes,
WebSocket handshakes and write/body-bearing requests are never automatically replayed.
The HTTP/3 settings of each TLS listener include:
## DNS and upstream TLS
| Parameter | Default | Accepted values | Takes effect |
|---|---:|---|---|
| `udp_receive_buffer_bytes` | 4194304 (4 MiB) | `0` (OS default) or 65536–16777216 | On listener restart |
| `udp_send_buffer_bytes` | 4194304 (4 MiB) | `0` (OS default) or 65536–16777216 | On listener restart |
Only explicit HTTP/HTTPS backend origins are accepted; no credentials, path, query
or fragment are allowed. Client requests cannot choose an arbitrary upstream URL.
Use **Settings → HTTP/3** to edit these as whole MiB values (0–16); the JSON API stores bytes. No global `net.core.*_default` adjustment is necessary: RustProxy explicitly sets its own socket options. An OS limit may still clamp the request. Linux doubles `setsockopt(SO_RCVBUF / SO_SNDBUF)` values for bookkeeping; RustProxy targets the final effective size shown as `rb`/`tb` in `ss`.
Each new DNS connection uses a guarded resolver. Every returned IPv4/IPv6 address
must pass the configured address policy, with IPv4-mapped IPv6 normalized for checks.
A mixed safe/unsafe response is rejected as a whole. Up to 32 answers and 16 OS
resolver workers are allowed. Validated answers go directly to the HTTP connector:
there is no second unvalidated DNS lookup before dialing. Existing pooled sockets
remain bound to their original peer until closed; they do not silently move when
DNS changes. New snapshots get new connection pools.
`allow_private` is explicit approval for eligible private/loopback addresses, not a
universal SSRF bypass. `address_allowlist` further restricts eligible addresses by
CIDR. Include both address families when a hostname legitimately resolves to both.
Link-local/metadata, multicast, unspecified and certain special ranges remain
blocked. Remote origins may use any valid backend port, including 9090. A target that
resolves to an actual/local RustProxy proxy, TLS or administration listener remains
blocked by default, including validated DNS answers. An administrator may intentionally
override only that self-target guard per backend with `accept_local_listener_risk: true`;
loopback/private targets still require `allow_private: true`. This opt-in can create a
proxy loop or expose the administration plane, so keep it disabled unless that route is
deliberate and separately access-controlled. For example, an intentional local backend
`http://127.0.0.1:9090` requires both `allow_private: true` and
`accept_local_listener_risk: true`; this only permits the connection and does not bypass
the administration listener's own Host/Origin/authentication policy. The special-range
policy is a basic safeguard, not a substitute for network egress controls or an independent
security audit.
HTTPS upstream transport remains encrypted, but certificate-chain/hostname verification
is **off by default**. Set `tls_verify: true` to authenticate the backend identity. An
optional `tls_ca_pem` adds up to 16 trust anchors within 64 KiB per backend when
verification is enabled. The configured `tls_server_name`, when present, determines TLS
identity while DNS/TCP still targets the configured URL. Upstream redirects are returned
to the client, not followed internally.
## Public certificates
`dev.sh` enables `127.0.0.1:8443` by default. Direct binary startup enables TLS only
when `serve --tls-listen ADDRESS` or the corresponding environment setting is used.
Changing listener addresses or enabling a new listener requires a restart.
`data/certs/default.pem` and `default.key` are created once when needed. Generated
certificates are self-signed development material; they do not provide public trust.
Explicit bindings use exact hostname names and already-present certificate/key files.
Manual entries support two addressing modes: simple filenames are loaded from
`<data-dir>/certs`, while two absolute paths are read in place (for example Certbot
`/etc/letsencrypt/live/<name>/fullchain.pem` plus `privkey.pem`). Mixed relative/absolute
pairs are rejected. The API stores filenames/paths and names, never private key bytes.
CLI generate/import refuses to overwrite named files. Input size, regular-file status,
private-key permissions, matching keys, chain parsing and name coverage are validated.
For a 4 MiB target on Debian/Linux, if the default `net.core.rmem_max` or `net.core.wmem_max` prevents reaching the target, set adequate socket *maxima* after reviewing host memory constraints, for example:
```bash
rust-proxy tls generate --name app --hosts app.localhost,alias.localhost
rust-proxy tls import --name app-v2 --cert fullchain.pem --key private.key
rust-proxy tls deploy --id shared-cert \
--cert /etc/letsencrypt/live/example/fullchain.pem \
--key /etc/letsencrypt/live/example/privkey.pem
rust-proxy tls reload
sudo sysctl -w net.core.rmem_max=8388608
sudo sysctl -w net.core.wmem_max=8388608
sudo systemctl restart rustproxy.service
sudo ss -uapm | grep -A1 rust-proxy
```
Select these files in the panel or `tls.certificates` configuration. Set
`tls.default_certificate` to a binding ID to change the fallback. Unknown/no SNI
receives that fallback, not a default HTTP route. Unknown Host still returns 404.
Wildcard/SAN coverage and certificate expiry/issuer/renewal status are exposed by the certificate inventory; certificate ownership remains explicit per certificate id.
Read the `HTTP/3 UDP effective socket buffers` startup log message. `ss` should show approximately `rb4194304,tb4194304` (or larger if the OS adjusts values) when the requested size is granted. Changes to the OS maxima alone do not resize an existing socket; the configured buffer size is applied when a QUIC listener starts. High default/maxima for every program are not recommended as a substitute for explicit per-socket configuration. No specific throughput gain is guaranteed; compare H2/H3 at matched traffic before/after.
`tls deploy` is intended for an external issuer such as a system Certbot. Certbot's
`live/*.pem` symlinks are resolved locally; the final files are bounded regular files and
the private key must not be group/world readable. The local CLI performs bounded file/permission checks and sends the PEM pair through the authenticated loopback deploy API. The running RustProxy process treats the request as a convergent upsert: it creates a missing manual `certificate_id`, rotates changed material through immutable generation-specific files, and returns a no-op for an identical RustProxy-owned generation. The same id may be assigned to many vhosts, which all switch together when the pair changes. RustProxy-managed ACME certificate ids are deliberately rejected by this command so one lineage never has two renewal owners.
## Backend protocol selection
`POST /api/certificates/reload` and `rust-proxy tls reload` re-read all currently configured
certificate/key files and publish a fresh public TLS snapshot without changing the desired
configuration or its revision. This supports an operator that replaces the already configured
files by another safe local mechanism. Validation and file reads happen on bounded control-plane
workers; invalid material leaves the previous snapshot active. New public TLS/H2 and HTTP/3
handshakes use the refreshed snapshot, while established sessions/tunnels are not renegotiated.
The administration TLS listener remains startup-fixed and reports that a service restart is
required when its selected certificate changed.
| Backend mode | HTTPS origin | Plain HTTP origin |
|---|---|---|
| `http1` | HTTP/1.1 | HTTP/1.1 |
| `auto` | ALPN H2 or H1 | HTTP/1.1 |
| Legacy `http2` | Compatible ALPN H2/H1 | H2C |
| Strict `h2` | Requires H2 | Requires H2C |
For an externally managed/manual certificate whose configured files were replaced in place, `POST /api/certificates/{certificate_id}/reload` provides the same revision-stable hot-publication contract while targeting that certificate ID. For absolute external paths RustProxy canonicalizes the path again on every reload, intentionally following Certbot `live/` symlinks to the current `archive/` generation before opening the final regular file. The TLS resolver is immutable, so RustProxy validates and republishes a complete snapshot atomically; this avoids partially updated SNI state. Managed ACME certificate IDs are not accepted by this manual reload path.
WebSockets normally use HTTP/1.1 Upgrade. HTTP/2/HTTP/3 client requests can still reach HTTP/1.1 backends when the selected bridge/path is supported; do not assume the frontend and origin protocol always match. HTTP/2 GOAWAY, H3 stream cancellation and retries are constrained to safe lifecycle cases. Use backend protocol telemetry and logs when diagnosing negotiation.
External absolute-path files are not copied into RustProxy full backups. Their path strings remain in configuration; restore or mount the certificate material separately on the destination host before starting TLS. The `rustproxy` service identity must be able to traverse every parent directory and read both files. RustProxy accepts POSIX ACL read access and does not require an exact chmod value. External certificate/key files must not be group/world-writable, and private keys must not be world-readable. The configured and resolved parent chains must be root- or RustProxy-owned and not group/world-writable, except for root-owned sticky ancestors such as `/tmp`. The final file is opened with `O_NOFOLLOW` after path validation.
## TLS and trust
## WebSocket behavior
Public HTTPS requires a correct certificate with hostname coverage. Administration TLS is separate. Backend `https://` encrypts traffic, but **upstream certificate verification is opt-in by default** in the current backend model; enable it and specify CA/name where needed. Private/loopback addresses and routing to a local RustProxy listener require explicit risk overrides.
The default WebSocket path validates an HTTP/1.1 GET upgrade, version 13, one valid key,
connection upgrade tokens and a body-free handshake. RFC 8441 over HTTP/2 and RFC 9220 over
HTTP/3 must be enabled explicitly for the selected vhost and use Extended CONNECT rather
than H1-only Upgrade/Connection/Key headers. When RFC 8441/9220 is bridged to an HTTP/1.1
backend, RustProxy performs and validates the backend RFC 6455 handshake before exposing a
successful downstream tunnel. Direct HTTP/2 upstream RFC 8441 validates the 2xx response and
selected subprotocol. Protected handshake fields cannot be replaced by generic custom-header
rules. Unsupported/malformed upgrades fail; unsolicited upstream 101 responses become a
gateway error.
## Post-quantum TLS key exchange (optional)
After an accepted handshake the proxy copies bytes in both directions using bounded
buffers and backpressure. It does not aggregate messages, interpret application
payloads, log frames or hardcode a particular WebSocket application. Fragmentation,
control frames and negotiated extensions remain the peers' responsibility.
The demo echo server separately caps messages/frames at 64 KiB.
**Settings → TLS policy → Post-quantum key exchange** enables the hybrid TLS 1.3 group **X25519MLKEM768** (X25519 combined with ML-KEM-768) for incoming TLS. It is a key-exchange group, not a cipher suite: certificates and AES-GCM/ChaCha20-Poly1305 ciphers stay unchanged, and classic groups remain available as a fallback. The setting is off by default and requires TLS 1.3.
Request and TCP-connection leases survive Hyper's upgrade and are held until the
tunnel closes. An independent idle timeout observes traffic in either direction.
Shutdown cancels tunnels; routing/ACL changes affect new handshakes, not existing tunnels.
The administration API and dashboard expose active tunnels with a stable exchange ID, client,
host, upstream, transport, duration and RX/TX counters. An administrator can cancel one tunnel;
the action and supplied reason are written to the audit log.
When enabled, incoming TLS uses the `aws-lc-rs` provider; otherwise `ring` is used. Outbound connections (backends, ACME, update checks) are not affected. The setting is stored as `tls.policy.post_quantum`:
## HTTP/2 connection retirement
```json
{"tls": {"policy": {"tls12": true, "tls13": true, "cipher_suites": [], "post_quantum": true}}}
```
Downstream H2 idle retirement is activity-aware: RustProxy tracks transport I/O and accepted streams, rechecks that a connection is actually unused, then asks Hyper for graceful shutdown. Hyper sends GOAWAY and refuses newer streams while accepted streams continue. Each H2/H2C listener uses `binding.http2_settings.max_connection_lifetime_seconds` for the independent absolute lifetime (zero disables it) and `binding.http2_settings.drain_timeout_seconds` for the maximum drain before TCP is forcibly dropped. Older configurations without per-listener values inherit the legacy top-level `http2` settings. RFC 8441 Extended CONNECT/WebSocket streams remain active for this accounting until their tunnel body ends.
- Vhosts inherit the global policy. A per-Vhost `tls_policy` override can disable PQC but cannot enable it when the global switch is off. HTTP/3 Vhosts need an override that matches the global policy exactly.
- Only new handshakes use the updated policy; open connections keep their keys.
- Hybrid handshakes are larger and may hit MTU or middlebox limits, especially over QUIC; some legacy clients reject them.
- Roll back by unchecking the switch and saving.
## Resource and timeout defaults
Verify the negotiated group with OpenSSL 3.5 or newer, directly against the RustProxy listener (not a CDN in front of it):
| Resource | Default / bound |
|---|---|
| Shared public TCP connections, HTTP + TLS | 512; configurable up to 4096 |
| Active request/body/tunnel exchanges | 256; configurable up to 4096 |
| Concurrent HTTP/2 streams per public connection | 64 |
| HTTP/2 header list / send buffer | 32 KiB / 64 KiB |
| HTTP/2 stream / connection receive window | 65,535 bytes / 1 MiB |
| HTTP/2 idle / max lifetime / drain | 300 s / disabled (0) / 10 s |
| Public HTTP/1 header timeout | 10 seconds |
| TLS accept / upstream connect including TLS | 10 seconds |
| DNS lookup caller timeout / OS workers | 5 seconds / 16 |
| Backend response-header deadline | 120 seconds, including upload until headers |
| HTTP body idle timeout | 60 seconds |
| WebSocket idle timeout / upgrade completion | 300 seconds / 10 seconds |
| Idle pooled upstream connections | 4 per backend origin, 30-second idle timeout |
| Configuration writers / log-viewer streams | 8 / 8 |
```bash
openssl s_client -connect example.com:443 -servername example.com \
-tls1_3 -groups X25519MLKEM768 -brief
```
CLI serve flags expose connection/in-flight counts and HTTP body, WebSocket idle,
upstream and shutdown timeouts. OS DNS calls cannot be forcibly cancelled; their
worker permits remain occupied until completion. Runtime shutdown stops waiting
for blocking workers after an additional bounded timeout.
## Timeouts, request bodies and streaming
Request and response bodies stream without collection, including trailers. Body-bearing/write requests are not transparently replayed. Replay-safe body-free
GET/HEAD/OPTIONS may use the bounded backend retry policy described above. Bytes counted at the body layer are not
acknowledged network bytes. WebSocket counters measure raw frame bytes separately
from HTTP body counters and exclude TLS/TCP overhead.
Idle/read/connect/response-header timeouts and backend connection pools are configurable. A response may stream after its headers have arrived; response-header metrics are not full-body duration. WebSockets and Streams have separate idle lifetimes and should be tested with the actual application. RustProxy rejects suspicious ambiguous request targets rather than normalizing them into potentially unsafe backend paths.
## Vhost sockets and keepalive in 0.22.3
## Troubleshooting
Public connections across global and vhost endpoints share the same connection
budget. Per-vhost routing uses the actual accepted local IP/port/TLS state, including
when an OS wildcard socket is shared. See VHOSTS.md for overlap rules and restrictions.
Changing sockets or their ALPN policy needs restart. Certificate refresh remains hot.
- **HTTPS works but H3 does not:** check UDP firewall/NAT, listener H3 enablement, TLS certificate, network middleboxes and actual browser negotiation.
- **502 / protocol error to backend:** verify origin supports selected `auto`, `http2`, `h2` or H2C mode and the correct TLS hostname/verification policy.
- **WebSocket disconnects:** enable websocket forwarding in the Vhost and ensure idle limits/proxy/backend are compatible.
- **Unexpected HTTP→HTTPS redirect:** inspect Vhost `force_ssl`, selected listeners and actual public scheme.
TLS `http2: true` advertises h2 followed by http/1.1; false advertises HTTP/1.1 only.
An HTTP/1.1-only client, including one without ALPN, can connect when h2 is enabled.
HTTPS origins configured `http2` also negotiate h2 or HTTP/1.1. This is one TLS
negotiation, not a failed-request retry or TLS-verification downgrade. Plaintext
h2c remains explicit and strict: when `h2c: true`, a cleartext listener accepts both HTTP/2
prior knowledge and validated HTTP/1.1 `Upgrade: h2c`. The Upgrade bootstrap is body-free,
requires `HTTP2-Settings`, and becomes HTTP/2 stream 1 after `101`. Optional downstream HTTP/3
remains a separate QUIC listener capability (see HTTP3.md).
For listener configuration see [Configuration](CONFIGURATION.md); for safer deployment see [Security](SECURITY.md).
Pool keepalive settings configure idle connection reuse, idle timeout/limit, TCP
keepalive and HTTP/2 active-connection pings. They also bound each backend Hyper client
generation by request count/age and cap the upstream HTTP/1 adaptive read buffer. Client
generation recycling is local to the backend pool and does not publish a new vhost
snapshot or purge cache. Health probes are separate no-reuse HTTP/1.1 or raw TCP
operations; their concurrency cap is 16. Existing active exchanges are not intentionally
killed by disabling reuse, recycling a generation or draining a member. Config
publication still rebuilds client pools while preserving compatible observations and
existing exchange leases. The full contract and bounds are in VHOSTS.md. Keepalived/VRRP
is not involved.
## Docker vhost frontend protocols
A published Docker vhost stores `http2_enabled` and `http3_enabled` (both `true` by default, including older assignments without these fields). Both can be disabled individually in **Docker vhosts → Configure vhost → Frontend protocols**. HTTP/1.1 remains available. These switches control only the frontend transport on listeners selected for the agent; the separate Docker backend HTTP/HTTPS scheme is unchanged. Disabled H2 is not offered by SNI/ALPN; disabled H3 rejects HTTP/3 requests and withdraws `Alt-Svc` where HTTP/3 was previously advertised. A listener must already support a protocol to use it, and adding a new QUIC listener may require a restart.
+92
View File
@@ -0,0 +1,92 @@
# Troubleshooting and validation checklist
Start with a reproducible request, its hostname/path and the exact listener. Compare **Vhost**, **Backend**, **Cache**, **Logs** and the service journal before changing multiple settings at once.
## Quick symptoms
| Symptom | First checks |
|---|---|
| Domain returns the wrong site or local 404 | DNS A/AAAA, selected listener ID, exact vs wildcard host precedence, Vhost enabled state, redirect/404 host ownership. |
| HTTP works, HTTPS does not | Trusted certificate, hostname/SAN coverage, TLS listener, port 443 firewall, certificate assignment and whether restart was needed. |
| HTTPS/H2 works but H3 fails | UDP firewall/NAT, H3 toggle on TLS listener, browser QUIC support and actual transport negotiation. |
| H2 runs at link speed but H3 is slower | Compare the same origin and client with browser H3 vs H2, keeping TLS/cache/compression consistent. Inspect QUIC loss/cwnd/MTU and VM UDP offload, not only total CPU. |
| Ordinary `502` / `503` / `504` to origin | Backend IP/port, pool eligibility/health, private-address policy, upstream HTTPS verification, protocol `auto`/`h2`, timeout and container/PHP process availability. A locally generated ordinary 502 has the generic **Bad gateway** page. |
| `502 Tinyauth connection error` | The authentication backend cannot be reached. Check Tinyauth process, DNS, verifier URL/port, network firewall, TLS/certificate, private backend allowlist and the RustProxy service journal. Do not route the verification URL back through the protected Vhost. |
| `502 Tinyauth verification error` | Tinyauth returned an unexpected status/body or identity headers were too large. Verify `/api/auth/nginx`, proxy header injection and the installed Tinyauth version; check the server journal for details. |
| `504 Tinyauth timeout` | The Tinyauth verification deadline was reached. Check service load/latency and `connect_timeout_seconds` / `timeout_seconds` (Vhost Authentication tab). |
| `502 <provider> connection error` / `502 <provider> verification error` / `504 <provider> timeout` | All delegated providers use the same `error_auth_provider.html` template. The name identifies the failing provider; the status identifies network, response-validation, or timeout failure. Check the internal URL, DNS/TLS, allowlist, deadline and RustProxy journal; origin access stays denied. |
| Authelia redirects repeatedly | Check `login_url` HTTPS origin, `session.cookies` domains/`authelia_url` mapping, real proxy forwarding headers and original application host. Unrelated registered domains require matching per-domain cookie/portal settings. |
| authentik redirects repeatedly or `/outpost.goauthentik.io` is 404 | Check its proxy provider is **Forward auth (single application)** for the exact external application URL, assigned to the correct outpost. Use the outpost internal origin in the Vhost; RustProxy reserves this path on each protected host. Ensure the outpost can reach authentik and browser accepts HTTPS cookies. |
| WebSocket handshake fails or disconnects | Vhost WebSockets flag, backend Upgrade support, TLS/frontend protocol path, WebSocket idle limits and auth redirect. |
| `403` / `405` / `429` | Applicable IP/ACL/country policy, HTTP methods, rate limit, GeoIP availability, Basic/Tinyauth permissions. |
| Cache always BYPASS or MISS | Global + Vhost enablement, cookies/login, origin `Cache-Control`, `Set-Cookie`, `Vary`, TTL, size and exclusion rules. |
| Content remains stale | Cache Age/resident age, origin validator, policy changes, matching scheme/Host/query and selected purge scope. |
| Static file not found | Document root, process read permissions, index files, symlink policy, `try_files` and rewrite ordering. |
| PHP returns errors or another site's PHP settings | FPM endpoint/socket permission, distinct pool/INI settings, pool restart, PHP `PATH_INFO`, application logs. |
| Docker Vhost remains WAITING / 503 | First agent poll, token/certificate pin, published host TCP binding, backend IP, container state and health. |
| Stream TCP/UDP unreachable | Actual listen address/port, protocol and firewall, backend IP, Stream health/session limits; HTTP Vhost settings do not apply. |
| Administration cannot be accessed remotely | Admin binds loopback by default. Use SSH tunnel or configure **separate admin HTTPS** and intentional bind address. |
| Prometheus exporter unavailable | Dedicated listener, CIDR and token, configured path, startup/restart state and monitoring server route. |
| Logs missing or search incomplete | Check sink enabled/path/permissions, Live vs Stored distinction, log rotation and bounded scan/truncation notices. |
| Certificate renewal fails | ACME runtime, HTTP-01 TCP 80, DNS-01 token/zone, expired credentials, Certbot files and external key ACL after rotation. |
## Useful commands
Local service status for installed Linux/systemd deployments:
```bash
systemctl status rustproxy
journalctl -u rustproxy -n 200 --no-pager
```
Inspect a specific HTTP frontend with the correct Host (adapt hostname, port and path):
```bash
curl -v -H 'Host: app.example.com' http://127.0.0.1:8080/health
```
For HTTPS certificate/routing testing use the real site DNS or a deliberate `curl --resolve` mapping; avoid permanently disabling certificate verification as a workaround. The local CLI can check configuration without saving:
```bash
rust-proxy --data-dir /var/lib/rustproxy config get
rust-proxy --data-dir /var/lib/rustproxy config validate config.json
```
Use **Logs → Live Logs** or the `--livelogs` CLI mode to correlate request IDs, Vhost/route/backend and status. Inspect **Stored Logs** for previously saved JSONL and selected low-level protocol failures.
## HTTP/3 throughput and CPU diagnostics
Do not assume that low H3 throughput is a CPU bottleneck from the overall VM percentage alone. A 50% VM reading can mean one saturated vCPU on a 2-vCPU machine. Quinn uses UDP datagrams, per-packet QUIC/TLS, congestion control and OS/virtual NIC offloads; H1/H2 over TCP can behave differently under the same load. Quinn enables UDP GSO by default when the platform supports it, but some VM/network stacks fall back to per-datagram sends.
1. Run **four matched tests** against the same OpenSpeedTest/large streaming endpoint: direct backend H1, proxied HTTPS H2, proxied HTTPS H3 download, proxied H3 upload. Record client speed, per-process CPU, **per-thread** CPU, and whether the problem persists with a larger number of simultaneous clients.
2. On Linux, inspect the NIC and guest kernel counters before and after an H3 run (replace `eth0` with the actual VM interface):
```bash
ip -s link show dev eth0
ethtool -k eth0 | grep -E 'generic-segmentation-offload|generic-receive-offload|udp-segmentation|rx-udp-gro-forwarding'
nstat -az | grep -E 'Udp(InErrors|RcvbufErrors|SndbufErrors)|Ip(InDiscards|OutDiscards)'
top -H -p "$(pidof rust-proxy)"
```
3. If available, use `perf top -p "$(pidof rust-proxy)"` briefly during the benchmark, and inspect `%sys` / `%soft` in `mpstat -P ALL 1`. Check whether guest CPU has `aes` instruction support with `lscpu`.
4. Set `RUST_LOG=rust_proxy::http3=debug` in a **test** deployment and inspect `HTTP/3 bulk transfer transport snapshot` logs: `quic_lost_packets`, `quic_congestion_events`, `quic_rtt_ms`, `quic_cwnd_bytes`, `quic_path_mtu`, `quic_udp_tx_datagrams` and `quic_udp_tx_ios`. Those Quinn counters are **connection-wide cumulative snapshots**, not stream-specific deltas; compare differences for one isolated connection or across successive samples.
5. Run `ss -uapm | grep -A1 rust-proxy` during a transfer; check `skmem rb`, `tb` and whether dropped-datagram `d` increments. RustProxy defaults to 4 MiB per direction and logs the actual SO_RCVBUF/SO_SNDBUF effective values on bind. If Linux clamps them, inspect `sysctl net.core.rmem_max net.core.wmem_max`; saving new socket buffer targets in the panel requires a listener restart. See [HTTP transport](TRANSPORT.md#quic-udp-socket-buffers).
A rising loss/retransmission count or shrinking congestion window with low CPU suggests packet pacing, network path, UDP buffer/offload or VM issues. With negligible loss and one saturated worker thread, investigate QUIC encryption, UDP send/receive and application streaming costs using `perf` before changing window sizes. HTTP/3 overhead can also affect large file transfers, uploads and long-lived H3 streams; speedtest is only one way to expose it. No throughput improvement is claimed without a production-equivalent QUIC benchmark.
## Release rollout checklist
1. Verify HTTP/1.1, HTTPS, HTTP/2, HTTP/3 (if enabled), WebSockets and multi-backend failover against a real origin.
2. Test private origin constraints, ACL/authentication, rate limits and protected cache content.
3. Test Web Server static files, PHP-FPM, cache revalidation/invalidation and `sendfile=auto` if enabled.
4. Exercise certificate renewal/reload and a backup **restore** on staging.
5. Compare performance only with matched configurations and repeatable traffic; see [Performance](PERFORMANCE.md).
For persistent problems, use the feature-specific guide in [Documentation](README.md) and keep a redacted error example with the version and request ID.
## OIDC central login
- **Login does not start / 502:** verify that the HTTPS Issuer URL is the exact `issuer` from `/.well-known/openid-configuration`, including any path or trailing slash. RustProxy must resolve that address and connect to it with a trusted certificate, or you must configure an explicit internal backend origin for server-side OIDC requests. Discovery, authorization, token, UserInfo and JWKS endpoints must have the same HTTPS origin as the issuer.
- **Redirect URI mismatch:** register the exact `https://APP-HOST/_rustproxy/oidc/callback` value with the identity provider. The callback hostname must be explicitly listed in the protected Vhost hosts and be accessed over HTTPS. Don't protect the IdP's own login Vhost using this OIDC setting.
- **Invalid code, nonce or signature:** ensure that the provider issues an `id_token` using RS256 or ES256 and that a confidential client uses `client_secret_basic` with authorization code and PKCE S256. Repeated callback requests or overwritten browser transaction cookies are refused. A single browser login flow per protected host avoids cookie conflicts.
- **No identity on the backend:** trust only requests through RustProxy. A verified login injects `Remote-Sub`, `Remote-Issuer`, `Remote-User`, and optional profile headers; the application must explicitly consume them. An OIDC session cookie on one app/domain is not a login cookie for another.
- **User removed at the provider but session still works:** local application sessions are independent and expire after the configured `session_ttl_seconds` (one hour by default); visit `/_rustproxy/oidc/logout` on the application origin to clear the local session. RustProxy does not implement back-channel logout or token revocation.
+64 -458
View File
@@ -1,493 +1,99 @@
> 0.7 adds graphical locations, Basic Auth, cumulative IP ACL editing and a
> WebSocket toggle. See [host access and locations](VHOST_ACCESS.md).
# Vhosts, domains and backends
# Virtual hosts, frontend bindings and backend pools
A **Vhost** publishes one or more hostnames on named HTTP/HTTPS listeners. Its source can be an HTTP reverse-proxy backend pool or a reusable local Web Service. Use **Vhosts → Add** in the panel.
This is the source implementation contract. Rust compilation and runtime acceptance
were not performed in the packaging environment; see TEST_REPORT.md.
## Publish a site
## Backend quick-view in Vhosts
1. Enter an ID and the exact domain (`app.example.com`) or supported wildcard (`*.example.com`). Exact names win over wildcards. Configure DNS independently.
2. Choose the enabled HTTP/HTTPS frontend listeners. Select a certificate for HTTPS.
3. Choose **Reverse Proxy** and add an upstream address such as `http://10.0.0.20:3000`, or select an existing **Web Service**.
4. Check backend reachability and TLS policy; configure health checks and load balancing for multiple origins.
5. Decide whether to force HTTPS, redirect www/non-www, enable WebSockets, cache, access rules and custom paths.
6. Save, visit the hostname, inspect the Vhost's health and logs, then test failure and recovery.
Click the **Backends** count or the **Status** tile of a Vhost to open its
backend list. Each member displays the configured ID, origin host/IP and port,
HTTP/HTTPS scheme and HTTP mode, current reported health and `last_status`.
`last_status` is the most recent HTTP code from a proxied request; zero/no
member means no known HTTP response, not a failed probe. DNS origins show the
configured hostname, as the status API does not report the resolved IP.
The panel updates from the existing status feed without initiating requests,
and supports Escape and click-outside dismissal.
A Vhost may bind several frontends. Hostnames and selected listeners must match: the existence of a certificate or an upstream does not create DNS entries or a listening socket. Existing Vhosts retain their bindings when new listeners are created.
## One vhost, one backend pool
## Live request-rate graph
A `vhosts` entry owns an `id`, `enabled`, `frontend`, `source`, `backends`, `load_balancing`,
`routes`, optional `acl`, `headers` and `security_headers`. `source` defaults to Reverse Proxy
for older documents; it can instead reference an independently configured Web Service. See
[WEB_SERVER.md](WEB_SERVER.md). The pre-production top-level
routing and inline-listener formats are no longer accepted. Unknown JSON fields are
rejected. The examples are complete documents, not JSON merge patches:
Each Vhost card has a **Lifetime request rate** chart showing sampled requests per second since the current browser session began observing that Vhost. The Y-axis automatically follows recent traffic (85th percentile of up to 24 newest displayed samples, with 25% headroom). One anomalous surge no longer compresses normal traffic into a flat line. After sustained changes in request load, the range adjusts up or down automatically.
- `examples/vhosts-private.json`: explicit loopback IP, two existing HTTP origins,
adaptive balancing, HTTP health checks, keepalive, no cache or cookie affinity.
- `examples/vhosts-balanced.json`: explicitly public HTTP 80 and TLS 443, cookie
affinity and a deliberately short HSTS value; requires a trusted, imported `app`
certificate/key and real origins. It does not create those services or files.
A peak above the current scale touches the top edge of the sparkline, **not** its exact proportional height; the numerical **peak** still shows the maximum recorded in retained session history. Hover the rate/peak legend to see the plotted 0-to-maximum range and whether peaks are clipped. This is a display-only change: HTTP counters, sampling intervals, status endpoints and persisted traffic metrics are unchanged. Reloading or signing out resets the browser-side chart history. Docker Vhosts use the same scale; see [Docker discovery](DOCKER_DISCOVERY.md).
In the examples, both origins need `/health` to return a quick 2xx response. Adjust
that path/status range for the real service. New pools start quarantined until
`rise` successful checks; an unavailable or misconfigured check deliberately causes
503 instead of sending traffic to an unverified member. `mode: off` opts out.
## Block request Content-Type values (ordinary Vhosts)
The editor includes frontends, members, balancing, health, connection reuse, sticky
sessions and response protection. Custom locations have graphical match/action/rewrite/ACL/header
controls plus synchronized Advanced JSON for automation. Unsaved drafts survive translation/SSE refreshes. A stale draft must be
reloaded before saving: the panel does not silently merge competing revisions.
Go to **Vhosts → Edit → Security → Request Content-Type**. Enable **Block selected Content-Type values**, enter one MIME pattern per line, then **Save**. The policy is off by default and is available in the regular Vhost editor, not in the Docker vhost editor.
Existing vhost IDs can be renamed in the dashboard as long as the new ID is valid and
unique. The rename keeps the configured frontend hostnames, routes and backend IDs, but it
intentionally starts a new vhost runtime identity for metrics/log attribution and current
health/traffic history. Because the vhost configuration participates in cache compatibility,
a rename also rotates that compatibility identity and invalidates incompatible cached objects.
The editor warns before applying the rename.
Example patterns:
## Internal error routing
```text
multipart/form-data
application/xml
image/*
```
The main vhost `source` may define `error_fallback`, and each Custom location may do the same. A fallback references the `id` of an enabled `internal` Custom location in the same vhost. Supported statuses are `403`, `404`, `500`, `502`, `503` and `504`; omitted status lists default to `404`. Internal targets are not reachable by ordinary path matching.
- Accepts up to **64** unique patterns: exact `type/subtype` or a media family `type/*`. The policy matches case-insensitively and ignores parameters (`multipart/form-data; boundary=...` matches `multipart/form-data`). Whitespace outside a pattern is trimmed; invalid patterns and duplicates are rejected at save/validation time.
- A matching request receives **HTTP 403** before reading the body, proxying to an upstream, serving a Web Server/PHP-FPM file, choosing a Custom location or returning a cached response. When the policy is enabled, malformed, repeated or ambiguous `Content-Type` values are also denied.
- Requests with no `Content-Type` are **not blocked**. This is header filtering, not content sniffing, body inspection or a comprehensive WAF. Applications can accept unsafe input through other formats and routes.
- Blocked types cannot be used for normal requests either: blocking `multipart/form-data` disables traditional file-upload forms for the selected Vhost. Evaluate legitimate traffic before enabling and test an allowed type and a denied type.
- The policy is stored in the Vhost's `content_type_policy` object, for example `{"enabled":true,"blocked":["multipart/form-data"]}`. Existing Vhosts default to disabled. Changes are applied on the regular configuration publish cycle, without listener restart.
Configured upstream/Web Server failures are intercepted before final response/cache publication. The original request method, URI/query and context are internally dispatched to the target, which then follows normal vhost/location processing. Validation rejects fallback cycles and runtime dispatch is depth-limited. See [Custom Locations](CUSTOM_LOCATIONS.md) for the active internal-fallback behavior.
Check with `curl -i -H 'Content-Type: multipart/form-data; boundary=test' --data 'x' https://your-vhost.example/upload` (expected **403**) and a valid application request without the blocked type. Applying this filter is **not a fix for an underlying PHP/application vulnerability**: keep PHP, extensions and application code patched.
## Per-backend health monitoring (0.29.7)
## Source and backends
Each backend has an independent `health_check_enabled` switch (default `true`).
Setting it to `false` excludes that member from both active health probes and
passive health ejection, **without** disabling the member for a named location.
This is useful for an internal 404 handler configured with `pool_eligible: false`
and a location pinned through `backend_id`: the 404 handler never receives
ordinary balanced traffic and does not need health monitoring.
- **Reverse Proxy:** one Vhost has a backend pool. Each member has a configured URL, ID, enabled flag, weight, optional backup/drain state, health settings and capacity limits.
- **Web Server:** Vhost references a Web Service by ID. Its document root, directory handling, PHP options and static-file headers remain configured under **Web Server**.
- **Custom locations:** can override selected URL paths and use the default pool, a pinned backend, a different Web Service, a redirect or a local response. Unmatched paths still use the main source.
A backend may also define `health_check` with the same fields as
`load_balancing.health_check` to use its own TCP/HTTP/off probe policy, timing,
path, Host and status ranges. Omitting it inherits the vhost policy. A custom
HTTP health check without `host` inherits the vhost Host default even when the
vhost's main health check is TCP. When monitoring is disabled, the backend is
assumed available; connection errors during real requests are still counted and
logged, so monitoring off is not a guarantee of backend reachability.
Sample documents in `examples/vhosts-private.json` and `examples/vhosts-balanced.json` illustrate JSON formats, but their IP addresses, certificates and health endpoints are examples only.
## Frontend selection and private IP isolation
## Backend connection and HTTP modes
`frontend.hosts` is a normalized hostname-pattern list. Exact hostnames and a single
leading wildcard such as `*.example.com` are supported; regex and mid-label wildcards
are not. Routing tries the exact hostname first, then the longest matching wildcard.
This means `api.example.com` can have its own vhost while `*.example.com` handles other
subdomains on the same listener without stealing the exact route.
`frontend.listener_ids` references named `listeners.endpoints` configured under
Settings. A listener owns its IP:port, TLS flag and H2/H3 transport capabilities;
a vhost never stores a listening address directly. For a new dashboard vhost, the
editor preselects both an enabled HTTP listener on port 80 and an enabled TLS listener
on port 443 when they exist (public-scope listeners are preferred). Existing vhost
bindings are never replaced by that convenience default.
`force_ssl` defaults to false. When enabled, any cleartext request that matches this
vhost receives HTTP `308 Permanent Redirect` to the same normalized hostname and
original path/query. At least one selected plain HTTP listener and one selected TLS
listener are required so there is both an ingress to redirect and an HTTPS target. If
several TLS ports are selected, port 443 is preferred; otherwise the selected TLS target
port must be unique.
`www_redirect` defaults to false. When enabled, `www_redirect_target` selects an exact DNS hostname from `frontend.hosts` as canonical (default: the first hostname for backwards compatibility). RustProxy also claims its direct companion (`example.com` <-> `www.example.com`) on the same listeners and returns `308 Permanent Redirect` from that companion to the first hostname while preserving path/query. Wildcards and IP literals cannot be the canonical hostname for this feature. The companion also participates in vhost certificate assignment, but the selected certificate must still contain both DNS names in its SANs. When `force_ssl` is also enabled, an HTTP companion request redirects directly to the HTTPS canonical URL instead of creating two redirect hops.
`forward_client_ip` defaults to true. Normal vhosts therefore receive the sanitized
`Forwarded`, `X-Forwarded-For` and `X-Real-IP` client identity generated by RustProxy.
Set it to false for applications whose own trusted-proxy/bypass logic must see the
RustProxy TCP peer instead. Only the backend application request loses those three
client-IP headers; ACLs, GeoIP, rate limiting and Tinyauth still use the effective client
address, and origin metadata such as `X-Forwarded-Proto`, `X-Forwarded-Host`,
`X-Forwarded-Port` and `X-Forwarded-URI` remains available to the backend.
`websockets` enables WebSocket handling for the vhost. The dashboard defaults new WebSocket
vhosts to the nginx-compatible HTTP/1.1 Upgrade transport. `websocket_legacy_support: true` is
a separate per-vhost compatibility switch for older appliances and embedded servers. It affects
only WebSocket handshakes: RustProxy forces an HTTP/1.1 upstream WebSocket hop, uses the configured backend authority as upstream `Host`,
rewrites `Origin` to the matching upstream scheme + backend authority, and tolerates legacy `101 Switching Protocols` response
framing while keeping the `Sec-WebSocket-Accept` proof and offered subprotocol checks strict.
Ordinary HTTP requests are unchanged. `websocket_rfc8441: true` and `websocket_rfc9220: true` are
separate opt-ins for HTTP/2 and HTTP/3 Extended CONNECT and are valid only when WebSocket
support is enabled. They are presented together in the vhost editor.
RFC 8441 requires an H2-capable selected listener (TLS ALPN or cleartext h2c prior knowledge).
Because the RFC 9220 advertisement is QUIC-connection scoped, one opted-in vhost causes the
shared QUIC listener to advertise Extended CONNECT, while request routing still rejects RFC
9220 on non-opted-in vhosts.
`http2_enabled` and `http3_enabled` are per-vhost opt-outs (both default true). The
panel presents the two switches together and enables each only when at least one
selected listener provides that protocol. For TLS H2, ClientHello/SNI selects H2+HTTP/1.1 or HTTP/1.1-only ALPN for
the matched vhost. H3 remains a shared UDP listener capability; disabling it on a
vhost removes Alt-Svc and rejects H3 for that host.
Each vhost can optionally set `tls_policy` with `tls12`, `tls13` and a list of rustls
cipher-suite names. Omitting it inherits `tls.policy` from Settings. The override is
selected from ClientHello/SNI before the TCP TLS handshake, so a vhost can for example
run TLS 1.2 only while another frontend continues to use the global TLS 1.2/1.3 policy.
At least one protocol version and a compatible effective cipher suite are required.
Because HTTP/3 shares QUIC TLS on the UDP listener and requires TLS 1.3, a vhost with
HTTP/3 enabled must use the same TLS policy as the global setting. Disable HTTP/3 on
that vhost before using a differing per-vhost TLS policy.
Routing checks Host/HTTP authority, actual accepted local IP, local port and actual
TLS state. Neither `Forwarded` nor `X-Forwarded-*` can create frontend access. A
request for a private hostname on a different local IP is not routed to that host.
Unknown or unmatched hosts return 404.
Bindings must be local addresses assigned to the OS. The process does not add IPs,
manage routes, configure firewall rules, acquire virtual IPs or run Keepalived/VRRP.
A missing IP, occupied port or permission error aborts startup without wildcard
fallback. Low ports may need deployment capabilities; see `../deploy/README.md`.
Identical or same-family wildcard sockets are consolidated only when the named
listener transport capabilities agree. Vhost routing restrictions are not
consolidated: a wildcard TCP listener can accept on a public IP while still rejecting
a private vhost there. Per-vhost H2 is narrowed by SNI and therefore does not require
a second TCP socket. IPv4/IPv6 wildcard overlap is rejected; use explicit addresses.
Native IPv4 binds are required instead of mapped IPv6 literals. The administration
port is reserved even on a different IP.
IP isolation is not client authentication. A firewall, router, NAT or preceding
proxy can forward external clients to the private destination IP; RustProxy sees
that local destination and the connecting peer. Use network ACLs, firewall rules
and application authentication too. Trusted forwarding headers have a separate explicit CIDR policy (see
INTEGRATIONS_040.md); PROXY protocol is not implemented. Interface changes require config
reload/restart to refresh the upstream loop-guard's local-address enumeration.
The same literal hostname pattern may occur only where its listener bindings remain
unambiguous. Exact and wildcard patterns are distinct ownership keys, so an exact host
and its parent wildcard may coexist. Disabled vhosts reserve their patterns and are
still validated, but create no pool or additional active listener.
## Paths, policies and migration
`routes` contains per-vhost paths: `id`, match fields, `enabled`, `source`, nullable `cache`,
optional `acl_profile_id`, inline `acl`, `country_access`, `method_policy`, `headers` and `backend_id`. `source` selects Reverse Proxy or a reusable Web Service;
`backend_id` is valid only for Reverse Proxy. An empty array leaves the main vhost source as the fallback. Longest
segment-boundary prefix wins. Origins receive the original path/query; prefixes
are not stripped. IDs must use ASCII letters, digits, hyphens and underscores.
Host method policy, reusable ACL profiles, vhost ACL/country policy and route method/profile/inline
ACL/country policies are cumulative before pool selection/DNS/cache. A vhost and each route can
attach a reusable `acl_profile_id`; profiles may contain an IP ACL plus a country block/allow-only
rule. Inline controls remain independent and cannot be weakened by a profile. Method-policy denial
returns 405; allow-list mode also emits `Allow`. Their client identity is the
validated effective peer address, not an untrusted forwarding header.
There is no legacy routing UI/runtime or automatic importer in 0.8.0. Development
databases using the old top-level routes or inline frontend listeners must be reset
or migrated explicitly before startup. An omitted path backend ID selects from the
balanced pool; a specified ID must belong to the same vhost and does not fail over to a
different application's member. Imported health checks start OFF. Ambiguous names,
unreferenced old backends and invalid/oversized documents stop migration before
saving. See MIGRATION.md before updating an existing installation.
## Backend members and connection reuse
Each member has `id`, HTTP(S) origin `url`, `weight` (1..256), `enabled` (true),
`backup` (false), `drain` (false), `allow_private` (false),
`accept_local_listener_risk` (false), `preserve_host` (true), `protocol` (`http1`),
`address_allowlist`, optional `tls_server_name`, `tls_verify` (false) and optional
`tls_ca_pem`. `tls_server_name` is an HTTPS-only DNS hostname
used for SNI and, while verification is enabled, certificate hostname verification;
DNS/TCP still targets the configured `url`. `tls_verify` defaults to false. HTTPS connections remain encrypted, but backend certificate authentication is skipped until an administrator explicitly sets `tls_verify: true`; this includes compatibility with legacy certificate encodings that modern webpki validation rejects. Address/DNS policy remains enforced; local-listener self-targeting has only the explicit risk exception described below. `tls_ca_pem` is ignored while verification is disabled.
Backend IDs are unique inside a vhost. URLs cannot contain credentials, paths,
queries or fragments.
Every DNS answer and every configured literal must pass the same upstream policy
for health probes and forwarding. Active, pending and prior-snapshot frontend
addresses and local administration are guarded against self-proxying by default.
For an intentional local target such as `http://127.0.0.1:9090`, enable both
`allow_private: true` and `accept_local_listener_risk: true`. The latter is a narrow,
explicit acceptance of loop/control-plane exposure risk; metadata/link-local and other
special-address blocks are not bypassed. In the dashboard this risk checkbox is hidden
unless the entered backend URL directly targets an administration listener reported by
persisted configuration or live status. Startup-owned/locked administration sockets are
therefore handled too. On the same administration port, loopback spellings such as
`localhost`, `127.0.0.1` (and the rest of `127.0.0.0/8`) and `::1` are treated as
equivalent for showing the explicit risk control, so it is not presented as a normal
backend option.
A vhost can independently enable two proxy-owned response diagnostics:
- `backend_header: true` -> `X-RustProxy-Backend: <backend-id>`,
- `backend_timing_header: true` -> `X-RustProxy-Backend-Time: <milliseconds>ms`.
The timing value covers the origin request through receipt of the backend response
headers; it is not full response-body duration. Both proxy-owned names are protected
from custom header rules and any upstream values are removed before RustProxy optionally
writes its own value. The former `X-Rust-Proxy-Backend-Time` spelling is also stripped
as a legacy lookalike but is never emitted. Responses served without contacting an
origin do not expose a selected backend or stale origin timing.
`timing_headers` remains the separate opt-in `Server-Timing` diagnostic.
`keepalive` controls **connection pooling**, not an HA service:
| Field | Default | Accepted range |
|---|---|---|
| `enabled` | true | Boolean |
| `idle_timeout_seconds` | 30 | 1..3600 |
| `max_idle_per_host` | 4 | 1..64 |
| `tcp_keepalive_seconds` | 30 | 1..3600 |
| `pool_recycle_requests` | 25000 | 0 (off) or 1000..10000000 |
| `pool_recycle_seconds` | 3600 | 0 (off) or 60..604800 |
| `http1_max_buffer_bytes` | 131072 | 8192..1048576 |
These configure HTTP idle reuse and TCP keepalive; HTTP/2 also uses pings for active
connections, with a 10-second ping timeout and no pinging solely to keep idle
connections alive. `pool_recycle_requests` and `pool_recycle_seconds` bound the lifetime
of each backend Hyper client generation. Recycling swaps only that backend client/pool;
it does not reload the vhost, purge cache or intentionally abort active exchanges. The
request counter is per backend client generation, not per TCP connection. Setting either
recycle value to `0` disables that trigger independently. `http1_max_buffer_bytes` bounds
Hyper's adaptive upstream HTTP/1 read buffer per connection; it does not apply to HTTP/2.
Compatible backend clients are retained across unrelated runtime snapshot rebuilds, so
generation age, request count and rotation totals remain continuous. A changed backend
origin, address policy, keepalive settings or connect timeout creates a fresh client
generation state instead of reusing an incompatible pool.
Disabling reuse sets the idle pool limit to zero and disables keepalive probes. The idle
limit is not a separate per-backend active-request cap; global connection/exchange bounds
still apply. HTTP health probes use separate no-reuse HTTP/1.1 connections.
Backend upstream `protocol` has four modes:
| UI mode | Serialized value | Backend behavior |
|---|---|---|
| **HTTP/1.1** | `http1` | HTTP/1.1 only over HTTPS or HTTP; default for new backends |
| **Auto** | `auto` | HTTPS: prefer H2 and negotiate H1 fallback with ALPN before sending a request; HTTP: H1 only, without H2C probing |
| **HTTP/2 compatible** | `http2` | **Unchanged 0.32.2 behavior**: HTTPS offers H2/H1 via ALPN; plaintext HTTP uses H2C prior knowledge (no H1 downgrade on HTTP) |
| **H2 only** | `h2` | HTTPS: require H2 via ALPN; reject H1-only peers. HTTP: prior-knowledge H2C, no fallback |
**0.32.2 -> 0.32.3 compatibility:** saved `"protocol": "http2"` retains its existing
HTTPS ALPN fallback and cleartext H2C behavior. No conversion, migration or
administrator action is needed to preserve the old selection. Choose the new
`"protocol": "h2"` only when H2 must be enforced. The editor describes the
transport for the selected backend URL; a descriptive notice next to compatible
HTTPS `http2` is informational, not a migration warning.
HTTPS Auto negotiation selects the wire protocol **before sending the request**.
RustProxy does not replay an ambiguous failed POST/PATCH/DELETE through H1, and
does not interpret backend 4xx/5xx responses as a reason to switch protocols.
Existing pool reuse, keep-alive, client generation rotation and the independent
HTTP/1.1 WebSocket Upgrade / H2 Extended CONNECT paths are retained.
HTTP health probes continue to use HTTP/1.1 independently of ordinary requests;
H2C-only peers can use TCP probes. A health probe that needs a virtual-host
name must configure `health_check.host` separately; `preserve_host` affects
visitor requests, not independent health probes.
**Since 0.32.4:** all four upstream modes allow `preserve_host: true`.
With HTTP/2, `:authority` follows the selected HTTP Host instead of the backend
IP; with HTTP/1.1 it remains the regular Host. TCP/DNS always target the
configured backend URL and still enforce the address and local-listener
policies; `tls_server_name` independently controls HTTPS SNI and certificate
identity, not the visitor Host. HTTPS backend certificate verification defaults
to OFF when `tls_verify` is omitted. Turn on verification and configure
`tls_server_name` / `tls_ca_pem` for a private DNS certificate; a valid IP-SAN
certificate can instead authenticate an IP backend without a DNS name.
The form's HTTP/1.1 troubleshooting preset is optional; it is not required
for Auto/H2, private origins or Host preservation.
## Scheduling
| `algorithm` | Decision |
| Setting | Use |
|---|---|
| `round_robin` | Smooth weighted round-robin |
| `least_connections` (default) | Lowest `(active exchanges + 1) / effective weight` |
| `least_traffic` | Lowest recent EWMA payload rate plus active-exchange allowance, divided by effective weight |
| `adaptive` | Combines active exchanges, EWMA time-to-headers and payload rate, divided by effective weight |
| `ip_hash` | Weighted rendezvous hash of vhost/member and canonical actual peer IP |
| `http1` | Standard HTTP/1.1 upstream. |
| `auto` | HTTPS negotiates HTTP/2 or HTTP/1.1; plaintext uses HTTP/1.1. |
| `http2` (legacy) | Compatible HTTPS ALPN or plaintext H2C, kept for existing configurations. |
| `h2` | Strict HTTP/2/H2C; requires origin support. |
An active exchange spans upload, response body and upgraded tunnel, not just the
wait for response headers. Slow-start gradually increases effective weight after
initial success/recovery (`slow_start_seconds`: 10, allowed 0..300; 0 disables it).
IP hashing uses configured weights instead of slow-start to keep assignments stable.
These are explicit heuristics, not workload predictions or benchmark-backed claims.
No remote host CPU measurement is used by the scheduler.
A backend's `preserve_host` option can retain the frontend authority when appropriate. HTTPS encryption alone does not guarantee verified backend identity: `tls_verify` is **false by default**. Enable verification and configure trusted CA/name for sensitive connections. Private, loopback and RustProxy-local listener targets require explicit security exceptions; do not point a public route to the admin listener. Read [Security](SECURITY.md).
Only enabled, healthy, non-draining primary members receive new assignments. If
none exists, eligible backups are used. All unavailable means 503 with Retry-After;
there is no fallback into another vhost. Balanced routes may retry a body-free `GET`,
`HEAD` or `OPTIONS` on another healthy member according to `load_balancing.retry`:
Connection reuse is bounded by configured idle-pool/recycle limits. The default H1 upstream idle pool applies to newly configured Vhosts; previously saved explicit values remain. Configure timeouts to suit expected request and upload behavior; a slow origin may need more than the default response-header timeout.
| Retry field | Default | Accepted range / meaning |
|---|---|---|
| `max_retries` | 1 | 0..3; zero disables failover retry |
| `retry_connect_errors` | true | Retry connection establishment failures |
| `retry_timeouts` | true | Retry the response-header timeout |
| `retry_statuses` | `[502,503,504]` | Up to 8 HTTP 5xx statuses |
## Load balancing and health
A backend already attempted in the same exchange is excluded from later attempts.
Path-pinned routes and WebSocket handshakes are not retried. POST/PUT/PATCH and any
request with a body are never automatically replayed. Failed attempts still feed the
normal passive-health policy and retry counters/logs; successful failover updates sticky
affinity to the backend that actually answered.
Backend pools support scheduling/weighting, standby members, draining and optional session affinity. Check the **Backends** status view for member health, recently observed HTTP response code, upstream protocol and resource usage. A reported status from normal traffic is not necessarily the result of a health probe.
## Health checks and recovery
- Health monitoring may use TCP or HTTP checks with a path, Host, interval and thresholds. Avoid write endpoints; use a lightweight `/health` or equivalent.
- **Health check port** (`health_check.port`) probes a different port than the one receiving traffic, like `check port` in HAProxy. Example: a backend serving traffic on `http://10.0.0.5:80` with a status endpoint on port `8079` uses `"health_check": {"mode": "http", "path": "/status", "port": 8079}`. The probe keeps the backend host, scheme, TLS settings and address restrictions; only the port changes. Leave it empty to probe the traffic port. The port can be set for the whole pool or per backend.
- **Expected response text** (`health_check.expect_body`) makes an HTTP probe fail unless the first 64 KiB of the response body contain the given text, like `http-check expect string` in HAProxy. Use it for status pages that answer `200` even when the application is degraded, for example `"expect_body": "\"status\":\"ok\""`. Failed matches appear as `http_body` in backend health details.
- Each member can override the pool health check or disable active/passive monitoring with `health_check_enabled=false`. Disabling checks treats the member as available, not proven healthy.
- New monitored pools can temporarily have no eligible healthy member until rise checks succeed. A failed/incorrect check can produce `503` even if the process is reachable.
- `backup` members act as fallbacks; `drain` prevents new normal selection while letting existing work complete within normal lifetimes. Explicitly pinned backends need careful policy review.
- Concurrency and queue limits protect overloaded origins. Retries are restricted; do not assume non-idempotent requests are automatically replayed.
| Field | Default | Accepted range / meaning |
|---|---|---|
| `mode` | `tcp` | `off`, `tcp`, `http` |
| `path` | `/` | Leading slash, up to 1024 bytes; no authority/fragment |
| `host` | null | Optional validated HTTP Host override; otherwise the first exact vhost DNS hostname is used |
| `interval_ms` | 5000 | 250..300000 |
| `timeout_ms` | 2000 | 50..60000; no greater than interval |
| `slow_response_ms` | 1500 | 0 disables; otherwise no greater than timeout |
| `fall` / `rise` | 3 / 2 | 1..20 consecutive failed/successful checks |
| `expected_status_min` / `max` | 200 / 399 | Inclusive, ordered HTTP status range within 100..599 |
The **Redirects** and **404 hosts** editors distinguish host identity/domains from frontend listeners and certificate selection. The configuration options have not changed.
TCP checks verify that an approved origin address accepts a connection. HTTP checks
perform GET on the configured path, without redirects, cookies or authorization.
They use HTTP/1.1, including verified TLS when appropriate, and measure time to
headers, not the full body. When `health_check.host` is empty, RustProxy automatically uses the first exact DNS hostname configured on the vhost so an IP-address backend can still route the probe through its name-based virtual host. If no exact DNS hostname exists, the existing backend-host fallback remains. Set `health_check.host` only when the probe must use a different Host value. For an h2c-only service, use TCP checks. Slow probes
count as active failures. Active TCP/HTTP health is independent from `passive_health`.
## Hostnames, redirects and errors
`passive_health.enabled` defaults to `false`. With that default, normal user traffic
never changes the shared backend healthy/unhealthy state: HTTP 5xx, connection errors,
timeouts, response-body I/O failures and slow responses are still counted/logged but
cannot quarantine the member. When explicitly enabled, the administrator can choose
`count_connect_errors`, `count_timeouts`, `count_io_errors`, `count_http_5xx` and
`count_slow_responses`, with optional passive `fall`/`rise` overrides. Missing overrides
inherit the active health thresholds. Active and passive streaks are separate, so a
successful user request cannot erase an active-probe failure streak and an active probe
cannot erase a passive failure streak. `mode: off` disables only active probes; an
explicitly enabled passive policy may still quarantine a backend.
- `force_ssl` redirects matching plaintext traffic to HTTPS using `308`, preserving path/query; configure both HTTP and HTTPS frontends.
- WWW controls can direct `www.example.com` to `example.com` or the opposite as supported by the editor; ensure DNS/certificate coverage for both.
- Use the dedicated **Redirects** workspace for redirect-only domains. Use **404 hosts** to claim a hostname and return a local not-found page without forwarding.
- **Custom locations** may return status/body or redirect for selected paths. **Internal locations** can be used as fallbacks for configured `403/404/500/502/503/504` application responses.
- The **Pages** section can replace supported origin error bodies without contacting a second backend. This keeps the response status and disables caching of the replaced error body.
One cancellation-aware supervisor schedules at most 16 probes, one per member,
with a rotating window for fairness. The tick is 250 ms; configured intervals are
best-effort under resource pressure, not precise service-level guarantees. DNS
workers have their own bounded slots. Probe bodies are not collected or cached.
## Safe operating practices
## Sticky sessions and drain
Keep Vhost IDs stable for monitoring/history unless renaming is intentional. Renaming may reset the runtime identity and invalidate incompatible cache objects. Confirm the deployed scheme and Host, HTTPS trust, method/access restrictions, health status and logs after saving. For per-path behavior see [Custom locations](CUSTOM_LOCATIONS.md); for policies see [Access](ACCESS.md); for caching see [Cache](CACHE.md).
`sticky.mode` is `off`, `cookie` or `ip_hash`. IP mode overrides the normal algorithm
using the actual peer IP. Users behind one NAT/proxy share that identity.
## Editor layout
Cookie mode uses a random 32-character opaque member token, not a backend URL.
The default name is `RP_AFFINITY_<vhost-id>`; custom names accept 1..96 ASCII letters,
digits, underscores or hyphens and cannot use the admin session name. TTL defaults
to 3600 seconds (60..604800), with Path=/, HttpOnly, SameSite=Lax and Secure on TLS.
`__Host-`/`__Secure-` names require every frontend to use TLS. Cookies are host-only,
so aliases have independent browser cookies. They do not authenticate users.
Within the Vhost editor, **SSL certificates** contains the named certificate selection and the explicit certificate/domain mismatch exception. The next **SSL settings** section contains the optional override of the global TLS 1.2/1.3 and cipher policy. Turning off the TLS override retains the inherited global policy. These two sections are on the same **SSL** tab.
Cookie-affine requests always bypass shared cache. The dedicated routing cookie
is stripped before forwarding; origins cannot override it via Set-Cookie. Unknown,
duplicate or unhealthy-target tokens are ignored/reassigned. Existing valid cookie
sessions may continue on a healthy draining/backup member; new sessions do not
choose a draining member. Disabling a member prevents all new assignments,
including existing cookie sessions, while already admitted exchanges finish.
The **Security** tab groups Traffic policy by reusable ACL profiles, HTTP methods, per-IP limits, country access, country-specific limits and User-Agent limits. It also separates HSTS, other security response headers and per-Vhost User-Agent blocking. Within **Custom locations → Access**, profile, HTTP method, country and inline IP/CIDR rules are likewise separated. **Details** separates base Vhost properties from WebSocket options; **Backends** separates balancing strategy, retry behavior and keepalive/connection reuse. The visual grouping does not change saved configuration keys or access precedence.
Affinity tokens, health and traffic observations survive compatible hot edits.
A changed origin/health policy or re-enabled member is checked afresh. Process
restart resets tokens/counters; clients can be reassigned. Session data itself is
never replicated: applications needing durable sessions require a shared store.
## Several ACL profiles and intentional TLS mismatch
## Response protection
Assign up to eight ACL profiles using the Vhost security tab. Explicit deny/blocked country wins, matching explicit allows combine across those profiles, and unmatched defaults must all allow. The legacy single `acl_profile_id` field still works. Inline, location and global policies are independently enforced. See [Access](ACCESS.md) for precedence and safe examples.
`security_headers` supports `hsts` (`enabled`, `max_age_seconds`,
`include_subdomains`, `preload`), `nosniff`, `frame_options` (`preserve`, `deny`,
`sameorigin`), `referrer_policy`, `content_security_policy` and `permissions_policy`.
Header values are validated against splitting and bounded to 8192 bytes.
HSTS is configured only for TLS responses. For a matched vhost, plaintext responses
remove Strict-Transport-Security even if an origin/custom rule adds it. On TLS,
disabled HSTS means no forced replacement of an origin value; use a custom remove
rule to suppress that value. Enabled HSTS overrides generic rules. `max_age_seconds`
accepts 0..63072000 (default31536000); setting zero is available for explicit expiry.
Preload is opt-in and requires enabled HSTS, includeSubDomains and at least one year.
This only emits the directive; it does not register a hostname in a preload list.
Start with a short lifetime and verify every affected hostname before increasing it.
Request rules run global -> vhost -> path, then protected identity fields are
regenerated. Response rules run global -> vhost -> path, followed by dedicated
security/Server policy and optional compression. Security headers cover cache hits
and matched-host synthetic errors/ACL denials too. Protected transport fields
remain uneditable. `false`, `preserve` or null security settings otherwise leave
origin values unchanged. CSP and Permissions-Policy must suit the application;
RustProxy validates header encoding, not the semantic correctness of those policies.
The **Compression** tab inherits the enclosing/global policy by default. It can enable/disable dynamic compression and independently inherit/enable/disable zstd, Brotli, gzip and deflate. Selecting **Disabled** hides and disables the codec selectors while preserving their saved values for a later Inherit/Enabled selection. Global **Settings -> Response** codec switches are the hard availability ceiling, so a vhost cannot activate a codec disabled globally; `min_bytes` also remains global. Client `q` values are honored; equal-quality codecs use `zstd -> br -> gzip -> deflate`, and an existing `Content-Encoding` is never recompressed.
## Live state, cache and limits
The dashboard defaults a newly created vhost to `cache.enabled: false`. When the
effective cache state is disabled, the editor hides the remaining per-vhost cache
controls; enabling cache (or explicitly choosing inheritance from an enabled global
cache) reveals them. This is a UI default for new drafts, not a migration that rewrites
existing vhost cache settings.
`GET /api/backends` requires an admin session and returns `pools`,
`active_frontends`, `desired_frontends`. `/api/status` and SSE contain `backend_pools`
and complete active/desired endpoint plans. The dashboard renders per-member state,
active exchanges, request/error totals, EWMA header latency and payload traffic.
Prometheus adds bounded vhost/member labels; access logs include `backend` as
`vhost/member` for actual pool forwarding (null when no upstream exchange is made).
Bytes are observed payload, not NIC throughput; response counters include the
client-delivered encoded representation. Header latency includes connection/setup and any request upload; it is not full
exchange time. Active probes use body-free requests for an independent measurement.
Health mode off is labeled as checks off, not proof of a probe passing. Statistics
are observations, not persisted configuration. They reset on process restart.
Cookie affinity bypasses cache entirely. A pool with no eligible member does not invent
an unselected cached fallback. Config changes invalidate incompatible cache state.
RFC 5861/request `max-stale` behavior is owned by the shared cache and does not alter
backend member selection; request coalescing remains separate from stale-while-revalidate
deduplication.
Limits: 64 vhosts; hostname lists are not capped by an application-level item count;
listener entries remain capped at 16 per host, with 32 members and 64 paths per host;
128 backend definitions and 256 paths across all vhosts;
128 additional declared frontend entries plus two globals; 256 KiB serialized
configuration. Existing request/connection/DNS/queue bounds remain in force.
Every successful config publication rebuilds upstream connection pools. Compatible
member observations/tokens and in-flight exchanges survive, but idle sockets are
not retained across publication. Listener/address/ALPN changes are saved immediately
and require restart; routing changes apply to existing bound sockets immediately.
When moving a host away from an old IP, it can become unavailable until restart
rather than being served on an address no longer permitted by desired policy.
## Per-vhost operations in 0.3.0
The compact list opens a tabbed editor rather than displaying a full form beside
the list. Health summaries update on the list and editor without replacing unsaved
form controls. Header rules have a visual request/response editor; Advanced JSON
remains available and stays synchronized with those rows.
The SSL tab selects an existing certificate ID or uploads a chain/private-key
pair into private staging. Only Save activates the selection. A shared global SNI
resolver cannot serve different certificates for the same DNS name by destination
IP, so conflicting selections are rejected. Browser upload is local-admin only;
there is no ACME, encrypted-key password prompt, certificate download or automated
cleanup. Details and limits: MANAGEMENT.md.
Cache overrides inherit field by field and use one shared memory budget. The list
and Cache tab offer selective purge. An explicitly disabled global cache can still be
overridden by an explicitly enabled vhost. Path exclusions and sticky-cookie safeguards
remain in force. See CACHE.md.
The **Transform** tab is a separate vhost opt-in. `transform.enabled` defaults to false
for every vhost even when the global transform engine is enabled. JavaScript, CSS, HTML,
SVG and JSON can inherit or override global format switches, and the extension list may
be replaced per vhost. Eligible origin responses are transformed before shared-cache
storage and before downstream compression. Origin `Cache-Control: no-transform` always
wins and cannot be overridden from the vhost.
Pages offers a static/maintenance response, a fallback for unassigned paths, and
error overrides. The static/maintenance response can set an explicit HTTP status independently of its HTML body, so an operator can serve a maintenance document as `503 Service Unavailable` instead of returning `200`. Vhosts still retain their configured backend pool; static mode
avoids forwarding but is not a backend-less static-file server. Host/path ACLs run
first. Explicit error-status overrides change presentation, not authorization.
Empty/inherit means use the global error policy; to disable one inherited error
page specifically, configure an explicit replacement rather than expecting null
to override inheritance. See MANAGEMENT.md for exact precedence.
### Backend Host compatibility
New backends use the backend URL host for the upstream `Host` header by default, matching nginx `proxy_pass` behavior. Enable **Preserve client Host** only when the application intentionally routes on the public hostname. This is especially important for embedded/lighttpd-style administration panels that may close or reject requests carrying an unknown Host. Existing explicit settings are preserved during upgrades.
When an upstream request fails before an HTTP response is received, access logs include a bounded `upstream_error` field in addition to `upstream_status`.
Multiple explicit hostnames can belong to a single vhost: `suba.local` and `subb.local` share the same routing, backends and configuration. Only the www/non-www companion of the selected canonical hostname redirects; other aliases continue serving traffic directly.
Normally RustProxy rejects a named certificate assigned to domains outside its DNS SubjectAltName coverage. To intentionally use such a certificate, select a **named** certificate and enable **Force an incompatible certificate** for that Vhost, then accept the confirmation. This records `allow_certificate_mismatch: true` on this Vhost only. Certificate/key matching remains mandatory, and client browsers will still warn/reject the certificate. Prefer a matching certificate; an inherited default certificate does not support this force option.
-136
View File
@@ -1,136 +0,0 @@
# Vhost access and visual locations
## 0.8.0 edge traffic controls
The Security/Traffic policy section now includes per-client-IP request limits, burst,
window length, country-specific limit overrides, country blocking or allow-only mode,
per-vhost HTTP/3 enable/disable, optional `Server-Timing`, and a Cloudflare real-client-IP
switch. Country rules use the install-time GeoIP country database. Allow-only mode
fails closed when a country cannot be resolved; block mode only blocks resolved matches.
Exceeded limits return 429 plus `Retry-After`; country policy denial returns 403. Both
use built-in HTML pages unless a configured custom error page replaces them.
Cloudflare mode trusts `CF-Connecting-IP` only when the **direct peer** is inside the
compiled Cloudflare edge CIDRs. An arbitrary client sending that header directly does
not become trusted. The verified address becomes the effective client IP for ACLs,
rate limiting and forwarded metadata. On the same verified Cloudflare hop, a single
two-letter `CF-IPCountry` value is accepted as the country source for country policy,
logging and Traffic analytics. Missing, duplicate, invalid and Cloudflare `XX` values
fall back to the local GeoIP lookup of that verified effective client IP.
Vhost listener editing is now selection-only. Listener addresses/IPs and transport
settings live under Settings > Listeners; a vhost selects one or more named endpoints.
Existing inline/legacy bindings are promoted to named endpoints when the vhost is saved.
New backend rows use the HTTPS-by-IP debugging defaults: include in the default LB pool,
allow private origin, HTTP/1.1, preserve client Host and backend TLS verification OFF.
The panel shows the risk and asks before saving an HTTPS-by-IP backend that does not
match the complete preset. Existing backend values are not rewritten automatically.
Health mode `off` disables active probes only. Passive Backend Health is a separate
opt-in policy and defaults to OFF. With the default policy, normal request failures are
still counted but never mark a backend healthy/unhealthy; an administrator may explicitly
enable passive health even while active probe mode remains `off`.
## WebSockets
`vhosts[].websockets` controls HTTP/1.1 Upgrade forwarding for that host. Existing
configuration omitting it retains the previous enabled behavior. New hosts created
in the panel default to disabled. Disabled valid upgrades return 403 without opening
an upstream connection; normal HTTP continues. Basic Auth/Tinyauth and ACLs still
apply to an allowed handshake. This does not implement WebSockets over HTTP/3,
WebTransport or generalized CONNECT, and does not terminate already open tunnels
when toggled; it governs subsequent handshakes. After a successful `101`, normal TCP
EOF/reset/abort/broken-pipe closure is recorded as tunnel closure rather than an invalid
proxy request; handshake/protocol errors remain errors.
## Basic Auth and ACL
The Authentication tab selects None, Tinyauth or HTTP Basic. Basic and Tinyauth
cannot both be enabled. The Basic editor manages users, realm, TLS requirement
and whether verified `Authorization: Basic ...` is passed to the backend.
Only bounded Argon2id password hashes are persisted. New/changed passwords are
sent to `POST /api/basic-auth/hash` through the protected administrator API; no
password is stored in browser storage, desired configuration, audit or access logs.
An empty password while editing keeps the saved hash. New passwords use the existing
12-128 byte administrator password policy; Basic credentials cannot contain control
characters. Usernames are case-sensitive, up to 128 UTF-8 bytes, without colons or
control characters. This is not an htpasswd/APR1/bcrypt importer.
TLS is required by default and is checked from the accepted connection, not
`X-Forwarded-Proto`. Plaintext HTTP is allowed only by an explicit host option; avoid
that outside a deliberately protected TLS-termination network. HTTP Basic encodes,
not encrypts, the username/password. Keep the frontend and any credential-carrying
upstream hop protected. Administrator access defaults to loopback; direct remote access is supported when Administration HTTPS is enabled and the bind/hostname policy is configured.
Successful credentials are removed before forwarding by default. Selecting forward
passes the original **verified** Basic Authorization header, not a password from
configuration or an unverified client value. The final enforcement step runs after
custom request-header rules, which cannot reintroduce removed credentials.
`Remote-User`/`Remote-Email` are stripped in Basic mode; the separate Tinyauth mode
continues to use verified identity headers from that service.
Global host policies, reusable ACL profiles, vhost ACLs and location ACLs remain
cumulative: access requires all applicable policy scopes **and** successful authentication.
An ACL profile can be defined once under **ACL profiles**, combine default allow/deny
IPv4/IPv6 CIDR rules with country block/allow-only policy, then be attached to several
vhosts. Per-vhost rules remain available and are evaluated in addition to the profile.
Within one policy scope, explicit IP deny rules and blocked countries take precedence.
When country mode is **Allow only**, a listed country or an explicit IP allow rule may
satisfy that scope; therefore `default=deny` can express e.g. private CIDRs **or** selected
countries. An unresolved country still fails closed unless the client matches an explicit
IP allow. This does not let a permitted IP skip another profile/vhost/location policy or
a password. The graphical inline ACL editor still supports default allow/deny and
IPv4/IPv6 CIDR rules.
Trusted-proxy settings control which peer can contribute the effective client IP;
untrusted forwarding headers do not grant network access.
Authentication runs before static pages, cache, origin connections and upgrades.
401 responses include a Basic challenge and `private, no-store`; custom pages or
header rules cannot convert these failures into successful access. Protected vhosts
bypass shared response caching. A process-wide limit allows two Argon2 workers;
failed-password accounting is bounded to 4096 IPs, with ten failures per minute
blocking that address for the rest of the window. Busy hashing returns 503; a
blocked address returns 429 with Retry-After. This is abuse resistance, not MFA or
a replacement for perimeter rate limiting, especially for many clients behind NAT.
## HTTP method policy
Each vhost can leave method filtering **Off**, define an **Allow only** list, or define a **Block** list. Methods are validated as HTTP method tokens and normalized to uppercase. A denied request returns `405 Method Not Allowed` before authentication, cache, DNS or origin connection; allow-list mode also returns an `Allow` header containing the configured methods.
A Custom location has the same independent method policy. Vhost and location policies are cumulative: a location cannot re-enable a method rejected by its vhost. This makes patterns such as `GET, HEAD, POST` allow-only at host level and a stricter `GET, HEAD` allow-only policy on a read-only location explicit and fail-closed.
## Custom locations
The **Custom locations** tab supports ordinary prefix, priority prefix (`^~`), exact (`=`),
case-sensitive regex (`~`) and case-insensitive regex (`~*`) matching. Exact matches win first;
a selected priority prefix skips regex evaluation; regexes are tested in declaration order; the
longest ordinary prefix is the fallback. Exact paths preserve a meaningful trailing slash.
A proxy location can use the normal pool, pin one backend, inherit/override cache behavior and
apply an additional inline ACL. It can also select a reusable ACL profile, apply its own country
allow/block policy and define an HTTP method allow/block policy. These controls are cumulative with
the vhost policies and are enforced before a backend connection is opened. Path handling can preserve the public URL, strip a prefix, replace a
prefix or perform a capture-based regex rewrite. Query parameters are preserved. Repeated literal
slashes are normalized only for routing/ACL decisions; without an explicit rewrite the backend
receives the original target, including `//`. Encoded separators, percent double-decoding and dot
segments remain rejected.
A location can alternatively **Return custom response** with a 200–599 status, Content-Type and
body without opening a backend connection. Route/vhost ACL and authentication still apply. A 3xx
return plus a visual `Location` response-header rule is the supported simple redirect pattern.
Request/response headers have a graphical `set`/`append`/`remove` builder, inline documentation
and a synchronized **Advanced JSON** view. Protected framing, forwarding-identity, request-ID and
WebSocket handshake headers cannot be overridden. The full routes JSON remains available as an
automation/escape hatch, but invalid/unknown fields are rejected rather than silently ignored.
RustProxy intentionally implements an explicit safe subset, not arbitrary Nginx configuration:
location-level `try_files`, filesystem `root`/`alias`, embedded scripting, arbitrary `if` and
module directives are not accepted. See [Custom locations](CUSTOM_LOCATIONS.md) for examples and
the exact supported subset.
Reference: HTTP Basic security considerations, RFC 7617:
https://www.rfc-editor.org/rfc/rfc7617.html#section-4
+55 -412
View File
@@ -1,441 +1,84 @@
# Web Server - RustProxy 0.25
# Web Server — static sites, downloads and routing
RustProxy 0.25 provides a Web Server source inside the existing vhost/location pipeline. A Web Service is a reusable configuration resource; vhosts and locations select it by ID and do not duplicate filesystem/PHP settings.
The built-in Web Server serves local files and, optionally, passes PHP scripts to an **external PHP-FPM service**. A reusable **Web Service** is referenced by a Vhost or by a Custom location. Its files and PHP settings are not duplicated in the Vhost.
## Architecture
## Create a Web Service
```text
listener -> vhost -> location -> shared ACL/auth/policy
-> source
|- Reverse Proxy
`- Web Server -> Web Service
|- static file
`- FastCGI -> PHP-FPM
-> shared response/error/header/compression pipeline
```
1. Open **Web Server → New Web Service**. Choose an ID/name and an absolute document root readable by the RustProxy service account.
2. Set index files (default `index.html`, `index.htm`), whether directory listing is allowed (off by default), MIME handling and download extensions.
3. Add `try_files`, rewrites, response headers and cache rules only if your application requires them.
4. Enable PHP only if you have a reachable, protected PHP-FPM endpoint; otherwise static files do not need any PHP runtime.
5. Save, create or edit a **Vhost**, select **Web Server** as source and choose the service ID.
6. Test index files, static assets, missing files, direct directory access, downloads and headers.
A Web Service can be reused by several vhosts and one vhost can mix Web Server and Reverse Proxy locations. Web Services live in the normal revisioned RustProxy configuration (`web_services`); there is no separate Web Server configuration database.
A Web Service can be shared across several Vhosts. A Vhost may also use ordinary Reverse Proxy as its default and a Web Service for `/assets` or another custom location. Web Server and HTTP reverse proxy use the normal frontend TLS, access, logging and eligible cache pipeline.
### Source-module layout
**Linux DEB/RPM paths:** With the updated package unit, `/home/user/public_html`,
`/var/www/site` and `/srv/site` are readable using an appropriately scoped
service-user group or POSIX ACL; no `systemctl edit` or
`ReadOnlyPaths=` exception is needed. The filesystem remains read-only to the
service outside its private state/log/config paths. Grant execute/traversal
on each parent directory and read permission on served files; see
[Installation: external paths](INSTALL.md#linux-debrpm-read-only-access-to-external-paths).
The Web Server implementation is split under `src/web_server/` by responsibility. `mod.rs` is only the public facade; `config.rs` owns persisted/compiled service configuration, `filesystem.rs` owns document-root confinement and directory reads, `security.rs` owns Web Service hardening, `response.rs` owns static HTTP response/validator/range/listing behavior, `routing.rs` owns rewrites/`try_files`/precompressed selection, `fastcgi.rs` owns PHP-FPM/FastCGI, `serve.rs` owns request dispatch, and `htaccess.rs` owns the compatibility layer. Regression tests live in `tests.rs`. This keeps filesystem/security and FastCGI changes reviewable without growing another single Web Server source file.
### Generated filesystem permission commands
## Live Web Service diagnostics (0.33.0)
As you enter an absolute **Document root**, the editor generates permission commands immediately below the path. They refresh when opening or clearing a service, applying a preset or changing the root, so the displayed commands refer to the current field. The path must not contain `.`/`..` components or control characters and must resolve to an existing directory when the commands run; `/` is not accepted. Install the Linux `acl` tools if needed, review the commands and run them as an administrator on the web server. Saving a Web Service does not execute shell commands or grant filesystem permissions.
Each configured Web Service card contains an **always-visible Live metrics**
section (since 0.33.4), styled like the Vhost live metrics: six responsive
metric tiles and a full-width per-service response-rate history chart. It shows
completed requests, responses/s, HTTP 5xx count, mean elapsed time until
response headers, PHP request permits in use/limit and delivered response body
bytes. Static/PHP/other counts appear below the chart. The section needs no
expansion and reuses the existing authenticated live status stream.
Unconfigured/non-PHP services do not claim PHP worker capacity.
The helper grants the `rustproxy` service account traversal (`--x`) of parent directories, read/traversal (`r-x`) on the website's directories and read (`r--`) on regular files. Symlinked document roots are resolved when the commands run, so both the configured path and the target's ancestors can be traversed. Symlinks within the served tree are not followed by the permission walk. Read-only ACLs do not grant writes or relax the Web Service's own symlink policy. Keep private content outside the document root.
**Overview** provides 12 additional opt-in Web Server blocks under
**Settings -> Appearance -> Dashboard layout -> Overview cards**. All 12 are
hidden by default, including when opening an older saved dashboard layout.
They follow normal widget reordering, theme, card sizing and SSE updates.
Sample history length is selectable as 60/120/300/600 points (temporary or
browser-persistent), independently of Cache and Transform.
Default read/traverse ACLs are added to the existing website directories for newly created content. Default ACL inheritance takes precedence over the creator's `umask`; missing defaults are seeded from the directory's existing permissions. Explicit restrictive creation modes can still mask RustProxy's read access. Files moved into the tree or installed with preserved permissions may need the commands reapplied. Repeat the commands after a deployment changes the document-root symlink target. For a custom service account, change **Service user** under **Options and help** before copying; this changes only the instructions, not the process identity or saved configuration.
The authenticated `/api/status` and compact `/api/status?view=live` payloads
include `web_server_metrics`, keyed only by *configured Web Service ID*:
For PHP applications, the separate **PHP-FPM pool user** needs its own access to scripts and any writable application directories. The generated RustProxy ACLs do not configure that user. Files must also be visible within the service's container/systemd namespace and permitted by SELinux/AppArmor if used; filesystem ACLs cannot override those policies.
```json
{
"web_server_metrics": {
"site": {
"web": {
"requests": 120, "status_2xx": 100, "status_3xx": 5,
"status_4xx": 12, "status_5xx": 3,
"static_requests": 90, "php_requests": 25, "other_requests": 5,
"response_headers_us": 1500000, "response_bytes": 98000,
"active_headers": 0,
"methods": [110, 8, 0, 0, 2],
"selected_status": [95, 1, 3, 4, 8, 0, 2, 1],
"failures": [0, 0, 0, 0, 0, 0, 0, 0, 0],
"compression": [12, 1, 0, 0]
},
"php": {
"metrics": {
"requests": 25, "response_headers_us": 750000,
"response_bytes": 24000,
"failures": [0, 0, 0, 0, 0, 0, 0, 0, 0]
},
"capacity_limit": 32, "capacity_available": 24,
"capacity_used": 8
}
}
}
}
```
## File serving options
The example is illustrative; the actual `php` object is `null` when PHP is
not configured. Array orders: methods `GET, POST, PUT, DELETE, other`;
selected statuses `200, 206, 304, 403, 404, 500, 502, 503`;
FastCGI failures `capacity, connect_timeout, connect_error, request_timeout,
request_io, response_timeout, response_io, invalid_response, body_io`;
compression `gzip, br, zstd, deflate`.
| Feature | Behavior / caution |
|---|---|
| **Document root** | Files are served only through the configured root; keep private keys, source secrets and writable control files outside it. |
| **Indexes / listing** | Configurable index priority; directory listing is disabled by default. |
| **`try_files`** | Try requested paths, then a fallback such as `/index.html` or the PHP front controller. Useful for SPAs and frameworks. |
| **Rewrites** | Bounded regex internal rewrites or supported redirect statuses; evaluated in the Web Service routing pipeline. |
| **MIME / downloads** | Built-in type mapping with extensions and explicit attachment options. |
| **Headers / cache** | Service-level headers and optional static cache-control rules; later Vhost/location response rules may override. |
| **Compression** | Dynamic gzip/Brotli/Zstandard/deflate policy and eligible precompressed `.br`/`.gz` sidecars. |
| **File validators / ranges** | Conditional `ETag`/date checks, HEAD and supported byte ranges for eligible files. |
| **Symlinks** | `deny` by default; `inside_root` is an explicit less restrictive policy. |
| **Hardening** | Optional checks for common sensitive/attack paths and removal of selected server-identifying headers; not a full WAF. |
**Accounting semantics:** The response-kind counters describe the handler
that produced the response; `other` covers early rejections not dispatched
to a static file or PHP. `active_headers` means requests still awaiting their
response headers, *not* live streaming body counts. Mean duration is until
response headers are ready. Body-byte counters advance only when body frames
are polled, **before final dynamic compression**; these are not network wire
bytes. The compression counter records only the downstream encoder, not static
precompressed `.gz`/`.br` sidecar selection. Failed PHP requests may count as
both PHP responses and FastCGI failures. PHP permit capacity is RustProxy's
configured request semaphore, *not* the number of processes in the external
PHP-FPM pool. All counters are process/snapshot lifetime observations and may
restart on a config publication.
### Static delivery (`sendfile`)
In **Web Server → Edit service → Delivery**, `sendfile` supports `off` (default) and `auto`. On **Linux**, eligible full static **GET** responses of at least **64 KiB** over **unencrypted HTTP/1.1** may use a kernel file-transfer path. TLS, HTTP/2, HTTP/3, ranged or transformed content, compressed responses and active object-cache responses keep the normal streaming path. This transport choice should not invalidate otherwise compatible cached objects. Measure on your own hardware before enabling it; see [Performance](PERFORMANCE.md).
## Dashboard configuration
## Rewrites and application presets
The Web Service editor is split into stable configuration tabs so later features do not create one long form:
- **General** - optional one-shot application preset, ID, name, document root, ordered index files, directory listing and quick hardening switches.
- **Routing** - `try_files` and Web Service rewrite rules.
- **.htaccess** - bounded migration-oriented Apache compatibility, recursive scan and strict-mode behavior.
- **MIME** - per-extension MIME overrides and fallback MIME type.
- **Cache & headers** - default `Cache-Control`, ordered cache rules and fixed response headers.
- **Delivery** - precompressed `.br`/`.gz` sidecars, forced-download extensions and Range/streaming information.
- **PHP-FPM** - attach a shared PHP-FPM profile through the selector, open the profile-management modal to expand/edit/create profiles, or choose local configuration for all FastCGI tuning options.
- **Security** - symlink policy, request/response hardening summary and filesystem-confinement notes.
After the Web Service is saved, attach it under **Vhosts** by selecting `Web Server` and the service ID for the main source or a custom location.
### Intercept application HTTP errors (0.29.7)
Open **Vhosts → Edit vhost → Pages** (not the Web Service PHP tab), enable
**Intercept application error responses**, and enter statuses such as `404, 500, 503`.
This opt-in vhost policy applies to response bodies returned by both **Reverse
Proxy origins** and **Web Server/PHP-FPM origins**. It is configured in
`vhosts[].pages.intercept_statuses`; an omitted/empty list disables it and
preserves the application error HTML. Supported codes: 400, 401, 403, 404,
408, 413, 429, 500, 502, 503, 504.
For each opted-in origin status, RustProxy replaces the *body* using the
vhost's configured error page, then the global error page, then its built-in
error page. The HTTP response status always remains the application's status,
even if the selected page has a custom `status` for standalone error handling.
`Cache-Control: no-store` is set and origin response cookies, compression
metadata and application error HTML are not forwarded. Relevant protocol
headers such as `WWW-Authenticate`, `Allow` and `Retry-After` are retained.
Local RustProxy security/routing failures are not treated as application
responses. If `error_fallback` sends an error to a named internal location,
that response takes precedence and is not overwritten by this option.
```json
{
"id": "my-vhost",
"pages": {
"intercept_statuses": [404, 500, 503],
"errors": {
"404": {"kind": "html", "status": null, "html": "<h1>Not found</h1>"}
}
}
}
```
This is a **vhost fragment**, not a standalone valid vhost object. A page's
`status` override still applies to locally generated RustProxy error pages,
but is ignored when replacing an application's error body. See also
[Custom locations](CUSTOM_LOCATIONS.md).
### Application presets
The editor provides one-shot presets for **Static site, SPA, PHP generic, WordPress, Drupal, Laravel, Symfony, Joomla, PrestaShop and Downloads/file repository**. Applying a preset writes normal Web Service fields; it does not store a hidden CMS mode and every generated field remains editable. Service ID/name, document root and the deployment-specific PHP-FPM endpoint/tuning are preserved. Laravel/Symfony operators should point `document_root` at the application's `public/` directory.
The preset control also exposes two independent hardening switches. They are saved as normal Web Service configuration and remain active even after the generated preset settings are edited manually.
## Configuration example
```json
{
"id": "main-site",
"name": "Main site",
"document_root": "/var/www/main",
"index": ["index.html", "index.php"],
"directory_listing": false,
"htaccess": {
"enabled": true,
"filename": ".htaccess",
"scan_subdirectories": true,
"strict": false
},
"try_files": ["$uri", "$uri/", "/index.php?$query_string"],
"rewrites": [
{"pattern": "^/old/(.*)$", "replacement": "/new/$1", "status": 301}
],
"mime_types": {"avif": "image/avif"},
"default_mime_type": "application/octet-stream",
"cache_control": "public, max-age=3600",
"cache_rules": [
{"matcher": "ext:html", "cache_control": "no-cache"},
{"matcher": "mime:image/", "cache_control": "public, max-age=604800"}
],
"response_headers": {"x-content-type-options": "nosniff"},
"precompressed": {"enabled": true, "brotli": true, "gzip": true},
"download_extensions": ["zip", "iso"],
"symlink_policy": "deny",
"hardening": {
"block_common_attacks": true,
"strip_unnecessary_headers": true
},
"php": {
"enabled": true,
"extensions": ["php"],
"endpoint": "unix:/run/php/php-fpm.sock",
"connect_timeout_ms": 1000,
"request_timeout_ms": 30000,
"max_concurrent_requests": 64
}
}
```
Older configurations remain compatible because all newly added settings have safe defaults.
## Request routing inside a Web Service
The outer vhost/location router always runs first. Inside the selected Web Service the file-serving sequence is:
1. validate and decode the already-selected public path;
2. apply configured Web Service rewrite rules in order;
3. apply the compiled `.htaccess` rewrite subset when enabled;
4. resolve `try_files` candidates when configured;
5. open the selected filesystem object within the document-root boundary;
6. execute configured PHP extensions through PHP-FPM or serve a static file;
7. for a directory, redirect to the slash form if required, then test the effective index list;
8. if no index exists, generate directory listing when effectively enabled or return the normal customizable 403 response.
This is Web Service file resolution, not a replacement for vhost/location routing.
### `try_files`
One candidate is configured per line. Supported substitutions are `$uri` and `$query_string`.
Example for an SPA:
```text
$uri
$uri/
/index.html
```
Example for a PHP front controller:
```text
$uri
$uri/
/index.php?$query_string
```
A final `=4xx`/`=5xx` candidate can return an error status, for example `=404`. Non-final candidates are tested for existence; the final path candidate is the fallback target.
### Rewrite rules
Rules use a regular-expression pattern and a replacement path. The replacement must start with `/`.
- without a status: internal rewrite;
- with `301`, `302`, `307` or `308`: client redirect.
The current query string is retained unless the replacement supplies its own query. Rewritten paths still pass normal Web Server path validation and filesystem confinement.
The editor supports common website presets to fill suggested fields; review their document root, `try_files`, MIME and PHP values before saving. The two independent hardening checkboxes (block common attack probes and remove fingerprinting headers) are directly below the preset selection and Apply action; the chosen values remain editable. Presets do not install WordPress, Roundcube or other applications. For a single-page app, a fallback similar to `$uri`, `$uri/`, `/index.html` serves the SPA entry for routes without an existing file. For PHP frameworks, use the appropriate front controller; see [PHP-FPM](PHP_FPM.md).
## `.htaccess` compatibility
RustProxy implements a **bounded compatibility subset for migration**, not a complete Apache runtime. The files are read and compiled when the Web Service is validated/saved/applied. They are **not parsed on every request**. After changing `.htaccess` on disk, apply the Web Service again.
An optional bounded Apache-style `.htaccess` compatibility layer supports a **subset** of rules useful for migrations, not arbitrary Apache modules or dynamic directive execution. It can scan configured paths and enforce stricter handling of unsupported expressions. Prefer explicit `try_files`/rewrite/header controls when possible and test migrated sites. A real Apache configuration cannot be pasted unchanged into RustProxy.
Supported directives in 0.25 development:
## Admin editor layout
- `RewriteEngine`, `RewriteBase`, `RewriteCond`, `RewriteRule`;
- `DirectoryIndex`;
- `Options +Indexes`, `Options -Indexes` and the common `-MultiViews` no-op;
- `AddType`;
- `AddHandler` when the handler is PHP-related;
- `Header set`, `Header add`, `Header unset`, including `always`/`onsuccess`;
- `ExpiresActive`, `ExpiresDefault`, `ExpiresByType`;
- `Require all granted` / `Require all denied` outside `Files` blocks.
In **Web Server → Edit → Delivery**, static file delivery, dynamic compression and precompressed assets have separate sections. In **PHP-FPM**, profile/endpoint, request options, resource limits and FastCGI/php.ini overrides are grouped. Disabled local controls indicate that PHP is off or a shared profile owns those settings; selection and saved values behave as before.
Rewrite compatibility covers the common front-controller patterns used by WordPress/Laravel-style deployments. Conditions can inspect `REQUEST_FILENAME` with `-f`, `-d` and `-s`, plus `REQUEST_URI`, `REQUEST_METHOD`, `QUERY_STRING`, `HTTP_HOST`, `HTTPS`, `REQUEST_SCHEME`, `DOCUMENT_ROOT`, `THE_REQUEST`, selected server/client variables and request headers. Supported rewrite flags are `L`, `END`, `R`, `R=301/302/307/308`, `QSA`, `NC`, `F` and condition `OR`. `PT`/`NE` are accepted as no-ops. `E=` is accepted as a compatibility no-op because HTTP request headers are already forwarded to PHP-FPM.
## PHP and error responses
Example front controller:
A Web Service may enable PHP via a shared PHP-FPM profile or its own configuration. PHP is executed by the FPM service, not RustProxy, and must not be exposed as a raw Internet port. Use dedicated pools for different applications. See [PHP-FPM](PHP_FPM.md).
```apache
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
```
Missing static files return **HTTP 404 Not Found**, unreadable/blocked paths **403 Forbidden**, unsupported static methods **405 Method Not Allowed** (with `Allow: GET, HEAD`), and internal failures **500 Internal Server Error**. The built-in Web Server now uses the existing **application-style HTML error pages** for statuses with a bundled template (including 400, 403, 404, 500 and PHP-FPM gateway errors). Other generated error statuses, such as 405, use the shared styled HTML fallback. The regular built-in 502 is styled with RustProxy branding; auth-specific Tinyauth failure pages have their own wording and are served before any origin application is accessed. Neither internal filesystem paths nor `RustProxy: Web Server ...` diagnostics are included in the public body. Details for 5xx filesystem failures remain in server logs.
`DirectoryIndex`, `Options ±Indexes`, MIME, header and Expires directives are scoped by the directory containing the compiled `.htaccess`. Subdirectory files can therefore override the effective index/listing/MIME/header/cache behavior for their subtree when recursive scanning is enabled.
These generated HTML responses retain the actual HTTP status, `Content-Type: text/html; charset=utf-8`, `X-Content-Type-Options: nosniff` and `Cache-Control: no-store`; `HEAD` preserves the matching `Content-Length` but sends no body. Explicit application/origin responses are still passed through without forced replacement, subject to the Vhost error interception options below.
PHP remains intentionally controlled by RustProxy: `AddHandler` may extend the set of PHP file extensions, but the PHP-FPM endpoint, timeouts and concurrency are configured only in the Web Service PHP-FPM tab. Apache `SetHandler` socket/proxy syntax cannot choose an arbitrary FastCGI endpoint.
Vhosts can intercept selected application error bodies in **Pages** and return a configured HTML error page while preserving the status. Internal location `error_fallback` is different: it routes the error to a named internal handler. Error-page replacements are marked uncacheable. Origin-supplied and explicitly configured custom error bodies are preserved unless interception is enabled.
The compatibility scanner is bounded to 128 files, depth 16, 128 KiB per file and 1 MiB total. Symlinked directories are not traversed. `.htaccess`, `.htpasswd` and `.ht*` control files are blocked from HTTP delivery. Unsupported non-security directives are logged and ignored by default; **Strict** mode rejects them instead. General `Files`/`FilesMatch`, unsupported authentication/authorization directives, non-PHP handlers and `SetHandler` are rejected even with Strict off because ignoring them could weaken access control or expose source. `ErrorDocument`, `SetEnvIf`, `Redirect`/`RedirectMatch` and `FallbackResource` are not part of this subset. Existing RustProxy vhost/location ACL, authentication, custom error pages and response rules remain authoritative.
## Monitoring and issues
## Index and directory behavior
A Web Service's dashboard shows request rate, status/error counts, response-header timing, delivered bytes and PHP permit usage (when PHP is configured). These are application observations, not proof of external PHP worker count or final compressed network bytes.
`index` is an ordered list and can contain static files and, when PHP is enabled, `index.php`. For every requested directory RustProxy checks the list from the beginning. If no configured index exists, directory listing is used only when enabled.
- **403 / unexpected block:** inspect document-root permissions (including parent traversal), symlink policy, hardening, inherited ACLs and any SELinux/AppArmor denial; verify the active systemd unit is the updated one.
- **404 / unwanted SPA rewrite:** check actual files, index list, `try_files`, location matching and rewrite order.
- **Wrong Content-Type/download:** check MIME override and attachment extensions.
- **PHP script downloaded instead of executed:** check PHP enablement, script extension and FPM endpoint.
- **Cache not filling:** inspect both global and Vhost cache, static response headers and cookies.
Directory listing is disabled by default. A generated listing:
- opens a fresh directory handle for every request, so repeated refreshes remain correct;
- HTML-escapes names and percent-encodes URL path segments;
- distinguishes files/directories without revealing local filesystem paths;
- sends `Cache-Control: no-store`.
If listing is disabled and no index exists, RustProxy returns the standard generic `403 Access denied` response. Existing vhost/global custom error pages may replace it.
## MIME handling
Static `Content-Type` is selected in this order:
1. Web Service extension override;
2. built-in MIME table;
3. `default_mime_type` (default `application/octet-stream`).
The built-in table covers common HTML/CSS/JS/data formats, archives, images, audio/video, fonts and common Office file formats. Extension override keys are normalized to lowercase without a leading dot.
## Cache policy and Web Service headers
For Web Server sources, filesystem/OS page cache still accelerates file reads, and
static files retain validators and normal browser cache headers. Additionally,
when global and vhost RustProxy caching are enabled, eligible Web Server
responses pass through the same shared HOT/COLD RustProxy object cache as
reverse-proxy origins. Cache policy requires an eligible GET/HEAD, public
freshness and response-side safety; `Set-Cookie`, `private`, `no-store` and
other ineligible responses are not stored. Vhost response-header rules and
transformations now run for Web Server responses before cache fill. PHP-FPM
responses are protected by these same rules.
A Web Service can define:
- default `Cache-Control`;
- ordered `cache_rules` using `ext:<extension>` or `mime:<prefix>`;
- fixed response headers.
Rules are evaluated before the default. Example:
```text
ext:html = no-cache
mime:image/ = public, max-age=604800, immutable
```
Vhost/location response-header rules still execute later in the shared pipeline and can impose URL-specific policy. Framing/transport headers such as `Content-Length`, `Content-Encoding`, `Content-Range`, `Transfer-Encoding`, connection headers and `Vary` cannot be replaced by Web Service fixed headers.
## Precompressed static files
Optional precompressed delivery checks sidecar files only after the original resource has been resolved safely:
- `file.js.br` for Brotli;
- `file.js.gz` for gzip.
The client must advertise the matching coding in `Accept-Encoding`. Brotli is preferred when both are available. The response keeps the original file's MIME type and adds the appropriate `Content-Encoding` and `Vary: Accept-Encoding`.
Validators, streaming and a single Range request operate on the selected representation. Multipart Range responses use the identity representation so MIME boundaries are never mixed with a precompressed sidecar. RustProxy does not compress the file on every request in this mode.
## Range and downloads
Static responses support:
- single `Range: bytes=...`;
- multipart `Range: bytes=0-99,200-299` with at most 16 ranges;
- `If-Range`;
- `206 Partial Content`;
- `416 Range Not Satisfiable`;
- `Accept-Ranges` and per-part `Content-Range`;
- streaming rather than whole-file buffering.
Single ranges can operate on a selected precompressed representation as before. Multipart ranges intentionally use the identity representation and stream `multipart/byteranges`.
Configured `download_extensions` add `Content-Disposition: attachment` using an encoded filename. This is useful for storage/download services without changing the MIME type.
## Validators
Static files emit metadata-based `ETag` and `Last-Modified` and handle `If-None-Match` / `If-Modified-Since` with `304 Not Modified`. Large files are not hashed on each request.
## PHP-FPM / FastCGI
PHP execution is optional per Web Service. RustProxy speaks FastCGI directly to a long-running PHP-FPM process; it never starts `php`, `php-cgi` or a shell command per request.
Supported endpoint forms:
```text
unix:/run/php/php-fpm.sock
tcp:127.0.0.1:9000
tcp:[::1]:9000
```
TCP endpoints are deliberately restricted to literal loopback addresses. The connector has explicit connect/request timeouts and a bounded semaphore. If all PHP execution slots are busy the request receives `503`; connection failures/timeouts become gateway errors and PHP source is never returned as a fallback.
The PHP-FPM tab also exposes an optional streaming request-body limit (`0` means no additional RustProxy limit), response-header and stderr bounds, an Authorization-forwarding switch, static custom FastCGI parameters and php.ini-style `PHP_VALUE` overrides. Custom parameters use `NAME = value` per line; PHP settings use `directive = value` per line. Core CGI/HTTP/PHP control parameter names cannot be overridden. The in-panel **FastCGI syntax and safety** dialog contains the exact syntax and examples.
Configured PHP extensions are executable rather than downloadable source. `index.php` and `try_files` front-controller patterns use the same Web Service root and path validation. See [PHP_FPM.md](PHP_FPM.md) for the operator/runtime details.
## Optional Web Service hardening
`hardening.block_common_attacks` is a deterministic high-confidence protection layer for the local Web Server. It is **not a generic WAF** and deliberately avoids broad SQL-injection/XSS regex matching that would create application false positives. When enabled it:
- denies TRACE, CONNECT and TRACK at the Web Service source;
- blocks hidden dot/control paths except `.well-known`;
- blocks common secret/configuration targets such as `.env`, `wp-config.php`, Joomla `configuration.php`, Drupal `settings.php`, Composer auth/config metadata and private-key file names;
- blocks common backup/database suffixes (`.bak`, `.old`, `.sql`, `.sqlite`, `.db`, editor swap files and `~` backups);
- blocks high-confidence exploit-probe paths such as exposed PHPUnit `eval-stdin`, Laravel Ignition execute-solution, Apache server-status/server-info and `cgi-bin`;
- suppresses the incoming `Proxy` header when building FastCGI parameters, mitigating the CGI/FastCGI `HTTP_PROXY`/httpoxy class.
Existing decoded-path/traversal validation, document-root confinement, `.ht*` protection, vhost ACL/authentication and PHP source protection continue to run independently of this switch. A denied hardening request uses the normal customizable 403 pipeline.
`hardening.strip_unnecessary_headers` removes common implementation/fingerprint response headers after Web Service and `.htaccess` response processing: `Server`, `X-Powered-By`, ASP.NET version headers, `X-Runtime`, `X-Generator` and Drupal cache fingerprints. Vhost/location response rules run later and can intentionally add an operator-controlled header if required.
## Filesystem security and symlinks
The request path rejects malformed encoding, encoded separators, backslashes, control characters and `.`/`..` traversal before filesystem access.
On Unix, filesystem lookup starts from an opened document-root descriptor and walks relative components with `openat`.
Two symlink policies are available:
- `deny` (default) - every path component is opened with `O_NOFOLLOW`; symlinks are rejected.
- `inside_root` - Linux-only; symlinks may be followed only when the opened descriptor resolves back inside the canonical document root through `/proc/self/fd` verification after every component.
`inside_root` is not available with PHP-FPM in 0.25 because PHP-FPM reopens `SCRIPT_FILENAME` itself. PHP therefore requires the safer `deny` policy.
The secure Web Server implementation remains Unix-only until an equivalent race-resistant confinement implementation exists for non-Unix systems.
## Shared RustProxy pipeline
Web Server dispatch occurs after the existing vhost/location selection and access controls. Responses continue through the existing common mechanisms, including:
- ACL, authentication and authorization;
- rate/access policy;
- vhost/location custom and security headers;
- custom error pages;
- access logging and per-vhost Traffic metrics;
- response processing/compression where applicable; each Web Service can inherit, enable or disable dynamic compression independently of its precompressed `.br`/`.gz` sidecar policy and can filter individual codecs; selecting Disabled hides/disables those codec selectors without clearing their saved values, while globally disabled codecs stay unavailable and the minimum size remains global;
- TLS and existing HTTP/1.1, HTTP/2 and HTTP/3 listeners.
A local Web Server request does not create a reverse-proxy backend connection. WebSocket upgrades remain unsupported for a Web Server source.
## Validation limits
Current configuration limits include:
- index files: maximum 16;
- MIME overrides: maximum 64;
- cache rules: maximum 64;
- fixed Web Service headers: maximum 32;
- `try_files`: maximum 16 candidates;
- rewrites: maximum 32 rules;
- download extensions: maximum 64;
- PHP extensions: maximum 16;
- PHP concurrency: 1..4096;
- PHP connect timeout: 50..60000 ms;
- PHP request timeout: 100..3600000 ms;
- `.htaccess`: maximum 128 files, depth 16, 128 KiB per file and 1 MiB total.
Document root must be absolute, exist and be a directory at configuration compile time. References to unknown Web Services are rejected and deletion of a referenced service is blocked/caught by validation.
## Remaining follow-up
The current implementation intentionally leaves multipart Range, named locations, `PATH_INFO` script splitting, full Apache `.htaccess` semantics and non-Unix confinement for later work. These additions must preserve the existing Web Service / vhost-source separation.
Keep all document roots and external application processes separately patched and secured.
Binary file not shown.

After

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

-8
View File
@@ -1,8 +0,0 @@
# UI preview policy
Only two legacy simulated previews are retained as narrow visual references. They were generated with injected HTML/CSS/JS and simulated API/fetch/storage, so they are **not current Rust runtime evidence** and may differ from the 0.22.3 UI. Runtime verification status belongs in [../TEST_REPORT.md](../TEST_REPORT.md).
- `login.png`: anonymous minimal Polish login document.
- `location-headers.png`: request/response rules for an individual location.
RustProxy 0.19.1 removes the other old simulated screenshots so stale layouts are not presented as current product state.
+22
View File
@@ -0,0 +1,22 @@
{
"id": "sample-oidc-app",
"frontend": {
"hosts": ["app.example.net"],
"listener_ids": ["https"]
},
"oidc": {
"enabled": true,
"issuer_url": "https://auth.example.com",
"backend_url": "http://192.0.2.21:9091",
"client_id": "CHANGE_ME_OIDC_CLIENT_ID",
"client_secret": "CHANGE_ME_CONFIDENTIAL_OIDC_CLIENT_SECRET",
"session_secret": "CHANGE_ME_RANDOM_64_OR_MORE_CHARACTERS_SECRET",
"redirect_url": "https://app.example.net/_rustproxy/oidc/callback",
"scopes": "openid profile email",
"allow_private": true,
"session_ttl_seconds": 3600
},
"backends": [
{"id": "app", "url": "http://192.0.2.20:8000", "allow_private": true}
]
}
+2 -2
View File
@@ -12,7 +12,7 @@ latency, backend health/EWMA latency/active work, traffic, probe age and upstrea
variables **Docker agent** and **Docker service** filter only this section, which keeps
multi-agent installations readable without changing the ordinary vhost panels.
The **0.33.0 Web Server** row adds header-ready request rates, status-class and
The **Web Server** row adds header-ready request rates, status-class and
static/PHP/other donut breakdowns, error percentage, response-header latency,
payload byte rates, and downstream compression by codec. **PHP-FPM / FastCGI**
adds request activity, RustProxy PHP permit capacity, and errors grouped by
@@ -29,7 +29,7 @@ classes, cache outcomes, capture-state gauges and configured-route traffic. Raw
are intentionally absent from Prometheus labels.
Import the JSON in Grafana and select a Prometheus datasource when prompted. The
dashboard expects RustProxy 0.33.0 for its newest Web Server metric names
dashboard expects a RustProxy version that exports the Web Server metric names
(and also retains the older panels); a scrape interval around 15s is suitable.
Collectors can be disabled in RustProxy settings; panels for disabled collectors
will simply show no series.
File diff suppressed because it is too large. Load diff
+5 -12
View File
@@ -106,18 +106,14 @@ run_builder() {
run_builder git clone --depth 1 "$REPO_URL" "$work/RustProxy"
# The builder, not root, owns the toolchain. Never update a system-wide
# apt-managed compiler or change the user's default rustup toolchain.
# rustup is needed even when an older system-installed cargo exists.
# The unprivileged builder owns its rustup toolchain; system compilers are left untouched.
if ! run_builder bash -c 'command -v rustup >/dev/null 2>&1'; then
echo "Installing rustup for build user: $builder"
run_builder env RUSTUP_INIT_SKIP_PATH_CHECK=yes bash -c \
'set -o pipefail; wget -qO- --https-only --secure-protocol=TLSv1_2 https://sh.rustup.rs | sh -s -- -y --profile minimal --no-modify-path --default-toolchain none'
fi
# Read the actual requirements from the checked-out sources, not a hardcoded
# Rust version. The project may inherit rust-version from [workspace.package].
# The channel from rust-toolchain.toml (or legacy rust-toolchain) takes precedence.
# Read the required Rust version and channel from the checked-out sources.
rust_settings=$(python3 - "$work/RustProxy" <<'PY_RUST'
import pathlib
import re
@@ -175,8 +171,7 @@ if [[ -z "$installed_rust" ]] || ! rust_version_at_least "$installed_rust" "$req
run_builder rustup update "$build_toolchain"
;;
*)
# A fixed toolchain must be respected. Do not silently override a pin
# that is older than the manifest's minimum supported Rust version.
# Never silently override a pinned toolchain older than rust-version.
if [[ -n "$installed_rust" ]]; then
echo "Pinned toolchain '$build_toolchain' is older than rust-version $required_rust; fix rust-toolchain.toml in the repository." >&2
exit 1
@@ -193,8 +188,7 @@ fi
run_builder rustup run "$build_toolchain" cargo --version
echo "Building with Rust $installed_rust ($build_toolchain); required >= $required_rust."
# Keep source builds reliable on small Debian VPSes. The repository release
# profile may use LTO/codegen settings that can make rustc exceed RAM limits.
# Lighter release settings keep builds within RAM limits on small VPSes.
BUILD_JOBS="${RUSTPROXY_BUILD_JOBS:-1}"
if ! [[ "$BUILD_JOBS" =~ ^[1-9][0-9]*$ ]]; then
echo "RUSTPROXY_BUILD_JOBS must be a positive integer." >&2
@@ -230,8 +224,7 @@ if [[ "$MODE" == install ]]; then
printf 'Administration is loopback-only by default. Use an SSH tunnel or enable Administration HTTPS before remote exposure.\n'
fi
else
# update.sh reads the saved installation record and therefore preserves the
# existing data directory, HTTP/TLS/admin binds and service account.
# update.sh preserves the saved data directory, binds and service account.
bash update.sh \
--binary target/release/rust-proxy \
--prefix "$PREFIX"
+5 -3
View File
@@ -5,7 +5,7 @@ A simple mechanism for automatically sending a certificate generated by `os-acme
## Files
- `opnsense-rustproxy-cert-deploy.sh` - sends the certificate and private key to RustProxy.
- `rustproxy-acme.conf` - connection settings, API token, and certificate file paths.
- `rustproxy-acme.conf.example` - example connection settings, API token, and certificate file paths.
- `actions_rustproxy_cert.conf` - optional `configd` action that allows the script to be executed from Cron in OPNsense.
## 1. Installation
@@ -50,13 +50,15 @@ KEY_FILE='/path/to/private.key'
## 3. RustProxy API
Create a certificate API key in RustProxy and enter its values in the configuration:
Create a certificate API key in **Settings -> Certificate API** and enter its values in the configuration:
```text
RUSTPROXY_KEY_ID
RUSTPROXY_TOKEN
```
The credential must explicitly allow `CERTIFICATE_ID` and **Also allow deploy** (`allow_deploy`) must be enabled. A reload-only credential will be rejected with HTTP 403.
The script sends the certificate to:
```text
@@ -88,7 +90,7 @@ cp actions_rustproxy_cert.conf /usr/local/opnsense/service/conf/actions.d/
service configd restart
```
Then go to **System -> Settings -> Cron** and add a scheduled execution of the `Deploy ACME certificate to RustProxy` action.
Verify the action first with `configctl rustproxy_cert deploy`. Then go to **System -> Settings -> Cron** and add a scheduled execution of the `Deploy ACME certificate to RustProxy` action.
The script deliberately reconciles on every run instead of trusting a local cache. The server is the source of truth: an identical RustProxy-owned certificate generation is a no-op, a missing id is created, and changed material is rotated atomically. This also repairs drift when a certificate entry or generation was removed outside OPNsense.
@@ -0,0 +1,6 @@
[deploy]
command:/usr/local/sbin/opnsense-rustproxy-cert-deploy.sh
parameters:
type:script_output
message:deploy ACME certificate to RustProxy
description:Deploy ACME certificate to RustProxy
Loaded 100 of 538 files, more files were not shown because too many files have changed in this diff. Show more