From e3643926590d258218bc615a194d192152db615a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Tue, 27 Oct 2026 12:18:20 +0100 Subject: [PATCH] v0.34.15 --- AGENTS.md | 9 +- CHANGELOG.md | 10 +- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 10 +- SHA256SUMS | 83 ++-- docs/ACCESS.md | 37 +- docs/API.md | 2 +- docs/INTEGRATIONS.md | 4 +- docs/README.md | 6 +- docs/TROUBLESHOOTING.md | 3 + grafana/rustproxy-overview.json | 4 +- langs/en_US.json | 8 +- langs/pl_pl.json | 8 +- src/admin/openapi.json | 28 +- src/admin/status.rs | 2 + src/admin/web/css/20-shell.css | 10 + ...nnection.html => error_auth_provider.html} | 7 +- src/admin/web/error_tinyauth_timeout.html | 37 -- .../web/error_tinyauth_verification.html | 37 -- src/admin/web/vhosts/editor.js | 66 ++- src/admin/web/vhosts/render.js | 5 +- src/auth_provider/errors.rs | 160 +++++++ src/auth_provider/mod.rs | 263 +++++++++++ src/auth_provider/testing.rs | 55 +++ src/authelia/mod.rs | 127 ++++++ src/authelia/tests.rs | 85 ++++ src/authentik/mod.rs | 221 +++++++++ src/authentik/tests.rs | 176 ++++++++ src/basic_auth/mod.rs | 2 +- src/config/model.rs | 7 +- src/config/routing.rs | 8 +- src/config/tests.rs | 37 ++ src/forward_auth/mod.rs | 419 ++++++++++++++++++ src/forward_auth/tests.rs | 251 +++++++++++ src/lib.rs | 4 + src/proxy/cache_helpers.rs | 3 +- src/proxy/cache_plan/tests.rs | 2 + src/proxy/forward/route_cache.rs | 6 +- src/proxy/forward/web_source.rs | 2 +- src/proxy/forward_entry.rs | 30 +- src/proxy/upstream_helpers.rs | 24 +- src/runtime/types.rs | 2 + src/tinyauth/mod.rs | 120 ++--- src/tinyauth/tests.rs | 4 +- src/vhost/model.rs | 21 +- src/vhost/validation.rs | 12 +- 47 files changed, 2164 insertions(+), 257 deletions(-) rename src/admin/web/{error_tinyauth_connection.html => error_auth_provider.html} (69%) delete mode 100644 src/admin/web/error_tinyauth_timeout.html delete mode 100644 src/admin/web/error_tinyauth_verification.html create mode 100644 src/auth_provider/errors.rs create mode 100644 src/auth_provider/mod.rs create mode 100644 src/auth_provider/testing.rs create mode 100644 src/authelia/mod.rs create mode 100644 src/authelia/tests.rs create mode 100644 src/authentik/mod.rs create mode 100644 src/authentik/tests.rs create mode 100644 src/forward_auth/mod.rs create mode 100644 src/forward_auth/tests.rs diff --git a/AGENTS.md b/AGENTS.md index 3fcadff..782981c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # RustProxy source and documentation maintenance -Current source version: **0.34.14**. The Cargo package and binary are named `rust-proxy`. +Current source version: **0.34.15**. The Cargo package and binary are named `rust-proxy`. Before editing, read [README.md](README.md), [ROADMAP.md](ROADMAP.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. @@ -12,6 +12,13 @@ Before editing, read [README.md](README.md), [ROADMAP.md](ROADMAP.md), the relev - 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. +## Authentication provider extension contract + +- Keep provider implementations isolated under `src//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. +- Update the Vhost selector, mutual-exclusion validation, config compiler, docs and provider tests together. Prove access/denial, redirects, header stripping, timeouts, and reserved routes with real verifiers before deployment. + ## Engineering safeguards - 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. diff --git a/CHANGELOG.md b/CHANGELOG.md index 2276857..7b6a66e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ # Changelog -Release history focused on **features, settings, default behavior, compatibility and fixes**. Changes to internal code structure and preliminary performance experiments are intentionally omitted. Historical descriptions reflect their respective releases; consult the current documentation for the settings available in 0.34.14. +Release history focused on **features, settings, default behavior, compatibility and fixes**. Changes to internal code structure and preliminary performance experiments are intentionally omitted. Historical descriptions reflect their respective releases; consult the current documentation for the settings available in 0.34.15. + +## 0.34.15 — optional Authelia and authentik forward auth (source candidate) + +- **Provider-neutral refactor:** Extracted `auth_provider::Provider` with unified selected-provider dispatch, verified identity/cookie handling and one `error_auth_provider.html` template. Removed three Tinyauth-only HTML template files; 502/504 titles still identify Tinyauth, Authelia or authentik and failure category. Shared `forward_auth` transport continues to be reused without embedding vendor-specific branching in the request pipeline. +- **Vhost → Authentication:** two additional, independently configurable choices: Authelia (`/api/authz/forward-auth`) and authentik (`/outpost.goauthentik.io/auth/nginx`), alongside unchanged Tinyauth and HTTP Basic Auth. Exactly one method can be active per Vhost; both new methods default off, with old configurations remaining compatible. Settings can be edited through the panel and vhosts configuration API. +- **Authelia:** bounded server-side verification, trusted original-request metadata, allowlisted identity headers and strict same-portal-origin login redirects. The Authelia portal and session cookies must be configured for each protected parent domain; a single portal URL cannot issue a valid browser session cookie for unrelated parent domains. +- **authentik:** supports forward auth (single application) with each protected host's reserved `/outpost.goauthentik.io` route automatically proxied to the configured outpost. The protected URL is passed to the outpost for SSO login; request cookies, identity headers and session-refresh `Set-Cookie` values are propagated with limits. Different parent domains can use one central authentik instance with distinct per-app proxy providers. +- **Fail closed:** verifier transport/invalid-response errors return 502; timeout returns 504; unauthenticated/denied requests never reach application origins. Provider identity is never accepted from incoming client headers. Cache sharing is disabled for protected requests; fail-closed auth responses are kept out of custom-error interception. ## 0.34.14 — Tinyauth gateway error pages (source candidate) diff --git a/Cargo.lock b/Cargo.lock index 46866e3..f4b733b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1925,7 +1925,7 @@ dependencies = [ [[package]] name = "rust-proxy" -version = "0.34.14" +version = "0.34.15" dependencies = [ "anyhow", "arc-swap", diff --git a/Cargo.toml b/Cargo.toml index 90fc7a9..8eee141 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust-proxy" -version = "0.34.14" +version = "0.34.15" edition = "2021" rust-version = "1.99" description = "RustProxy - independent reverse proxy with local administration" diff --git a/README.md b/README.md index 181a309..e146cd6 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,13 @@ -# RustProxy 0.34.14 +# RustProxy 0.34.15 RustProxy combines a reverse proxy, static/PHP web server, certificate management and traffic monitoring in one service with a browser administration panel. It supports several domains and backends without requiring a separate proxy configuration language. +## Changes in 0.34.15 + +- Added **Authelia** and **authentik** as separate, disabled-by-default Vhost authentication methods alongside existing Tinyauth and HTTP Basic Auth. Each Vhost selects at most one provider; older Vhost JSON imports keep their settings. +- Authelia integration uses `/api/authz/forward-auth` and validates login redirects against the configured public portal. authentik integration uses `/outpost.goauthentik.io/auth/nginx`, redirects unauthenticated clients to the protected hostname's outpost and forwards that reserved path to the configured outpost server. +- Authentication checks fail closed on provider outages, malformed responses and timeouts. Verifier-returned identity headers are selected/limited, client-forged identity headers are stripped, and successful sessions remain private/no-store. Protected application domains may differ when each provider is configured appropriately. + ## Changes in 0.34.14 - Preserves Tinyauth Nginx auth-request compatibility with old and new versions supporting `/api/auth/nginx`. @@ -14,7 +20,7 @@ RustProxy combines a reverse proxy, static/PHP web server, certificate managemen | Reverse proxy | HTTP/HTTPS origins, HTTP/1.1 and HTTP/2 upstream modes, health checks, load balancing, connection reuse, WebSockets, path-based routing, retries and error fallbacks | | Frontends | HTTP, HTTPS, HTTP/2, optional HTTP/3 (QUIC), named listeners, domain and wildcard routing, redirects, TLS certificates | | Web Server | Static content, downloads, directory/index policy, rewrite and `try_files`, selected `.htaccess` rules, optional PHP-FPM, application presets | -| Access | IP/CIDR and DNS/DDNS rules, country restrictions, reusable ACLs, rate limits, method rules, Basic Auth, Tinyauth, security headers | +| Access | IP/CIDR and DNS/DDNS rules, country restrictions, reusable ACLs, rate limits, method rules, Basic Auth, Tinyauth, Authelia and authentik, security headers | | Delivery | Optional shared HOT/RAM and COLD/disk cache, cache invalidation, compression, response transformations, static-file optimizations | | Integrations | Docker discovery via separate agents, TCP/UDP Streams, ACME certificate providers, Prometheus, Grafana/Nagios examples and administration API | | Operations | Dashboard presets, traffic analytics, live/stored logs, configuration history, encrypted backups and certificate reloads | diff --git a/SHA256SUMS b/SHA256SUMS index 60897b7..0f5244a 100644 --- a/SHA256SUMS +++ b/SHA256SUMS @@ -1,12 +1,12 @@ 61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore 4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example 9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore -9a882c75ba505b097de65a1073accd3ccb30395b2454f3c3fb9b25c7b2d9bd6e AGENTS.md -3e27930459e3225a19aa5174bb9523df3613c009b7b4fe117311b528f740ec61 CHANGELOG.md -11b42e2c29662b09b6ff1a575ec45a98856daa6cbc2c0351afa9f1fa4a11b1b5 Cargo.lock -f3cc334705c920f910dbc0fce374b7223c2e6a66abe614cf8074c6e89da1d537 Cargo.toml +0fac1a1a51c2b28a35ab3ca5ca78bc622d923f6338d060ddd28b024e0c585c94 AGENTS.md +0add2dca4bcce8a7b52aedaa91b80cb828055d19df3236803a6cf6a10702bea9 CHANGELOG.md +9990725b3ad88f93526c87d09cebc2505b3b34db7dcdaa1fdc5e40e5352891a0 Cargo.lock +0b93f835f1a5edc78105fcd2c2fdae02d48badab667089ec67543a93dee80b9f Cargo.toml 25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile -750f73027faed6039b06f75f87c5a3c1e646ea396a64750688d2ca1344ab72bf README.md +0230d86529bc60fa2db5ececff0ce3763fd44675f5ba3f48b6187bff27b474d6 README.md e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ROADMAP.md da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml ac07b3bdd46c787989e93f55ebd6ea3d7f5b150ef36f2422e2b58df3f6b7543a build.rs @@ -27,9 +27,9 @@ d660f46f7ef8c539dd3f5486a31d5a1254b9737be8a278c7f9352c254558f8ac docker-agent/m e52b31d0abf65ae3034f6cb367cca113633014ce8d1d799ad4dd5624d2c7b8bb docker-agent/network.go 634790f35ff4627df2a78cec2ed371f6e9ec9df00afb386075595fc7f3ed72eb docker-agent/rustproxy-docker-agent.service 5220d669171ec3ad65f50e0bc8de5178dadaa4904def8e1a4f61002aa9e9a55b docker-agent/server.go -93aa788dc1205bc9386a95a39b30a3862a42ba6a2e90e82fc5bfe681fc7db6a7 docs/ACCESS.md +e34ce67e7105be1e3a666beaaf28338b6ce443c18a6cabdf9e54cb7e4ce61963 docs/ACCESS.md 147755fb33e9e91098a4d2dfbe6df8b0d92d602e002d568e64a0f815641be2c2 docs/ACME_PROVIDERS.md -ba40dd0ec51e0992c0fecd12934b9875e5e7252eced8db533cb79ec01ac8dcc7 docs/API.md +0489f81f1a766850b12c69a44b9fc4caef9a7441eccf4ed854df67d9989e7d41 docs/API.md 2362ab34020d4e0dcd70e280e9539ef7764b63212856ee08a3a55906c48f6421 docs/API_ACCESS.md 728b15b9825f547f34c4121219784798dd046fbbdd72ece8906d0cd77e8efc64 docs/BACKUP.md 3219aae15fd2e10157343ecdad8b175bd9735f39bccb31a5cb811553c13084dd docs/CACHE.md @@ -39,18 +39,18 @@ dc670d912a6a946160c2f8528c9bb710ec38ebe406dcb7dcaf18fb2653f667b6 docs/CUSTOM_LO e350c5081085b79e579d1500fa07feee94a709adaf6dfde7b3d9af124a78d184 docs/DASHBOARD.md 11a08bf2110bbeabc924eb049a30177a216af5607ccdaf8be9178e751f78a2c4 docs/DOCKER_DISCOVERY.md c891ee5272e2e47e1a8f97133a413e2330844103d543fb5cbcc4d0724d59a1a5 docs/INSTALL.md -7c97df0b89e230851fe5211f5b295aac1f71a0f64385bf165d2aa7db893fdf74 docs/INTEGRATIONS.md +27f48388891a040a4895428749d7e95fda620ab45ea3d68c536645a5bd5d89f2 docs/INTEGRATIONS.md 694cb7b15c5bbdc94cecd15dc516c05dcb3467849322cdd125b872d36dee4699 docs/LOGS.md 888cdc8c56e10b8a7615012e192e085bf2899d0ec9b3ddfb5731e7de6ab5bcde docs/MIGRATION.md 27ba3ed337963d9cd05ba382666eed9c414f58c52e5897b4c1c45603f412b456 docs/MONITORING.md d5894c07c68ec4230987767198434abee13b0e525842fea21bcbe752fa82742b docs/PHP_FPM.md -0f66cef23c9a8caa378497787811edcb4f48e31b72bbd958b2862a9549d78077 docs/README.md +8d29408a99926e3c2ae76f52b8a41c76b80447b953bcba4df242ea8e54bd0d5d docs/README.md 490a6dbe870f2b1fd4ec7816768dde6c86fa7957fc9de2d2f075e123c9222222 docs/RESPONSE_DELIVERY.md a7c5b01ad1e6aeb27bccf1085504854b70ed4bf020b481915aafcc4a8aeb1f54 docs/SECURITY.md bbef60ff1771da9f48af812987766e1e479408873ae2f079b13f1d5c62ecc6ff docs/STREAMS.md 6508819f181e2a935ffa117451915aa5568a31e2890c57fa10e04543db82f3e7 docs/TLS_POST_QUANTUM.md 43a2c6230f95f81bf4efd1575a4f7cdc8b02244df38e19b1ec9c3cd736d3afe6 docs/TRANSPORT.md -df0d5e9e83ec3827ecf6887801137569118f8f29680b0dda23887723f8d4e030 docs/TROUBLESHOOTING.md +af1098e461c39c3f0ae09b8dbdbc4c1c362b2662f29c62602399de16f8912e97 docs/TROUBLESHOOTING.md e14c02f2c95daf5779d1aa5300751162b63fdd968832a1f0c2f191fa00ace6dc docs/VHOSTS.md 7e7eab2ba76c262e4000184f80f9a5cb69c7896fe7a046c0d3d84dc211c4b751 docs/WEB_SERVER.md 5502e8ce44276213803b66c58e3a4d804a1354f5ed5d83f47ee805e8c44ab088 examples/acl-headers.json @@ -64,7 +64,7 @@ b26833cb4251f9390a44192ecc827ff7f052fd19016a5ccf7498c94503ab4ce3 examples/mock- d242de9614fbb52933b4accd32f4715ca94107abe4a3b1d7e088362b07a6ba60 examples/vhosts-balanced.json af2bdb1343a8141069e94ccd2c7f92574ad848437e2f9fdb5c7576a9a18ef4a5 examples/vhosts-private.json 1b8974af79a5026e0648ceab58a32f233e1760e21e7465a0d46e9c41fb09964f grafana/README.md -e775dc98b44bb1c362d8de85ffdee105694807a946029544aa13780c806aac33 grafana/rustproxy-overview.json +0d389faa4f64f095e9ec62cbae329658682b96f5e4d02ead79c7d2892ab89fa8 grafana/rustproxy-overview.json a05cbbf101b288bf9cf83fedfc5017925a8b360a78f313d31c1a70f15dc1dd22 install-debian13.sh 909e9ab94617ace74a425f15084bf85f3086cc52ba5b28f2b441ff800abc6ef9 install.sh 42f9f1d18c021fc7734b5bd020844bf678725a0e539e2fcbe05ec676f6239eae integrations/README.md @@ -77,8 +77,8 @@ cb01fdef286f38b442dd9252c23c4f8e44eb72e9ecdd99adb926e8e9f6318bb1 integrations/r 96563a83e87cfb33e238c2a970ce36b1a1b51e91318d09b123c3755002ba2646 integrations/routeros-oci-build/build-routeros-oci.sh 3f267cafa16006e5456ade78ee2d5fae02cc7ce747839523068ffd8d7c98798a integrations/routeros-oci-build/docker-entrypoint.sh 13189fa546c3488dddd8efc80c47f6da5241e5fc28b79cbfe795414ea05c7a4f langs/README.md -0aa1109be04a8a3e32a3aebed25ae25054e4a44574d92556eccff1424e31c148 langs/en_US.json -1fcc95afa3f7374bb6a9105004877ee207ee6fec1b616fc72ee8f832054756bd langs/pl_pl.json +956566ae2aff162cca55961bd1a92341746f0fb8e7187d8167bcbd06677a0c2c langs/en_US.json +9d9385ca67e5fd01f3b27b21d5515adc87ecda53129cda71130bff00f809157a langs/pl_pl.json a99ad040be10fa028351e290b9363236b3513a1cadbdc9fb9e0260d517b9b0ae make_zip.py e830d37dd1f81a4b0aa7d535f5bba69e34e433b0fd99cc8ee0b296786c32379a migrations/0002_admin.sql 20112cc82192098b8d602f1c4f7ca08712e9e1366c845fabb92753187c370d35 migrations/0003_certificate_renewals.sql @@ -149,7 +149,7 @@ d58a2ed83319176de4e66790ed292447c9497ae031464ec55cfe27174acb639a src/admin/file 282352eda7957477e7da44f16b90984b26a4a67a8755833bc0a6b34eaf93e3af src/admin/file_logs_query.rs 444d7715406ffd2305a72decf7fdf633aa4c2752cd881c8fa3b95ed971d79820 src/admin/logging_api.rs 243f4a9c3fa99781e19c3952354d2632c0ce65e1dafac729567cbe3c177f94cb src/admin/mod.rs -cf7393a5a6c101bc739a4cbf73a2abf60ca8fe2184ec84ae244ef9bd5f99c2e1 src/admin/openapi.json +8ac7bd4270241e1a9834146cd9ace181a1facb72e70c1837a10d07c261bf7fde src/admin/openapi.json 1f4ec45d146e00ef664fae37517f8fcb5455d47daec496c02b2fc15ddec49131 src/admin/operations.rs 257d93a29d0bda96462bcf695aba8d0ec0f9412c379e066ec46ae8ee034e3a53 src/admin/panel_assets.rs f5127855c2994d052678865df36d703b74f4a0afc633daf7718b10da18d26e37 src/admin/panel_content.rs @@ -161,7 +161,7 @@ b3c3c2d33c150eb42ce210c6074e5eff6a8af9391118012e413bc9323d3ae50c src/admin/pref 6930b4f4b45257772b321b20a9becdf9bc4610274852948ba38c2d0cdf02dfaf src/admin/refresh_preferences.rs cc3aa1b094b0d869d141754ac2877817ec40a362acfd7fc3167ea3c9f1ff1a18 src/admin/router.rs e135431cb078ddb483165581123c64af8f00ea67acb3a395ec3e53c85d0d0d45 src/admin/session.rs -09cc50a86960897371730cc1abed83f4f640c86b680ec7ff6d6b84c7e96a3c0b src/admin/status.rs +7aeb784a3ef58795c8fe26467bb4e1a00e61420911f798a584ab09c12f73a754 src/admin/status.rs 2d5b4c28fd0bd48b06890fa8ec44a2296de948c1f88eb2909f4b495710d4e13e src/admin/status_live.rs 8315c856cd50f1072593ecb30c274d42d82f078b5fb93e7c68a8857cd81b0841 src/admin/status_system.rs cadd7dca29b9aad8c77c7bc23dd249ea37a15adfa4d636a8ef00273a91ce35dc src/admin/tail.rs @@ -194,7 +194,7 @@ b7ead49224a4ec67181d3f24f9d9634bd6d76756f826a4d5d993300637d1d8f3 src/admin/web/ e9788a9d1e0bce8775f612a5c842afea8a0dcf01301acdef4b69ff6a52d2ef45 src/admin/web/certificate-api.js 9d000e98497b3387641500d5273d23eda45d532c714f22e187572928da152a97 src/admin/web/css/00-tokens.css 51437cbc5ec2d9690a685f9ad1c9235e503049adc4438e1b10ed232a9909b862 src/admin/web/css/10-base.css -195a6af7f54f310d936d32b1074e3c5daeaac1a8c687b8dff0223be8ea34eb36 src/admin/web/css/20-shell.css +be10487cdaf89acfb7b4cf893d82580ac4c64ac68cb3094d349fc8de10c61d6c src/admin/web/css/20-shell.css 707d08b7b8505c7346d5a98f3a8058f10577ddac7b9e6a5a9894285bde9e300a src/admin/web/css/30-overview.css ce5ffb911d7e4cd2317ca047534921132b6f93efd70a26673f0c428e55320a4d src/admin/web/css/40-editors.css 6537a3f97d046b8325c744a281a74f8c4005a8bbcd97284ecf7eb5b6973ae1df src/admin/web/css/50-settings.css @@ -222,9 +222,7 @@ 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 -ce598fadd521ee000f05f7d29d70cdeca684a53dbd946a38af204fb2a9248b1b src/admin/web/error_tinyauth_connection.html -daa9ef52d96b21c492380486d1cf476c715dcd6c339b431887cc31776de966c8 src/admin/web/error_tinyauth_timeout.html -026361d7c3c3b596daa290feaa734f042fabdf236369cd4bebc39795f0c4fc96 src/admin/web/error_tinyauth_verification.html +003d382cc8dd69ad7237679632c04a9389366bb71dd7dca8ee64ee06614aa9a5 src/admin/web/error_auth_provider.html 0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg 7cbdef9192a9dee1f8eac42b85f52b3190f7f6efae50004aa7ad116a910e4036 src/admin/web/file-logs.js 3f0525ca1d4e088ccd4988b8a117b26a8cf8c12213b24c37081d41b68d2b0ff2 src/admin/web/host404.js @@ -244,11 +242,11 @@ a8afc6e70950bc70b98725eb049d73cff35dc3a3ee659229a15a8e08d3eb4f12 src/admin/web/ d290c048450655b3eeae7c6ec54a82d645d1965513694ab29fdf7eef796a1abd src/admin/web/traffic-panel.js c7e0ca45c19498d40ef6f4122de23f0e73094e905d39f798942f70d15b9cf86e src/admin/web/transform-panel.js a55f45dad5b540def419f8487161fb506b7e52744c76a5e661c7a5fa0a98a426 src/admin/web/vhosts/core.js -d88d89c8071d2458aef278498a5a7863fb45c374fca801b0b3a9de402c455b4c src/admin/web/vhosts/editor.js +d4e447c176a72a6f803f84c6a6925c4905acbcd31ea5c9bf7bc3ed9e0f455054 src/admin/web/vhosts/editor.js 8338ce13b5e3e3f421437631124cb691aeb5b9e17409d256b4b9bcac457b3c59 src/admin/web/vhosts/health.js b3239e67901f1796d7a974ade0796f2e3590ff8064628a5629b78c7f6ce2f32e src/admin/web/vhosts/protocol.js 8c3da49973c62333559b0e3588c02beb0b425c025eac5cfa82a1bb968f70d9dd src/admin/web/vhosts/rates.js -03f99423df8b32aed674478afa24f6b366fb3128d9bab56218dcef71456bedc4 src/admin/web/vhosts/render.js +cbfcaff4f83ed3058556ed8247e5d9f1ee16aee1772dde770c7bdacb699dc5d6 src/admin/web/vhosts/render.js 4dfdeb7c73c2775eacaca65fd8ef1dc80b275421b15522d319d338f5b96288cc src/admin/web/web-server-profiles.js dafce3a59a6007e59d7a4018fce96a14cd8e8cedb3ce77f02ee229a280c122d8 src/admin/web/web-server.js 2d40c2cb32d7365df0c9cd6c8cb6f0483c7a799786c648f4836a0d9740c6c5ef src/auth/files/mod.rs @@ -256,6 +254,13 @@ b2f948bcc3dbd24952c7b1b02d41d06757f49f0c38a78caf51f8f8a687d8480d src/auth/files 50ef648351271d78310e00caa6d246e0009171ed036a3e4e1144737df8c064c2 src/auth/mod.rs 25b9f78a7eee277902d375266bd0f023b79794bcdf646214fc161c5e6dab72d1 src/auth/store.rs 745226cabb007819370fc6aa680995a03a8a13fa057dacfab0fcc21d0c43bbe4 src/auth/tests.rs +514e99e0d3da21abd633d53872234bc4d7d0178583ab291e53cf4b1802fd5337 src/auth_provider/errors.rs +fffd302d2b4f5fa6575a09eae428445403375c5f26c29e4b4818f5f263918637 src/auth_provider/mod.rs +72a6e245cef078585bc39932033c05cd866ee518e9cb4e7b89bca87ce941e971 src/auth_provider/testing.rs +59e1af728770d28265198844b3dd5740328bcb1df96d170aae575d3d97ac42d8 src/authelia/mod.rs +f44b10fd3286b805008f746bee8164bd9c3ec7ebc86724314408f6703d6e1f41 src/authelia/tests.rs +0137e66fb7127f1ce023b1c6cfe3fac3fb1e8be17f1de722f8ef4e66d2594a13 src/authentik/mod.rs +fb244b3f44f9f4ba0be8c7572f43510afa0af7bb9c47759d0e9a93c8a96e1616 src/authentik/tests.rs 32a25e53ac9542424debeaf1b637155bd4e60abd8692df082729d81232eb90b3 src/backup.rs 0f07cac0259e849773466a6fd468fbe84ea34bdb79745dae8ace286b8c707727 src/backup/archive.rs 15213c87d4393739328ebf0eacfcb45f864ac12d7bd7509f750a241032f6c7e9 src/backup/ownership.rs @@ -270,7 +275,7 @@ a3d052a928f73ea430480637b867ae874d447e24b1ac913a96a068a4dd74ff63 src/balance/po 477132dd381e23f1be197c32ff58a0925f41d3e6aa63fe77c10973cefb2b76f6 src/balance/pool_selection.rs d789bf40cf9433e2fd16409d112210fe93154588b1ac9bddc9c7f78e1db14426 src/balance/pool_view.rs 3e75a3cba5f1cd1af996ab71b6ad50f6aa05ac9cd97ee3cf88fce7c6b7271fbe src/balance/tests.rs -16a22aa72f311d1fea6d8da03c3ea1590bc1f248b21876354eecc0da0fd93308 src/basic_auth/mod.rs +ca1699c931c9d80c3ba42f4f70f5c7260bf69ba3af40a64639311d461cf91e62 src/basic_auth/mod.rs 9a8858b7ff461a4d961ba701ca3ed6eaec15c3f7f05eef8e0e30edc63617605a src/basic_auth/tests.rs 76b7db53f38c95e128fee503381efd3fdcc13340c124ef277faf0fe5c1c25c39 src/cache/cache_regression_tests.rs 7def03ace07dc38a4806277d75b6ce673d33ddaaf1c4855fa0a3372be1cf9489 src/cache/cold.rs @@ -318,11 +323,11 @@ e695079333ebdc2a6e7b3337cfabef171b3a0a01da0597b77feff70d417f833e src/config.rs 9f6272bd6d1f403cf6324b12472fd5c7d911a387139b57105ba1819e48ea0a54 src/config/backend.rs f4df024acdb24be450822e58108af23db3dc82bf1f77531cd16fe340fc01062c src/config/compile.rs fbea01fea1213d98aea812c1b78d5db289e09200b98ae514d742235cd3fa70e1 src/config/internal_routing.rs -6e8116efb074397905b41331097cbf81099a21a896b686b4de2e3f210e783620 src/config/model.rs +101253b4c9275e38dc7e1088051d6cd55ae7616213eb1c02e3e1c5ffc6fea4a0 src/config/model.rs 75839458ddbc7480ec4492a95eeb67b617ba7d62f7b67e7a767a3453b74dbcbc src/config/route_policy_tests.rs -40e9483d7b2450f55d29b46c408727f4e331b85ecda44fbd7b96bd9a09f02655 src/config/routing.rs +f9db8dc17edadb1d549fed8b10f858a0bf1d0d51802df7d558f7bbf33c1c1486 src/config/routing.rs dd9a6ad8f58e0cdd1ae74f3f972fee0ac14383cd8b8f518403b9a36c169f35bd src/config/snapshot.rs -ba5829a5fb65e76e2e3450e897eb2d21105809f1d5e16b630a6faca27ca14252 src/config/tests.rs +094dc6688d5bd0e8bef92f17736bad740a95b4765cd29fe89d5362d155e868c5 src/config/tests.rs f35de0ee1516ece6bbfd2b2a7db7d1e0f5048b0ec47cf2a3765dffb799abfd93 src/config/transport_tests.rs e0f46d885ed81ce306a110560cdd2c217dadde054986d89fcec835ebf6939d65 src/db.rs c290d0cb02dfa5d2c60ecabac1cfb20c8884e3d14cca1944906c6d8e677f1784 src/db/api_tokens.rs @@ -337,6 +342,8 @@ f9e8539223b4f1d254ee960f59cd4dcf719627101dd9e0190fcd7d331b674fa4 src/diagnostic f69087a82cf353e9a92fc97ee96510dbe0382efa1c5e53821d16c455a3eb95a4 src/docker/model.rs 810415a436d842b79fa72392cc18977ad9c368d233ce43e135a60119722c9e77 src/docker/reconcile.rs 2e8d4841665cc1d29e6262a1789bb64ced1cecd0036681cc1ef7b5d5bfe22065 src/docker/tests.rs +468ad6df59092d97e79b42e4387460488040b5b7ea99c7639946f799c6863966 src/forward_auth/mod.rs +6c25951cf69e972c47cdf815f22a5062916b2b768e39d55a26a5fc9e625a2344 src/forward_auth/tests.rs b496edbfcb8fe7875a95835f8b10a0dbd46775227dbd1e5fee555b284c836216 src/forwarding.rs 08d2e08a798abb944a53570f0bdaa08f1266b1107677ceda070091af4c2fc121 src/forwarding/peer_formatting.rs b821e2efd2939cb13d01e402532f33feba6682acb6a7a3223be086c1cd171820 src/forwarding/tests.rs @@ -355,7 +362,7 @@ cfc75486e69c310fa6e7bf0448ed39a20cdad9dd8afe75e01592fc7c76a380db src/geoip/test 49ce4452ef7973cafa2e4071e2e14be0e16000e931a6501e4dc613652e49e230 src/http3/identity.rs 0b41072f6bcf28725c80f471584cd55d4900424ce5d55ac7f6909813788c63fd src/http3/settings.rs 5c550075c7b0fe687ad02095c1cad57b46b579fe21dc5371525b6c77db459886 src/http3/tests.rs -65cbe42886c89cd6675fe42f2051a8adba6b22d4bd106d93a205321baefd7a01 src/lib.rs +894bd4b19323d28969d53b1fd9e095cdb4a7a54ee3b6d9e4a41f4fe1d1eaf757 src/lib.rs e8399753a6ff49b7de19edb0a268bf871c9979a518354160cf07b4669051fcbb src/logging.rs e4be3ad758c3d93ce52dd3b1ab1a87810e5226339400ed997607a5b746354751 src/logging/error_runtime.rs b2ed730006fdc080cf7eafc6d20bb89661c639631c89642d110e0c1319e431b5 src/logging/format.rs @@ -377,7 +384,7 @@ ee401f943bd8b2706c33419ed69102269c08dcb4ceba6f4be2ac0861115874a7 src/metrics/vh 9c96c3e4edb20dd0c36b5c373f1925dd60368e75853002206523072d418d5417 src/not_found/mod.rs fa2fc6c6987a8356a9da92cade10db62426d8863e9cef3c0d4324c45780c5b33 src/not_found/tests.rs d32d757ace8753eccb842c24b7af77061141478d10fe0acdfb05263d19e9a64c src/pages/mod.rs -0e973bf65f232b5de00f0f39b349a6fe9cec7aa1760184e2474e0c7bde52f7be src/pages/tests.rs +a3474253e83d1548bdafcb696a80f064283062196d665bb6316952355d54b785 src/pages/tests.rs 5650a3f70162abd97021e0a51a92cac2c4dd39004426ef99dff36cbb0741aeac src/prometheus.rs 192a780d5874afc158d62a7dbe1687d734635afe9342fff526b8a2f6db4e5633 src/prometheus/core.rs 2321ee667569803dfe6c92c0e98adba0ac8fcdc6a3eacd5857233f1acae93daf src/prometheus/docker_misc.rs @@ -389,22 +396,22 @@ cdbfa0e7f49200dc70ea6df8f0594399ee3e0c5fc3d279cc336e2d83d0f8a535 src/prometheus f1b0254f53b1a4336bd8170ad441ba072b49a114e497b985cb20699b4e375044 src/prometheus/web_php.rs 569eda61453affd053fbe24fa59652289cb12d22b77e64caa38325bbea48f7df src/proxy/body/mod.rs 5beeac12ca7f041bdb9b74b2dbc9781f5d5524fcf51294b8b6ad88284754ef20 src/proxy/body/tests.rs -e5e32d86780119dd5eec4c8a369cb1aea44904ab62646e04e458640a3e4358eb src/proxy/cache_helpers.rs +e97894a7ca8a3c4dd753c8d4d1bff20cfa50f8380d8864b8cd4197241c018040 src/proxy/cache_helpers.rs 78caad08c548386958f605f82f6c4299ab94a910a1332a1e0817dc53920c96a3 src/proxy/cache_http_invalidation.rs a86b10ce0707b51e925e11ea14f7f40fa108a196166f62cf40007a97f9e17203 src/proxy/cache_http_invalidation_tests.rs a03b1735daa8fa8e40cf5bd9c6e200d211baf19c30f62522d4237ed56642bce7 src/proxy/cache_plan/mod.rs -11f11fd8131023d7bfeceb6f4cbf169db994e2b6ca1931167f96ae590d8e0e01 src/proxy/cache_plan/tests.rs +62da703feff7e51bddc8998cb4d459f3ddbb2c109874285fa27fc920030b9129 src/proxy/cache_plan/tests.rs 3ba479d72d0e94263acaf2eb84f4de0ff2b0b455ee6a4d3f04bb869a5a30a103 src/proxy/error_fallback.rs aacc3249b3cbce0726dc16e28ee09d78efc4ac89103675287b1adab870bb4e15 src/proxy/forward/cache_fast_path_tests.rs f7903200756dc6393392560656b23364f0d444d6357d264713b17c63fd8b636b src/proxy/forward/cache_lookup.rs 287012c81144d8f15af4a148ce3ca911d431bc59adff44e697567561aa372bb3 src/proxy/forward/h3_navigation_tests.rs a6580dc359d1f377a48ccc1a902056dc5c2ef86c89180b9b0995068d9817ae8a src/proxy/forward/response.rs -1a7322aadbe90e2a6940a27882c13e274b1c92f14de3ebad717054a9614d4ba8 src/proxy/forward/route_cache.rs +afcb095b0511072b8cfc197e13f2cd84454ab699155fa964646b8ee60ef00f81 src/proxy/forward/route_cache.rs 0e6e288fb8a0575aaa4e3bcb5c47ed5412a584386a2b00fb25db8428d652cbf3 src/proxy/forward/success_finish.rs 744daef016d4a6c821ab12eeedaa2f29e2de72e2ac9dae16481d4f43476cb713 src/proxy/forward/success_prepare.rs e9ac9be64f6f83c4ca2cd5d299649f24dd16197712083c6dc10e55c662c65b80 src/proxy/forward/upstream.rs -690ff6a6860a7ca8ecd502657a68e5012b7f176036d6d7033dabc514735dc2f5 src/proxy/forward/web_source.rs -ffbddc99112148dd28b4a81ec0527f608ce89fb412a0a8e99e26942cef13f9b5 src/proxy/forward_entry.rs +a6fb336b22c011e7e0936c430d372d20bc77d48ddf0aa491da98843d15f1a126 src/proxy/forward/web_source.rs +b4121b28db988ad338c52321436bd276d1c69865d2c18d2c1d1eef1e5b86909d src/proxy/forward_entry.rs 9b25f5e3ea4443a63ee6f45d12e1957dd316fe8e85325fc00ea416faaab1025a src/proxy/h2_graceful.rs 684ea1e2a7591a17e3c633576fc66e2a30d6636738fbce61dab5af44f2c51a67 src/proxy/headers/authority_tests.rs 66efc52d64d34667ba7419f8a2da89d2efb1af1f426684f4fff8caccfdf19996 src/proxy/headers/mod.rs @@ -415,7 +422,7 @@ da9148ce6a60226954cc839f606465b8c16a44806f4d5b2e22ad60484509b967 src/proxy/redi de1763b89d98607e20f0f6e3c9b546483927cbe5450efc2e5634fe691fd52ad6 src/proxy/request_id/mod.rs 78bbc1263bcd600527bf71acdf5af92bf186267a9084b076d1f75ad6492df659 src/proxy/request_id/tests.rs 580fefdc7dfb27da73f86f4d2067d46a1f856d2f398b0598d37a743e880d3dac src/proxy/types.rs -37b20f962563babd3602d21ed6326946635281c0b0d97ca59f4e33de170ae93f src/proxy/upstream_helpers.rs +faf7296f045fb3a748fa1c21024a2fe54b692a415a59c15992ea54e032b8c0ce src/proxy/upstream_helpers.rs 3fb4896f2ddf9dad783005e34e6d4ca0032fb5a69ee38865b5e0ecc9d7f97f30 src/proxy/web_cache_revalidation/mod.rs 5522456cc85948228250feac92308d53b130c4a7af98d24f669aca4dfe6999b6 src/proxy/web_cache_revalidation/tests.rs 3d591201595a3b1e928ded20a8736477a47e5acc3bcbde158f7c17547ce155ba src/proxy/websocket.rs @@ -435,7 +442,7 @@ e53c40b85792d4804a432e3c97dc13af8e29304bb0d250ee9ab9018bde522eeb src/redirect/m eb2b0eeeb617cad704732d87acb667530144836e26edb0189929b1f25f35c6b5 src/runtime/docker.rs 18f7460e3738386e070f66932cd77240001255447a769d6e19a40573254a1473 src/runtime/external_config.rs 5999d7e91ee4231da88c5e9c6d8a12ea9f92d20d6dcdb24319f9f90891d7253b src/runtime/tests.rs -d7a65497d1faa2f09a09c0a0ef77b444990cfb5ab379c85f6c27c1f9c0c143cc src/runtime/types.rs +bab3044f4ad8957b65476a0c307bb8a364825b91d2100233b7bd6efa52e771be src/runtime/types.rs 11e99eb425887ad12b73d949f4b497fabc6897fee8fc7647fcb3766269c2fdc0 src/runtime/updates.rs e3dbd60953db5a976985dd796e4268b41a0f3df59764c4664015bd41e2578058 src/secret_store/mod.rs 6dca8da072a3bd610704a391bb6e25c9042971f4ebc60edb47665ce1d74cbc58 src/secret_store/tests.rs @@ -462,8 +469,8 @@ c02e85af45590866f20933561f85ea9b4f2ead62c2ce41db75113632670a7b5f src/stream/mod d9123c623e2a97d09e41edaa29993b4b2c8cba39a3abdf2b48a205aa9a9c2939 src/system_stats/linux/tests.rs 2c7da96822d605193f0208df70de670612259fd8c762c998ae828848cb210e11 src/system_stats/mod.rs 050aa831b1012c4aaade63863f433f01bfacf5d3f95787e57975572116a30ab2 src/system_stats/tests.rs -f0a1109bf7be0dd6a8734be9d47221d99ac993ebfe8f73508191369db69cebcf src/tinyauth/mod.rs -e2ab9180f25b1f3c2723ff5a40d35a5a395a188481b766f2730b18e07500a636 src/tinyauth/tests.rs +41ef9c9ed19a9a0c6742140abdb7c8b8d6d9edc0f828d0cb2ba54eec427696c8 src/tinyauth/mod.rs +3d40febfbaf38ca2725630eed6b33106e353086081ea811107e4ec9d24f7de11 src/tinyauth/tests.rs df98becda799b6bf4ff5e2fec7d0e0a486e6b7eca6f9f5ad49ffdafd49a4f498 src/tls.rs 2af2164ecdf2b7fd9eef7c6e358dec053dfac9aedaa501dbb75df742d18638b2 src/tls/acme_tests.rs 84e3da17c59cc90e8575af8158ef4134cd058446a186df675d60106c53dd55ec src/tls/certificates.rs @@ -499,10 +506,10 @@ ae880b40197454ea3fbf4d2113984dc372098b2e54b97e87c83cc319de253377 src/upstream/t 696a2783a24f7f3d69fb820dc3a772ec49ea46ee017087952a8253363a9b7374 src/user_agent/tests.rs d61c427c1e9cf457f1232b822d73e846e9f329cfbe61215e7ca061d53762155d src/vhost.rs 846707452ccf10962f1763be66b8f56c050b0398b48968015fb799dac289c0d6 src/vhost/error_fallback.rs -9d358be766f767d9e1a42f585edcf7fc1cba8f22e5eaac294a83920425ec03c7 src/vhost/model.rs +4147d7afbb8f518e774e966a98422b8c03f32a1e792fdf518c0d4e50ded6dcd6 src/vhost/model.rs fc8af7342a3b147921e9040116af5dfe680b3df910db2416ada18198bbeea7df src/vhost/redirect.rs dd6e121445402a2ac8744f0e4f23d879b4356f47c1bb00737bd15ba459661978 src/vhost/tests.rs -a69e6138d77ead1558eb9303c68dc317bdfaadbe297fe973d62d4dba41afae73 src/vhost/validation.rs +5e48267e62301a2fa3e5a6e9da49223949e73b4ae518fb77d0a62a44a6a8c6d5 src/vhost/validation.rs 8d1eff4b24f01d137414ef5419f75dd281879228adb70409645b11b3c78c20e9 src/web_server/config.rs 9744e8c29ae72a4d9fbc9a76166a97f8292c4c312295ea40d1cb699e03d80a9d src/web_server/config/compile.rs 5ed79fa5b9c53f95ca200d89a9ba927dda816b61fbdf3b852bd3c9670e3d2d4f src/web_server/config/model.rs diff --git a/docs/ACCESS.md b/docs/ACCESS.md index e3806f6..aa1c0d1 100644 --- a/docs/ACCESS.md +++ b/docs/ACCESS.md @@ -19,7 +19,7 @@ Global restrictions add to per-vhost and path rules. Rate-limit requests are cou 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 is for Basic Auth and Tinyauth only. 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. +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 selects exactly one of Basic Auth, Tinyauth, Authelia and authentik. 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 @@ -29,13 +29,42 @@ Ordinary Vhosts can reject selected MIME types under **Vhosts → Security → R 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. -## Basic Auth and Tinyauth +## Basic Auth, Tinyauth, Authelia and authentik - **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. -- **Tinyauth error pages (0.34.14):** RustProxy renders built-in, self-contained HTML for verifier failures: `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. +- **Provider-neutral gateway error pages (0.34.15):** 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`). -- The two authentication integrations should not be enabled simultaneously for the same Vhost. Protected responses bypass unsafe shared caching. Confirm that login redirections and WebSockets behave as expected. +- **Authelia (0.34.15):** select **Authelia** 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 (0.34.15):** select **authentik** 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. +- **Adding another provider:** Keep the new vendor's `Settings` and `Compiled` implementation under `src//mod.rs` and its tests under `src//tests.rs`. Implement the `auth_provider::Provider` trait for runtime authorization, optional `reserved_path`/`serve_reserved` hooks for same-host callbacks, and (for Nginx-compatible providers) the shared `forward_auth::Adapter`. Add an enabled-by-default-false Vhost settings field, validate mutual exclusivity in `auth_provider::Selection`, compile it into the **single** `CompiledFrontend.auth_provider` slot, and register the method in the panel. Reuse `auth_provider::deny` / `auth_provider::gateway_failure` instead of adding separate HTML templates. Existing `tinyauth`, `authelia`, and `authentik` configuration keys remain backward compatible. +- **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 of the four authentication methods 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: + +```json +"authelia": { + "enabled": true, + "url": "http://authelia.internal:9091", + "login_url": "https://auth.example.com", + "allow_private": true +} +``` + +authentik (same URL for the outpost check and its reserved public path): + +```json +"authentik": { + "enabled": true, + "url": "http://authentik.internal:9000", + "allow_private": true +} +``` + +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. diff --git a/docs/API.md b/docs/API.md index 2989d49..f317c1d 100644 --- a/docs/API.md +++ b/docs/API.md @@ -78,7 +78,7 @@ The section name in the URL and its corresponding JSON member in a full configur | `logging` | `logging`, `error_log` | | `global-security` | `global_security`, including `user_agent_blocking` | -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. +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. In 0.34.15 each ordinary Vhost may also configure mutually exclusive `authelia` and `authentik` objects alongside the existing `tinyauth`/`basic_auth` settings; both new providers default to disabled. See [Access](ACCESS.md). ### Complete section-update example diff --git a/docs/INTEGRATIONS.md b/docs/INTEGRATIONS.md index 8a2dcc3..5755875 100644 --- a/docs/INTEGRATIONS.md +++ b/docs/INTEGRATIONS.md @@ -5,7 +5,9 @@ RustProxy offers built-in integrations and optional helper components. Each has | Integration | Purpose | Setup and limits | |---|---|---| | **Docker agent** | Discover running Docker services and publish generated Vhosts | Install separate agent on Docker host; use Bearer token and TLS certificate pin. [Guide](DOCKER_DISCOVERY.md). | -| **Tinyauth** | Delegate authentication for selected Vhosts | Configure verifier URL/login URL, allowed headers and HTTPS. Failed verifier connections return a fail-closed local 502 page; verifier timeouts return 504. [Access](ACCESS.md). | +| **Tinyauth** | Delegate authentication for selected Vhosts | Existing Nginx auth-request support remains unchanged, with fail-closed 502/504 errors. [Access](ACCESS.md). | +| **Authelia** | Forward auth for selected Vhosts | Select it in Vhost Authentication, set internal service origin and trusted public HTTPS login URL, and configure the portal's per-domain cookie rules. [Access](ACCESS.md). | +| **authentik** | Forward auth for selected Vhosts, including unrelated application domains | Configure one authentik single-application proxy provider per public application and its serving outpost; enter the outpost's internal origin. RustProxy handles the reserved outpost path automatically. [Access](ACCESS.md). | | **ACME / Certbot** | Issue/renew HTTPS certificates | HTTP-01 or DNS-01 via Cloudflare, OVH or RFC2136; protect credentials. [Certificates](ACME_PROVIDERS.md). | | **Prometheus** | Scrape operational metrics | Separate endpoint/token/CIDR rules. [Monitoring](MONITORING.md). | | **Grafana** | Visualize Prometheus results | Example dashboard under `grafana/README.md`. | diff --git a/docs/README.md b/docs/README.md index eec442a..6a934f4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@ # RustProxy documentation -Practical guides for **RustProxy 0.34.14**. This index and the listed pages are also available in the authenticated panel at **`/docs → Reader`**. The adjacent **API** tab provides the embedded OpenAPI reference. Paths below are links to actual files in the source archive. +Practical guides for **RustProxy 0.34.15**. This index and the listed pages are also available in the authenticated panel at **`/docs → Reader`**. The adjacent **API** tab provides the embedded OpenAPI reference. Paths below are links to actual files in the source archive. ## Getting started @@ -13,7 +13,7 @@ Practical guides for **RustProxy 0.34.14**. This index and the listed pages are - [Vhosts and backends](VHOSTS.md) — domains, listener bindings, request Content-Type filtering, balancing, health and redirects. - [Custom locations](CUSTOM_LOCATIONS.md) — path routing, rewrites, local responses and error fallbacks. -- [Access and authentication](ACCESS.md) — ACL, country/DDNS, Basic Auth, Tinyauth and rate limits. +- [Access and authentication](ACCESS.md) — ACL, country/DDNS, Basic Auth, Tinyauth, Authelia, authentik and rate limits. - [Static Web Server](WEB_SERVER.md) — document roots, generated filesystem permission commands, downloads, rules, built-in error pages and file delivery. - [PHP-FPM](PHP_FPM.md) — PHP service profiles, security and application examples. - [Cache](CACHE.md) — HOT/COLD storage, privacy, TTL, RFC 9211 `Cache-Status`, origin diagnostic headers and invalidation. @@ -22,7 +22,7 @@ Practical guides for **RustProxy 0.34.14**. This index and the listed pages are - [Experimental TLS post-quantum key exchange](TLS_POST_QUANTUM.md) — opt-in X25519MLKEM768, compatibility, rollback and validation. - [TCP/UDP Streams](STREAMS.md) — non-HTTP forwarding and its security boundaries. -The Vhosts and Docker discovery guides include the shared adaptive request-rate chart scale introduced in 0.34.11. Version 0.34.12 preserves that adaptive range and adds a disabled-by-default X/Y scale display option under **Settings → Appearance → Charts**. Release 0.34.13 adds global HTTP security policies and UI layout improvements, groups the global User-Agent policy under `global_security`, provides consistent section-based GET/PUT APIs, and moves manual GeoIP refresh to `POST /api/config/geoip/update`. See [Dashboard](DASHBOARD.md). Release 0.34.14 keeps the Tinyauth Nginx-header compatibility patch, adds fail-closed branded Tinyauth 502/504 error pages, without changing the existing generic 502 page, error-code registry, or other standard error templates. See [Access](ACCESS.md) and [Troubleshooting](TROUBLESHOOTING.md). +The Vhosts and Docker discovery guides include the shared adaptive request-rate chart scale introduced in 0.34.11. Version 0.34.12 preserves that adaptive range and adds a disabled-by-default X/Y scale display option under **Settings → Appearance → Charts**. Release 0.34.13 adds global HTTP security policies and UI layout improvements, groups the global User-Agent policy under `global_security`, provides consistent section-based GET/PUT APIs, and moves manual GeoIP refresh to `POST /api/config/geoip/update`. See [Dashboard](DASHBOARD.md). Release 0.34.14 keeps the Tinyauth Nginx-header compatibility patch and dedicated fail-closed 502/504 pages. Release 0.34.15 adds optional Authelia and authentik forward-auth integrations, a shared provider runtime and a single provider-neutral HTML error template. See [Access](ACCESS.md) and [Troubleshooting](TROUBLESHOOTING.md). ## Integrations and operations diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index c71d420..7e27d57 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -14,6 +14,9 @@ Start with a reproducible request, its hostname/path and the exact listener. Com | `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 connection error` / `502 verification error` / `504 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. | diff --git a/grafana/rustproxy-overview.json b/grafana/rustproxy-overview.json index 6c84e2a..da4d141 100644 --- a/grafana/rustproxy-overview.json +++ b/grafana/rustproxy-overview.json @@ -6868,9 +6868,9 @@ }, "timepicker": {}, "timezone": "browser", - "title": "RustProxy 0.34.14 - Operations", + "title": "RustProxy 0.34.15 - Operations", "uid": "rustproxy-0170", "version": 7, "weekStart": "", - "description": "RustProxy 0.34.14 Prometheus operations dashboard: traffic and errors, vhosts and backends, Docker discovery, cache capacity and pressure, process and system diagnostics, HTTP/2, HTTP/3, TLS, WebSockets, Layer-4 streams, log sinks, Web Server and PHP-FPM. Some metrics are optional depending on exporter collector settings and enabled services." + "description": "RustProxy 0.34.15 Prometheus operations dashboard: traffic and errors, vhosts and backends, Docker discovery, cache capacity and pressure, process and system diagnostics, HTTP/2, HTTP/3, TLS, WebSockets, Layer-4 streams, log sinks, Web Server and PHP-FPM. Some metrics are optional depending on exporter collector settings and enabled services." } \ No newline at end of file diff --git a/langs/en_US.json b/langs/en_US.json index 2785613..cb723db 100644 --- a/langs/en_US.json +++ b/langs/en_US.json @@ -643,7 +643,6 @@ "certSelectHint": "Select an imported certificate. Uploads and rotation are in the Certificates tab. Assignments are validated against vhost names.", "vhTinyauth": "Authentication", "vhTabAuth": "Authentication", - "taEnable": "Enable Tinyauth protection", "taUrl": "Tinyauth URL / verification endpoint", "taLogin": "Public login URL", "taRedirectForbidden": "Accept same-origin provider redirects for 403", @@ -861,6 +860,13 @@ "authMode": "Authentication", "authNone": "None", "authTinyauth": "Tinyauth", + "authAuthelia": "Authelia", + "authAuthentik": "authentik", + "forwardAuthUrl": "Forward-auth service URL (internal)", + "forwardAuthLogin": "Authelia public login URL (HTTPS)", + "forwardAuthAdvanced": "Advanced provider settings", + "autheliaHelp": "Calls /api/authz/forward-auth. Select an internal verifier origin and a trusted public login URL. Protected apps can be on separate domains if Authelia is configured with matching session cookies and authentication URLs.", + "authentikHelp": "Calls /outpost.goauthentik.io/auth/nginx; RustProxy forwards the /outpost.goauthentik.io path to the same outpost, without requiring separate Custom Locations. Use an authentik Forward auth (single application) provider for each protected app, including apps on different domains.", "authBasic": "HTTP Basic Auth", "basicRealm": "Realm (login prompt)", "basicRequireTls": "Require HTTPS for Basic Auth", diff --git a/langs/pl_pl.json b/langs/pl_pl.json index 5a55091..d13d7e8 100644 --- a/langs/pl_pl.json +++ b/langs/pl_pl.json @@ -643,7 +643,6 @@ "certSelectHint": "Wybierz zaimportowany certyfikat. Import i rotacja są w zakładce Certyfikaty. Przypisania są sprawdzane względem nazw vhosta.", "vhTinyauth": "Uwierzytelnianie", "vhTabAuth": "Uwierzytelnianie", - "taEnable": "Włącz ochronę Tinyauth", "taUrl": "URL Tinyauth / punkt weryfikacji", "taLogin": "Publiczny URL logowania", "taRedirectForbidden": "Akceptuj przekierowanie Tinyauth dla 403 (ten sam origin)", @@ -861,6 +860,13 @@ "authMode": "Uwierzytelnianie", "authNone": "Brak", "authTinyauth": "Tinyauth", + "authAuthelia": "Authelia", + "authAuthentik": "authentik", + "forwardAuthUrl": "Adres usługi autoryzacji (wewnętrzny)", + "forwardAuthLogin": "Publiczny adres logowania Authelia (HTTPS)", + "forwardAuthAdvanced": "Zaawansowane ustawienia dostawcy", + "autheliaHelp": "Weryfikacja przez /api/authz/forward-auth. Podaj wewnętrzny adres Authelii i publiczny adres logowania HTTPS. Dla różnych domen skonfiguruj odpowiednie cookies i adresy portali w Authelii.", + "authentikHelp": "Weryfikacja przez /outpost.goauthentik.io/auth/nginx. RustProxy automatycznie przekazuje ścieżkę /outpost.goauthentik.io do outpostu, bez dodatkowych lokalizacji. Wybierz Forward auth (single application) dla każdej aplikacji, również na innych domenach.", "authBasic": "HTTP Basic Auth", "basicRealm": "Realm (nazwa logowania)", "basicRequireTls": "Wymagaj HTTPS dla Basic Auth", diff --git a/src/admin/openapi.json b/src/admin/openapi.json index 801c8c8..38c6adf 100644 --- a/src/admin/openapi.json +++ b/src/admin/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "RustProxy Administration API", - "version": "0.34.14", + "version": "0.34.15", "description": "Administration API for the active panel origin. Sign in through the panel; the same-origin HttpOnly cookie authenticates all private operations. Mutations also require the exact Origin, X-RustProxy-Request: 1 and a session-bound X-CSRF-Token. Loopback HTTP is the default; Settings can enable HTTPS before using a non-loopback administration bind. This document contains no live configuration or credentials. Object schemas marked extensible are summaries: full configuration validation is authoritative in Rust. The API also permits explicitly scoped GeneralApiBearer tokens on a narrow route allowlist, with RO/RW and source CIDR checks; API-token management and Prometheus-token management are browser-session-only." }, "servers": [ @@ -439,6 +439,24 @@ "id" ] }, + "ForwardAuthConfig": { + "type": "object", + "description": "Per-Vhost Authelia or authentik verification. Disabled by default. All other settings are optional and use RustProxy defaults; when enabled, url must identify an internal HTTP(S) origin (or expected verifier path). Authelia additionally requires a public HTTPS login_url. External addresses retain normal safe upstream validation, with TLS verification always enabled for HTTPS.", + "additionalProperties": false, + "properties": { + "enabled": {"type": "boolean", "default": false}, + "url": {"type": "string", "maxLength": 2048, "description": "Internal service/outpost origin or the provider's verifier endpoint"}, + "login_url": {"type": "string", "maxLength": 2048, "description": "Required public HTTPS portal URL for Authelia; ignored for authentik"}, + "allow_private": {"type": "boolean", "default": false}, + "address_allowlist": {"type": "array", "maxItems": 32, "items": {"type": "string"}}, + "tls_ca_pem": {"type": "string", "nullable": true}, + "connect_timeout_seconds": {"type": "integer", "minimum": 1, "maximum": 30, "default": 5}, + "timeout_seconds": {"type": "integer", "minimum": 1, "maximum": 60, "default": 10}, + "request_headers": {"type": "array", "maxItems": 32, "items": {"type": "string", "maxLength": 128}, "description": "Only cookie, authorization, user-agent, accept, accept-language and x-api-key are allowed"}, + "response_headers": {"type": "array", "maxItems": 32, "items": {"type": "string", "maxLength": 128}, "description": "Allowed Remote-*, X-Auth-Request-* and X-Authentik-* identity headers; total injected identity data capped at 8 KiB"}, + "redirect_forbidden": {"type": "boolean", "default": false, "description": "Authelia only: accept trusted portal Location on explicit 403 if provided"} + } + }, "Vhost": { "type": "object", "properties": { @@ -534,6 +552,14 @@ "properties": {}, "additionalProperties": true }, + "authelia": { + "$ref": "#/components/schemas/ForwardAuthConfig", + "description": "Authelia ForwardAuth. Requires a public HTTPS login_url and /api/authz/forward-auth verifier. Disabled by default; mutually exclusive with basic_auth, tinyauth and authentik." + }, + "authentik": { + "$ref": "#/components/schemas/ForwardAuthConfig", + "description": "authentik single-application forward auth. The URL is the internal outpost origin; RustProxy proxies its reserved path on the protected host. Disabled by default; mutually exclusive with basic_auth, tinyauth and authelia." + }, "cache": { "type": "object", "properties": { diff --git a/src/admin/status.rs b/src/admin/status.rs index 1c5e604..2a4173f 100644 --- a/src/admin/status.rs +++ b/src/admin/status.rs @@ -41,6 +41,8 @@ fn status_payload(state: &App) -> Value { "security_headers", "http3", "tinyauth", + "authelia_forward_auth", + "authentik_forward_auth", "trusted_forwarding", "named_listeners", "stream_proxy", diff --git a/src/admin/web/css/20-shell.css b/src/admin/web/css/20-shell.css index a005894..07daa97 100644 --- a/src/admin/web/css/20-shell.css +++ b/src/admin/web/css/20-shell.css @@ -500,6 +500,16 @@ main { font-size: 12px; } +.auth-provider-options { + display: flex; + flex-wrap: wrap; + gap: 7px 18px; +} + +.auth-provider-options>.vh-check { + flex: 1 1 250px; +} + .auth-advanced { border-top: 1px solid var(--line); padding-top: 4px; diff --git a/src/admin/web/error_tinyauth_connection.html b/src/admin/web/error_auth_provider.html similarity index 69% rename from src/admin/web/error_tinyauth_connection.html rename to src/admin/web/error_auth_provider.html index 7f114e6..efc4095 100644 --- a/src/admin/web/error_tinyauth_connection.html +++ b/src/admin/web/error_auth_provider.html @@ -4,7 +4,7 @@ - 502 - Tinyauth connection error - RustProxy + {{STATUS}} - {{PROVIDER}} {{ERROR}} - RustProxy - - - -
-

504 - Tinyauth timeout

-

The Tinyauth authentication service did not respond in time.

-
- - - \ No newline at end of file diff --git a/src/admin/web/error_tinyauth_verification.html b/src/admin/web/error_tinyauth_verification.html deleted file mode 100644 index 6208239..0000000 --- a/src/admin/web/error_tinyauth_verification.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - 502 - Tinyauth verification error - RustProxy - - - - -
-

502 - Tinyauth verification error

-

The Tinyauth authentication service returned an invalid response.

-
- - - \ No newline at end of file diff --git a/src/admin/web/vhosts/editor.js b/src/admin/web/vhosts/editor.js index 457ca10..bb1b8e5 100644 --- a/src/admin/web/vhosts/editor.js +++ b/src/admin/web/vhosts/editor.js @@ -33,6 +33,18 @@ function hostTagEditor(parent, hosts = [], onUpdate = () => { }) { return () => { if (input.value.trim()) consume(input.value); if (!values.length) throw new Error(t("vhHostRequired")); return [...values]; }; } +function readForwardProvider(id) { + const value = key => str(`${id}-${key}`); + return { + enabled: str("auth-mode") === id, + url: value("url"), login_url: id === "authelia" ? value("login") : "", + allow_private: yes(`${id}-private`), redirect_forbidden: yes(`${id}-redirect-forbidden`), + connect_timeout_seconds: num(`${id}-connect`), timeout_seconds: num(`${id}-timeout`), + request_headers: lines(value("request-headers")), response_headers: lines(value("response-headers")), + address_allowlist: lines(value("allowlist")), tls_ca_pem: nullable(`${id}-ca`), + }; +} + function load(value = defaults(), migrationIds = [], show = true) { draftEpoch++; stagedCertificates = []; uploading = false; backendHealthEditorControl = null; editing = value.id || null; revision = envelope?.revision ?? null; dirty = false; initialized = true; @@ -332,19 +344,18 @@ function load(value = defaults(), migrationIds = [], show = true) { const syncTlsPolicy = () => { for (const input of [tls12, tls13, tlsCiphers, tlsPqc]) input.disabled = !customTlsPolicy.checked; }; customTlsPolicy.addEventListener("change", syncTlsPolicy); syncTlsPolicy(); note(g, "vhTlsOverrideHelp"); note(g, "tlsPqcVhostHelp"); g = group("vhTinyauth"); const auth = value.tinyauth || {}; - const authMode = field(g, "auth-mode", "authMode", value.basic_auth?.enabled ? "basic" : auth.enabled ? "tinyauth" : "none", "select", { values: [["none", "authNone"], ["tinyauth", "authTinyauth"], ["basic", "authBasic"]] }); + const authMode = field(g, "auth-mode", "authMode", value.basic_auth?.enabled ? "basic" : auth.enabled ? "tinyauth" : value.authelia?.enabled ? "authelia" : value.authentik?.enabled ? "authentik" : "none", "select", { values: [["none", "authNone"], ["tinyauth", "authTinyauth"], ["authelia", "authAuthelia"], ["authentik", "authAuthentik"], ["basic", "authBasic"]] }); note(g, "authModeHelp"); const taBody = el("section", undefined, "vh-wide auth-subcard"), basicBody = el("section", undefined, "vh-wide auth-subcard"); g.append(taBody, basicBody); taBody.append(text("h3", "authTinyauth")); const taPrimary = el("div", undefined, "vh-grid"); taBody.append(taPrimary); - const authEnable = field(taPrimary, "auth-enabled", "taEnable", auth.enabled, "checkbox"); const authUrl = field(taPrimary, "auth-url", "taUrl", auth.url || "", "url", { maxLength: 2048 }); const loginUrl = field(taPrimary, "auth-login", "taLogin", auth.login_url || "", "url", { maxLength: 2048 }); authUrl.placeholder = "http://10.87.7.99:3000"; loginUrl.placeholder = "https://auth.example.com/login"; - field(taPrimary, "auth-private", "vhPrivate", auth.allow_private, "checkbox"); - field(taPrimary, "auth-redirect-forbidden", "taRedirectForbidden", auth.redirect_forbidden !== false, "checkbox"); - authEnable.closest("label").hidden = true; + const taOptions = el("div", undefined, "vh-wide auth-provider-options"); taPrimary.append(taOptions); + field(taOptions, "auth-private", "vhPrivate", auth.allow_private, "checkbox"); + field(taOptions, "auth-redirect-forbidden", "taRedirectForbidden", auth.redirect_forbidden !== false, "checkbox"); note(taBody, "taHelp"); const taAdvanced = el("details", undefined, "vh-wide auth-advanced"); taAdvanced.append(text("summary", "taAdvanced")); @@ -356,14 +367,49 @@ function load(value = defaults(), migrationIds = [], show = true) { field(taAdvancedGrid, "auth-allowlist", "vhAddressAllowlist", (auth.address_allowlist || []).join("\n"), "textarea", { rows: 2, maxLength: 8192 }); field(taAdvancedGrid, "auth-ca", "vhCa", auth.tls_ca_pem || "", "textarea", { rows: 2, maxLength: 65536 }); + const forwardEditors = {}; + for (const id of ["authelia", "authentik"]) { + const current = value[id] || {}, title = id === "authelia" ? "authAuthelia" : "authAuthentik"; + const body = el("section", undefined, "vh-wide auth-subcard"); g.append(body); + body.append(text("h3", title)); + const primary = el("div", undefined, "vh-grid"); body.append(primary); + const url = field(primary, `${id}-url`, "forwardAuthUrl", current.url || "", "url", { maxLength: 2048 }); + url.placeholder = id === "authelia" ? "http://authelia:9091" : "http://authentik:9000"; + let login = null; + if (id === "authelia") { + login = field(primary, `${id}-login`, "forwardAuthLogin", current.login_url || "", "url", { maxLength: 2048 }); + login.placeholder = "https://auth.example.com"; + } + const options = el("div", undefined, "vh-wide auth-provider-options"); primary.append(options); + field(options, `${id}-private`, "vhPrivate", current.allow_private, "checkbox"); + field(options, `${id}-redirect-forbidden`, "taRedirectForbidden", current.redirect_forbidden === true, "checkbox"); + note(body, id === "authelia" ? "autheliaHelp" : "authentikHelp"); + const advanced = el("details", undefined, "vh-wide auth-advanced"); advanced.append(text("summary", "forwardAuthAdvanced")); + const details = el("div", undefined, "vh-grid"); advanced.append(details); body.append(advanced); + field(details, `${id}-connect`, "taConnect", current.connect_timeout_seconds ?? 5, "number", { required: true, min: 1, max: 30 }); + field(details, `${id}-timeout`, "taTimeout", current.timeout_seconds ?? 10, "number", { required: true, min: 1, max: 60 }); + field(details, `${id}-request-headers`, "taRequestHeaders", (current.request_headers || ["cookie", "authorization", "user-agent", "accept", "accept-language"]).join("\n"), "textarea", { rows: 3, maxLength: 8192 }); + const autheliaHeaders = ["remote-user", "remote-groups", "remote-name", "remote-email"]; + const authentikHeaders = ["x-authentik-username", "x-authentik-email", "x-authentik-name", "x-authentik-groups", "x-authentik-uid", "x-authentik-entitlements"]; + field(details, `${id}-response-headers`, "taResponseHeaders", (current.response_headers || [...autheliaHeaders, ...authentikHeaders]).join("\n"), "textarea", { rows: 4, maxLength: 8192 }); + field(details, `${id}-allowlist`, "vhAddressAllowlist", (current.address_allowlist || []).join("\n"), "textarea", { rows: 2, maxLength: 8192 }); + field(details, `${id}-ca`, "vhCa", current.tls_ca_pem || "", "textarea", { rows: 2, maxLength: 65536 }); + forwardEditors[id] = { body, url, login }; + } + basicBody.append(text("h3", "authBasic")); const basicGrid = el("div", undefined, "vh-grid"); basicBody.append(basicGrid); basicEditor = basicAuthEditor(basicGrid, value.basic_auth || {}); const syncAuth = () => { - authEnable.checked = authMode.value === "tinyauth"; - taBody.hidden = !authEnable.checked; + const tinyauthSelected = authMode.value === "tinyauth"; + taBody.hidden = !tinyauthSelected; basicBody.hidden = authMode.value !== "basic"; - authUrl.required = loginUrl.required = authEnable.checked; + authUrl.required = loginUrl.required = tinyauthSelected; + for (const [id, editor] of Object.entries(forwardEditors)) { + editor.body.hidden = id !== authMode.value; + editor.url.required = id === authMode.value; + if (editor.login) editor.login.required = id === authMode.value; + } }; authMode.addEventListener("change", syncAuth); syncAuth(); g = group("vhResponsePolicy"); const server = value.server_header, diagnostics = value.diagnostic_headers; @@ -508,7 +554,9 @@ function collect() { content_type_policy: { enabled: yes("content-type-block-enabled"), blocked: lines(str("content-type-block-list")).map(value => value.toLowerCase()) }, server_header: str("server-mode") === "inherit" ? null : { mode: str("server-mode"), value: str("server-value") }, diagnostic_headers: yes("diag-inherit") ? null : { request_id: yes("diag-request_id") }, - tinyauth: { enabled: yes("auth-enabled"), url: str("auth-url"), login_url: str("auth-login"), allow_private: yes("auth-private"), redirect_forbidden: yes("auth-redirect-forbidden"), connect_timeout_seconds: num("auth-connect"), timeout_seconds: num("auth-timeout"), request_headers: lines(str("auth-request-headers")), response_headers: lines(str("auth-response-headers")), address_allowlist: lines(str("auth-allowlist")), tls_ca_pem: nullable("auth-ca") }, backends: members, routes: paths, acl, headers, + tinyauth: { enabled: str("auth-mode") === "tinyauth", url: str("auth-url"), login_url: str("auth-login"), allow_private: yes("auth-private"), redirect_forbidden: yes("auth-redirect-forbidden"), connect_timeout_seconds: num("auth-connect"), timeout_seconds: num("auth-timeout"), request_headers: lines(str("auth-request-headers")), response_headers: lines(str("auth-response-headers")), address_allowlist: lines(str("auth-allowlist")), tls_ca_pem: nullable("auth-ca") }, + authelia: readForwardProvider("authelia"), authentik: readForwardProvider("authentik"), + backends: members, routes: paths, acl, headers, load_balancing: { algorithm: str("algorithm"), slow_start_seconds: num("slow-start"), keepalive: { enabled: yes("keepalive"), idle_timeout_seconds: num("idle-timeout"), max_idle_per_host: num("max-idle"), tcp_keepalive_seconds: num("tcp-keepalive"), pool_recycle_requests: num("pool-recycle-requests"), pool_recycle_seconds: num("pool-recycle-seconds"), http1_max_buffer_bytes: num("http1-max-buffer") }, retry: { max_retries: num("retry-max"), retry_connect_errors: yes("retry-connect"), retry_timeouts: yes("retry-timeouts"), retry_statuses: str("retry-statuses").split(/[\s,]+/).filter(Boolean).map(value => Number(value)) }, diff --git a/src/admin/web/vhosts/render.js b/src/admin/web/vhosts/render.js index bf3ab73..8aecf36 100644 --- a/src/admin/web/vhosts/render.js +++ b/src/admin/web/vhosts/render.js @@ -1,5 +1,3 @@ -// Shared, anchored Vhost popover for backend quick-view and upstream HTTP -// protocol observations. Both consume existing live status without extra probes. let activeVhostBackendPopup = null; function closeVhostBackendPopup(returnFocus = false) { const current = activeVhostBackendPopup; @@ -390,6 +388,8 @@ function render() { const policy = el("div", undefined, "vhost-policy-pills"); if (v.basic_auth?.enabled) policy.append(el("small", t("authBasic"), "pill")); if (v.tinyauth?.enabled) policy.append(el("small", "Tinyauth", "pill")); + if (v.authelia?.enabled) policy.append(el("small", "Authelia", "pill")); + if (v.authentik?.enabled) policy.append(el("small", "authentik", "pill")); if (v.rate_limit?.enabled) policy.append(el("small", t("vhRateEnabled"), "pill")); if (v.acl_profile_id) policy.append(el("small", `${t("vhAclProfile")}: ${v.acl_profile_id}`, "pill")); if (v.force_ssl) policy.append(el("small", t("vhForceSsl"), "pill")); @@ -573,7 +573,6 @@ form.addEventListener("submit", async event => { const value = collect(), next = structuredClone(envelope.config); next.vhosts ||= []; const debugIssues = debugOriginIssues(value.backends); if (debugIssues.length && !(await appConfirm(`${t("vhDebugOriginMismatch")}\n\n${debugIssues.map(member => member.id).join(", ")}`, { danger: true }))) return; const oldVhost = (envelope.config.vhosts || []).find(v => v.id === editing); - // A previous acceptance must not silently authorize new hostnames or a different certificate. const certificateScope = item => JSON.stringify({ certificate: item?.certificate_id || null, hosts: [...(item?.frontend?.hosts || [])].map(v => v.toLowerCase().replace(/\.$/, "")).sort(), diff --git a/src/auth_provider/errors.rs b/src/auth_provider/errors.rs new file mode 100644 index 0000000..4e311bb --- /dev/null +++ b/src/auth_provider/errors.rs @@ -0,0 +1,160 @@ +use axum::response::{IntoResponse, Response}; +use http::{header, HeaderValue, StatusCode}; + +use super::AuthenticationResponse; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum GatewayFailure { + Connection, + Timeout, + Verification, +} + +pub fn deny(status: StatusCode) -> Response { + let body = format!( + "{} {}\n", + status.as_u16(), + status.canonical_reason().unwrap_or("Error") + ); + let mut response = (status, body).into_response(); + response.headers_mut().insert( + header::CACHE_CONTROL, + HeaderValue::from_static("private, no-store"), + ); + response.extensions_mut().insert(AuthenticationResponse); + response +} + +fn escape_html(raw: &str) -> String { + raw.replace('&', "&") + .replace('<', "<") + .replace('>', ">") + .replace('"', """) + .replace('\'', "'") + .replace('{', "{") + .replace('}', "}") +} + +pub fn gateway_failure(provider_name: &str, kind: GatewayFailure) -> Response { + let (status, label, message) = match kind { + GatewayFailure::Connection => ( + StatusCode::BAD_GATEWAY, + "connection error", + "Unable to connect to the", + ), + GatewayFailure::Timeout => (StatusCode::GATEWAY_TIMEOUT, "timeout", "The"), + GatewayFailure::Verification => (StatusCode::BAD_GATEWAY, "verification error", "The"), + }; + + let name = escape_html(provider_name); + let explanation = match kind { + GatewayFailure::Connection => format!("{message} {name} authentication service."), + GatewayFailure::Timeout => { + format!("{message} {name} authentication service did not respond in time.") + } + GatewayFailure::Verification => { + format!("{message} {name} authentication service returned an invalid response.") + } + }; + let html = include_str!("../admin/web/error_auth_provider.html") + .replace("{{STATUS}}", &status.as_u16().to_string()) + .replace("{{PROVIDER}}", &name) + .replace("{{ERROR}}", label) + .replace("{{MESSAGE}}", &explanation); + let mut response = (status, html).into_response(); + response.headers_mut().insert( + header::CONTENT_TYPE, + HeaderValue::from_static("text/html; charset=utf-8"), + ); + response.headers_mut().insert( + header::CACHE_CONTROL, + HeaderValue::from_static("private, no-store"), + ); + response.headers_mut().insert( + "x-content-type-options", + HeaderValue::from_static("nosniff"), + ); + response.extensions_mut().insert(AuthenticationResponse); + response +} + +#[cfg(test)] +mod tests { + use super::*; + use http_body_util::BodyExt; + + #[tokio::test] + async fn one_template_covers_every_provider_and_gateway_failure() { + for name in ["Tinyauth", "Authelia", "authentik", "Future Provider"] { + for (kind, status, description) in [ + ( + GatewayFailure::Connection, + StatusCode::BAD_GATEWAY, + "connection error", + ), + ( + GatewayFailure::Verification, + StatusCode::BAD_GATEWAY, + "verification error", + ), + ( + GatewayFailure::Timeout, + StatusCode::GATEWAY_TIMEOUT, + "timeout", + ), + ] { + let response = gateway_failure(name, kind); + assert_eq!(response.status(), status); + assert_eq!( + response.headers()[header::CONTENT_TYPE], + "text/html; charset=utf-8" + ); + assert_eq!( + response.headers()[header::CACHE_CONTROL], + "private, no-store" + ); + assert_eq!(response.headers()["x-content-type-options"], "nosniff"); + assert!(response + .extensions() + .get::() + .is_some()); + let body = response.into_body().collect().await.unwrap().to_bytes(); + let html = std::str::from_utf8(&body).unwrap(); + assert!(html.contains(&format!("{} - {name} {description}", status.as_u16()))); + assert!( + !html.contains("{{") + && !html.contains("alert(1)", GatewayFailure::Connection); + let html = String::from_utf8( + response + .into_body() + .collect() + .await + .unwrap() + .to_bytes() + .to_vec(), + ) + .unwrap(); + assert!(html.contains("<script>")); + assert!(!html.contains("