This commit is contained in:
Mateusz Gruszczyński committed 2026-10-22 23:43:19 +02:00
1 parent 70388578cb
commit 2b7c67d755
22 files changed
+369 -59

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
# RustProxy source and documentation maintenance # 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. 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
View File
@@ -1,6 +1,12 @@
# Changelog # 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 ## 0.34.8
Generated
+47 -1
View File
@@ -202,6 +202,29 @@ version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" 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]] [[package]]
name = "axum" name = "axum"
version = "0.8.9" version = "0.8.9"
@@ -488,6 +511,15 @@ version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486"
[[package]]
name = "cmake"
version = "0.1.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "cmov" name = "cmov"
version = "0.5.4" version = "0.5.4"
@@ -669,6 +701,12 @@ version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
[[package]]
name = "dunce"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
[[package]] [[package]]
name = "either" name = "either"
version = "1.18.0" version = "1.18.0"
@@ -769,6 +807,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "fs_extra"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
[[package]] [[package]]
name = "futures-channel" name = "futures-channel"
version = "0.3.34" version = "0.3.34"
@@ -1881,7 +1925,7 @@ dependencies = [
[[package]] [[package]]
name = "rust-proxy" name = "rust-proxy"
version = "0.34.8" version = "0.34.9"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"arc-swap", "arc-swap",
@@ -1970,6 +2014,7 @@ version = "0.23.45"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634"
dependencies = [ dependencies = [
"aws-lc-rs",
"once_cell", "once_cell",
"ring", "ring",
"rustls-pki-types", "rustls-pki-types",
@@ -2003,6 +2048,7 @@ version = "0.103.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2"
dependencies = [ dependencies = [
"aws-lc-rs",
"ring", "ring",
"rustls-pki-types", "rustls-pki-types",
"untrusted", "untrusted",
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "rust-proxy" name = "rust-proxy"
version = "0.34.8" version = "0.34.9"
edition = "2021" edition = "2021"
rust-version = "1.99" rust-version = "1.99"
description = "RustProxy - independent reverse proxy with local administration" description = "RustProxy - independent reverse proxy with local administration"
@@ -40,7 +40,7 @@ url = "2.5"
base64 = "0.22" base64 = "0.22"
sha1 = "0.10" 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" ring = "0.17"
tokio-rustls = { version = "0.26", default-features = false, features = ["ring", "tls12"] } tokio-rustls = { version = "0.26", default-features = false, features = ["ring", "tls12"] }
hyper-rustls = { version = "0.27", default-features = false, features = ["ring", "http1", "http2", "tls12"] } hyper-rustls = { version = "0.27", default-features = false, features = ["ring", "http1", "http2", "tls12"] }
+6 -2
View File
@@ -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. 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. 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 ## 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**. - **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 ## 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
View File
@@ -1,12 +1,12 @@
61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore 61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example 4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore 9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore
e9aa7d23f867dabab4b254154dc81833511138f7679ef7d0583afcdff2ff231e AGENTS.md 237ed6e2c0d4fa1f125180c349856749c13820b72593b5fc58e1b58cce47c840 AGENTS.md
82fea9271a4c7a112334076c5c76b6bb2f354b362aef6fe7c7822d4477c50e6a CHANGELOG.md 4f695dd4ded1ef0c6fbe421ae9bc01993140ddbb4f77cb7a46dd8191a2584ddb CHANGELOG.md
3bce5792c1588ec7de8d97239d7e9e3304ce21eb76c619f48e6e62577c6cfac5 Cargo.lock 310d60da6c7f501ccdde86b93ee1d5b0102033ba71ac9bb42a4e3ce184692ef9 Cargo.lock
15821dc3b17d2da6d139b91a885269a817457d271fbeabb49c3d68bccc5efd22 Cargo.toml c18c0ba1a22a8534a4cf3b6db907f41f04e0b6cb47cd305a21af139a06e303fb Cargo.toml
25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile 25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile
ca24bbf88f886637f91735daba5a7eee83cac13dc3b49085ca9eb56cd102f789 README.md fb75f6ff1fee8e5af4c89943c67b7e75cbac048254cecc3dc9fd0ea33d53ac25 README.md
ae6d14ada6fde7d0b8514840b821678418cf5b1816c18434e3b7fb2ae27a0b21 ROADMAP.md ae6d14ada6fde7d0b8514840b821678418cf5b1816c18434e3b7fb2ae27a0b21 ROADMAP.md
da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml
7309cfaa188c7a1fcf30be1b8313ee99c00b7fe5d6d626ddefcc7345fa066112 build.rs 7309cfaa188c7a1fcf30be1b8313ee99c00b7fe5d6d626ddefcc7345fa066112 build.rs
@@ -44,11 +44,12 @@ f1baabe34e8a1b204d7c78e2f558c019c156b03adfa6ddecb931b4b8a5501f78 docs/INSTALL.m
888cdc8c56e10b8a7615012e192e085bf2899d0ec9b3ddfb5731e7de6ab5bcde docs/MIGRATION.md 888cdc8c56e10b8a7615012e192e085bf2899d0ec9b3ddfb5731e7de6ab5bcde docs/MIGRATION.md
140b95ad778261455f269f2144a1c61c2315bd3b85300fb613822eacddf3cfc2 docs/MONITORING.md 140b95ad778261455f269f2144a1c61c2315bd3b85300fb613822eacddf3cfc2 docs/MONITORING.md
d5894c07c68ec4230987767198434abee13b0e525842fea21bcbe752fa82742b docs/PHP_FPM.md d5894c07c68ec4230987767198434abee13b0e525842fea21bcbe752fa82742b docs/PHP_FPM.md
0aa40fce348f18b4e9b319c25cd81d42f418502bba31511e349a8f82ab4992ce docs/README.md 8b22a8f738f62cb27faf97683cc467de0fba02ab5999217b502f24a38f10ab4b docs/README.md
490a6dbe870f2b1fd4ec7816768dde6c86fa7957fc9de2d2f075e123c9222222 docs/RESPONSE_DELIVERY.md 490a6dbe870f2b1fd4ec7816768dde6c86fa7957fc9de2d2f075e123c9222222 docs/RESPONSE_DELIVERY.md
e2b36152c13d65621a367c69dd400118dae1e4426a4d2b2139d2f63fa30442d6 docs/SECURITY.md e2b36152c13d65621a367c69dd400118dae1e4426a4d2b2139d2f63fa30442d6 docs/SECURITY.md
bbef60ff1771da9f48af812987766e1e479408873ae2f079b13f1d5c62ecc6ff docs/STREAMS.md bbef60ff1771da9f48af812987766e1e479408873ae2f079b13f1d5c62ecc6ff docs/STREAMS.md
5bedc7d8b9fcaa80cacea225b1f6750101a7ac1b34eb9ce58310ec60329e380d docs/TRANSPORT.md 11157c4bd43f51ea84ef7bde040cca3e818f2ada04a43b6bf1620c477abb53f7 docs/TLS_POST_QUANTUM.md
43a2c6230f95f81bf4efd1575a4f7cdc8b02244df38e19b1ec9c3cd736d3afe6 docs/TRANSPORT.md
8542c4a5c9d5dfe474459099546ae590b4c4d3f363f0bba9721c6107643c2d3a docs/TROUBLESHOOTING.md 8542c4a5c9d5dfe474459099546ae590b4c4d3f363f0bba9721c6107643c2d3a docs/TROUBLESHOOTING.md
25b32d0d906333969df9e6a0a8c1b399cd34ad177b601219ba8ade8e164d3887 docs/VHOSTS.md 25b32d0d906333969df9e6a0a8c1b399cd34ad177b601219ba8ade8e164d3887 docs/VHOSTS.md
05efc113d33be361bac25c90064b1865cf2f574dabd0eaa73bdddda229b2946e docs/WEB_SERVER.md 05efc113d33be361bac25c90064b1865cf2f574dabd0eaa73bdddda229b2946e docs/WEB_SERVER.md
@@ -76,8 +77,8 @@ cb01fdef286f38b442dd9252c23c4f8e44eb72e9ecdd99adb926e8e9f6318bb1 integrations/r
96563a83e87cfb33e238c2a970ce36b1a1b51e91318d09b123c3755002ba2646 integrations/routeros-oci-build/build-routeros-oci.sh 96563a83e87cfb33e238c2a970ce36b1a1b51e91318d09b123c3755002ba2646 integrations/routeros-oci-build/build-routeros-oci.sh
3f267cafa16006e5456ade78ee2d5fae02cc7ce747839523068ffd8d7c98798a integrations/routeros-oci-build/docker-entrypoint.sh 3f267cafa16006e5456ade78ee2d5fae02cc7ce747839523068ffd8d7c98798a integrations/routeros-oci-build/docker-entrypoint.sh
13189fa546c3488dddd8efc80c47f6da5241e5fc28b79cbfe795414ea05c7a4f langs/README.md 13189fa546c3488dddd8efc80c47f6da5241e5fc28b79cbfe795414ea05c7a4f langs/README.md
c04792fcc2e2e237b28e22d64f2db44964f26b779db09622f2162d0f068d5526 langs/en_US.json 9f83bd975c041421a42ecd00bedcfd3bd6285d9397412644a9375ffdf4bcff5a langs/en_US.json
95564b46ad8526610dc04d5696b4f26d4c5b46fdbd38290c4850d57d45e90c49 langs/pl_pl.json 28f5738f0635a71b5a13cdc1647aa281c0d124ef12561ef28e9e51cbafbd2ee3 langs/pl_pl.json
a99ad040be10fa028351e290b9363236b3513a1cadbdc9fb9e0260d517b9b0ae make_zip.py a99ad040be10fa028351e290b9363236b3513a1cadbdc9fb9e0260d517b9b0ae make_zip.py
e830d37dd1f81a4b0aa7d535f5bba69e34e433b0fd99cc8ee0b296786c32379a migrations/0002_admin.sql e830d37dd1f81a4b0aa7d535f5bba69e34e433b0fd99cc8ee0b296786c32379a migrations/0002_admin.sql
20112cc82192098b8d602f1c4f7ca08712e9e1366c845fabb92753187c370d35 migrations/0003_certificate_renewals.sql 20112cc82192098b8d602f1c4f7ca08712e9e1366c845fabb92753187c370d35 migrations/0003_certificate_renewals.sql
@@ -135,15 +136,15 @@ a402209141667fdce69698a60dc0b867248effa191494d0a0d93d468d7df9476 src/admin/cert
521ac794501570cfa2e8e50c3a5cec2ca08ee4dbce8ac9c0e0310c09612f1062 src/admin/config_reload.rs 521ac794501570cfa2e8e50c3a5cec2ca08ee4dbce8ac9c0e0310c09612f1062 src/admin/config_reload.rs
159c3970d25fa29acf2483fc9422b003e5d63884713f62baa8375e4a4020730e src/admin/dashboard_presets.rs 159c3970d25fa29acf2483fc9422b003e5d63884713f62baa8375e4a4020730e src/admin/dashboard_presets.rs
aae1ca0e70d987830bd63948d431f54302083148177c1b551565be12df7fe771 src/admin/documentation.rs aae1ca0e70d987830bd63948d431f54302083148177c1b551565be12df7fe771 src/admin/documentation.rs
d4a249e8c2bf06a12fbd561fcdf5a5211aa5ed0e0dcd5111b84bbb13b3640c49 src/admin/documentation/documents.rs da28dd57087df138913b784b71dca11d36c68100f2f100f452fbf4e4d7982433 src/admin/documentation/documents.rs
67b901485d5858d5b5bf78a6260035db32241531d99481754c3533fdf404facb src/admin/documentation/tests.rs 67fa0e842f6789ed38e101ed6717072736e85c34af873d1a27d7efee5dc0c83d src/admin/documentation/tests.rs
1ce779b84aa8f47bdb3c34f83593c1f2f06d2235f0319847096cfcd035434168 src/admin/file_log_search.rs 1ce779b84aa8f47bdb3c34f83593c1f2f06d2235f0319847096cfcd035434168 src/admin/file_log_search.rs
ab18acf6fb4ac86fb22635b9219a12dac76d24952c8f15538486267a9b4b7855 src/admin/file_logs.rs ab18acf6fb4ac86fb22635b9219a12dac76d24952c8f15538486267a9b4b7855 src/admin/file_logs.rs
d58a2ed83319176de4e66790ed292447c9497ae031464ec55cfe27174acb639a src/admin/file_logs_io.rs d58a2ed83319176de4e66790ed292447c9497ae031464ec55cfe27174acb639a src/admin/file_logs_io.rs
282352eda7957477e7da44f16b90984b26a4a67a8755833bc0a6b34eaf93e3af src/admin/file_logs_query.rs 282352eda7957477e7da44f16b90984b26a4a67a8755833bc0a6b34eaf93e3af src/admin/file_logs_query.rs
f66ff718f82a326f80808b0a5088bd05a48625be802e5ba305fff3c344b59948 src/admin/logging_api.rs f66ff718f82a326f80808b0a5088bd05a48625be802e5ba305fff3c344b59948 src/admin/logging_api.rs
1ba0520c6348246cbb35b60e596e99dc600137c0696de86508cf06587bd86563 src/admin/mod.rs 1ba0520c6348246cbb35b60e596e99dc600137c0696de86508cf06587bd86563 src/admin/mod.rs
7bce0ba5a9fe3d1de2b10a29e58d88300e09ffcce50e6d94e68240c78db944b8 src/admin/openapi.json 8e1012a8ec776c3525310b03a70f274955dce9de3528109070ea11de0ff5c16c src/admin/openapi.json
7cfd6593ac9bcea3d1b9e61bbcb7ed98d65ed3190f54f94c1e104541f4fbb4f5 src/admin/operations.rs 7cfd6593ac9bcea3d1b9e61bbcb7ed98d65ed3190f54f94c1e104541f4fbb4f5 src/admin/operations.rs
f14afb6ae141053d83d0dc59df835b20e38a37a9e83f86f0f2053f2bd5d9ce57 src/admin/panel_assets.rs f14afb6ae141053d83d0dc59df835b20e38a37a9e83f86f0f2053f2bd5d9ce57 src/admin/panel_assets.rs
f5127855c2994d052678865df36d703b74f4a0afc633daf7718b10da18d26e37 src/admin/panel_content.rs f5127855c2994d052678865df36d703b74f4a0afc633daf7718b10da18d26e37 src/admin/panel_content.rs
@@ -155,7 +156,7 @@ f5127855c2994d052678865df36d703b74f4a0afc633daf7718b10da18d26e37 src/admin/pane
6930b4f4b45257772b321b20a9becdf9bc4610274852948ba38c2d0cdf02dfaf src/admin/refresh_preferences.rs 6930b4f4b45257772b321b20a9becdf9bc4610274852948ba38c2d0cdf02dfaf src/admin/refresh_preferences.rs
00b5dd3090792986217b28c69bf826748145129e5517b260d95019b1d7b2ebb2 src/admin/router.rs 00b5dd3090792986217b28c69bf826748145129e5517b260d95019b1d7b2ebb2 src/admin/router.rs
e135431cb078ddb483165581123c64af8f00ea67acb3a395ec3e53c85d0d0d45 src/admin/session.rs e135431cb078ddb483165581123c64af8f00ea67acb3a395ec3e53c85d0d0d45 src/admin/session.rs
0c86d94b714cfff26e184541ed3bfcb317c88e45afe084d5f3b28558946dd9b3 src/admin/status.rs 09cc50a86960897371730cc1abed83f4f640c86b680ec7ff6d6b84c7e96a3c0b src/admin/status.rs
2d5b4c28fd0bd48b06890fa8ec44a2296de948c1f88eb2909f4b495710d4e13e src/admin/status_live.rs 2d5b4c28fd0bd48b06890fa8ec44a2296de948c1f88eb2909f4b495710d4e13e src/admin/status_live.rs
8315c856cd50f1072593ecb30c274d42d82f078b5fb93e7c68a8857cd81b0841 src/admin/status_system.rs 8315c856cd50f1072593ecb30c274d42d82f078b5fb93e7c68a8857cd81b0841 src/admin/status_system.rs
cadd7dca29b9aad8c77c7bc23dd249ea37a15adfa4d636a8ef00273a91ce35dc src/admin/tail.rs cadd7dca29b9aad8c77c7bc23dd249ea37a15adfa4d636a8ef00273a91ce35dc src/admin/tail.rs
@@ -183,7 +184,7 @@ fa00bce2651308a4e0b1303cc0146be17ed4794b67b2f0cebfaf861558d8c78c src/admin/web/
8386710827e5ccc9432fb46413368f94afc1016425404a6092b460903636837e src/admin/web/app/stream-dashboard.js 8386710827e5ccc9432fb46413368f94afc1016425404a6092b460903636837e src/admin/web/app/stream-dashboard.js
7f89bfae66e2a08a05a112733864e908f036bb1ee3d8b5b9ff23783b870acd96 src/admin/web/app/web-overview.js 7f89bfae66e2a08a05a112733864e908f036bb1ee3d8b5b9ff23783b870acd96 src/admin/web/app/web-overview.js
849b2ca9d183bdf6cb1e04651cda52da55b8a631e11ab1d827a052c8436bbd76 src/admin/web/cache-panel.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 560a8e047182fbcee08aa4f8d999400a8a5ddca0b6a4640efd46f6b5c34fde91 src/admin/web/dashboard-defaults.json
14213011cfb6ac334c8711b76bbdde97588a7b7ba4eb2592a1e387f877d6cbe0 src/admin/web/dashboard-widgets.json 14213011cfb6ac334c8711b76bbdde97588a7b7ba4eb2592a1e387f877d6cbe0 src/admin/web/dashboard-widgets.json
50d1ebcbaf4d0b2968da23bb943fc3f67131c5d32df3f61d23ff37aad9da5b81 src/admin/web/demo.html 50d1ebcbaf4d0b2968da23bb943fc3f67131c5d32df3f61d23ff37aad9da5b81 src/admin/web/demo.html
@@ -207,7 +208,7 @@ e6c4d91d1542d0a5b47bb8736f9539d43a6713df5a473b635519d5b7eac2eb9c src/admin/web/
0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg 0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg
7cbdef9192a9dee1f8eac42b85f52b3190f7f6efae50004aa7ad116a910e4036 src/admin/web/file-logs.js 7cbdef9192a9dee1f8eac42b85f52b3190f7f6efae50004aa7ad116a910e4036 src/admin/web/file-logs.js
b14e7b3578322f309627f6fe965cc16abcf85da3437c9070be6caecae1908743 src/admin/web/host404.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 7915a06cebb3dfa2c51833c326586920691ba4431b1f984429b039333f9c2a93 src/admin/web/integrations.js
7a12f020fc587205694978ab41b660e9a80487d413fbf6e34fc525eabc58308f src/admin/web/login-keys.json 7a12f020fc587205694978ab41b660e9a80487d413fbf6e34fc525eabc58308f src/admin/web/login-keys.json
cbd41678eadd994dc5252628ee8ef9551fa9f7d8694ca5abd206240d9a96ad30 src/admin/web/login.html cbd41678eadd994dc5252628ee8ef9551fa9f7d8694ca5abd206240d9a96ad30 src/admin/web/login.html
@@ -217,14 +218,14 @@ c2e5f554810b8e34fb47361fb90a01073a2e332ee26d82e030956fad3ec1f83e src/admin/web/
a8afc6e70950bc70b98725eb049d73cff35dc3a3ee659229a15a8e08d3eb4f12 src/admin/web/partials/overview-metrics.html a8afc6e70950bc70b98725eb049d73cff35dc3a3ee659229a15a8e08d3eb4f12 src/admin/web/partials/overview-metrics.html
3928afce2ed6d5f2d3e29475dd29e4c3bd3c6df180ad5119beaff0c7a58e3b3b src/admin/web/prometheus-token.js 3928afce2ed6d5f2d3e29475dd29e4c3bd3c6df180ad5119beaff0c7a58e3b3b src/admin/web/prometheus-token.js
90d9a0836662135b79fb6f183c4f3eff60f8a64392d6c5795ccf1d8ef6862263 src/admin/web/redirects.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 8eadbada619b15087783dcfa0e889a81f88e78034d084c5ab616f7912b835940 src/admin/web/status_200.html
6feb1996b1caa9d19e0dc56eae7ca5b48b7e7cdf0a93bb2e4b527fb8f13a2fe5 src/admin/web/streams.js 6feb1996b1caa9d19e0dc56eae7ca5b48b7e7cdf0a93bb2e4b527fb8f13a2fe5 src/admin/web/streams.js
38716fae968aeb8f6a8510f28bb9054a73a438a969c3b69f9c9d921acfed6e06 src/admin/web/style.css 38716fae968aeb8f6a8510f28bb9054a73a438a969c3b69f9c9d921acfed6e06 src/admin/web/style.css
d290c048450655b3eeae7c6ec54a82d645d1965513694ab29fdf7eef796a1abd src/admin/web/traffic-panel.js d290c048450655b3eeae7c6ec54a82d645d1965513694ab29fdf7eef796a1abd src/admin/web/traffic-panel.js
dfb4ded2fefb90fc1e2d33370cdef10b040a85cb43235ea9dbc1d73bef823a79 src/admin/web/transform-panel.js dfb4ded2fefb90fc1e2d33370cdef10b040a85cb43235ea9dbc1d73bef823a79 src/admin/web/transform-panel.js
a55f45dad5b540def419f8487161fb506b7e52744c76a5e661c7a5fa0a98a426 src/admin/web/vhosts/core.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 8338ce13b5e3e3f421437631124cb691aeb5b9e17409d256b4b9bcac457b3c59 src/admin/web/vhosts/health.js
b3239e67901f1796d7a974ade0796f2e3590ff8064628a5629b78c7f6ce2f32e src/admin/web/vhosts/protocol.js b3239e67901f1796d7a974ade0796f2e3590ff8064628a5629b78c7f6ce2f32e src/admin/web/vhosts/protocol.js
8c3da49973c62333559b0e3588c02beb0b425c025eac5cfa82a1bb968f70d9dd src/admin/web/vhosts/rates.js 8c3da49973c62333559b0e3588c02beb0b425c025eac5cfa82a1bb968f70d9dd src/admin/web/vhosts/rates.js
@@ -302,7 +303,7 @@ fbea01fea1213d98aea812c1b78d5db289e09200b98ae514d742235cd3fa70e1 src/config/int
75839458ddbc7480ec4492a95eeb67b617ba7d62f7b67e7a767a3453b74dbcbc src/config/route_policy_tests.rs 75839458ddbc7480ec4492a95eeb67b617ba7d62f7b67e7a767a3453b74dbcbc src/config/route_policy_tests.rs
40e9483d7b2450f55d29b46c408727f4e331b85ecda44fbd7b96bd9a09f02655 src/config/routing.rs 40e9483d7b2450f55d29b46c408727f4e331b85ecda44fbd7b96bd9a09f02655 src/config/routing.rs
dd9a6ad8f58e0cdd1ae74f3f972fee0ac14383cd8b8f518403b9a36c169f35bd src/config/snapshot.rs dd9a6ad8f58e0cdd1ae74f3f972fee0ac14383cd8b8f518403b9a36c169f35bd src/config/snapshot.rs
b7b0f851b4ca459fe8844a2e4f9cdcfc1135dec4c06f99beeea008c5b35c2ba5 src/config/tests.rs e943525706c51846adbac23d4a4f4b1a22bd20103b3004b7de1cd1e5e270ca50 src/config/tests.rs
f35de0ee1516ece6bbfd2b2a7db7d1e0f5048b0ec47cf2a3765dffb799abfd93 src/config/transport_tests.rs f35de0ee1516ece6bbfd2b2a7db7d1e0f5048b0ec47cf2a3765dffb799abfd93 src/config/transport_tests.rs
eb04a8922da26e45c9b6005c0cdf1b1ddf2da3c3b680650b22a1d1f16d17a4b2 src/db.rs eb04a8922da26e45c9b6005c0cdf1b1ddf2da3c3b680650b22a1d1f16d17a4b2 src/db.rs
c290d0cb02dfa5d2c60ecabac1cfb20c8884e3d14cca1944906c6d8e677f1784 src/db/api_tokens.rs c290d0cb02dfa5d2c60ecabac1cfb20c8884e3d14cca1944906c6d8e677f1784 src/db/api_tokens.rs
@@ -329,7 +330,7 @@ c1b49bef28d87712e4c46138e70b52c4bcfb1a00196579031fac043d0b4aaa7b src/geoip/upda
9a912dad0b8d1e10410fd95d0da3684656092ef85cf8b23eda4ffe7fb92921d8 src/geoip/update_tests.rs 9a912dad0b8d1e10410fd95d0da3684656092ef85cf8b23eda4ffe7fb92921d8 src/geoip/update_tests.rs
61a2b01db9010f9debfb38b43a26bf588438a5e470dff938e4e54f9d7ba46732 src/headers/mod.rs 61a2b01db9010f9debfb38b43a26bf588438a5e470dff938e4e54f9d7ba46732 src/headers/mod.rs
e4f07bfb7eb75b6897ef812870caff158679a3046d64582428abe8c6df490dbe src/headers/tests.rs e4f07bfb7eb75b6897ef812870caff158679a3046d64582428abe8c6df490dbe src/headers/tests.rs
d7c764af29c132e3d8d1fae1bdbe6bf20a27046dc294f83761fea335726f2306 src/http3.rs b458510eb89ae8bf8fda84989073c6e64c69f2b12fde96c454b33f9df6a57df2 src/http3.rs
0cc484bf0cc59a55a13e0e70435833a9a7a22ad7dfb5b9f86e059cf70a80cc2d src/http3/body.rs 0cc484bf0cc59a55a13e0e70435833a9a7a22ad7dfb5b9f86e059cf70a80cc2d src/http3/body.rs
741972222414c280104acbcd8f8071bed9cbfcfa2f503c487c11c56f553f53cd src/http3/diagnostics.rs 741972222414c280104acbcd8f8071bed9cbfcfa2f503c487c11c56f553f53cd src/http3/diagnostics.rs
49ce4452ef7973cafa2e4071e2e14be0e16000e931a6501e4dc613652e49e230 src/http3/identity.rs 49ce4452ef7973cafa2e4071e2e14be0e16000e931a6501e4dc613652e49e230 src/http3/identity.rs
@@ -450,8 +451,8 @@ df98becda799b6bf4ff5e2fec7d0e0a486e6b7eca6f9f5ad49ffdafd49a4f498 src/tls.rs
2ff3c219f87bd82677045a4809d3d94921c5b230bbc2f3e7131b07548f73ea7e src/tls/external_paths.rs 2ff3c219f87bd82677045a4809d3d94921c5b230bbc2f3e7131b07548f73ea7e src/tls/external_paths.rs
f88a6c8c2e35c3aa58b7763c71a5a2cec82a34f24478a98b0960b81659a1ec4c src/tls/external_paths_tests.rs f88a6c8c2e35c3aa58b7763c71a5a2cec82a34f24478a98b0960b81659a1ec4c src/tls/external_paths_tests.rs
7b4c0f58b3a1e1791bb3c682a001b78bfdbbdafdbc143db8063b3f22dc168616 src/tls/server_config.rs 7b4c0f58b3a1e1791bb3c682a001b78bfdbbdafdbc143db8063b3f22dc168616 src/tls/server_config.rs
9d6f048e791a3b7152904d1728258d298ad9d34aa3f83d13baf1b09a6fb7601d src/tls/tests.rs 68a29d90f1675c7d97953e50ebccd4fa21c47fd0a4d8a0838b436ee84399afe0 src/tls/tests.rs
d9d79edf5a8ef6e3b39a21d35dd5ec6e5a336047117918949e7ec6f5b20ed3f1 src/tls/types.rs bf0ccec8909e678aaed84d358058b1fa865b38e09d10471b02fca76eea38633d src/tls/types.rs
a535c8526b38de2990da7781e158b9ea70ef8c1f6fae0aac5ce6e9da21d3a309 src/traffic/mod.rs a535c8526b38de2990da7781e158b9ea70ef8c1f6fae0aac5ce6e9da21d3a309 src/traffic/mod.rs
dc8a83fae47da0d86e6ce0a329cb5274233264df56eebdb56b9bf37cb7be2bb4 src/traffic/tests.rs dc8a83fae47da0d86e6ce0a329cb5274233264df56eebdb56b9bf37cb7be2bb4 src/traffic/tests.rs
65fcac840f8b5f1508213e3f374aa318b450b0f9f90ba827c5ea935b9c60bee7 src/transform/apply.rs 65fcac840f8b5f1508213e3f374aa318b450b0f9f90ba827c5ea935b9c60bee7 src/transform/apply.rs
+2 -1
View File
@@ -1,6 +1,6 @@
# RustProxy documentation # 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 ## 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. - [Cache](CACHE.md) — HOT/COLD storage, privacy, TTL and invalidation.
- [Response delivery](RESPONSE_DELIVERY.md) — compression, static transformations and response headers. - [Response delivery](RESPONSE_DELIVERY.md) — compression, static transformations and response headers.
- [HTTP transport](TRANSPORT.md) — HTTPS/H2/H3, WebSockets, origin protocols and timeouts. - [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. - [TCP/UDP Streams](STREAMS.md) — non-HTTP forwarding and its security boundaries.
## Integrations and operations ## Integrations and operations
+53
View File
@@ -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.
+2
View File
@@ -55,6 +55,8 @@ WebSockets normally use HTTP/1.1 Upgrade. HTTP/2/HTTP/3 client requests can stil
## TLS and trust ## 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. 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 ## Timeouts, request bodies and streaming
+7
View File
@@ -991,6 +991,13 @@
"tls13": "TLS 1.3", "tls13": "TLS 1.3",
"tlsPolicy": "TLS policy", "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.", "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)", "tlsCiphers": "Cipher suites (one per line; empty = rustls defaults)",
"tlsAvailableCiphers": "Available cipher suites", "tlsAvailableCiphers": "Available cipher suites",
"tlsPolicyVersionRequired": "Enable at least one TLS version.", "tlsPolicyVersionRequired": "Enable at least one TLS version.",
+7
View File
@@ -991,6 +991,13 @@
"tls13": "TLS 1.3", "tls13": "TLS 1.3",
"tlsPolicy": "Polityka TLS", "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.", "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)", "tlsCiphers": "Szyfry (jeden na linię; puste = domyślne rustls)",
"tlsAvailableCiphers": "Dostępne szyfry", "tlsAvailableCiphers": "Dostępne szyfry",
"tlsPolicyVersionRequired": "Włącz co najmniej jedną wersję TLS.", "tlsPolicyVersionRequired": "Włącz co najmniej jedną wersję TLS.",
+1
View File
@@ -24,6 +24,7 @@ pub(super) static READER_DOCUMENTS: &[(&str, &str, &str)] = &[
reader_doc!("docs/CACHE.md", "guides"), reader_doc!("docs/CACHE.md", "guides"),
reader_doc!("docs/RESPONSE_DELIVERY.md", "guides"), reader_doc!("docs/RESPONSE_DELIVERY.md", "guides"),
reader_doc!("docs/TRANSPORT.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/STREAMS.md", "guides"),
reader_doc!("docs/ACME_PROVIDERS.md", "guides"), reader_doc!("docs/ACME_PROVIDERS.md", "guides"),
reader_doc!("docs/DOCKER_DISCOVERY.md", "guides"), reader_doc!("docs/DOCKER_DISCOVERY.md", "guides"),
+12 -3
View File
@@ -6,13 +6,22 @@ mod tests {
let value: Value = serde_json::from_slice(reader_bundle()).unwrap(); let value: Value = serde_json::from_slice(reader_bundle()).unwrap();
let documents = value["documents"].as_array().unwrap(); let documents = value["documents"].as_array().unwrap();
assert!(documents.iter().any(|doc| doc["path"] == "README.md")); 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/PHP_FPM.md"));
assert!(documents
.iter()
.any(|doc| doc["path"] == "docs/TLS_POST_QUANTUM.md"));
assert!(documents.iter().all(|doc| { assert!(documents.iter().all(|doc| {
doc["path"].as_str().is_some_and(|value| !value.is_empty()) doc["path"].as_str().is_some_and(|value| !value.is_empty())
&& doc["title"].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["category"]
&& doc["content"].as_str().is_some_and(|value| !value.is_empty()) .as_str()
.is_some_and(|value| !value.is_empty())
&& doc["content"]
.as_str()
.is_some_and(|value| !value.is_empty())
})); }));
} }
+35 -2
View File
@@ -2,7 +2,7 @@
"openapi": "3.0.3", "openapi": "3.0.3",
"info": { "info": {
"title": "RustProxy Administration API", "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." "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": [ "servers": [
@@ -622,6 +622,11 @@
"items": { "items": {
"type": "string" "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, "additionalProperties": false,
@@ -772,7 +777,35 @@
}, },
"tls": { "tls": {
"type": "object", "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 "additionalProperties": true
}, },
"cache": { "cache": {
+3 -2
View File
@@ -223,8 +223,9 @@ fn status_payload(state: &App) -> Value {
}); });
payload["tls"] = json!({ payload["tls"] = json!({
"policy": &snapshot.envelope.config.tls.policy, "policy": &snapshot.envelope.config.tls.policy,
"cipher_suites": crate::tls::cipher_suite_names(), "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() "effective_cipher_suites": snapshot.envelope.config.tls.policy.effective_cipher_suites(),
"key_exchange_groups": snapshot.envelope.config.tls.policy.key_exchange_groups()
}); });
payload payload
} }
+13
View File
@@ -3071,6 +3071,19 @@
<label><span data-i18n="tlsCiphers">Cipher suites (one per line; empty = rustls <label><span data-i18n="tlsCiphers">Cipher suites (one per line; empty = rustls
defaults)</span><textarea id="tls-policy-ciphers" rows="8" defaults)</span><textarea id="tls-policy-ciphers" rows="8"
spellcheck="false"></textarea></label> 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 tls-policy-effective" id="tls-policy-effective"></p>
<p class="fine" id="tls-policy-available"></p> <p class="fine" id="tls-policy-available"></p>
<div class="form-actions"><button class="primary" data-i18n="apply" <div class="form-actions"><button class="primary" data-i18n="apply"
+5 -2
View File
@@ -244,9 +244,11 @@
]) { const node = $(id); if (node) node.textContent = value || t("unavailable"); } ]) { const node = $(id); if (node) node.textContent = value || t("unavailable"); }
const policy = envelope.config.tls?.policy || { tls12: true, tls13: true, cipher_suites: [] }; 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 available = latestStatus?.tls?.cipher_suites || [];
const effective = latestStatus?.tls?.effective_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-effective").textContent = effective.length ? `${t("tlsEffectiveCiphers")}: ${effective.join(", ")}` : "";
$("tls-policy-available").textContent = available.length ? `${t("tlsAvailableCiphers")}: ${available.join(", ")}` : ""; $("tls-policy-available").textContent = available.length ? `${t("tlsAvailableCiphers")}: ${available.join(", ")}` : "";
window.rustProxyCertificateApi?.render(); window.rustProxyCertificateApi?.load(); window.rustProxyCertificateApi?.render(); window.rustProxyCertificateApi?.load();
@@ -287,7 +289,8 @@
}, "administration"); }, "administration");
bindSettingsForm("tls-policy-form", next => { bindSettingsForm("tls-policy-form", next => {
if (!$("tls-policy-12").checked && !$("tls-policy-13").checked) throw fieldError($("tls-policy-12"), t("tlsPolicyVersionRequired")); 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"); }, "tls");
$("admin-tls").addEventListener("change", () => { $("admin-certificate").disabled = !$("admin-tls").checked; }); $("admin-tls").addEventListener("change", () => { $("admin-certificate").disabled = !$("admin-tls").checked; });
$("prometheus-form").addEventListener("input", example); $("prometheus-form").addEventListener("input", example);
+8 -5
View File
@@ -328,8 +328,9 @@ function load(value = defaults(), migrationIds = [], show = true) {
const tls12 = field(g, "tls-policy-12", "tls12", tlsPolicy.tls12 !== false, "checkbox"); 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 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 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; }; const tlsPqc = field(g, "tls-policy-pqc", "tlsPqcLabel", tlsPolicy.post_quantum === true, "checkbox");
customTlsPolicy.addEventListener("change", syncTlsPolicy); syncTlsPolicy(); note(g, "vhTlsOverrideHelp"); 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 || {}; 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" : "none", "select", { values: [["none", "authNone"], ["tinyauth", "authTinyauth"], ["basic", "authBasic"]] });
note(g, "authModeHelp"); note(g, "authModeHelp");
@@ -485,19 +486,21 @@ function collect() {
if (!tlsPorts.has(443) && tlsPorts.size !== 1) throw new Error(t("vhForceSslPortConflict")); 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-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 selectedUsesHttp3 = bindingSelection.some(id => {
const endpoint = envelope?.config.listeners?.endpoints?.find(item => item.id === id); const endpoint = envelope?.config.listeners?.endpoints?.find(item => item.id === id);
return !!endpoint?.binding?.http3; return !!endpoint?.binding?.http3;
}); });
if (yes("http3-enabled") && selectedUsesHttp3 && yes("tls-policy-custom")) { 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 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 (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")); if (yes("content-type-block-enabled") && !lines(str("content-type-block-list")).length) throw new Error(t("vhContentTypeRequired"));
return { 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")), 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") }, 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() }, 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() },
+17
View File
@@ -749,10 +749,27 @@ mod tests {
tls12: true, tls12: true,
tls13: false, tls13: false,
cipher_suites: vec![], cipher_suites: vec![],
post_quantum: false,
}); });
assert!(compile(c, 1, &policy()).is_ok()); 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] #[test]
fn persisted_remote_administration_requires_tls() { fn persisted_remote_administration_requires_tls() {
let mut c = demo_config("127.0.0.1:9000".parse().unwrap()); let mut c = demo_config("127.0.0.1:9000".parse().unwrap());
+1 -10
View File
@@ -34,16 +34,7 @@ pub fn crypto(
policy.allows_http3(), policy.allows_http3(),
"HTTP/3 requires TLS 1.3 and a TLS 1.3 cipher suite" "HTTP/3 requires TLS 1.3 and a TLS 1.3 cipher suite"
); );
let mut provider = rustls::crypto::ring::default_provider(); let provider = crate::tls::provider_for(policy)?;
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 mut quic = rustls::ServerConfig::builder_with_provider(Arc::new(provider)) let mut quic = rustls::ServerConfig::builder_with_provider(Arc::new(provider))
.with_protocol_versions(&[&rustls::version::TLS13])? .with_protocol_versions(&[&rustls::version::TLS13])?
.with_no_client_auth() .with_no_client_auth()
+64 -1
View File
@@ -186,6 +186,7 @@ mod tests {
tls12: true, tls12: true,
tls13: false, tls13: false,
cipher_suites: Vec::new(), cipher_suites: Vec::new(),
post_quantum: false,
}, },
..Settings::default() ..Settings::default()
}; };
@@ -199,6 +200,7 @@ mod tests {
tls12: false, tls12: false,
tls13: false, tls13: false,
cipher_suites: vec![], cipher_suites: vec![],
post_quantum: false,
} }
.validate() .validate()
.is_err()); .is_err());
@@ -214,6 +216,7 @@ mod tests {
tls12: true, tls12: true,
tls13: false, tls13: false,
cipher_suites: vec![tls13.clone()], cipher_suites: vec![tls13.clone()],
post_quantum: false,
} }
.validate() .validate()
.is_err()); .is_err());
@@ -223,6 +226,7 @@ mod tests {
tls12: false, tls12: false,
tls13: true, tls13: true,
cipher_suites: vec![tls12.clone()], cipher_suites: vec![tls12.clone()],
post_quantum: false,
} }
.validate() .validate()
.is_err()); .is_err());
@@ -231,11 +235,13 @@ mod tests {
tls12: true, tls12: true,
tls13: false, tls13: false,
cipher_suites: vec![], cipher_suites: vec![],
post_quantum: false,
}; };
assert!(ProtocolPolicy { assert!(ProtocolPolicy {
tls12: true, tls12: true,
tls13: false, tls13: false,
cipher_suites: vec![] cipher_suites: vec![],
post_quantum: false,
} }
.validate_narrower_than(&global) .validate_narrower_than(&global)
.is_ok()); .is_ok());
@@ -243,6 +249,63 @@ mod tests {
.validate_narrower_than(&global) .validate_narrower_than(&global)
.is_err()); .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] #[test]
fn administration_tls_supports_default_and_selected_certificate() { fn administration_tls_supports_default_and_selected_certificate() {
let dir = tempfile::tempdir().unwrap(); let dir = tempfile::tempdir().unwrap();
+54 -5
View File
@@ -52,6 +52,12 @@ pub struct ProtocolPolicy {
pub tls12: bool, pub tls12: bool,
pub tls13: bool, pub tls13: bool,
pub cipher_suites: Vec<String>, 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 { impl Default for ProtocolPolicy {
fn default() -> Self { fn default() -> Self {
@@ -59,6 +65,7 @@ impl Default for ProtocolPolicy {
tls12: true, tls12: true,
tls13: true, tls13: true,
cipher_suites: Vec::new(), cipher_suites: Vec::new(),
post_quantum: false,
} }
} }
} }
@@ -68,11 +75,15 @@ impl ProtocolPolicy {
self.tls12 || self.tls13, self.tls12 || self.tls13,
"at least one TLS protocol version must be enabled" "at least one TLS protocol version must be enabled"
); );
ensure!(
!self.post_quantum || self.tls13,
"post-quantum key exchange requires TLS 1.3"
);
ensure!( ensure!(
self.cipher_suites.len() <= 16, self.cipher_suites.len() <= 16,
"at most 16 TLS cipher suites may be selected" "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(); let mut seen = HashSet::new();
for suite in &self.cipher_suites { for suite in &self.cipher_suites {
ensure!( ensure!(
@@ -97,7 +108,7 @@ impl ProtocolPolicy {
} }
pub fn effective_cipher_suites(&self) -> Vec<String> { pub fn effective_cipher_suites(&self) -> Vec<String> {
let selected = if self.cipher_suites.is_empty() { let selected = if self.cipher_suites.is_empty() {
cipher_suite_names() cipher_suite_names_for(self.post_quantum)
} else { } else {
self.cipher_suites.clone() self.cipher_suites.clone()
}; };
@@ -120,6 +131,10 @@ impl ProtocolPolicy {
pub fn validate_narrower_than(&self, global: &Self) -> Result<()> { pub fn validate_narrower_than(&self, global: &Self) -> Result<()> {
self.validate()?; self.validate()?;
global.validate()?; global.validate()?;
ensure!(
!self.post_quantum || global.post_quantum,
"per-vhost post-quantum key exchange must be enabled globally"
);
ensure!( ensure!(
!self.tls12 || global.tls12, !self.tls12 || global.tls12,
"per-vhost TLS policy cannot enable TLS 1.2 when it is disabled globally" "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> { pub fn cipher_suite_names() -> Vec<String> {
rustls::crypto::ring::default_provider() base_provider(false)
.cipher_suites .cipher_suites
.iter() .iter()
.map(|suite| format!("{:?}", suite.suite())) .map(|suite| format!("{:?}", suite.suite()))
.collect() .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()?; policy.validate()?;
let mut provider = rustls::crypto::ring::default_provider(); let mut provider = base_provider(policy.post_quantum);
if !policy.cipher_suites.is_empty() { if !policy.cipher_suites.is_empty() {
provider.cipher_suites.retain(|suite| { provider.cipher_suites.retain(|suite| {
let name = format!("{:?}", suite.suite()); let name = format!("{:?}", suite.suite());