0.34.9
This commit is contained in:
1 parent
70388578cb
commit
2b7c67d755
22 files changed
+369
-59
No files matched your search
@@ -1,6 +1,6 @@
|
||||
# RustProxy source and documentation maintenance
|
||||
|
||||
Current source version: **0.34.8**. The Cargo package and binary are named `rust-proxy`.
|
||||
Current source version: **0.34.9**. 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.
|
||||
|
||||
|
||||
+7
-1
@@ -1,6 +1,12 @@
|
||||
# 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.8.
|
||||
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.9.
|
||||
|
||||
## 0.34.9 — experimental TLS post-quantum key exchange (source candidate)
|
||||
|
||||
- **Settings → TLS policy:** new disabled-by-default experimental `post_quantum` toggle. When enabled with TLS 1.3, inbound TCP TLS and HTTP/3 QUIC use an explicit aws-lc-rs provider, prioritizing the hybrid **X25519MLKEM768** key-exchange group while retaining classical alternatives. When disabled, the original ring provider is retained. This changes KEX, not TLS cipher suites or certificate signing; no change to upstream/client-side TLS providers.
|
||||
- **Per-vhost TLS override:** may opt out when global PQC is enabled; cannot enable PQC if the global policy disables it. TLS 1.3 is required. Existing per-vhost policies lacking the field continue to default to non-PQC. HTTP/3 continues to require exactly matching global policy.
|
||||
- **UI/docs/API:** English/Polish switch and key-exchange group readout, authenticated Reader guide linked from the TLS policy page, OpenAPI and regression unit tests.
|
||||
|
||||
## 0.34.8
|
||||
|
||||
|
||||
Generated
+47
-1
@@ -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"
|
||||
@@ -1881,7 +1925,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rust-proxy"
|
||||
version = "0.34.8"
|
||||
version = "0.34.9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
@@ -1970,6 +2014,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",
|
||||
@@ -2003,6 +2048,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",
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rust-proxy"
|
||||
version = "0.34.8"
|
||||
version = "0.34.9"
|
||||
edition = "2021"
|
||||
rust-version = "1.99"
|
||||
description = "RustProxy - independent reverse proxy with local administration"
|
||||
@@ -40,7 +40,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"] }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# RustProxy 0.34.8
|
||||
# RustProxy 0.34.9
|
||||
|
||||
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.
|
||||
|
||||
@@ -16,6 +16,10 @@ RustProxy combines a reverse proxy, static/PHP web server, certificate managemen
|
||||
|
||||
Features and default settings vary by component; configure only the capabilities you need. The [documentation index](docs/README.md) covers each workflow, including prerequisites, security boundaries, troubleshooting and API access.
|
||||
|
||||
## 0.34.9 — experimental PQC (source candidate)
|
||||
|
||||
- **Settings → TLS policy:** opt-in experimental TLS 1.3 hybrid post-quantum key exchange (X25519MLKEM768), using aws-lc-rs only for enabled frontend TLS policies. Classic key-exchange groups remain enabled for client compatibility. Off by default; cipher-suite choices remain separate. HTTP/3 and per-vhost TLS overrides follow the selected policy. Details and rollback: [TLS PQC guide](docs/TLS_POST_QUANTUM.md) (also in the authenticated panel Reader).
|
||||
|
||||
## 0.34.8
|
||||
|
||||
- **Docker vhosts:** automatic hostname uses the container name, without image or port suffix; prior auto-generated names are updated while custom names and aliases are preserved. Docker Agent **1.0.1**.
|
||||
@@ -103,4 +107,4 @@ The authenticated **`/docs`** page includes the guides and a separate interactiv
|
||||
|
||||
## Status of this source archive
|
||||
|
||||
Version **0.34.8** is provided as a **source release candidate**. Configuration, ACL and TLS changes require native compilation and integration testing on the target systems before a production deployment is declared. See [Installation](docs/INSTALL.md) and [Roadmap](ROADMAP.md).
|
||||
Version **0.34.9** is provided as a **source-only experimental candidate**. PQC dependencies were provisionally locked using published crate metadata, not checked by Cargo in the packaging environment. Run `cargo metadata --locked`, `cargo check --locked --all-targets`, `cargo test --locked --all-targets` and protocol compatibility checks on your target system before production deployment. See [Installation](docs/INSTALL.md) and [Roadmap](ROADMAP.md).
|
||||
+22
-21
@@ -1,12 +1,12 @@
|
||||
61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore
|
||||
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
|
||||
9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore
|
||||
e9aa7d23f867dabab4b254154dc81833511138f7679ef7d0583afcdff2ff231e AGENTS.md
|
||||
82fea9271a4c7a112334076c5c76b6bb2f354b362aef6fe7c7822d4477c50e6a CHANGELOG.md
|
||||
3bce5792c1588ec7de8d97239d7e9e3304ce21eb76c619f48e6e62577c6cfac5 Cargo.lock
|
||||
15821dc3b17d2da6d139b91a885269a817457d271fbeabb49c3d68bccc5efd22 Cargo.toml
|
||||
237ed6e2c0d4fa1f125180c349856749c13820b72593b5fc58e1b58cce47c840 AGENTS.md
|
||||
4f695dd4ded1ef0c6fbe421ae9bc01993140ddbb4f77cb7a46dd8191a2584ddb CHANGELOG.md
|
||||
310d60da6c7f501ccdde86b93ee1d5b0102033ba71ac9bb42a4e3ce184692ef9 Cargo.lock
|
||||
c18c0ba1a22a8534a4cf3b6db907f41f04e0b6cb47cd305a21af139a06e303fb Cargo.toml
|
||||
25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile
|
||||
ca24bbf88f886637f91735daba5a7eee83cac13dc3b49085ca9eb56cd102f789 README.md
|
||||
fb75f6ff1fee8e5af4c89943c67b7e75cbac048254cecc3dc9fd0ea33d53ac25 README.md
|
||||
ae6d14ada6fde7d0b8514840b821678418cf5b1816c18434e3b7fb2ae27a0b21 ROADMAP.md
|
||||
da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml
|
||||
7309cfaa188c7a1fcf30be1b8313ee99c00b7fe5d6d626ddefcc7345fa066112 build.rs
|
||||
@@ -44,11 +44,12 @@ f1baabe34e8a1b204d7c78e2f558c019c156b03adfa6ddecb931b4b8a5501f78 docs/INSTALL.m
|
||||
888cdc8c56e10b8a7615012e192e085bf2899d0ec9b3ddfb5731e7de6ab5bcde docs/MIGRATION.md
|
||||
140b95ad778261455f269f2144a1c61c2315bd3b85300fb613822eacddf3cfc2 docs/MONITORING.md
|
||||
d5894c07c68ec4230987767198434abee13b0e525842fea21bcbe752fa82742b docs/PHP_FPM.md
|
||||
0aa40fce348f18b4e9b319c25cd81d42f418502bba31511e349a8f82ab4992ce docs/README.md
|
||||
8b22a8f738f62cb27faf97683cc467de0fba02ab5999217b502f24a38f10ab4b docs/README.md
|
||||
490a6dbe870f2b1fd4ec7816768dde6c86fa7957fc9de2d2f075e123c9222222 docs/RESPONSE_DELIVERY.md
|
||||
e2b36152c13d65621a367c69dd400118dae1e4426a4d2b2139d2f63fa30442d6 docs/SECURITY.md
|
||||
bbef60ff1771da9f48af812987766e1e479408873ae2f079b13f1d5c62ecc6ff docs/STREAMS.md
|
||||
5bedc7d8b9fcaa80cacea225b1f6750101a7ac1b34eb9ce58310ec60329e380d docs/TRANSPORT.md
|
||||
11157c4bd43f51ea84ef7bde040cca3e818f2ada04a43b6bf1620c477abb53f7 docs/TLS_POST_QUANTUM.md
|
||||
43a2c6230f95f81bf4efd1575a4f7cdc8b02244df38e19b1ec9c3cd736d3afe6 docs/TRANSPORT.md
|
||||
8542c4a5c9d5dfe474459099546ae590b4c4d3f363f0bba9721c6107643c2d3a docs/TROUBLESHOOTING.md
|
||||
25b32d0d906333969df9e6a0a8c1b399cd34ad177b601219ba8ade8e164d3887 docs/VHOSTS.md
|
||||
05efc113d33be361bac25c90064b1865cf2f574dabd0eaa73bdddda229b2946e docs/WEB_SERVER.md
|
||||
@@ -76,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
|
||||
c04792fcc2e2e237b28e22d64f2db44964f26b779db09622f2162d0f068d5526 langs/en_US.json
|
||||
95564b46ad8526610dc04d5696b4f26d4c5b46fdbd38290c4850d57d45e90c49 langs/pl_pl.json
|
||||
9f83bd975c041421a42ecd00bedcfd3bd6285d9397412644a9375ffdf4bcff5a langs/en_US.json
|
||||
28f5738f0635a71b5a13cdc1647aa281c0d124ef12561ef28e9e51cbafbd2ee3 langs/pl_pl.json
|
||||
a99ad040be10fa028351e290b9363236b3513a1cadbdc9fb9e0260d517b9b0ae make_zip.py
|
||||
e830d37dd1f81a4b0aa7d535f5bba69e34e433b0fd99cc8ee0b296786c32379a migrations/0002_admin.sql
|
||||
20112cc82192098b8d602f1c4f7ca08712e9e1366c845fabb92753187c370d35 migrations/0003_certificate_renewals.sql
|
||||
@@ -135,15 +136,15 @@ a402209141667fdce69698a60dc0b867248effa191494d0a0d93d468d7df9476 src/admin/cert
|
||||
521ac794501570cfa2e8e50c3a5cec2ca08ee4dbce8ac9c0e0310c09612f1062 src/admin/config_reload.rs
|
||||
159c3970d25fa29acf2483fc9422b003e5d63884713f62baa8375e4a4020730e src/admin/dashboard_presets.rs
|
||||
aae1ca0e70d987830bd63948d431f54302083148177c1b551565be12df7fe771 src/admin/documentation.rs
|
||||
d4a249e8c2bf06a12fbd561fcdf5a5211aa5ed0e0dcd5111b84bbb13b3640c49 src/admin/documentation/documents.rs
|
||||
67b901485d5858d5b5bf78a6260035db32241531d99481754c3533fdf404facb src/admin/documentation/tests.rs
|
||||
da28dd57087df138913b784b71dca11d36c68100f2f100f452fbf4e4d7982433 src/admin/documentation/documents.rs
|
||||
67fa0e842f6789ed38e101ed6717072736e85c34af873d1a27d7efee5dc0c83d src/admin/documentation/tests.rs
|
||||
1ce779b84aa8f47bdb3c34f83593c1f2f06d2235f0319847096cfcd035434168 src/admin/file_log_search.rs
|
||||
ab18acf6fb4ac86fb22635b9219a12dac76d24952c8f15538486267a9b4b7855 src/admin/file_logs.rs
|
||||
d58a2ed83319176de4e66790ed292447c9497ae031464ec55cfe27174acb639a src/admin/file_logs_io.rs
|
||||
282352eda7957477e7da44f16b90984b26a4a67a8755833bc0a6b34eaf93e3af src/admin/file_logs_query.rs
|
||||
f66ff718f82a326f80808b0a5088bd05a48625be802e5ba305fff3c344b59948 src/admin/logging_api.rs
|
||||
1ba0520c6348246cbb35b60e596e99dc600137c0696de86508cf06587bd86563 src/admin/mod.rs
|
||||
7bce0ba5a9fe3d1de2b10a29e58d88300e09ffcce50e6d94e68240c78db944b8 src/admin/openapi.json
|
||||
8e1012a8ec776c3525310b03a70f274955dce9de3528109070ea11de0ff5c16c src/admin/openapi.json
|
||||
7cfd6593ac9bcea3d1b9e61bbcb7ed98d65ed3190f54f94c1e104541f4fbb4f5 src/admin/operations.rs
|
||||
f14afb6ae141053d83d0dc59df835b20e38a37a9e83f86f0f2053f2bd5d9ce57 src/admin/panel_assets.rs
|
||||
f5127855c2994d052678865df36d703b74f4a0afc633daf7718b10da18d26e37 src/admin/panel_content.rs
|
||||
@@ -155,7 +156,7 @@ f5127855c2994d052678865df36d703b74f4a0afc633daf7718b10da18d26e37 src/admin/pane
|
||||
6930b4f4b45257772b321b20a9becdf9bc4610274852948ba38c2d0cdf02dfaf src/admin/refresh_preferences.rs
|
||||
00b5dd3090792986217b28c69bf826748145129e5517b260d95019b1d7b2ebb2 src/admin/router.rs
|
||||
e135431cb078ddb483165581123c64af8f00ea67acb3a395ec3e53c85d0d0d45 src/admin/session.rs
|
||||
0c86d94b714cfff26e184541ed3bfcb317c88e45afe084d5f3b28558946dd9b3 src/admin/status.rs
|
||||
09cc50a86960897371730cc1abed83f4f640c86b680ec7ff6d6b84c7e96a3c0b src/admin/status.rs
|
||||
2d5b4c28fd0bd48b06890fa8ec44a2296de948c1f88eb2909f4b495710d4e13e src/admin/status_live.rs
|
||||
8315c856cd50f1072593ecb30c274d42d82f078b5fb93e7c68a8857cd81b0841 src/admin/status_system.rs
|
||||
cadd7dca29b9aad8c77c7bc23dd249ea37a15adfa4d636a8ef00273a91ce35dc src/admin/tail.rs
|
||||
@@ -183,7 +184,7 @@ fa00bce2651308a4e0b1303cc0146be17ed4794b67b2f0cebfaf861558d8c78c src/admin/web/
|
||||
8386710827e5ccc9432fb46413368f94afc1016425404a6092b460903636837e src/admin/web/app/stream-dashboard.js
|
||||
7f89bfae66e2a08a05a112733864e908f036bb1ee3d8b5b9ff23783b870acd96 src/admin/web/app/web-overview.js
|
||||
849b2ca9d183bdf6cb1e04651cda52da55b8a631e11ab1d827a052c8436bbd76 src/admin/web/cache-panel.js
|
||||
02a9f5a61379e68e1723e64ef4c589500e9a993adcb02fe352f1cf9456c90c52 src/admin/web/certificate-api.js
|
||||
e9788a9d1e0bce8775f612a5c842afea8a0dcf01301acdef4b69ff6a52d2ef45 src/admin/web/certificate-api.js
|
||||
560a8e047182fbcee08aa4f8d999400a8a5ddca0b6a4640efd46f6b5c34fde91 src/admin/web/dashboard-defaults.json
|
||||
14213011cfb6ac334c8711b76bbdde97588a7b7ba4eb2592a1e387f877d6cbe0 src/admin/web/dashboard-widgets.json
|
||||
50d1ebcbaf4d0b2968da23bb943fc3f67131c5d32df3f61d23ff37aad9da5b81 src/admin/web/demo.html
|
||||
@@ -207,7 +208,7 @@ e6c4d91d1542d0a5b47bb8736f9539d43a6713df5a473b635519d5b7eac2eb9c src/admin/web/
|
||||
0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg
|
||||
7cbdef9192a9dee1f8eac42b85f52b3190f7f6efae50004aa7ad116a910e4036 src/admin/web/file-logs.js
|
||||
b14e7b3578322f309627f6fe965cc16abcf85da3437c9070be6caecae1908743 src/admin/web/host404.js
|
||||
43ea73b9f4e87025aa5c74cbe6062bf419b44956ea929d9132088583d5bf2dde src/admin/web/index.html
|
||||
ede1f17067ac1622c66dfaa54bf7b7bbb0ce160ee883235be743ef5b2aa83243 src/admin/web/index.html
|
||||
7915a06cebb3dfa2c51833c326586920691ba4431b1f984429b039333f9c2a93 src/admin/web/integrations.js
|
||||
7a12f020fc587205694978ab41b660e9a80487d413fbf6e34fc525eabc58308f src/admin/web/login-keys.json
|
||||
cbd41678eadd994dc5252628ee8ef9551fa9f7d8694ca5abd206240d9a96ad30 src/admin/web/login.html
|
||||
@@ -217,14 +218,14 @@ c2e5f554810b8e34fb47361fb90a01073a2e332ee26d82e030956fad3ec1f83e src/admin/web/
|
||||
a8afc6e70950bc70b98725eb049d73cff35dc3a3ee659229a15a8e08d3eb4f12 src/admin/web/partials/overview-metrics.html
|
||||
3928afce2ed6d5f2d3e29475dd29e4c3bd3c6df180ad5119beaff0c7a58e3b3b src/admin/web/prometheus-token.js
|
||||
90d9a0836662135b79fb6f183c4f3eff60f8a64392d6c5795ccf1d8ef6862263 src/admin/web/redirects.js
|
||||
be9b78bee4ef0026608191d36a2aed831f93c1baaa5bc73da86bf09bc217ce76 src/admin/web/settings.js
|
||||
3ee4508cdda0b0b0e0f880ecac4b085368098b46240d24f2e6efed21c96bd5e9 src/admin/web/settings.js
|
||||
8eadbada619b15087783dcfa0e889a81f88e78034d084c5ab616f7912b835940 src/admin/web/status_200.html
|
||||
6feb1996b1caa9d19e0dc56eae7ca5b48b7e7cdf0a93bb2e4b527fb8f13a2fe5 src/admin/web/streams.js
|
||||
38716fae968aeb8f6a8510f28bb9054a73a438a969c3b69f9c9d921acfed6e06 src/admin/web/style.css
|
||||
d290c048450655b3eeae7c6ec54a82d645d1965513694ab29fdf7eef796a1abd src/admin/web/traffic-panel.js
|
||||
dfb4ded2fefb90fc1e2d33370cdef10b040a85cb43235ea9dbc1d73bef823a79 src/admin/web/transform-panel.js
|
||||
a55f45dad5b540def419f8487161fb506b7e52744c76a5e661c7a5fa0a98a426 src/admin/web/vhosts/core.js
|
||||
73925be3157bae23cd0a4058a596ac5eb7311f3cd719f7006974e4731c2d4843 src/admin/web/vhosts/editor.js
|
||||
d88d89c8071d2458aef278498a5a7863fb45c374fca801b0b3a9de402c455b4c 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
|
||||
@@ -302,7 +303,7 @@ fbea01fea1213d98aea812c1b78d5db289e09200b98ae514d742235cd3fa70e1 src/config/int
|
||||
75839458ddbc7480ec4492a95eeb67b617ba7d62f7b67e7a767a3453b74dbcbc src/config/route_policy_tests.rs
|
||||
40e9483d7b2450f55d29b46c408727f4e331b85ecda44fbd7b96bd9a09f02655 src/config/routing.rs
|
||||
dd9a6ad8f58e0cdd1ae74f3f972fee0ac14383cd8b8f518403b9a36c169f35bd src/config/snapshot.rs
|
||||
b7b0f851b4ca459fe8844a2e4f9cdcfc1135dec4c06f99beeea008c5b35c2ba5 src/config/tests.rs
|
||||
e943525706c51846adbac23d4a4f4b1a22bd20103b3004b7de1cd1e5e270ca50 src/config/tests.rs
|
||||
f35de0ee1516ece6bbfd2b2a7db7d1e0f5048b0ec47cf2a3765dffb799abfd93 src/config/transport_tests.rs
|
||||
eb04a8922da26e45c9b6005c0cdf1b1ddf2da3c3b680650b22a1d1f16d17a4b2 src/db.rs
|
||||
c290d0cb02dfa5d2c60ecabac1cfb20c8884e3d14cca1944906c6d8e677f1784 src/db/api_tokens.rs
|
||||
@@ -329,7 +330,7 @@ c1b49bef28d87712e4c46138e70b52c4bcfb1a00196579031fac043d0b4aaa7b src/geoip/upda
|
||||
9a912dad0b8d1e10410fd95d0da3684656092ef85cf8b23eda4ffe7fb92921d8 src/geoip/update_tests.rs
|
||||
61a2b01db9010f9debfb38b43a26bf588438a5e470dff938e4e54f9d7ba46732 src/headers/mod.rs
|
||||
e4f07bfb7eb75b6897ef812870caff158679a3046d64582428abe8c6df490dbe src/headers/tests.rs
|
||||
d7c764af29c132e3d8d1fae1bdbe6bf20a27046dc294f83761fea335726f2306 src/http3.rs
|
||||
b458510eb89ae8bf8fda84989073c6e64c69f2b12fde96c454b33f9df6a57df2 src/http3.rs
|
||||
0cc484bf0cc59a55a13e0e70435833a9a7a22ad7dfb5b9f86e059cf70a80cc2d src/http3/body.rs
|
||||
741972222414c280104acbcd8f8071bed9cbfcfa2f503c487c11c56f553f53cd src/http3/diagnostics.rs
|
||||
49ce4452ef7973cafa2e4071e2e14be0e16000e931a6501e4dc613652e49e230 src/http3/identity.rs
|
||||
@@ -450,8 +451,8 @@ df98becda799b6bf4ff5e2fec7d0e0a486e6b7eca6f9f5ad49ffdafd49a4f498 src/tls.rs
|
||||
2ff3c219f87bd82677045a4809d3d94921c5b230bbc2f3e7131b07548f73ea7e src/tls/external_paths.rs
|
||||
f88a6c8c2e35c3aa58b7763c71a5a2cec82a34f24478a98b0960b81659a1ec4c src/tls/external_paths_tests.rs
|
||||
7b4c0f58b3a1e1791bb3c682a001b78bfdbbdafdbc143db8063b3f22dc168616 src/tls/server_config.rs
|
||||
9d6f048e791a3b7152904d1728258d298ad9d34aa3f83d13baf1b09a6fb7601d src/tls/tests.rs
|
||||
d9d79edf5a8ef6e3b39a21d35dd5ec6e5a336047117918949e7ec6f5b20ed3f1 src/tls/types.rs
|
||||
68a29d90f1675c7d97953e50ebccd4fa21c47fd0a4d8a0838b436ee84399afe0 src/tls/tests.rs
|
||||
bf0ccec8909e678aaed84d358058b1fa865b38e09d10471b02fca76eea38633d src/tls/types.rs
|
||||
a535c8526b38de2990da7781e158b9ea70ef8c1f6fae0aac5ce6e9da21d3a309 src/traffic/mod.rs
|
||||
dc8a83fae47da0d86e6ce0a329cb5274233264df56eebdb56b9bf37cb7be2bb4 src/traffic/tests.rs
|
||||
65fcac840f8b5f1508213e3f374aa318b450b0f9f90ba827c5ea935b9c60bee7 src/transform/apply.rs
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
# RustProxy documentation
|
||||
|
||||
Practical guides for **RustProxy 0.34.8**. 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.9**. 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
|
||||
|
||||
@@ -19,6 +19,7 @@ Practical guides for **RustProxy 0.34.8**. This index and the listed pages are a
|
||||
- [Cache](CACHE.md) — HOT/COLD storage, privacy, TTL and invalidation.
|
||||
- [Response delivery](RESPONSE_DELIVERY.md) — compression, static transformations and response headers.
|
||||
- [HTTP transport](TRANSPORT.md) — HTTPS/H2/H3, WebSockets, origin protocols and timeouts.
|
||||
- [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.
|
||||
|
||||
## Integrations and operations
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# Experimental post-quantum TLS key exchange (PQC)
|
||||
|
||||
**Version:** RustProxy 0.34.9 (source candidate). **Default: off.** This is an experimental opt-in feature and not a production compatibility guarantee.
|
||||
|
||||
## What it does
|
||||
|
||||
Post-quantum cryptography (PQC) aims to protect key exchange from attackers with future quantum computing capabilities. **X25519MLKEM768** combines a conventional X25519 ephemeral key agreement with post-quantum **ML-KEM-768** in one TLS 1.3 *hybrid key-exchange group*. Both contributions protect the derived traffic secret. This is **not a TLS cipher suite**: AES-GCM or ChaCha20-Poly1305 and ordinary X.509 RSA/ECDSA certificates remain in use. It does not change certificate signatures to post-quantum algorithms, and does not make all parts of TLS quantum-safe.
|
||||
|
||||
When enabled, RustProxy uses the `aws-lc-rs` cryptography provider for selected incoming TLS policies, offering X25519MLKEM768 first and classical key-exchange groups as fallback. When disabled it keeps the existing `ring` TLS provider. The selected key exchange depends on client support and negotiation; turning the switch on does not imply every connection uses PQC. It does **not** change outbound/backend, update-check, certificate-automation or other HTTP client TLS settings.
|
||||
|
||||
For implementation details see [rustls defaults and PQC](https://rustls.dev/docs/rustls/manual/_05_defaults/index.html) and [X25519MLKEM768 rustls API](https://docs.rs/rustls/0.23.45/rustls/crypto/aws_lc_rs/kx_group/static.X25519MLKEM768.html).
|
||||
|
||||
## Enable
|
||||
|
||||
1. Make a configuration backup; ensure that **TLS 1.3** is enabled.
|
||||
2. Open **Settings → TLS policy** and enable **Experimental post-quantum key exchange (PQC)**. Keep your current **cipher suite** list unless you have a specific reason to change it. The KEX group is enabled by the switch, **not** by adding X25519MLKEM768 to the cipher-suite textarea.
|
||||
3. Select **Validate, save and apply**. The TLS status reports the configured key-exchange groups, not the group used by each individual connection.
|
||||
4. Test HTTPS, HTTP/1.1, HTTP/2 and (if enabled) HTTP/3 on staging with a client that supports hybrid KEX, and with older clients that use classical KEX.
|
||||
|
||||
`post_quantum` is stored on the global policy (the default is false):
|
||||
|
||||
```json
|
||||
{"tls": {"policy": {"tls12": true, "tls13": true, "cipher_suites": [], "post_quantum": true}}}
|
||||
```
|
||||
|
||||
This fragment illustrates the `tls` field; do not replace your full configuration with it.
|
||||
|
||||
### Per-vhost behavior
|
||||
|
||||
A Vhost **inherits** the global TLS policy by default. If it has an explicit `tls_policy` override, the override may disable PQC, but it cannot enable PQC while the global setting is disabled. Existing overrides that do not contain `post_quantum` are treated as `false`, even after you enable PQC globally. HTTP/3 Vhosts require their override, if any, to match the global TLS policy exactly, including PQC. Update or remove conflicting overrides before enabling PQC on H3.
|
||||
|
||||
The administration TLS listener also uses the global policy when administration HTTPS is enabled. Open connections keep their negotiated keys; only **new handshakes** use the updated policy.
|
||||
|
||||
## Test negotiated group
|
||||
|
||||
Requires **OpenSSL 3.5+** with `X25519MLKEM768` support:
|
||||
|
||||
```bash
|
||||
openssl s_client -connect example.com:443 -servername example.com \
|
||||
-tls1_3 -groups X25519MLKEM768 -brief
|
||||
```
|
||||
|
||||
Check for `Protocol version: TLSv1.3` and `Negotiated TLS1.3 group: X25519MLKEM768` (format depends on OpenSSL). Compare with a classic-only client using `-groups X25519` and test TLS 1.2 if it is enabled. For HTTP/3, also use an actual QUIC/H3 client and test with UDP blocked to verify fallback to H2/H1; `openssl s_client` does not test QUIC.
|
||||
|
||||
If your TLS connection ends at a CDN, load balancer or TLS-terminating proxy, the test shows **that intermediary**, not RustProxy. To check RustProxy, connect to its listener directly while preserving SNI, and verify its actual certificate.
|
||||
|
||||
## Caveats and rollback
|
||||
|
||||
- Hybrid handshakes are larger and can hit MTU/middlebox limits, especially over QUIC. Measure connection errors, handshake latency, memory and CPU under expected load. Some legacy clients and devices may reject hybrid negotiation.
|
||||
- Enabling the switch changes the incoming TLS cryptographic provider to `aws-lc-rs`; some uncommon certificates, algorithms or hardware platforms may behave differently. `aws-lc-rs` requires a native C/C++ build toolchain; CMake may be needed for some build modes (non-FIPS commonly does not require it). The `ring` provider remains used where the switch is off and for existing outbound integrations.
|
||||
- A non-empty cipher-suite allowlist must contain suites supported by the selected provider; invalid combinations are rejected. PQC requires TLS 1.3.
|
||||
- For immediate rollback, **uncheck PQC in Settings → TLS policy and save**, and if necessary remove per-Vhost overrides that require PQC. Confirm new connections negotiate classic X25519. Existing sessions cannot renegotiate in place; close/reconnect them to retest. Keep the prior binary and backups available for a full rollback.
|
||||
- **Release verification:** this 0.34.9 source candidate has not undergone native Cargo build, TLS handshake, QUIC interoperability or production load testing. Verify the provisionally updated `Cargo.lock` with `cargo metadata --locked` (and refresh it with Cargo if needed), then run `cargo check --all-targets`, `cargo test --all-targets`, `cargo build --release`, and protocol tests before deployment. See the release changelog and source verification notes.
|
||||
@@ -55,6 +55,8 @@ WebSockets normally use HTTP/1.1 Upgrade. HTTP/2/HTTP/3 client requests can stil
|
||||
|
||||
## TLS and trust
|
||||
|
||||
**Experimental (0.34.9):** Settings → TLS policy has a disabled-by-default **Post-quantum key exchange (PQC)** switch for hybrid TLS 1.3 X25519MLKEM768. This is a TLS key-exchange group, not a cipher suite. See [Post-quantum TLS](TLS_POST_QUANTUM.md) for prerequisites, fallback, HTTP/3 and rollback.
|
||||
|
||||
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.
|
||||
|
||||
## Timeouts, request bodies and streaming
|
||||
|
||||
@@ -991,6 +991,13 @@
|
||||
"tls13": "TLS 1.3",
|
||||
"tlsPolicy": "TLS policy",
|
||||
"tlsPolicyHelp": "This is the global TLS policy inherited by every TLS host in the application. Per-vhost policy may only narrow it. HTTP/3 requires TLS 1.3.",
|
||||
"tlsPqcLabel": "Experimental post-quantum key exchange (PQC)",
|
||||
"tlsPqcHelp": "TLS 1.3 only: enable hybrid X25519MLKEM768; classic key exchange remains available for older clients. Key exchange groups are not cipher suites. Disabled by default.",
|
||||
"tlsPqcDocs": "What is PQC? Compatibility, risks and testing (documentation)",
|
||||
"tlsPqcKeyExchangeGroups": "Available key exchange groups",
|
||||
"tlsPqcTls13Required": "PQC requires TLS 1.3 to be enabled.",
|
||||
"tlsPqcGlobalRequired": "Enable PQC in Settings > TLS policy before enabling it for a Vhost.",
|
||||
"tlsPqcVhostHelp": "The global PQC switch must be on. A Vhost override can disable PQC; HTTP/3 requires the Vhost TLS policy to exactly match the global policy.",
|
||||
"tlsCiphers": "Cipher suites (one per line; empty = rustls defaults)",
|
||||
"tlsAvailableCiphers": "Available cipher suites",
|
||||
"tlsPolicyVersionRequired": "Enable at least one TLS version.",
|
||||
|
||||
@@ -991,6 +991,13 @@
|
||||
"tls13": "TLS 1.3",
|
||||
"tlsPolicy": "Polityka TLS",
|
||||
"tlsPolicyHelp": "To globalna polityka TLS dziedziczona przez każdy host TLS w aplikacji. Ustawienia per-vhost mogą ją tylko zawężać. HTTP/3 wymaga TLS 1.3.",
|
||||
"tlsPqcLabel": "Eksperymentalna postkwantowa wymiana kluczy (PQC)",
|
||||
"tlsPqcHelp": "Tylko TLS 1.3: włącza hybrydową grupę X25519MLKEM768; starsze klienty mogą używać klasycznych grup. Grupa wymiany kluczy nie jest szyfrem TLS. Domyślnie wyłączone.",
|
||||
"tlsPqcDocs": "Czym jest PQC? Zgodność, ryzyka i testowanie (dokumentacja)",
|
||||
"tlsPqcKeyExchangeGroups": "Dostępne grupy wymiany kluczy",
|
||||
"tlsPqcTls13Required": "PQC wymaga włączonego TLS 1.3.",
|
||||
"tlsPqcGlobalRequired": "Najpierw włącz PQC w Ustawienia > Polityka TLS.",
|
||||
"tlsPqcVhostHelp": "Globalny przełącznik PQC musi być włączony. Nadpisanie w vhoście może wyłączyć PQC; przy HTTP/3 polityka musi być identyczna z globalną.",
|
||||
"tlsCiphers": "Szyfry (jeden na linię; puste = domyślne rustls)",
|
||||
"tlsAvailableCiphers": "Dostępne szyfry",
|
||||
"tlsPolicyVersionRequired": "Włącz co najmniej jedną wersję TLS.",
|
||||
|
||||
@@ -24,6 +24,7 @@ pub(super) static READER_DOCUMENTS: &[(&str, &str, &str)] = &[
|
||||
reader_doc!("docs/CACHE.md", "guides"),
|
||||
reader_doc!("docs/RESPONSE_DELIVERY.md", "guides"),
|
||||
reader_doc!("docs/TRANSPORT.md", "guides"),
|
||||
reader_doc!("docs/TLS_POST_QUANTUM.md", "guides"),
|
||||
reader_doc!("docs/STREAMS.md", "guides"),
|
||||
reader_doc!("docs/ACME_PROVIDERS.md", "guides"),
|
||||
reader_doc!("docs/DOCKER_DISCOVERY.md", "guides"),
|
||||
|
||||
@@ -6,13 +6,22 @@ mod tests {
|
||||
let value: Value = serde_json::from_slice(reader_bundle()).unwrap();
|
||||
let documents = value["documents"].as_array().unwrap();
|
||||
assert!(documents.iter().any(|doc| doc["path"] == "README.md"));
|
||||
assert!(documents.iter().any(|doc| doc["path"] == "docs/WEB_SERVER.md"));
|
||||
assert!(documents
|
||||
.iter()
|
||||
.any(|doc| doc["path"] == "docs/WEB_SERVER.md"));
|
||||
assert!(documents.iter().any(|doc| doc["path"] == "docs/PHP_FPM.md"));
|
||||
assert!(documents
|
||||
.iter()
|
||||
.any(|doc| doc["path"] == "docs/TLS_POST_QUANTUM.md"));
|
||||
assert!(documents.iter().all(|doc| {
|
||||
doc["path"].as_str().is_some_and(|value| !value.is_empty())
|
||||
&& doc["title"].as_str().is_some_and(|value| !value.is_empty())
|
||||
&& doc["category"].as_str().is_some_and(|value| !value.is_empty())
|
||||
&& doc["content"].as_str().is_some_and(|value| !value.is_empty())
|
||||
&& doc["category"]
|
||||
.as_str()
|
||||
.is_some_and(|value| !value.is_empty())
|
||||
&& doc["content"]
|
||||
.as_str()
|
||||
.is_some_and(|value| !value.is_empty())
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
+35
-2
@@ -2,7 +2,7 @@
|
||||
"openapi": "3.0.3",
|
||||
"info": {
|
||||
"title": "RustProxy Administration API",
|
||||
"version": "0.34.8",
|
||||
"version": "0.34.9",
|
||||
"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": [
|
||||
@@ -622,6 +622,11 @@
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"post_quantum": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Experimental hybrid X25519MLKEM768 TLS 1.3 key exchange. Can only be enabled for a Vhost when enabled in the global TLS policy. HTTP/3 Vhosts must match the global policy."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -772,7 +777,35 @@
|
||||
},
|
||||
"tls": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"properties": {
|
||||
"policy": {
|
||||
"type": "object",
|
||||
"description": "Global TLS policy inherited by frontends, including experimental post-quantum key exchange.",
|
||||
"properties": {
|
||||
"tls12": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"tls13": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"cipher_suites": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "TLS cipher suites, independent of key exchange groups."
|
||||
},
|
||||
"post_quantum": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Experimental hybrid X25519MLKEM768 TLS 1.3 key exchange. Requires tls13=true, uses aws-lc-rs for frontend TLS; classic groups remain as fallback."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
},
|
||||
"cache": {
|
||||
|
||||
+3
-2
@@ -223,8 +223,9 @@ fn status_payload(state: &App) -> Value {
|
||||
});
|
||||
payload["tls"] = json!({
|
||||
"policy": &snapshot.envelope.config.tls.policy,
|
||||
"cipher_suites": crate::tls::cipher_suite_names(),
|
||||
"effective_cipher_suites": snapshot.envelope.config.tls.policy.effective_cipher_suites()
|
||||
"cipher_suites": crate::tls::cipher_suite_names_for(snapshot.envelope.config.tls.policy.post_quantum),
|
||||
"effective_cipher_suites": snapshot.envelope.config.tls.policy.effective_cipher_suites(),
|
||||
"key_exchange_groups": snapshot.envelope.config.tls.policy.key_exchange_groups()
|
||||
});
|
||||
payload
|
||||
}
|
||||
|
||||
@@ -3071,6 +3071,19 @@
|
||||
<label><span data-i18n="tlsCiphers">Cipher suites (one per line; empty = rustls
|
||||
defaults)</span><textarea id="tls-policy-ciphers" rows="8"
|
||||
spellcheck="false"></textarea></label>
|
||||
<div class="tls-pqc-control">
|
||||
<label class="check"><input id="tls-policy-pqc" type="checkbox" />
|
||||
<span data-i18n="tlsPqcLabel">Experimental post-quantum key exchange
|
||||
(PQC)</span>
|
||||
</label>
|
||||
<p class="fine" data-i18n="tlsPqcHelp">TLS 1.3 only: prioritize hybrid
|
||||
X25519MLKEM768 with classic key exchanges as fallback. This is a key exchange
|
||||
group, not a cipher suite. Disabled by default.</p>
|
||||
<a href="/docs#doc=docs%2FTLS_POST_QUANTUM.md" target="_blank"
|
||||
rel="noopener noreferrer" data-i18n="tlsPqcDocs">Learn about PQC, compatibility
|
||||
and testing (documentation)</a>
|
||||
</div>
|
||||
<p class="fine" id="tls-policy-key-exchange"></p>
|
||||
<p class="fine tls-policy-effective" id="tls-policy-effective"></p>
|
||||
<p class="fine" id="tls-policy-available"></p>
|
||||
<div class="form-actions"><button class="primary" data-i18n="apply"
|
||||
|
||||
@@ -244,9 +244,11 @@
|
||||
]) { const node = $(id); if (node) node.textContent = value || t("unavailable"); }
|
||||
|
||||
const policy = envelope.config.tls?.policy || { tls12: true, tls13: true, cipher_suites: [] };
|
||||
if (!settingsDirty.has("tls-policy-form")) { $("tls-policy-12").checked = policy.tls12 !== false; $("tls-policy-13").checked = policy.tls13 !== false; $("tls-policy-ciphers").value = (policy.cipher_suites || []).join("\n"); }
|
||||
if (!settingsDirty.has("tls-policy-form")) { $("tls-policy-12").checked = policy.tls12 !== false; $("tls-policy-13").checked = policy.tls13 !== false; $("tls-policy-ciphers").value = (policy.cipher_suites || []).join("\n"); $("tls-policy-pqc").checked = policy.post_quantum === true; }
|
||||
const available = latestStatus?.tls?.cipher_suites || [];
|
||||
const effective = latestStatus?.tls?.effective_cipher_suites || [];
|
||||
const kxGroups = latestStatus?.tls?.key_exchange_groups || [];
|
||||
$("tls-policy-key-exchange").textContent = kxGroups.length ? `${t("tlsPqcKeyExchangeGroups")}: ${kxGroups.join(", ")}` : "";
|
||||
$("tls-policy-effective").textContent = effective.length ? `${t("tlsEffectiveCiphers")}: ${effective.join(", ")}` : "";
|
||||
$("tls-policy-available").textContent = available.length ? `${t("tlsAvailableCiphers")}: ${available.join(", ")}` : "";
|
||||
window.rustProxyCertificateApi?.render(); window.rustProxyCertificateApi?.load();
|
||||
@@ -287,7 +289,8 @@
|
||||
}, "administration");
|
||||
bindSettingsForm("tls-policy-form", next => {
|
||||
if (!$("tls-policy-12").checked && !$("tls-policy-13").checked) throw fieldError($("tls-policy-12"), t("tlsPolicyVersionRequired"));
|
||||
next.tls ||= {}; next.tls.policy = { tls12: $("tls-policy-12").checked, tls13: $("tls-policy-13").checked, cipher_suites: $("tls-policy-ciphers").value.split(/[\n,]+/).map(v => v.trim()).filter(Boolean) };
|
||||
if ($("tls-policy-pqc").checked && !$("tls-policy-13").checked) throw fieldError($("tls-policy-13"), t("tlsPqcTls13Required"));
|
||||
next.tls ||= {}; next.tls.policy = { tls12: $("tls-policy-12").checked, tls13: $("tls-policy-13").checked, cipher_suites: $("tls-policy-ciphers").value.split(/[\n,]+/).map(v => v.trim()).filter(Boolean), post_quantum: $("tls-policy-pqc").checked };
|
||||
}, "tls");
|
||||
$("admin-tls").addEventListener("change", () => { $("admin-certificate").disabled = !$("admin-tls").checked; });
|
||||
$("prometheus-form").addEventListener("input", example);
|
||||
|
||||
@@ -328,8 +328,9 @@ function load(value = defaults(), migrationIds = [], show = true) {
|
||||
const tls12 = field(g, "tls-policy-12", "tls12", tlsPolicy.tls12 !== false, "checkbox");
|
||||
const tls13 = field(g, "tls-policy-13", "tls13", tlsPolicy.tls13 !== false, "checkbox");
|
||||
const tlsCiphers = field(g, "tls-policy-ciphers", "tlsCiphers", (tlsPolicy.cipher_suites || []).join("\n"), "textarea", { rows: 7, maxLength: 8192 });
|
||||
const syncTlsPolicy = () => { for (const input of [tls12, tls13, tlsCiphers]) input.disabled = !customTlsPolicy.checked; };
|
||||
customTlsPolicy.addEventListener("change", syncTlsPolicy); syncTlsPolicy(); note(g, "vhTlsOverrideHelp");
|
||||
const tlsPqc = field(g, "tls-policy-pqc", "tlsPqcLabel", tlsPolicy.post_quantum === true, "checkbox");
|
||||
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"]] });
|
||||
note(g, "authModeHelp");
|
||||
@@ -485,19 +486,21 @@ function collect() {
|
||||
if (!tlsPorts.has(443) && tlsPorts.size !== 1) throw new Error(t("vhForceSslPortConflict"));
|
||||
}
|
||||
if (yes("tls-policy-custom") && !yes("tls-policy-12") && !yes("tls-policy-13")) throw new Error(t("tlsPolicyVersionRequired"));
|
||||
if (yes("tls-policy-custom") && yes("tls-policy-pqc") && !yes("tls-policy-13")) throw new Error(t("tlsPqcTls13Required"));
|
||||
if (yes("tls-policy-custom") && yes("tls-policy-pqc") && envelope?.config.tls?.policy?.post_quantum !== true) throw new Error(t("tlsPqcGlobalRequired"));
|
||||
const selectedUsesHttp3 = bindingSelection.some(id => {
|
||||
const endpoint = envelope?.config.listeners?.endpoints?.find(item => item.id === id);
|
||||
return !!endpoint?.binding?.http3;
|
||||
});
|
||||
if (yes("http3-enabled") && selectedUsesHttp3 && yes("tls-policy-custom")) {
|
||||
const selected = { tls12: yes("tls-policy-12"), tls13: yes("tls-policy-13"), cipher_suites: lines(str("tls-policy-ciphers")).sort() };
|
||||
const selected = { tls12: yes("tls-policy-12"), tls13: yes("tls-policy-13"), cipher_suites: lines(str("tls-policy-ciphers")).sort(), post_quantum: yes("tls-policy-pqc") };
|
||||
const inherited = envelope?.config.tls?.policy || { tls12: true, tls13: true, cipher_suites: [] };
|
||||
const globalPolicy = { tls12: inherited.tls12 !== false, tls13: inherited.tls13 !== false, cipher_suites: [...(inherited.cipher_suites || [])].sort() };
|
||||
const globalPolicy = { tls12: inherited.tls12 !== false, tls13: inherited.tls13 !== false, cipher_suites: [...(inherited.cipher_suites || [])].sort(), post_quantum: inherited.post_quantum === true };
|
||||
if (JSON.stringify(selected) !== JSON.stringify(globalPolicy)) throw new Error(t("vhTlsHttp3Conflict"));
|
||||
}
|
||||
if (yes("content-type-block-enabled") && !lines(str("content-type-block-list")).length) throw new Error(t("vhContentTypeRequired"));
|
||||
return {
|
||||
id: str("id"), enabled: yes("enabled"), source: mainSourceType === "web_server" ? { type: "web_server", web_service_id: str("source-web-service") } : { type: "reverse_proxy" }, error_fallback: mainFallbackTarget ? { target: mainFallbackTarget, statuses: mainFallbackStatuses.length ? mainFallbackStatuses : [404] } : null, force_ssl: yes("force-ssl"), www_redirect: yes("www-redirect"), www_redirect_target: str("www-redirect-target") || null, forward_client_ip: yes("forward-client-ip"), websockets: yes("websockets"), websocket_legacy_support: yes("websockets") && yes("websocket-legacy-support"), websocket_rfc8441: yes("websockets") && ["h2", "h2h3"].includes(str("websocket-mode")), websocket_rfc9220: yes("websockets") && ["h3", "h2h3"].includes(str("websocket-mode")), http2_enabled: yes("http2-enabled"), http3_enabled: yes("http3-enabled"), cloudflare_client_ip: yes("cloudflare-client-ip"), timing_headers: yes("timing-headers"), backend_header: yes("backend-header"), backend_timing_header: yes("backend-timing-header"), basic_auth: basicEditor.read(), certificate_id: nullable("certificate-id"), allow_certificate_mismatch: yes("allow-certificate-mismatch"), tls_policy: yes("tls-policy-custom") ? { tls12: yes("tls-policy-12"), tls13: yes("tls-policy-13"), cipher_suites: lines(str("tls-policy-ciphers")) } : null, cache: collectCache(), compression: collectCompression(), transform: collectTransform(), pages: pagesEditor.read(), frontend: { hosts: readHosts(), listener_ids: bindingSelection }, acl_profile_id: null,
|
||||
id: str("id"), enabled: yes("enabled"), source: mainSourceType === "web_server" ? { type: "web_server", web_service_id: str("source-web-service") } : { type: "reverse_proxy" }, error_fallback: mainFallbackTarget ? { target: mainFallbackTarget, statuses: mainFallbackStatuses.length ? mainFallbackStatuses : [404] } : null, force_ssl: yes("force-ssl"), www_redirect: yes("www-redirect"), www_redirect_target: str("www-redirect-target") || null, forward_client_ip: yes("forward-client-ip"), websockets: yes("websockets"), websocket_legacy_support: yes("websockets") && yes("websocket-legacy-support"), websocket_rfc8441: yes("websockets") && ["h2", "h2h3"].includes(str("websocket-mode")), websocket_rfc9220: yes("websockets") && ["h3", "h2h3"].includes(str("websocket-mode")), http2_enabled: yes("http2-enabled"), http3_enabled: yes("http3-enabled"), cloudflare_client_ip: yes("cloudflare-client-ip"), timing_headers: yes("timing-headers"), backend_header: yes("backend-header"), backend_timing_header: yes("backend-timing-header"), basic_auth: basicEditor.read(), certificate_id: nullable("certificate-id"), allow_certificate_mismatch: yes("allow-certificate-mismatch"), tls_policy: yes("tls-policy-custom") ? { tls12: yes("tls-policy-12"), tls13: yes("tls-policy-13"), cipher_suites: lines(str("tls-policy-ciphers")), post_quantum: yes("tls-policy-pqc") } : null, cache: collectCache(), compression: collectCompression(), transform: collectTransform(), pages: pagesEditor.read(), frontend: { hosts: readHosts(), listener_ids: bindingSelection }, acl_profile_id: null,
|
||||
acl_profile_ids: window.rustProxyAclPicker.read(form.querySelector(".vh-acl-profile-choices")),
|
||||
proxy_timeouts: { connect_timeout_seconds: num("proxy-connect-timeout"), response_header_timeout_seconds: num("proxy-header-timeout"), read_timeout_seconds: num("proxy-read-timeout"), send_timeout_seconds: num("proxy-send-timeout") },
|
||||
rate_limit: { enabled: yes("rate-enabled"), requests: num("rate-requests"), window_seconds: num("rate-window"), burst: num("rate-burst"), country_access: { mode: str("country-mode"), countries: lines(str("country-list")).map(value => value.toUpperCase()) }, country_limits: countryRatesReader(), user_agent_limits: uaRatesReader() },
|
||||
|
||||
@@ -749,10 +749,27 @@ mod tests {
|
||||
tls12: true,
|
||||
tls13: false,
|
||||
cipher_suites: vec![],
|
||||
post_quantum: false,
|
||||
});
|
||||
assert!(compile(c, 1, &policy()).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pqc_vhost_override_requires_global_opt_in_and_can_opt_out() {
|
||||
let mut config = demo_config("127.0.0.1:9000".parse().unwrap());
|
||||
config.vhosts[0].tls_policy = Some(crate::tls::ProtocolPolicy {
|
||||
post_quantum: true,
|
||||
..crate::tls::ProtocolPolicy::default()
|
||||
});
|
||||
assert!(compile(config.clone(), 1, &policy()).is_err());
|
||||
config.tls.policy.post_quantum = true;
|
||||
assert!(compile(config.clone(), 1, &policy()).is_ok());
|
||||
config.vhosts[0].tls_policy = Some(crate::tls::ProtocolPolicy::default());
|
||||
assert!(compile(config.clone(), 1, &policy()).is_ok());
|
||||
config.tls.policy.post_quantum = false;
|
||||
assert!(compile(config, 1, &policy()).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn persisted_remote_administration_requires_tls() {
|
||||
let mut c = demo_config("127.0.0.1:9000".parse().unwrap());
|
||||
|
||||
+1
-10
@@ -34,16 +34,7 @@ pub fn crypto(
|
||||
policy.allows_http3(),
|
||||
"HTTP/3 requires TLS 1.3 and a TLS 1.3 cipher suite"
|
||||
);
|
||||
let mut provider = rustls::crypto::ring::default_provider();
|
||||
if !policy.cipher_suites.is_empty() {
|
||||
provider.cipher_suites.retain(|suite| {
|
||||
let name = format!("{:?}", suite.suite());
|
||||
policy
|
||||
.cipher_suites
|
||||
.iter()
|
||||
.any(|selected| selected == &name)
|
||||
});
|
||||
}
|
||||
let provider = crate::tls::provider_for(policy)?;
|
||||
let mut quic = rustls::ServerConfig::builder_with_provider(Arc::new(provider))
|
||||
.with_protocol_versions(&[&rustls::version::TLS13])?
|
||||
.with_no_client_auth()
|
||||
|
||||
+64
-1
@@ -186,6 +186,7 @@ mod tests {
|
||||
tls12: true,
|
||||
tls13: false,
|
||||
cipher_suites: Vec::new(),
|
||||
post_quantum: false,
|
||||
},
|
||||
..Settings::default()
|
||||
};
|
||||
@@ -199,6 +200,7 @@ mod tests {
|
||||
tls12: false,
|
||||
tls13: false,
|
||||
cipher_suites: vec![],
|
||||
post_quantum: false,
|
||||
}
|
||||
.validate()
|
||||
.is_err());
|
||||
@@ -214,6 +216,7 @@ mod tests {
|
||||
tls12: true,
|
||||
tls13: false,
|
||||
cipher_suites: vec![tls13.clone()],
|
||||
post_quantum: false,
|
||||
}
|
||||
.validate()
|
||||
.is_err());
|
||||
@@ -223,6 +226,7 @@ mod tests {
|
||||
tls12: false,
|
||||
tls13: true,
|
||||
cipher_suites: vec![tls12.clone()],
|
||||
post_quantum: false,
|
||||
}
|
||||
.validate()
|
||||
.is_err());
|
||||
@@ -231,11 +235,13 @@ mod tests {
|
||||
tls12: true,
|
||||
tls13: false,
|
||||
cipher_suites: vec![],
|
||||
post_quantum: false,
|
||||
};
|
||||
assert!(ProtocolPolicy {
|
||||
tls12: true,
|
||||
tls13: false,
|
||||
cipher_suites: vec![]
|
||||
cipher_suites: vec![],
|
||||
post_quantum: false,
|
||||
}
|
||||
.validate_narrower_than(&global)
|
||||
.is_ok());
|
||||
@@ -243,6 +249,63 @@ mod tests {
|
||||
.validate_narrower_than(&global)
|
||||
.is_err());
|
||||
}
|
||||
#[test]
|
||||
fn pqc_is_opt_in_and_not_a_cipher_suite() {
|
||||
let legacy: ProtocolPolicy = serde_json::from_value(serde_json::json!({
|
||||
"tls12": true, "tls13": true, "cipher_suites": []
|
||||
}))
|
||||
.unwrap();
|
||||
assert!(!legacy.post_quantum);
|
||||
assert!(serde_json::to_value(&legacy)
|
||||
.unwrap()
|
||||
.get("post_quantum")
|
||||
.is_none());
|
||||
let mut pqc = legacy.clone();
|
||||
pqc.post_quantum = true;
|
||||
assert!(pqc.validate().is_ok());
|
||||
let groups = pqc.key_exchange_groups();
|
||||
assert_eq!(groups.first().map(String::as_str), Some("X25519MLKEM768"));
|
||||
assert!(groups.iter().any(|name| name == "X25519"));
|
||||
assert!(!legacy
|
||||
.key_exchange_groups()
|
||||
.contains(&"X25519MLKEM768".into()));
|
||||
assert!(!pqc
|
||||
.effective_cipher_suites()
|
||||
.iter()
|
||||
.any(|suite| suite.contains("MLKEM")));
|
||||
assert!(provider_for(&pqc).unwrap().kx_groups.len() > 1);
|
||||
assert!(serde_json::to_value(&pqc).unwrap()["post_quantum"] == true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pqc_policy_requires_tls13_and_global_permission() {
|
||||
let legacy = ProtocolPolicy::default();
|
||||
let mut pqc = legacy.clone();
|
||||
pqc.post_quantum = true;
|
||||
assert!(pqc.validate_narrower_than(&legacy).is_err());
|
||||
assert!(legacy.validate_narrower_than(&pqc).is_ok());
|
||||
pqc.tls13 = false;
|
||||
assert!(pqc.validate().is_err());
|
||||
pqc.tls13 = true;
|
||||
pqc.cipher_suites = vec!["X25519MLKEM768".into()];
|
||||
assert!(pqc.validate().is_err()); // KEX name is not a cipher suite
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pqc_builds_both_tcp_tls_and_quic_configs() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
initialize_default(dir.path()).unwrap();
|
||||
let mut settings = Settings::default();
|
||||
settings.policy.post_quantum = true;
|
||||
let tls = compile(&settings, Some(dir.path())).unwrap().unwrap();
|
||||
assert!(tls.alpn_protocols.contains(&b"h2".to_vec()));
|
||||
let h3 = crate::http3::crypto(&tls, &settings.policy);
|
||||
assert!(h3.is_ok(), "hybrid HTTP/3 TLS config must build");
|
||||
let mut tls12_only = settings.policy.clone();
|
||||
tls12_only.tls13 = false;
|
||||
assert!(crate::http3::crypto(&tls, &tls12_only).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn administration_tls_supports_default_and_selected_certificate() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
|
||||
+54
-5
@@ -52,6 +52,12 @@ pub struct ProtocolPolicy {
|
||||
pub tls12: bool,
|
||||
pub tls13: bool,
|
||||
pub cipher_suites: Vec<String>,
|
||||
#[serde(default, skip_serializing_if = "is_false")]
|
||||
pub post_quantum: bool,
|
||||
}
|
||||
|
||||
fn is_false(enabled: &bool) -> bool {
|
||||
!*enabled
|
||||
}
|
||||
impl Default for ProtocolPolicy {
|
||||
fn default() -> Self {
|
||||
@@ -59,6 +65,7 @@ impl Default for ProtocolPolicy {
|
||||
tls12: true,
|
||||
tls13: true,
|
||||
cipher_suites: Vec::new(),
|
||||
post_quantum: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,11 +75,15 @@ impl ProtocolPolicy {
|
||||
self.tls12 || self.tls13,
|
||||
"at least one TLS protocol version must be enabled"
|
||||
);
|
||||
ensure!(
|
||||
!self.post_quantum || self.tls13,
|
||||
"post-quantum key exchange requires TLS 1.3"
|
||||
);
|
||||
ensure!(
|
||||
self.cipher_suites.len() <= 16,
|
||||
"at most 16 TLS cipher suites may be selected"
|
||||
);
|
||||
let available = cipher_suite_names();
|
||||
let available = cipher_suite_names_for(self.post_quantum);
|
||||
let mut seen = HashSet::new();
|
||||
for suite in &self.cipher_suites {
|
||||
ensure!(
|
||||
@@ -97,7 +108,7 @@ impl ProtocolPolicy {
|
||||
}
|
||||
pub fn effective_cipher_suites(&self) -> Vec<String> {
|
||||
let selected = if self.cipher_suites.is_empty() {
|
||||
cipher_suite_names()
|
||||
cipher_suite_names_for(self.post_quantum)
|
||||
} else {
|
||||
self.cipher_suites.clone()
|
||||
};
|
||||
@@ -120,6 +131,10 @@ impl ProtocolPolicy {
|
||||
pub fn validate_narrower_than(&self, global: &Self) -> Result<()> {
|
||||
self.validate()?;
|
||||
global.validate()?;
|
||||
ensure!(
|
||||
!self.post_quantum || global.post_quantum,
|
||||
"per-vhost post-quantum key exchange must be enabled globally"
|
||||
);
|
||||
ensure!(
|
||||
!self.tls12 || global.tls12,
|
||||
"per-vhost TLS policy cannot enable TLS 1.2 when it is disabled globally"
|
||||
@@ -144,17 +159,51 @@ impl ProtocolPolicy {
|
||||
}
|
||||
}
|
||||
|
||||
fn base_provider(post_quantum: bool) -> rustls::crypto::CryptoProvider {
|
||||
if !post_quantum {
|
||||
return rustls::crypto::ring::default_provider();
|
||||
}
|
||||
let mut provider = rustls::crypto::aws_lc_rs::default_provider();
|
||||
let hybrid = rustls::crypto::aws_lc_rs::kx_group::X25519MLKEM768;
|
||||
provider
|
||||
.kx_groups
|
||||
.retain(|group| group.name() != hybrid.name());
|
||||
provider.kx_groups.insert(0, hybrid);
|
||||
provider
|
||||
}
|
||||
|
||||
pub fn cipher_suite_names() -> Vec<String> {
|
||||
rustls::crypto::ring::default_provider()
|
||||
base_provider(false)
|
||||
.cipher_suites
|
||||
.iter()
|
||||
.map(|suite| format!("{:?}", suite.suite()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn provider_for(policy: &ProtocolPolicy) -> Result<rustls::crypto::CryptoProvider> {
|
||||
pub fn cipher_suite_names_for(post_quantum: bool) -> Vec<String> {
|
||||
if !post_quantum {
|
||||
return cipher_suite_names();
|
||||
}
|
||||
base_provider(post_quantum)
|
||||
.cipher_suites
|
||||
.iter()
|
||||
.map(|suite| format!("{:?}", suite.suite()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl ProtocolPolicy {
|
||||
pub fn key_exchange_groups(&self) -> Vec<String> {
|
||||
base_provider(self.post_quantum)
|
||||
.kx_groups
|
||||
.iter()
|
||||
.map(|group| format!("{:?}", group.name()))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn provider_for(policy: &ProtocolPolicy) -> Result<rustls::crypto::CryptoProvider> {
|
||||
policy.validate()?;
|
||||
let mut provider = rustls::crypto::ring::default_provider();
|
||||
let mut provider = base_provider(policy.post_quantum);
|
||||
if !policy.cipher_suites.is_empty() {
|
||||
provider.cipher_suites.retain(|suite| {
|
||||
let name = format!("{:?}", suite.suite());
|
||||
|
||||
Reference in new issue
Block a user