From eaf747b2d0600ec9605ebd574ce95b51abb2c2f2 Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Fri, 19 Dec 2025 10:28:52 +0100 Subject: [PATCH 1/9] certificate handshake --- Cargo.lock | 1401 ++++++++++++++++++++++++++-- Cargo.toml | 1 + src/grpc.rs | 347 ++++++- src/handlers/desktop_client_mfa.rs | 3 +- src/handlers/enrollment.rs | 1 - src/handlers/register_mfa.rs | 3 +- src/http.rs | 115 ++- src/lib.rs | 8 + 8 files changed, 1734 insertions(+), 145 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 89be08bd..6fb2ca2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -80,6 +80,15 @@ dependencies = [ "url", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.21" @@ -136,6 +145,45 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +[[package]] +name = "asn1-rs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 2.0.17", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "async-trait" version = "0.1.89" @@ -147,12 +195,27 @@ dependencies = [ "syn", ] +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + [[package]] name = "axum" version = "0.8.6" @@ -264,11 +327,20 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" + [[package]] name = "bitflags" version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +dependencies = [ + "serde", +] [[package]] name = "block-buffer" @@ -279,6 +351,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + [[package]] name = "bstr" version = "1.12.0" @@ -295,6 +376,12 @@ version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.10.1" @@ -319,6 +406,23 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "num-traits", + "windows-link", +] + [[package]] name = "cipher" version = "0.4.4" @@ -375,6 +479,21 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "cookie" version = "0.18.1" @@ -390,6 +509,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -415,6 +544,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.5.0" @@ -424,6 +568,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -539,6 +692,7 @@ dependencies = [ "axum-extra", "base64", "clap", + "defguard_certs", "defguard_version", "dotenvy", "futures-util", @@ -565,6 +719,18 @@ dependencies = [ "vergen-git2", ] +[[package]] +name = "defguard_certs" +version = "0.0.0" +dependencies = [ + "base64", + "rcgen", + "rustls-pki-types", + "serde", + "sqlx", + "thiserror 2.0.17", +] + [[package]] name = "defguard_version" version = "0.0.0" @@ -582,6 +748,31 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + [[package]] name = "deranged" version = "0.5.4" @@ -629,7 +820,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", + "subtle", +] + +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags", + "objc2", ] [[package]] @@ -669,6 +872,9 @@ name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] [[package]] name = "equivalent" @@ -686,6 +892,28 @@ dependencies = [ "windows-sys 0.61.1", ] +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -714,6 +942,17 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + [[package]] name = "fnv" version = "1.0.7" @@ -726,6 +965,21 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -762,6 +1016,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -770,6 +1025,34 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + [[package]] name = "futures-macro" version = "0.3.31" @@ -806,9 +1089,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", + "futures-io", "futures-macro", "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", @@ -956,6 +1241,15 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + [[package]] name = "headers" version = "0.4.1" @@ -986,6 +1280,39 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.1", +] + [[package]] name = "html5ever" version = "0.35.0" @@ -1106,6 +1433,30 @@ dependencies = [ "tracing", ] +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "2.0.0" @@ -1249,6 +1600,15 @@ dependencies = [ "libc", ] +[[package]] +name = "ipnetwork" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" +dependencies = [ + "serde", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -1295,6 +1655,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "libc" @@ -1315,20 +1678,47 @@ dependencies = [ ] [[package]] -name = "libz-sys" -version = "1.1.22" +name = "libm" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "libredox" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ - "cc", + "bitflags", "libc", + "redox_syscall", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ "pkg-config", "vcpkg", ] [[package]] -name = "linux-raw-sys" -version = "0.11.0" +name = "libz-sys" +version = "1.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" @@ -1405,6 +1795,16 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.7.6" @@ -1427,6 +1827,12 @@ dependencies = [ "unicase", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -1454,12 +1860,51 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework 2.11.1", + "security-framework-sys", + "tempfile", +] + [[package]] name = "new_debug_unreachable" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nonempty" version = "0.7.0" @@ -1481,12 +1926,68 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + [[package]] name = "num_threads" version = "0.1.7" @@ -1496,6 +1997,165 @@ dependencies = [ "libc", ] +[[package]] +name = "objc2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags", + "block2", + "libc", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-location", + "objc2-core-text", + "objc2-foundation", + "objc2-quartz-core", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "objc2", + "objc2-foundation", +] + [[package]] name = "object" version = "0.37.3" @@ -1505,6 +2165,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -1523,24 +2192,72 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "openssl-probe" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "os_info" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0e1ac5fde8d43c34139135df8ea9ee9465394b2d8d20f032d38998f64afffc3" +checksum = "e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224" dependencies = [ + "android_system_properties", "log", - "plist", + "nix", + "objc2", + "objc2-foundation", + "objc2-ui-kit", "serde", - "windows-sys 0.52.0", + "windows-sys 0.61.1", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.5" @@ -1564,6 +2281,25 @@ dependencies = [ "windows-link", ] +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -1665,24 +2401,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] -name = "pkg-config" -version = "0.3.32" +name = "pkcs1" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] [[package]] -name = "plist" -version = "1.8.0" +name = "pkcs8" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "base64", - "indexmap", - "quick-xml", - "serde", - "time", + "der", + "spki", ] +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + [[package]] name = "polyval" version = "0.6.2" @@ -1839,15 +2583,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "quick-xml" -version = "0.38.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89" -dependencies = [ - "memchr", -] - [[package]] name = "quote" version = "1.0.41" @@ -1931,6 +2666,20 @@ dependencies = [ "bitflags", ] +[[package]] +name = "rcgen" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ec0a99f2de91c3cddc84b37e7db80e4d96b743e05607f647eb236fc0455907f" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "x509-parser", + "yasna", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -1983,6 +2732,26 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rsa" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rust-embed" version = "8.7.2" @@ -2024,6 +2793,15 @@ version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + [[package]] name = "rustix" version = "1.1.2" @@ -2061,14 +2839,14 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework", + "security-framework 3.5.1", ] [[package]] name = "rustls-pki-types" -version = "1.12.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" dependencies = [ "zeroize", ] @@ -2120,6 +2898,19 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + [[package]] name = "security-framework" version = "3.5.1" @@ -2127,7 +2918,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" dependencies = [ "bitflags", - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -2240,72 +3031,303 @@ dependencies = [ ] [[package]] -name = "sha2" -version = "0.10.9" +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spinning_top" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlx" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +dependencies = [ + "base64", + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.15.5", + "hashlink", + "indexmap", + "ipnetwork", + "log", + "memchr", + "native-tls", + "once_cell", + "percent-encoding", + "serde", + "serde_json", + "sha2", + "smallvec", + "thiserror 2.0.17", + "tokio", + "tokio-stream", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" dependencies = [ - "cfg-if", - "cpufeatures", - "digest", + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn", ] [[package]] -name = "sharded-slab" -version = "0.1.7" +name = "sqlx-macros-core" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" dependencies = [ - "lazy_static", + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn", + "tokio", + "url", ] [[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "simd-adler32" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" - -[[package]] -name = "siphasher" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" - -[[package]] -name = "slab" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" - -[[package]] -name = "smallvec" -version = "1.15.1" +name = "sqlx-mysql" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" +dependencies = [ + "atoi", + "base64", + "bitflags", + "byteorder", + "bytes", + "chrono", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.5", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.17", + "tracing", + "uuid", + "whoami", +] [[package]] -name = "socket2" -version = "0.6.0" +name = "sqlx-postgres" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ - "libc", - "windows-sys 0.59.0", + "atoi", + "base64", + "bitflags", + "byteorder", + "chrono", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "ipnetwork", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand 0.8.5", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.17", + "tracing", + "uuid", + "whoami", ] [[package]] -name = "spinning_top" -version = "0.3.0" +name = "sqlx-sqlite" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300" +checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" dependencies = [ - "lock_api", + "atoi", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "thiserror 2.0.17", + "tracing", + "url", + "uuid", ] [[package]] @@ -2339,6 +3361,17 @@ dependencies = [ "quote", ] +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + [[package]] name = "strsim" version = "0.11.1" @@ -2495,6 +3528,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.47.1" @@ -2843,12 +3891,33 @@ version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + [[package]] name = "unicode-ident" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + [[package]] name = "universal-hash" version = "0.5.1" @@ -2895,6 +3964,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" @@ -2995,6 +4074,12 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" version = "0.2.104" @@ -3086,6 +4171,16 @@ dependencies = [ "string_cache_codegen", ] +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", +] + [[package]] name = "winapi" version = "0.3.9" @@ -3117,12 +4212,74 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.62.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-link" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +[[package]] +name = "windows-result" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -3159,6 +4316,21 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -3192,6 +4364,12 @@ dependencies = [ "windows_x86_64_msvc 0.53.0", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -3204,6 +4382,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -3216,6 +4400,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -3240,6 +4430,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -3252,6 +4448,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -3264,6 +4466,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -3276,6 +4484,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -3306,6 +4520,33 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +[[package]] +name = "x509-parser" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3e137310115a65136898d2079f003ce33331a6c4b0d51f1531d1be082b6425" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "ring", + "rusticata-macros", + "thiserror 2.0.17", + "time", +] + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + [[package]] name = "yoke" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index 002d3184..5a12983a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ repository = "https://github.com/DefGuard/proxy" [dependencies] defguard_version = { git = "https://github.com/DefGuard/defguard.git", rev = "8649a9ba225d7bd2066a09c9e1347705c34bd158" } +defguard_certs = { path = "../defguard/crates/defguard_certs" } # base `axum` deps axum = { version = "0.8", features = ["ws"] } axum-client-ip = "0.7" diff --git a/src/grpc.rs b/src/grpc.rs index e1fef7ec..34485320 100644 --- a/src/grpc.rs +++ b/src/grpc.rs @@ -4,30 +4,62 @@ use std::{ net::SocketAddr, sync::{ atomic::{AtomicBool, AtomicU64, Ordering}, - Arc, Mutex, + Arc, LazyLock, Mutex, }, }; -use defguard_version::{get_tracing_variables, ComponentInfo, DefguardComponent, Version}; +use defguard_version::{ + get_tracing_variables, + server::{grpc::DefguardVersionInterceptor, DefguardVersionLayer}, + ComponentInfo, DefguardComponent, Version, +}; use tokio::sync::{mpsc, oneshot}; use tokio_stream::wrappers::UnboundedReceiverStream; -use tonic::{Request, Response, Status, Streaming}; +use tonic::{ + transport::{Identity, Server, ServerTlsConfig}, + Request, Response, Status, Streaming, +}; +use tower::ServiceBuilder; use tracing::Instrument; use crate::{ error::ApiError, - proto::{core_request, core_response, proxy_server, CoreRequest, CoreResponse, DeviceInfo}, + http::GRPC_SERVER_RESTART_CHANNEL, + proto::{ + core_request, core_response, proxy_server, proxy_setup_request, proxy_setup_response, + CertResponse, CoreRequest, CoreResponse, CsrRequest, DeviceInfo, Done, ProxySetupRequest, + ProxySetupResponse, + }, + CommsChannel, MIN_CORE_VERSION, VERSION, }; // connected clients type ClientMap = HashMap>>; +pub(crate) const GRPC_CERTS_PATH: &str = "/Volumes/dysk/praca/proxy/proxy_certs/"; + +static SETUP_CHANNEL: LazyLock>> = LazyLock::new(|| { + let (tx, rx) = mpsc::channel(10); + ( + Arc::new(tokio::sync::Mutex::new(tx)), + Arc::new(tokio::sync::Mutex::new(rx)), + ) +}); + +#[derive(Debug, Clone, Default)] +pub(crate) struct Configuration { + pub(crate) grpc_key_pem: Option, + pub(crate) grpc_cert_pem: Option, +} + pub(crate) struct ProxyServer { current_id: Arc, clients: Arc>, results: Arc>>>, pub(crate) connected: Arc, pub(crate) core_version: Arc>>, + config: Arc>>, + setup_complete: Arc, } impl ProxyServer { @@ -40,12 +72,120 @@ impl ProxyServer { results: Arc::new(Mutex::new(HashMap::new())), connected: Arc::new(AtomicBool::new(false)), core_version: Arc::new(Mutex::new(None)), + config: Arc::new(Mutex::new(None)), + setup_complete: Arc::new(AtomicBool::new(false)), } } - /// Sends message to the other side of RPC, with given `payload` and optional `device_info`. - /// Returns `tokio::sync::oneshot::Reveicer` to let the caller await reply. - #[instrument(name = "send_grpc_message", level = "debug", skip(self, payload))] + pub(crate) async fn set_tls_config( + &self, + cert_pem: String, + key_pem: String, + ) -> Result<(), ApiError> { + let mut lock = self.config.lock().unwrap(); + let config = lock.get_or_insert_with(Configuration::default); + config.grpc_cert_pem = Some(cert_pem); + config.grpc_key_pem = Some(key_pem); + Ok(()) + } + + pub(crate) async fn await_setup(&self, addr: SocketAddr) -> Result { + info!("gRPC waiting for setup connection from Core on {addr}"); + let mut server_builder = Server::builder(); + let mut server_config = None; + + server_builder + .add_service(proxy_server::ProxyServer::new(self.clone())) + .serve_with_shutdown(addr, async { + let config = SETUP_CHANNEL.1.lock().await.recv().await; + if let Some(cfg) = config { + info!("Received setup configuration: {:?}", cfg); + server_config = cfg; + } else { + error!("Setup communication channel closed unexpectedly"); + } + }) + .await + .map_err(|err| { + error!("gRPC server error during setup: {err}"); + ApiError::Unexpected("gRPC server error during setup".into()) + })?; + + info!("gRPC setup server on {addr} has shut down"); + + server_config.map_or_else( + || { + error!("No server configuration received after setup completion"); + Err(ApiError::Unexpected( + "No server configuration received after setup".into(), + )) + }, + |cfg| { + info!("gRPC setup handshake completed."); + Ok(cfg) + }, + ) + } + + pub(crate) fn configure(&self, config: Configuration) { + let mut lock = self.config.lock().unwrap(); + *lock = Some(config); + } + + pub(crate) fn get_configuration(&self) -> Option { + let lock = self.config.lock().unwrap(); + lock.clone() + } + + pub(crate) async fn run(self, addr: SocketAddr) -> Result<(), anyhow::Error> { + info!("Starting gRPC server on {addr}"); + let config = self.get_configuration(); + let (grpc_cert, grpc_key) = if let Some(cfg) = config { + (cfg.grpc_cert_pem, cfg.grpc_key_pem) + } else { + (None, None) + }; + + info!("Configured gRPC certificate: {grpc_cert:?}"); + info!("Configured gRPC key: {grpc_key:?}"); + + let mut builder = if let (Some(cert), Some(key)) = (grpc_cert, grpc_key) { + let identity = Identity::from_pem(cert, key); + Server::builder().tls_config(ServerTlsConfig::new().identity(identity))? + } else { + Server::builder() + }; + let own_version = Version::parse(VERSION)?; + let versioned_service = ServiceBuilder::new() + .layer(tonic::service::InterceptorLayer::new( + DefguardVersionInterceptor::new( + own_version.clone(), + DefguardComponent::Core, + MIN_CORE_VERSION, + false, + ), + )) + .layer(DefguardVersionLayer::new(own_version)) + .service(proxy_server::ProxyServer::new(self.clone())); + + builder + .add_service(versioned_service) + .serve_with_shutdown(addr, async move { + let mut rx_lock = GRPC_SERVER_RESTART_CHANNEL.1.lock().await; + rx_lock.recv().await; + info!("Shutting down gRPC server for restart..."); + }) + .await + .map_err(|err| { + error!("gRPC server error: {err}"); + err + })?; + + Ok(()) + } + + /// Sends message to the other side of RPC, with given `payload` and `device_info`. + #[instrument(level = "debug", skip(self, payload))] pub(crate) fn send( &self, payload: core_request::Payload, @@ -63,8 +203,7 @@ impl ProxyServer { return Err(ApiError::Unexpected("Failed to send CoreRequest".into())); } let (tx, rx) = oneshot::channel(); - let mut results = self.results.lock().unwrap(); - results.insert(id, tx); + self.results.lock().unwrap().insert(id, tx); self.connected.store(true, Ordering::Relaxed); Ok(rx) } else { @@ -75,6 +214,20 @@ impl ProxyServer { )) } } + + fn setup_completed(&self) -> bool { + let lock = self.config.lock().unwrap(); + lock.is_some() + } + + fn tls_configured(&self) -> bool { + let lock = self.config.lock().unwrap(); + if let Some(cfg) = &*lock { + cfg.grpc_cert_pem.is_some() && cfg.grpc_key_pem.is_some() + } else { + false + } + } } impl Clone for ProxyServer { @@ -85,6 +238,8 @@ impl Clone for ProxyServer { results: Arc::clone(&self.results), connected: Arc::clone(&self.connected), core_version: Arc::clone(&self.core_version), + config: Arc::clone(&self.config), + setup_complete: Arc::clone(&self.setup_complete), } } } @@ -92,6 +247,163 @@ impl Clone for ProxyServer { #[tonic::async_trait] impl proxy_server::Proxy for ProxyServer { type BidiStream = UnboundedReceiverStream>; + type ProxySetupStream = UnboundedReceiverStream>; + + async fn proxy_setup( + &self, + request: Request>, + ) -> Result, Status> { + info!("Starting proxy setup handshake"); + + let (tx, rx) = mpsc::unbounded_channel(); + let tls_configured = self.tls_configured(); + let current_configuration = self.get_configuration(); + + tokio::spawn( + async move { + let mut stream = request.into_inner(); + let initial_info = + if let Some(initial_message) = stream.message().await.unwrap_or(None) { + info!("Received initial connection from Defguard Core"); + if let Some(proxy_setup_response::Payload::InitialSetupInfo(info)) = + initial_message.payload + { + info!("Initial message confirmed from Defguard Core: {:?}", info); + info + } else { + error!("Unexpected payload type in initial message"); + return; + } + } else { + error!("No initial message received from Defguard Core"); + return; + }; + + info!( + "Received initial connection from Defguard Core: {:?}", + initial_info + ); + + let mut key_pair = None; + + if tls_configured { + info!("TLS is already configured, skipping CSR generation"); + if let Err(err) = tx.send(Ok(ProxySetupRequest { + payload: Some(proxy_setup_request::Payload::Done(Done {})), + })) { + error!("Failed to send Done message: {err}"); + } + } else { + let new_key_pair = match defguard_certs::generate_key_pair() { + Ok(kp) => kp, + Err(err) => { + error!("Failed to generate key pair: {err}"); + return; + } + }; + + let subject_alt_names = vec![initial_info.cert_hostname]; + + let csr = match defguard_certs::Csr::new( + &new_key_pair, + &subject_alt_names, + vec![ + (defguard_certs::DnType::CommonName, "Defguard Proxy"), + (defguard_certs::DnType::OrganizationName, "Defguard"), + ], + ) { + Ok(csr) => csr, + Err(err) => { + error!("Failed to generate CSR: {err}"); + return; + } + }; + + let csr_der = csr.to_der(); + + let csr_request = CsrRequest { + csr_der: csr_der.to_vec(), + }; + + if let Err(err) = tx.send(Ok(ProxySetupRequest { + payload: Some(proxy_setup_request::Payload::CsrRequest(csr_request)), + })) { + error!("Failed to send CsrRequest: {err}"); + return; + } + + info!("Sent CSR request to Defguard Core"); + + key_pair = Some(new_key_pair); + } + + let mut configuration = current_configuration; + + loop { + match stream.message().await { + Ok(Some(response)) => match response.payload { + Some(proxy_setup_response::Payload::CertResponse(CertResponse { + cert_der, + })) => { + configuration = Some(Configuration { + grpc_key_pem: key_pair.as_ref().map(|kp| kp.serialize_pem()), + grpc_cert_pem: Some( + defguard_certs::der_to_pem( + &cert_der, + &defguard_certs::PemLabel::Certificate, + ) + .unwrap_or_default(), + ), + }); + + if let Err(err) = tx.send(Ok(ProxySetupRequest { + payload: Some(proxy_setup_request::Payload::Done(Done {})), + })) { + error!("Failed to send Done message: {err}"); + return; + } + + info!("Sent Done message to Defguard Core"); + } + Some(proxy_setup_response::Payload::Done(Done {})) => { + info!("Received Done message from Defguard Core"); + let lock = SETUP_CHANNEL.0.lock().await; + if let Some(configuration) = configuration.take() { + if let Err(err) = lock.send(Some(configuration)).await { + error!("Failed to send setup configuration: {err:?}"); + } + } else { + error!( + "No configuration available to send on setup completion" + ); + } + + info!("Setup handshake completed successfully"); + } + _ => { + error!( + "Unexpected payload type while waiting for CsrAck: {:?}", + response.payload + ); + return; + } + }, + Ok(None) => { + error!("gRPC stream has been closed unexpectedly"); + return; + } + Err(err) => { + error!("gRPC client error while waiting for CertResponse: {err}"); + return; + } + } + } + } + .instrument(tracing::Span::current()), + ); + + Ok(Response::new(UnboundedReceiverStream::new(rx))) + } /// Handle bidirectional communication with Defguard core. #[instrument(name = "bidirectional_communication", level = "info", skip(self))] @@ -99,14 +411,20 @@ impl proxy_server::Proxy for ProxyServer { &self, request: Request>, ) -> Result, Status> { + if !self.setup_completed() { + error!("Received bidi connection before setup completion"); + return Err(Status::failed_precondition( + "Setup must be completed before establishing bidi connection", + )); + } + let Some(address) = request.remote_addr() else { error!("Failed to determine client address for request: {request:?}"); return Err(Status::internal("Failed to determine client address")); }; let maybe_info = ComponentInfo::from_metadata(request.metadata()); let (version, info) = get_tracing_variables(&maybe_info); - let mut core_version = self.core_version.lock().unwrap(); - *core_version = Some(version.clone()); + *self.core_version.lock().unwrap() = Some(version.clone()); let span = tracing::info_span!("core_bidi_stream", component = %DefguardComponent::Core, version = version.to_string(), info); @@ -129,9 +447,9 @@ impl proxy_server::Proxy for ProxyServer { Ok(Some(response)) => { debug!("Received message from Defguard Core ID={}", response.id); connected.store(true, Ordering::Relaxed); - // Discard empty payloads. if let Some(payload) = response.payload { - if let Some(rx) = results.lock().unwrap().remove(&response.id) { + let maybe_rx = results.lock().unwrap().remove(&response.id); + if let Some(rx) = maybe_rx { if let Err(err) = rx.send(payload) { error!("Failed to send message to rx {:?}", err.type_id()); } @@ -153,6 +471,9 @@ impl proxy_server::Proxy for ProxyServer { info!("Defguard core client disconnected: {address}"); connected.store(false, Ordering::Relaxed); clients.lock().unwrap().remove(&address); + if let Err(err) = GRPC_SERVER_RESTART_CHANNEL.0.lock().await.send(()).await { + error!("Failed to notify gRPC server restart: {err}"); + } } .instrument(tracing::Span::current()), ); diff --git a/src/handlers/desktop_client_mfa.rs b/src/handlers/desktop_client_mfa.rs index 26662a89..55a62b68 100644 --- a/src/handlers/desktop_client_mfa.rs +++ b/src/handlers/desktop_client_mfa.rs @@ -1,3 +1,5 @@ +use std::collections::hash_map::Entry; + use axum::{ extract::{ ws::{Message, WebSocket}, @@ -10,7 +12,6 @@ use axum::{ use futures_util::{sink::SinkExt, stream::StreamExt}; use serde::Deserialize; use serde_json::json; -use std::collections::hash_map::Entry; use tokio::{sync::oneshot, task::JoinSet}; use crate::{ diff --git a/src/handlers/enrollment.rs b/src/handlers/enrollment.rs index 788df218..b8c0e73c 100644 --- a/src/handlers/enrollment.rs +++ b/src/handlers/enrollment.rs @@ -3,7 +3,6 @@ use axum_extra::extract::{cookie::Cookie, PrivateCookieJar}; use time::OffsetDateTime; use super::register_mfa::router as register_mfa_router; - use crate::{ error::ApiError, handlers::{get_core_response, mobile_client::register_mobile_auth}, diff --git a/src/handlers/register_mfa.rs b/src/handlers/register_mfa.rs index caef630c..a8a29941 100644 --- a/src/handlers/register_mfa.rs +++ b/src/handlers/register_mfa.rs @@ -1,7 +1,6 @@ -use serde::Deserialize; - use axum::{extract::State, response::IntoResponse, routing::post, Json, Router}; use axum_extra::extract::PrivateCookieJar; +use serde::Deserialize; use crate::{ error::ApiError, diff --git a/src/http.rs b/src/http.rs index 40466842..065e6fc2 100644 --- a/src/http.rs +++ b/src/http.rs @@ -2,7 +2,8 @@ use std::{ collections::HashMap, fs::read_to_string, net::{IpAddr, Ipv4Addr, SocketAddr}, - sync::{atomic::Ordering, Arc}, + path::Path, + sync::{atomic::Ordering, Arc, LazyLock}, time::Duration, }; @@ -22,7 +23,11 @@ use defguard_version::{ DefguardComponent, Version, }; use serde::Serialize; -use tokio::{net::TcpListener, sync::oneshot, task::JoinSet}; +use tokio::{ + net::TcpListener, + sync::{oneshot, Mutex}, + task::JoinSet, +}; use tonic::transport::{Identity, Server, ServerTlsConfig}; use tower::ServiceBuilder; use tower_governor::{ @@ -37,10 +42,10 @@ use crate::{ config::Config, enterprise::handlers::openid_login::{self, FlowType}, error::ApiError, - grpc::ProxyServer, + grpc::{ProxyServer, GRPC_CERTS_PATH}, handlers::{desktop_client_mfa, enrollment, password_reset, polling}, proto::proxy_server, - MIN_CORE_VERSION, VERSION, + CommsChannel, MIN_CORE_VERSION, VERSION, }; pub(crate) static ENROLLMENT_COOKIE_NAME: &str = "defguard_proxy"; @@ -51,6 +56,11 @@ const RATE_LIMITER_CLEANUP_PERIOD: Duration = Duration::from_secs(60); const X_FORWARDED_FOR: &str = "x-forwarded-for"; const X_POWERED_BY: &str = "x-powered-by"; +pub static GRPC_SERVER_RESTART_CHANNEL: LazyLock> = LazyLock::new(|| { + let (tx, rx) = tokio::sync::mpsc::channel(100); + (Arc::new(Mutex::new(tx)), Arc::new(Mutex::new(rx))) +}); + #[derive(Clone)] pub(crate) struct AppState { pub(crate) grpc_server: ProxyServer, @@ -124,7 +134,7 @@ fn get_client_addr(request: &Request) -> String { request .extensions() .get::>() - .map_or("unknown".to_string(), |addr| addr.0.to_string()) + .map_or_else(|| "unknown".to_string(), |addr| addr.0.to_string()) }, |ip| ip.trim().to_string(), ) @@ -171,9 +181,8 @@ pub async fn run_server(config: Config) -> anyhow::Result<()> { debug!("Using config: {config:?}"); let mut tasks = JoinSet::new(); - // connect to upstream gRPC server - let grpc_server = ProxyServer::new(); + let mut grpc_server = ProxyServer::new(); // build application debug!("Setting up API server"); @@ -185,51 +194,61 @@ pub async fn run_server(config: Config) -> anyhow::Result<()> { url: config.url.clone(), }; - // Read gRPC TLS certificate and key. - debug!("Configuring certificates for gRPC"); - let grpc_cert = config - .grpc_cert - .as_ref() - .and_then(|path| read_to_string(path).ok()); - let grpc_key = config - .grpc_key - .as_ref() - .and_then(|path| read_to_string(path).ok()); - debug!("Configured gRPC certificate: {grpc_cert:?}"); + let server_clone = grpc_server.clone(); // Start gRPC server. debug!("Spawning gRPC server"); tasks.spawn(async move { - let addr = SocketAddr::new( - config - .grpc_bind_address - .unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)), - config.grpc_port, - ); - info!("gRPC server is listening on {addr}"); - let mut builder = if let (Some(cert), Some(key)) = (grpc_cert, grpc_key) { - let identity = Identity::from_pem(cert, key); - Server::builder().tls_config(ServerTlsConfig::new().identity(identity))? - } else { - Server::builder() - }; - let own_version = Version::parse(VERSION)?; - let versioned_service = ServiceBuilder::new() - .layer(tonic::service::InterceptorLayer::new( - DefguardVersionInterceptor::new( - own_version.clone(), - DefguardComponent::Core, - MIN_CORE_VERSION, - false, - ), - )) - .layer(DefguardVersionLayer::new(own_version)) - .service(proxy_server::ProxyServer::new(grpc_server)); - builder - .add_service(versioned_service) - .serve(addr) - .await - .context("Error running gRPC server") + loop { + let mut server_to_run = server_clone.clone(); + + let cert_dir = Path::new(GRPC_CERTS_PATH); + if !cert_dir.exists() { + tokio::fs::create_dir_all(cert_dir).await?; + } + + if let (Some(cert), Some(key)) = ( + read_to_string(cert_dir.join("grpc_cert.pem")).ok(), + read_to_string(cert_dir.join("grpc_key.pem")).ok(), + ) { + info!("Using existing gRPC TLS certificates from {cert_dir:?}"); + server_clone + .set_tls_config(cert, key) + .await?; + } else { + info!("No gRPC TLS certificates found at {cert_dir:?}, new certificates will be generated"); + } + + let configuration = server_clone + .await_setup(SocketAddr::new( + config + .grpc_bind_address + .unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)), + config.grpc_port, + )) + .await?; + + if let (Some(cert), Some(key)) = + (&configuration.grpc_cert_pem, &configuration.grpc_key_pem) + { + let cert_path = cert_dir.join("grpc_cert.pem"); + let key_path = cert_dir.join("grpc_key.pem"); + tokio::fs::write(&cert_path, cert).await?; + tokio::fs::write(&key_path, key).await?; + } + + let addr = SocketAddr::new( + config + .grpc_bind_address + .unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)), + config.grpc_port, + ); + + server_to_run.configure(configuration); + if let Err(e) = server_to_run.run(addr).await { + error!("gRPC server error: {e:?}, restarting..."); + } + } }); // Setup tower_governor rate-limiter diff --git a/src/lib.rs b/src/lib.rs index e4ccce8d..b3e2d13b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,7 @@ +use std::sync::Arc; + use defguard_version::Version; +use tokio::sync::mpsc; pub mod assets; pub mod config; @@ -18,3 +21,8 @@ extern crate tracing; pub static VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), "+", env!("VERGEN_GIT_SHA")); pub const MIN_CORE_VERSION: Version = Version::new(1, 5, 0); + +type CommsChannel = ( + Arc>>, + Arc>>, +); From 5f1dcd39c22ecaddbe579ff74791861514ed68c6 Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Fri, 19 Dec 2025 13:54:59 +0100 Subject: [PATCH 2/9] cleanup --- src/grpc.rs | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/src/grpc.rs b/src/grpc.rs index 34485320..a4c6dace 100644 --- a/src/grpc.rs +++ b/src/grpc.rs @@ -77,11 +77,7 @@ impl ProxyServer { } } - pub(crate) async fn set_tls_config( - &self, - cert_pem: String, - key_pem: String, - ) -> Result<(), ApiError> { + pub(crate) fn set_tls_config(&self, cert_pem: String, key_pem: String) -> Result<(), ApiError> { let mut lock = self.config.lock().unwrap(); let config = lock.get_or_insert_with(Configuration::default); config.grpc_cert_pem = Some(cert_pem); @@ -99,7 +95,7 @@ impl ProxyServer { .serve_with_shutdown(addr, async { let config = SETUP_CHANNEL.1.lock().await.recv().await; if let Some(cfg) = config { - info!("Received setup configuration: {:?}", cfg); + info!("Received Proxy setup configuration from Core"); server_config = cfg; } else { error!("Setup communication channel closed unexpectedly"); @@ -146,9 +142,6 @@ impl ProxyServer { (None, None) }; - info!("Configured gRPC certificate: {grpc_cert:?}"); - info!("Configured gRPC key: {grpc_key:?}"); - let mut builder = if let (Some(cert), Some(key)) = (grpc_cert, grpc_key) { let identity = Identity::from_pem(cert, key); Server::builder().tls_config(ServerTlsConfig::new().identity(identity))? @@ -222,11 +215,9 @@ impl ProxyServer { fn tls_configured(&self) -> bool { let lock = self.config.lock().unwrap(); - if let Some(cfg) = &*lock { - cfg.grpc_cert_pem.is_some() && cfg.grpc_key_pem.is_some() - } else { - false - } + (*lock) + .as_ref() + .is_some_and(|config| config.grpc_cert_pem.is_some() && config.grpc_key_pem.is_some()) } } @@ -350,7 +341,7 @@ impl proxy_server::Proxy for ProxyServer { grpc_cert_pem: Some( defguard_certs::der_to_pem( &cert_der, - &defguard_certs::PemLabel::Certificate, + defguard_certs::PemLabel::Certificate, ) .unwrap_or_default(), ), From cf024eb5d54dee9479f52230b55fff2c32374f00 Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Fri, 19 Dec 2025 15:50:01 +0100 Subject: [PATCH 3/9] versions --- src/grpc.rs | 21 ++++++++++++++++++--- src/http.rs | 5 ++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/grpc.rs b/src/grpc.rs index a4c6dace..e3ff1bcf 100644 --- a/src/grpc.rs +++ b/src/grpc.rs @@ -85,12 +85,27 @@ impl ProxyServer { Ok(()) } - pub(crate) async fn await_setup(&self, addr: SocketAddr) -> Result { + pub(crate) async fn await_setup( + &self, + addr: SocketAddr, + ) -> Result { info!("gRPC waiting for setup connection from Core on {addr}"); let mut server_builder = Server::builder(); let mut server_config = None; + // let own_version = Version::parse(VERSION)?; + let own_version = Version::parse(VERSION)?; + server_builder + .layer(tonic::service::InterceptorLayer::new( + DefguardVersionInterceptor::new( + own_version.clone(), + DefguardComponent::Core, + MIN_CORE_VERSION, + false, + ), + )) + .layer(DefguardVersionLayer::new(own_version)) .add_service(proxy_server::ProxyServer::new(self.clone())) .serve_with_shutdown(addr, async { let config = SETUP_CHANNEL.1.lock().await.recv().await; @@ -109,7 +124,7 @@ impl ProxyServer { info!("gRPC setup server on {addr} has shut down"); - server_config.map_or_else( + Ok(server_config.map_or_else( || { error!("No server configuration received after setup completion"); Err(ApiError::Unexpected( @@ -120,7 +135,7 @@ impl ProxyServer { info!("gRPC setup handshake completed."); Ok(cfg) }, - ) + )?) } pub(crate) fn configure(&self, config: Configuration) { diff --git a/src/http.rs b/src/http.rs index 065e6fc2..88386c91 100644 --- a/src/http.rs +++ b/src/http.rs @@ -200,7 +200,7 @@ pub async fn run_server(config: Config) -> anyhow::Result<()> { debug!("Spawning gRPC server"); tasks.spawn(async move { loop { - let mut server_to_run = server_clone.clone(); + let server_to_run = server_clone.clone(); let cert_dir = Path::new(GRPC_CERTS_PATH); if !cert_dir.exists() { @@ -213,8 +213,7 @@ pub async fn run_server(config: Config) -> anyhow::Result<()> { ) { info!("Using existing gRPC TLS certificates from {cert_dir:?}"); server_clone - .set_tls_config(cert, key) - .await?; + .set_tls_config(cert, key)?; } else { info!("No gRPC TLS certificates found at {cert_dir:?}, new certificates will be generated"); } From 694467787aa6f00b6b39f40713f31ad7cff04810 Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Fri, 2 Jan 2026 13:47:03 +0100 Subject: [PATCH 4/9] prevent concurrent setups, allow multiple connections --- src/grpc.rs | 80 +++++++++++++++++++++++++++++++++++++++-------------- src/http.rs | 47 ++++++++++++++++--------------- 2 files changed, 84 insertions(+), 43 deletions(-) diff --git a/src/grpc.rs b/src/grpc.rs index e3ff1bcf..d17bd040 100644 --- a/src/grpc.rs +++ b/src/grpc.rs @@ -36,7 +36,8 @@ use crate::{ // connected clients type ClientMap = HashMap>>; -pub(crate) const GRPC_CERTS_PATH: &str = "/Volumes/dysk/praca/proxy/proxy_certs/"; +// TODO: Change this +pub(crate) const GRPC_CERTS_PATH: &str = "./proxy_certs"; static SETUP_CHANNEL: LazyLock>> = LazyLock::new(|| { let (tx, rx) = mpsc::channel(10); @@ -59,7 +60,7 @@ pub(crate) struct ProxyServer { pub(crate) connected: Arc, pub(crate) core_version: Arc>>, config: Arc>>, - setup_complete: Arc, + setup_in_progress: Arc, } impl ProxyServer { @@ -73,7 +74,7 @@ impl ProxyServer { connected: Arc::new(AtomicBool::new(false)), core_version: Arc::new(Mutex::new(None)), config: Arc::new(Mutex::new(None)), - setup_complete: Arc::new(AtomicBool::new(false)), + setup_in_progress: Arc::new(AtomicBool::new(false)), } } @@ -223,7 +224,7 @@ impl ProxyServer { } } - fn setup_completed(&self) -> bool { + pub(crate) fn setup_completed(&self) -> bool { let lock = self.config.lock().unwrap(); lock.is_some() } @@ -245,7 +246,7 @@ impl Clone for ProxyServer { connected: Arc::clone(&self.connected), core_version: Arc::clone(&self.core_version), config: Arc::clone(&self.config), - setup_complete: Arc::clone(&self.setup_complete), + setup_in_progress: Arc::clone(&self.setup_in_progress), } } } @@ -259,31 +260,52 @@ impl proxy_server::Proxy for ProxyServer { &self, request: Request>, ) -> Result, Status> { + if self.setup_in_progress.swap(true, Ordering::SeqCst) { + warn!("Proxy setup already in progress, rejecting concurrent setup attempt"); + return Err(Status::already_exists("Proxy setup is already in progress")); + } + info!("Starting proxy setup handshake"); let (tx, rx) = mpsc::unbounded_channel(); let tls_configured = self.tls_configured(); let current_configuration = self.get_configuration(); + let setup_in_progress = Arc::clone(&self.setup_in_progress); tokio::spawn( async move { let mut stream = request.into_inner(); - let initial_info = - if let Some(initial_message) = stream.message().await.unwrap_or(None) { + let mut setup_successful = false; + let initial_info = match stream.message().await { + Ok(Some(ProxySetupResponse { + payload: Some(proxy_setup_response::Payload::InitialSetupInfo(info)), + })) => { info!("Received initial connection from Defguard Core"); - if let Some(proxy_setup_response::Payload::InitialSetupInfo(info)) = - initial_message.payload - { - info!("Initial message confirmed from Defguard Core: {:?}", info); - info - } else { - error!("Unexpected payload type in initial message"); - return; - } - } else { + info + } + Ok(Some(ProxySetupResponse { + payload: Some(proxy_setup_response::Payload::Done(Done {})), + })) => { + info!("Received Done message from Defguard Core, skipping setup phase"); + setup_in_progress.store(false, Ordering::SeqCst); + return; + } + Ok(Some(msg)) => { + error!("Unexpected payload type in initial message: {:?}", msg); + setup_in_progress.store(false, Ordering::SeqCst); + return; + } + Ok(None) => { error!("No initial message received from Defguard Core"); + setup_in_progress.store(false, Ordering::SeqCst); return; - }; + } + Err(err) => { + error!("Error receiving initial message from Defguard Core: {err}"); + setup_in_progress.store(false, Ordering::SeqCst); + return; + } + }; info!( "Received initial connection from Defguard Core: {:?}", @@ -298,12 +320,15 @@ impl proxy_server::Proxy for ProxyServer { payload: Some(proxy_setup_request::Payload::Done(Done {})), })) { error!("Failed to send Done message: {err}"); + } else { + setup_successful = true; } } else { let new_key_pair = match defguard_certs::generate_key_pair() { Ok(kp) => kp, Err(err) => { error!("Failed to generate key pair: {err}"); + setup_in_progress.store(false, Ordering::SeqCst); return; } }; @@ -321,6 +346,7 @@ impl proxy_server::Proxy for ProxyServer { Ok(csr) => csr, Err(err) => { error!("Failed to generate CSR: {err}"); + setup_in_progress.store(false, Ordering::SeqCst); return; } }; @@ -335,6 +361,7 @@ impl proxy_server::Proxy for ProxyServer { payload: Some(proxy_setup_request::Payload::CsrRequest(csr_request)), })) { error!("Failed to send CsrRequest: {err}"); + setup_in_progress.store(false, Ordering::SeqCst); return; } @@ -366,6 +393,7 @@ impl proxy_server::Proxy for ProxyServer { payload: Some(proxy_setup_request::Payload::Done(Done {})), })) { error!("Failed to send Done message: {err}"); + setup_in_progress.store(false, Ordering::SeqCst); return; } @@ -377,6 +405,8 @@ impl proxy_server::Proxy for ProxyServer { if let Some(configuration) = configuration.take() { if let Err(err) = lock.send(Some(configuration)).await { error!("Failed to send setup configuration: {err:?}"); + } else { + setup_successful = true; } } else { error!( @@ -385,25 +415,36 @@ impl proxy_server::Proxy for ProxyServer { } info!("Setup handshake completed successfully"); + break; } _ => { error!( "Unexpected payload type while waiting for CsrAck: {:?}", response.payload ); + setup_in_progress.store(false, Ordering::SeqCst); return; } }, Ok(None) => { error!("gRPC stream has been closed unexpectedly"); + setup_in_progress.store(false, Ordering::SeqCst); return; } Err(err) => { error!("gRPC client error while waiting for CertResponse: {err}"); + setup_in_progress.store(false, Ordering::SeqCst); return; } } } + + setup_in_progress.store(false, Ordering::SeqCst); + if setup_successful { + info!("Setup completed successfully"); + } else { + warn!("Setup did not complete successfully"); + } } .instrument(tracing::Span::current()), ); @@ -477,9 +518,6 @@ impl proxy_server::Proxy for ProxyServer { info!("Defguard core client disconnected: {address}"); connected.store(false, Ordering::Relaxed); clients.lock().unwrap().remove(&address); - if let Err(err) = GRPC_SERVER_RESTART_CHANNEL.0.lock().await.send(()).await { - error!("Failed to notify gRPC server restart: {err}"); - } } .instrument(tracing::Span::current()), ); diff --git a/src/http.rs b/src/http.rs index 88386c91..4cd8e704 100644 --- a/src/http.rs +++ b/src/http.rs @@ -212,28 +212,32 @@ pub async fn run_server(config: Config) -> anyhow::Result<()> { read_to_string(cert_dir.join("grpc_key.pem")).ok(), ) { info!("Using existing gRPC TLS certificates from {cert_dir:?}"); - server_clone - .set_tls_config(cert, key)?; - } else { + server_clone.set_tls_config(cert, key)?; + } else if !server_clone.setup_completed() { + // Only attempt setup if not already configured info!("No gRPC TLS certificates found at {cert_dir:?}, new certificates will be generated"); - } - - let configuration = server_clone - .await_setup(SocketAddr::new( - config - .grpc_bind_address - .unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)), - config.grpc_port, - )) - .await?; - - if let (Some(cert), Some(key)) = - (&configuration.grpc_cert_pem, &configuration.grpc_key_pem) - { - let cert_path = cert_dir.join("grpc_cert.pem"); - let key_path = cert_dir.join("grpc_key.pem"); - tokio::fs::write(&cert_path, cert).await?; - tokio::fs::write(&key_path, key).await?; + let configuration = server_clone + .await_setup(SocketAddr::new( + config + .grpc_bind_address + .unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)), + config.grpc_port, + )) + .await?; + info!("Generated new gRPC TLS certificates"); + + if let (Some(cert), Some(key)) = + (&configuration.grpc_cert_pem, &configuration.grpc_key_pem) + { + let cert_path = cert_dir.join("grpc_cert.pem"); + let key_path = cert_dir.join("grpc_key.pem"); + tokio::fs::write(&cert_path, cert).await?; + tokio::fs::write(&key_path, key).await?; + } + + server_to_run.configure(configuration); + } else { + info!("Proxy already configured, skipping setup phase"); } let addr = SocketAddr::new( @@ -243,7 +247,6 @@ pub async fn run_server(config: Config) -> anyhow::Result<()> { config.grpc_port, ); - server_to_run.configure(configuration); if let Err(e) = server_to_run.run(addr).await { error!("gRPC server error: {e:?}, restarting..."); } From 304c99b438d757d7d57dea47a5d6a9bfcdfd4dbc Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Fri, 2 Jan 2026 14:03:05 +0100 Subject: [PATCH 5/9] inform core about errors --- src/grpc.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/grpc.rs b/src/grpc.rs index d17bd040..7be3f88e 100644 --- a/src/grpc.rs +++ b/src/grpc.rs @@ -292,16 +292,25 @@ impl proxy_server::Proxy for ProxyServer { } Ok(Some(msg)) => { error!("Unexpected payload type in initial message: {:?}", msg); + let _ = tx.send(Err(Status::invalid_argument( + "Unexpected payload type in initial message", + ))); setup_in_progress.store(false, Ordering::SeqCst); return; } Ok(None) => { error!("No initial message received from Defguard Core"); + let _ = tx.send(Err(Status::aborted( + "No initial message received from Defguard Core", + ))); setup_in_progress.store(false, Ordering::SeqCst); return; } Err(err) => { error!("Error receiving initial message from Defguard Core: {err}"); + let _ = tx.send(Err(Status::internal(format!( + "Error receiving initial message: {err}" + )))); setup_in_progress.store(false, Ordering::SeqCst); return; } @@ -328,6 +337,9 @@ impl proxy_server::Proxy for ProxyServer { Ok(kp) => kp, Err(err) => { error!("Failed to generate key pair: {err}"); + let _ = tx.send(Err(Status::internal(format!( + "Failed to generate key pair: {err}" + )))); setup_in_progress.store(false, Ordering::SeqCst); return; } @@ -346,6 +358,9 @@ impl proxy_server::Proxy for ProxyServer { Ok(csr) => csr, Err(err) => { error!("Failed to generate CSR: {err}"); + let _ = tx.send(Err(Status::internal(format!( + "Failed to generate CSR: {err}" + )))); setup_in_progress.store(false, Ordering::SeqCst); return; } @@ -393,6 +408,7 @@ impl proxy_server::Proxy for ProxyServer { payload: Some(proxy_setup_request::Payload::Done(Done {})), })) { error!("Failed to send Done message: {err}"); + // Can't send error since tx already failed setup_in_progress.store(false, Ordering::SeqCst); return; } @@ -422,17 +438,25 @@ impl proxy_server::Proxy for ProxyServer { "Unexpected payload type while waiting for CsrAck: {:?}", response.payload ); + let _ = tx.send(Err(Status::invalid_argument( + "Unexpected payload type while waiting for response", + ))); setup_in_progress.store(false, Ordering::SeqCst); return; } }, Ok(None) => { error!("gRPC stream has been closed unexpectedly"); + let _ = tx.send(Err(Status::aborted( + "gRPC stream has been closed unexpectedly", + ))); setup_in_progress.store(false, Ordering::SeqCst); return; } Err(err) => { error!("gRPC client error while waiting for CertResponse: {err}"); + let _ = + tx.send(Err(Status::internal(format!("gRPC client error: {err}")))); setup_in_progress.store(false, Ordering::SeqCst); return; } From 9a04621d16319c114e685d6ecc7a9cb2c5f3e9de Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Wed, 7 Jan 2026 11:53:49 +0100 Subject: [PATCH 6/9] cleanup --- src/config.rs | 8 ++ src/grpc.rs | 211 ++++++++++++++++++++++++-------------------------- src/http.rs | 52 ++++++------- 3 files changed, 134 insertions(+), 137 deletions(-) diff --git a/src/config.rs b/src/config.rs index a6367b01..6a1e9e0d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -59,6 +59,14 @@ pub struct Config { #[arg(long, env = "DEFGUARD_GRPC_BIND_ADDRESS")] pub grpc_bind_address: Option, + + // TODO: On different platforms this may be different + #[arg( + long, + env = "DEFGUARD_PROXY_CERT_DIR", + default_value = "/etc/defguard/certs" + )] + pub cert_dir: PathBuf, } #[derive(thiserror::Error, Debug)] diff --git a/src/grpc.rs b/src/grpc.rs index 7be3f88e..78d7eb42 100644 --- a/src/grpc.rs +++ b/src/grpc.rs @@ -36,9 +36,6 @@ use crate::{ // connected clients type ClientMap = HashMap>>; -// TODO: Change this -pub(crate) const GRPC_CERTS_PATH: &str = "./proxy_certs"; - static SETUP_CHANNEL: LazyLock>> = LazyLock::new(|| { let (tx, rx) = mpsc::channel(10); ( @@ -49,8 +46,8 @@ static SETUP_CHANNEL: LazyLock>> = LazyLock:: #[derive(Debug, Clone, Default)] pub(crate) struct Configuration { - pub(crate) grpc_key_pem: Option, - pub(crate) grpc_cert_pem: Option, + pub(crate) grpc_key_pem: String, + pub(crate) grpc_cert_pem: String, } pub(crate) struct ProxyServer { @@ -81,8 +78,8 @@ impl ProxyServer { pub(crate) fn set_tls_config(&self, cert_pem: String, key_pem: String) -> Result<(), ApiError> { let mut lock = self.config.lock().unwrap(); let config = lock.get_or_insert_with(Configuration::default); - config.grpc_cert_pem = Some(cert_pem); - config.grpc_key_pem = Some(key_pem); + config.grpc_cert_pem = cert_pem; + config.grpc_key_pem = key_pem; Ok(()) } @@ -91,10 +88,9 @@ impl ProxyServer { addr: SocketAddr, ) -> Result { info!("gRPC waiting for setup connection from Core on {addr}"); - let mut server_builder = Server::builder(); + let server_builder = Server::builder(); let mut server_config = None; - // let own_version = Version::parse(VERSION)?; let own_version = Version::parse(VERSION)?; server_builder @@ -111,7 +107,7 @@ impl ProxyServer { .serve_with_shutdown(addr, async { let config = SETUP_CHANNEL.1.lock().await.recv().await; if let Some(cfg) = config { - info!("Received Proxy setup configuration from Core"); + debug!("Received the passed Proxy configuration"); server_config = cfg; } else { error!("Setup communication channel closed unexpectedly"); @@ -123,7 +119,7 @@ impl ProxyServer { ApiError::Unexpected("gRPC server error during setup".into()) })?; - info!("gRPC setup server on {addr} has shut down"); + debug!("gRPC setup server on {addr} has shutdown after completing setup"); Ok(server_config.map_or_else( || { @@ -133,7 +129,7 @@ impl ProxyServer { )) }, |cfg| { - info!("gRPC setup handshake completed."); + debug!("Returning received server configuration"); Ok(cfg) }, )?) @@ -155,15 +151,13 @@ impl ProxyServer { let (grpc_cert, grpc_key) = if let Some(cfg) = config { (cfg.grpc_cert_pem, cfg.grpc_key_pem) } else { - (None, None) + return Err(anyhow::anyhow!("gRPC server configuration is missing")); }; - let mut builder = if let (Some(cert), Some(key)) = (grpc_cert, grpc_key) { - let identity = Identity::from_pem(cert, key); - Server::builder().tls_config(ServerTlsConfig::new().identity(identity))? - } else { - Server::builder() - }; + let identity = Identity::from_pem(grpc_cert, grpc_key); + let mut builder = + Server::builder().tls_config(ServerTlsConfig::new().identity(identity))?; + let own_version = Version::parse(VERSION)?; let versioned_service = ServiceBuilder::new() .layer(tonic::service::InterceptorLayer::new( @@ -228,13 +222,6 @@ impl ProxyServer { let lock = self.config.lock().unwrap(); lock.is_some() } - - fn tls_configured(&self) -> bool { - let lock = self.config.lock().unwrap(); - (*lock) - .as_ref() - .is_some_and(|config| config.grpc_cert_pem.is_some() && config.grpc_key_pem.is_some()) - } } impl Clone for ProxyServer { @@ -254,21 +241,21 @@ impl Clone for ProxyServer { #[tonic::async_trait] impl proxy_server::Proxy for ProxyServer { type BidiStream = UnboundedReceiverStream>; - type ProxySetupStream = UnboundedReceiverStream>; + type SetupStream = UnboundedReceiverStream>; - async fn proxy_setup( + async fn setup( &self, request: Request>, - ) -> Result, Status> { + ) -> Result, Status> { if self.setup_in_progress.swap(true, Ordering::SeqCst) { - warn!("Proxy setup already in progress, rejecting concurrent setup attempt"); + info!("Proxy setup already in progress, rejecting concurrent setup attempt"); return Err(Status::already_exists("Proxy setup is already in progress")); } info!("Starting proxy setup handshake"); let (tx, rx) = mpsc::unbounded_channel(); - let tls_configured = self.tls_configured(); + let tls_configured = self.setup_completed(); let current_configuration = self.get_configuration(); let setup_in_progress = Arc::clone(&self.setup_in_progress); @@ -280,34 +267,35 @@ impl proxy_server::Proxy for ProxyServer { Ok(Some(ProxySetupResponse { payload: Some(proxy_setup_response::Payload::InitialSetupInfo(info)), })) => { - info!("Received initial connection from Defguard Core"); + info!("Received initial setup information from Defguard Core"); + debug!("Initial setup info: {:?}", info); info } Ok(Some(ProxySetupResponse { payload: Some(proxy_setup_response::Payload::Done(Done {})), })) => { - info!("Received Done message from Defguard Core, skipping setup phase"); + info!("Received setup termination message from Defguard Core, skipping setup phase"); setup_in_progress.store(false, Ordering::SeqCst); return; } Ok(Some(msg)) => { - error!("Unexpected payload type in initial message: {:?}", msg); + error!("Unexpected payload type in initial Proxy setup message: {:?}", msg); let _ = tx.send(Err(Status::invalid_argument( - "Unexpected payload type in initial message", + "Unexpected payload type in initial Proxy setup message", ))); setup_in_progress.store(false, Ordering::SeqCst); return; } Ok(None) => { - error!("No initial message received from Defguard Core"); + error!("No initial Proxy setup message received from Defguard Core"); let _ = tx.send(Err(Status::aborted( - "No initial message received from Defguard Core", + "No initial Proxy setup message received from Defguard Core", ))); setup_in_progress.store(false, Ordering::SeqCst); return; } Err(err) => { - error!("Error receiving initial message from Defguard Core: {err}"); + error!("Error receiving initial Proxy setup message from Defguard Core: {err}"); let _ = tx.send(Err(Status::internal(format!( "Error receiving initial message: {err}" )))); @@ -316,75 +304,66 @@ impl proxy_server::Proxy for ProxyServer { } }; - info!( - "Received initial connection from Defguard Core: {:?}", - initial_info - ); - - let mut key_pair = None; - if tls_configured { - info!("TLS is already configured, skipping CSR generation"); + info!("Certificates already generated, skipping CSR generation"); if let Err(err) = tx.send(Ok(ProxySetupRequest { payload: Some(proxy_setup_request::Payload::Done(Done {})), })) { error!("Failed to send Done message: {err}"); - } else { - setup_successful = true; } - } else { - let new_key_pair = match defguard_certs::generate_key_pair() { - Ok(kp) => kp, - Err(err) => { - error!("Failed to generate key pair: {err}"); - let _ = tx.send(Err(Status::internal(format!( - "Failed to generate key pair: {err}" - )))); - setup_in_progress.store(false, Ordering::SeqCst); - return; - } - }; - - let subject_alt_names = vec![initial_info.cert_hostname]; - - let csr = match defguard_certs::Csr::new( - &new_key_pair, - &subject_alt_names, - vec![ - (defguard_certs::DnType::CommonName, "Defguard Proxy"), - (defguard_certs::DnType::OrganizationName, "Defguard"), - ], - ) { - Ok(csr) => csr, - Err(err) => { - error!("Failed to generate CSR: {err}"); - let _ = tx.send(Err(Status::internal(format!( - "Failed to generate CSR: {err}" - )))); - setup_in_progress.store(false, Ordering::SeqCst); - return; - } - }; - - let csr_der = csr.to_der(); + return; + } - let csr_request = CsrRequest { - csr_der: csr_der.to_vec(), - }; + info!("Generating new key pair and CSR for certificate issuance"); + let key_pair = match defguard_certs::generate_key_pair() { + Ok(kp) => kp, + Err(err) => { + error!("Failed to generate key pair: {err}"); + let _ = tx.send(Err(Status::internal(format!( + "Failed to generate key pair: {err}" + )))); + setup_in_progress.store(false, Ordering::SeqCst); + return; + } + }; - if let Err(err) = tx.send(Ok(ProxySetupRequest { - payload: Some(proxy_setup_request::Payload::CsrRequest(csr_request)), - })) { - error!("Failed to send CsrRequest: {err}"); + let subject_alt_names = vec![initial_info.cert_hostname]; + + let csr = match defguard_certs::Csr::new( + &key_pair, + &subject_alt_names, + vec![ + // TODO: Change it? + (defguard_certs::DnType::CommonName, "Defguard Proxy"), + (defguard_certs::DnType::OrganizationName, "Defguard"), + ], + ) { + Ok(csr) => csr, + Err(err) => { + error!("Failed to generate CSR: {err}"); + let _ = tx.send(Err(Status::internal(format!( + "Failed to generate CSR: {err}" + )))); setup_in_progress.store(false, Ordering::SeqCst); return; } + }; - info!("Sent CSR request to Defguard Core"); + let csr_der = csr.to_der(); + let csr_request = CsrRequest { + csr_der: csr_der.to_vec(), + }; - key_pair = Some(new_key_pair); + if let Err(err) = tx.send(Ok(ProxySetupRequest { + payload: Some(proxy_setup_request::Payload::CsrRequest(csr_request)), + })) { + error!("Failed to send CsrRequest: {err}"); + setup_in_progress.store(false, Ordering::SeqCst); + return; } + debug!("Sent CSR request to Core"); + let mut configuration = current_configuration; loop { @@ -393,15 +372,25 @@ impl proxy_server::Proxy for ProxyServer { Some(proxy_setup_response::Payload::CertResponse(CertResponse { cert_der, })) => { + debug!("Received CertResponse from Defguard Core"); + let grpc_cert_pem = match defguard_certs::der_to_pem( + &cert_der, + defguard_certs::PemLabel::Certificate, + ) { + Ok(pem) => pem, + Err(err) => { + error!("Failed to convert certificate DER to PEM: {err}"); + let _ = tx.send(Err(Status::internal(format!( + "Failed to convert certificate DER to PEM: {err}" + )))); + setup_in_progress.store(false, Ordering::SeqCst); + return; + } + }; + configuration = Some(Configuration { - grpc_key_pem: key_pair.as_ref().map(|kp| kp.serialize_pem()), - grpc_cert_pem: Some( - defguard_certs::der_to_pem( - &cert_der, - defguard_certs::PemLabel::Certificate, - ) - .unwrap_or_default(), - ), + grpc_key_pem: key_pair.serialize_pem(), + grpc_cert_pem, }); if let Err(err) = tx.send(Ok(ProxySetupRequest { @@ -413,20 +402,22 @@ impl proxy_server::Proxy for ProxyServer { return; } - info!("Sent Done message to Defguard Core"); + debug!("Sent Done message to Defguard Core"); } Some(proxy_setup_response::Payload::Done(Done {})) => { - info!("Received Done message from Defguard Core"); + debug!("Received setup termination message from Defguard Core"); let lock = SETUP_CHANNEL.0.lock().await; + debug!("Passing Proxy configuration to the gRPC server"); if let Some(configuration) = configuration.take() { if let Err(err) = lock.send(Some(configuration)).await { - error!("Failed to send setup configuration: {err:?}"); + error!("Failed to pass the received Proxy configuration to the gRPC server: {err:?}"); } else { + debug!("Passed Proxy configuration to the gRPC server"); setup_successful = true; } } else { error!( - "No configuration available to send on setup completion" + "No configuration available to pass to the gRPC server on setup completion" ); } @@ -435,18 +426,18 @@ impl proxy_server::Proxy for ProxyServer { } _ => { error!( - "Unexpected payload type while waiting for CsrAck: {:?}", + "Unexpected payload type while waiting for setup message: {:?}", response.payload ); let _ = tx.send(Err(Status::invalid_argument( - "Unexpected payload type while waiting for response", + "Unexpected payload type while waiting for setup message", ))); setup_in_progress.store(false, Ordering::SeqCst); return; } }, Ok(None) => { - error!("gRPC stream has been closed unexpectedly"); + error!("gRPC setup stream has been closed unexpectedly"); let _ = tx.send(Err(Status::aborted( "gRPC stream has been closed unexpectedly", ))); @@ -454,7 +445,7 @@ impl proxy_server::Proxy for ProxyServer { return; } Err(err) => { - error!("gRPC client error while waiting for CertResponse: {err}"); + error!("gRPC setup client error while waiting for CertResponse: {err}"); let _ = tx.send(Err(Status::internal(format!("gRPC client error: {err}")))); setup_in_progress.store(false, Ordering::SeqCst); @@ -465,9 +456,9 @@ impl proxy_server::Proxy for ProxyServer { setup_in_progress.store(false, Ordering::SeqCst); if setup_successful { - info!("Setup completed successfully"); + info!("Proxy setup completed successfully"); } else { - warn!("Setup did not complete successfully"); + warn!("Proxy setup did not complete successfully"); } } .instrument(tracing::Span::current()), diff --git a/src/http.rs b/src/http.rs index 4cd8e704..571271f4 100644 --- a/src/http.rs +++ b/src/http.rs @@ -18,18 +18,13 @@ use axum::{ }; use axum_extra::extract::cookie::Key; use clap::crate_version; -use defguard_version::{ - server::{grpc::DefguardVersionInterceptor, DefguardVersionLayer}, - DefguardComponent, Version, -}; +use defguard_version::{server::DefguardVersionLayer, Version}; use serde::Serialize; use tokio::{ net::TcpListener, sync::{oneshot, Mutex}, task::JoinSet, }; -use tonic::transport::{Identity, Server, ServerTlsConfig}; -use tower::ServiceBuilder; use tower_governor::{ governor::GovernorConfigBuilder, key_extractor::SmartIpKeyExtractor, GovernorLayer, }; @@ -42,10 +37,9 @@ use crate::{ config::Config, enterprise::handlers::openid_login::{self, FlowType}, error::ApiError, - grpc::{ProxyServer, GRPC_CERTS_PATH}, + grpc::{Configuration, ProxyServer}, handlers::{desktop_client_mfa, enrollment, password_reset, polling}, - proto::proxy_server, - CommsChannel, MIN_CORE_VERSION, VERSION, + CommsChannel, VERSION, }; pub(crate) static ENROLLMENT_COOKIE_NAME: &str = "defguard_proxy"; @@ -55,6 +49,8 @@ const DEFGUARD_CORE_VERSION_HEADER: &str = "defguard-core-version"; const RATE_LIMITER_CLEANUP_PERIOD: Duration = Duration::from_secs(60); const X_FORWARDED_FOR: &str = "x-forwarded-for"; const X_POWERED_BY: &str = "x-powered-by"; +const GRPC_CERT_NAME: &str = "proxy_grpc_cert.pem"; +const GRPC_KEY_NAME: &str = "proxy_grpc_key.pem"; pub static GRPC_SERVER_RESTART_CHANNEL: LazyLock> = LazyLock::new(|| { let (tx, rx) = tokio::sync::mpsc::channel(100); @@ -182,7 +178,7 @@ pub async fn run_server(config: Config) -> anyhow::Result<()> { let mut tasks = JoinSet::new(); // connect to upstream gRPC server - let mut grpc_server = ProxyServer::new(); + let grpc_server = ProxyServer::new(); // build application debug!("Setting up API server"); @@ -199,17 +195,17 @@ pub async fn run_server(config: Config) -> anyhow::Result<()> { // Start gRPC server. debug!("Spawning gRPC server"); tasks.spawn(async move { + let cert_dir = Path::new(&config.cert_dir); + if !cert_dir.exists() { + tokio::fs::create_dir_all(cert_dir).await?; + } + loop { let server_to_run = server_clone.clone(); - let cert_dir = Path::new(GRPC_CERTS_PATH); - if !cert_dir.exists() { - tokio::fs::create_dir_all(cert_dir).await?; - } - if let (Some(cert), Some(key)) = ( - read_to_string(cert_dir.join("grpc_cert.pem")).ok(), - read_to_string(cert_dir.join("grpc_key.pem")).ok(), + read_to_string(cert_dir.join(GRPC_CERT_NAME)).ok(), + read_to_string(cert_dir.join(GRPC_KEY_NAME)).ok(), ) { info!("Using existing gRPC TLS certificates from {cert_dir:?}"); server_clone.set_tls_config(cert, key)?; @@ -224,16 +220,18 @@ pub async fn run_server(config: Config) -> anyhow::Result<()> { config.grpc_port, )) .await?; - info!("Generated new gRPC TLS certificates"); + info!("Generated new gRPC TLS certificates and signed by Defguard Core"); + + let Configuration { + grpc_cert_pem, + grpc_key_pem, + .. + } = &configuration; - if let (Some(cert), Some(key)) = - (&configuration.grpc_cert_pem, &configuration.grpc_key_pem) - { - let cert_path = cert_dir.join("grpc_cert.pem"); - let key_path = cert_dir.join("grpc_key.pem"); - tokio::fs::write(&cert_path, cert).await?; - tokio::fs::write(&key_path, key).await?; - } + let cert_path = cert_dir.join(GRPC_CERT_NAME); + let key_path = cert_dir.join(GRPC_KEY_NAME); + tokio::fs::write(&cert_path, grpc_cert_pem).await?; + tokio::fs::write(&key_path, grpc_key_pem).await?; server_to_run.configure(configuration); } else { @@ -353,7 +351,7 @@ pub async fn run_server(config: Config) -> anyhow::Result<()> { .context("Error running HTTP server") }); - info!("Defguard Proxy server initialization complete"); + info!("Defguard Proxy HTTP server initialization complete"); while let Some(Ok(result)) = tasks.join_next().await { result?; } From 2c436012ed94128ff10f4f1bff538e1a90c94cf8 Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Wed, 7 Jan 2026 14:10:19 +0100 Subject: [PATCH 7/9] bump qs --- web/package.json | 2 +- web/pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/web/package.json b/web/package.json index ebc6adce..0fd7c6e4 100644 --- a/web/package.json +++ b/web/package.json @@ -30,7 +30,7 @@ "lodash-es": "^4.17.21", "motion": "^12.23.25", "qrcode.react": "^4.2.0", - "qs": "^6.14.0", + "qs": "^6.14.1", "react": "^19.2.1", "react-dom": "^19.2.1", "react-markdown": "^10.1.0", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index fe3a4be8..3cd53a91 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -60,8 +60,8 @@ importers: specifier: ^4.2.0 version: 4.2.0(react@19.2.1) qs: - specifier: ^6.14.0 - version: 6.14.0 + specifier: ^6.14.1 + version: 6.14.1 react: specifier: ^19.2.1 version: 19.2.1 @@ -2545,8 +2545,8 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - qs@6.14.0: - resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} + qs@6.14.1: + resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==} engines: {node: '>=0.6'} queue-microtask@1.2.3: @@ -5386,7 +5386,7 @@ snapshots: dependencies: react: 19.2.1 - qs@6.14.0: + qs@6.14.1: dependencies: side-channel: 1.1.0 From 4740f831053ee1c576d6582e7d9f1863dacdc3aa Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Fri, 9 Jan 2026 13:42:06 +0100 Subject: [PATCH 8/9] simplify setup --- src/grpc.rs | 295 +-------------------------------------------------- src/http.rs | 7 +- src/lib.rs | 1 + src/setup.rs | 209 ++++++++++++++++++++++++++++++++++++ 4 files changed, 219 insertions(+), 293 deletions(-) create mode 100644 src/setup.rs diff --git a/src/grpc.rs b/src/grpc.rs index 78d7eb42..571ff1a0 100644 --- a/src/grpc.rs +++ b/src/grpc.rs @@ -4,7 +4,7 @@ use std::{ net::SocketAddr, sync::{ atomic::{AtomicBool, AtomicU64, Ordering}, - Arc, LazyLock, Mutex, + Arc, Mutex, }, }; @@ -25,25 +25,13 @@ use tracing::Instrument; use crate::{ error::ApiError, http::GRPC_SERVER_RESTART_CHANNEL, - proto::{ - core_request, core_response, proxy_server, proxy_setup_request, proxy_setup_response, - CertResponse, CoreRequest, CoreResponse, CsrRequest, DeviceInfo, Done, ProxySetupRequest, - ProxySetupResponse, - }, - CommsChannel, MIN_CORE_VERSION, VERSION, + proto::{core_request, core_response, proxy_server, CoreRequest, CoreResponse, DeviceInfo}, + MIN_CORE_VERSION, VERSION, }; // connected clients type ClientMap = HashMap>>; -static SETUP_CHANNEL: LazyLock>> = LazyLock::new(|| { - let (tx, rx) = mpsc::channel(10); - ( - Arc::new(tokio::sync::Mutex::new(tx)), - Arc::new(tokio::sync::Mutex::new(rx)), - ) -}); - #[derive(Debug, Clone, Default)] pub(crate) struct Configuration { pub(crate) grpc_key_pem: String, @@ -83,58 +71,6 @@ impl ProxyServer { Ok(()) } - pub(crate) async fn await_setup( - &self, - addr: SocketAddr, - ) -> Result { - info!("gRPC waiting for setup connection from Core on {addr}"); - let server_builder = Server::builder(); - let mut server_config = None; - - let own_version = Version::parse(VERSION)?; - - server_builder - .layer(tonic::service::InterceptorLayer::new( - DefguardVersionInterceptor::new( - own_version.clone(), - DefguardComponent::Core, - MIN_CORE_VERSION, - false, - ), - )) - .layer(DefguardVersionLayer::new(own_version)) - .add_service(proxy_server::ProxyServer::new(self.clone())) - .serve_with_shutdown(addr, async { - let config = SETUP_CHANNEL.1.lock().await.recv().await; - if let Some(cfg) = config { - debug!("Received the passed Proxy configuration"); - server_config = cfg; - } else { - error!("Setup communication channel closed unexpectedly"); - } - }) - .await - .map_err(|err| { - error!("gRPC server error during setup: {err}"); - ApiError::Unexpected("gRPC server error during setup".into()) - })?; - - debug!("gRPC setup server on {addr} has shutdown after completing setup"); - - Ok(server_config.map_or_else( - || { - error!("No server configuration received after setup completion"); - Err(ApiError::Unexpected( - "No server configuration received after setup".into(), - )) - }, - |cfg| { - debug!("Returning received server configuration"); - Ok(cfg) - }, - )?) - } - pub(crate) fn configure(&self, config: Configuration) { let mut lock = self.config.lock().unwrap(); *lock = Some(config); @@ -241,231 +177,6 @@ impl Clone for ProxyServer { #[tonic::async_trait] impl proxy_server::Proxy for ProxyServer { type BidiStream = UnboundedReceiverStream>; - type SetupStream = UnboundedReceiverStream>; - - async fn setup( - &self, - request: Request>, - ) -> Result, Status> { - if self.setup_in_progress.swap(true, Ordering::SeqCst) { - info!("Proxy setup already in progress, rejecting concurrent setup attempt"); - return Err(Status::already_exists("Proxy setup is already in progress")); - } - - info!("Starting proxy setup handshake"); - - let (tx, rx) = mpsc::unbounded_channel(); - let tls_configured = self.setup_completed(); - let current_configuration = self.get_configuration(); - let setup_in_progress = Arc::clone(&self.setup_in_progress); - - tokio::spawn( - async move { - let mut stream = request.into_inner(); - let mut setup_successful = false; - let initial_info = match stream.message().await { - Ok(Some(ProxySetupResponse { - payload: Some(proxy_setup_response::Payload::InitialSetupInfo(info)), - })) => { - info!("Received initial setup information from Defguard Core"); - debug!("Initial setup info: {:?}", info); - info - } - Ok(Some(ProxySetupResponse { - payload: Some(proxy_setup_response::Payload::Done(Done {})), - })) => { - info!("Received setup termination message from Defguard Core, skipping setup phase"); - setup_in_progress.store(false, Ordering::SeqCst); - return; - } - Ok(Some(msg)) => { - error!("Unexpected payload type in initial Proxy setup message: {:?}", msg); - let _ = tx.send(Err(Status::invalid_argument( - "Unexpected payload type in initial Proxy setup message", - ))); - setup_in_progress.store(false, Ordering::SeqCst); - return; - } - Ok(None) => { - error!("No initial Proxy setup message received from Defguard Core"); - let _ = tx.send(Err(Status::aborted( - "No initial Proxy setup message received from Defguard Core", - ))); - setup_in_progress.store(false, Ordering::SeqCst); - return; - } - Err(err) => { - error!("Error receiving initial Proxy setup message from Defguard Core: {err}"); - let _ = tx.send(Err(Status::internal(format!( - "Error receiving initial message: {err}" - )))); - setup_in_progress.store(false, Ordering::SeqCst); - return; - } - }; - - if tls_configured { - info!("Certificates already generated, skipping CSR generation"); - if let Err(err) = tx.send(Ok(ProxySetupRequest { - payload: Some(proxy_setup_request::Payload::Done(Done {})), - })) { - error!("Failed to send Done message: {err}"); - } - return; - } - - info!("Generating new key pair and CSR for certificate issuance"); - let key_pair = match defguard_certs::generate_key_pair() { - Ok(kp) => kp, - Err(err) => { - error!("Failed to generate key pair: {err}"); - let _ = tx.send(Err(Status::internal(format!( - "Failed to generate key pair: {err}" - )))); - setup_in_progress.store(false, Ordering::SeqCst); - return; - } - }; - - let subject_alt_names = vec![initial_info.cert_hostname]; - - let csr = match defguard_certs::Csr::new( - &key_pair, - &subject_alt_names, - vec![ - // TODO: Change it? - (defguard_certs::DnType::CommonName, "Defguard Proxy"), - (defguard_certs::DnType::OrganizationName, "Defguard"), - ], - ) { - Ok(csr) => csr, - Err(err) => { - error!("Failed to generate CSR: {err}"); - let _ = tx.send(Err(Status::internal(format!( - "Failed to generate CSR: {err}" - )))); - setup_in_progress.store(false, Ordering::SeqCst); - return; - } - }; - - let csr_der = csr.to_der(); - let csr_request = CsrRequest { - csr_der: csr_der.to_vec(), - }; - - if let Err(err) = tx.send(Ok(ProxySetupRequest { - payload: Some(proxy_setup_request::Payload::CsrRequest(csr_request)), - })) { - error!("Failed to send CsrRequest: {err}"); - setup_in_progress.store(false, Ordering::SeqCst); - return; - } - - debug!("Sent CSR request to Core"); - - let mut configuration = current_configuration; - - loop { - match stream.message().await { - Ok(Some(response)) => match response.payload { - Some(proxy_setup_response::Payload::CertResponse(CertResponse { - cert_der, - })) => { - debug!("Received CertResponse from Defguard Core"); - let grpc_cert_pem = match defguard_certs::der_to_pem( - &cert_der, - defguard_certs::PemLabel::Certificate, - ) { - Ok(pem) => pem, - Err(err) => { - error!("Failed to convert certificate DER to PEM: {err}"); - let _ = tx.send(Err(Status::internal(format!( - "Failed to convert certificate DER to PEM: {err}" - )))); - setup_in_progress.store(false, Ordering::SeqCst); - return; - } - }; - - configuration = Some(Configuration { - grpc_key_pem: key_pair.serialize_pem(), - grpc_cert_pem, - }); - - if let Err(err) = tx.send(Ok(ProxySetupRequest { - payload: Some(proxy_setup_request::Payload::Done(Done {})), - })) { - error!("Failed to send Done message: {err}"); - // Can't send error since tx already failed - setup_in_progress.store(false, Ordering::SeqCst); - return; - } - - debug!("Sent Done message to Defguard Core"); - } - Some(proxy_setup_response::Payload::Done(Done {})) => { - debug!("Received setup termination message from Defguard Core"); - let lock = SETUP_CHANNEL.0.lock().await; - debug!("Passing Proxy configuration to the gRPC server"); - if let Some(configuration) = configuration.take() { - if let Err(err) = lock.send(Some(configuration)).await { - error!("Failed to pass the received Proxy configuration to the gRPC server: {err:?}"); - } else { - debug!("Passed Proxy configuration to the gRPC server"); - setup_successful = true; - } - } else { - error!( - "No configuration available to pass to the gRPC server on setup completion" - ); - } - - info!("Setup handshake completed successfully"); - break; - } - _ => { - error!( - "Unexpected payload type while waiting for setup message: {:?}", - response.payload - ); - let _ = tx.send(Err(Status::invalid_argument( - "Unexpected payload type while waiting for setup message", - ))); - setup_in_progress.store(false, Ordering::SeqCst); - return; - } - }, - Ok(None) => { - error!("gRPC setup stream has been closed unexpectedly"); - let _ = tx.send(Err(Status::aborted( - "gRPC stream has been closed unexpectedly", - ))); - setup_in_progress.store(false, Ordering::SeqCst); - return; - } - Err(err) => { - error!("gRPC setup client error while waiting for CertResponse: {err}"); - let _ = - tx.send(Err(Status::internal(format!("gRPC client error: {err}")))); - setup_in_progress.store(false, Ordering::SeqCst); - return; - } - } - } - - setup_in_progress.store(false, Ordering::SeqCst); - if setup_successful { - info!("Proxy setup completed successfully"); - } else { - warn!("Proxy setup did not complete successfully"); - } - } - .instrument(tracing::Span::current()), - ); - - Ok(Response::new(UnboundedReceiverStream::new(rx))) - } /// Handle bidirectional communication with Defguard core. #[instrument(name = "bidirectional_communication", level = "info", skip(self))] diff --git a/src/http.rs b/src/http.rs index 571271f4..855accc0 100644 --- a/src/http.rs +++ b/src/http.rs @@ -39,6 +39,7 @@ use crate::{ error::ApiError, grpc::{Configuration, ProxyServer}, handlers::{desktop_client_mfa, enrollment, password_reset, polling}, + setup::ProxySetupServer, CommsChannel, VERSION, }; @@ -192,7 +193,10 @@ pub async fn run_server(config: Config) -> anyhow::Result<()> { let server_clone = grpc_server.clone(); + let setup_server = ProxySetupServer::new(); + // Start gRPC server. + // TODO: Wait with spawning the HTTP server until gRPC server is ready. debug!("Spawning gRPC server"); tasks.spawn(async move { let cert_dir = Path::new(&config.cert_dir); @@ -212,7 +216,7 @@ pub async fn run_server(config: Config) -> anyhow::Result<()> { } else if !server_clone.setup_completed() { // Only attempt setup if not already configured info!("No gRPC TLS certificates found at {cert_dir:?}, new certificates will be generated"); - let configuration = server_clone + let configuration = setup_server .await_setup(SocketAddr::new( config .grpc_bind_address @@ -351,6 +355,7 @@ pub async fn run_server(config: Config) -> anyhow::Result<()> { .context("Error running HTTP server") }); + // TODO: Possibly switch to using select! macro info!("Defguard Proxy HTTP server initialization complete"); while let Some(Ok(result)) = tasks.join_next().await { result?; diff --git a/src/lib.rs b/src/lib.rs index ae2966b0..a9e22f5c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ mod grpc; mod handlers; pub mod http; pub mod logging; +mod setup; pub(crate) mod proto { tonic::include_proto!("defguard.proxy"); diff --git a/src/setup.rs b/src/setup.rs new file mode 100644 index 00000000..a736b526 --- /dev/null +++ b/src/setup.rs @@ -0,0 +1,209 @@ +use std::{ + net::SocketAddr, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, LazyLock, Mutex, + }, +}; + +use defguard_version::{ + server::{grpc::DefguardVersionInterceptor, DefguardVersionLayer}, + DefguardComponent, Version, +}; +use tokio::sync::mpsc; +use tonic::{transport::Server, Request, Response, Status}; + +use crate::{ + error::ApiError, + grpc::Configuration, + proto::{proxy_setup_server, DerPayload, InitialSetupInfo}, + CommsChannel, MIN_CORE_VERSION, VERSION, +}; + +static SETUP_CHANNEL: LazyLock>> = LazyLock::new(|| { + let (tx, rx) = mpsc::channel(10); + ( + Arc::new(tokio::sync::Mutex::new(tx)), + Arc::new(tokio::sync::Mutex::new(rx)), + ) +}); + +pub(crate) struct ProxySetupServer { + setup_in_progress: Arc, + key_pair: Arc>>, +} + +impl Clone for ProxySetupServer { + fn clone(&self) -> Self { + Self { + setup_in_progress: Arc::clone(&self.setup_in_progress), + key_pair: Arc::clone(&self.key_pair), + } + } +} + +impl ProxySetupServer { + pub fn new() -> Self { + Self { + setup_in_progress: Arc::new(AtomicBool::new(false)), + key_pair: Arc::new(Mutex::new(None)), + } + } + + /// Await setup connection from Defguard Core and process it. + /// Spins up a dedicated temporary gRPC server over HTTP to handle the setup process. + /// The setup process involves generating a CSR, sending it to Core and receiving signed TLS certificates. + /// Returns the received gRPC configuration (locally generated key pair and remotely signed certificate) upon successful setup. + pub(crate) async fn await_setup( + &self, + addr: SocketAddr, + ) -> Result { + info!("gRPC waiting for setup connection from Core on {addr}"); + let server_builder = Server::builder(); + let mut server_config = None; + + let own_version = Version::parse(VERSION)?; + + server_builder + .layer(tonic::service::InterceptorLayer::new( + DefguardVersionInterceptor::new( + own_version.clone(), + DefguardComponent::Core, + MIN_CORE_VERSION, + false, + ), + )) + .layer(DefguardVersionLayer::new(own_version)) + .add_service(proxy_setup_server::ProxySetupServer::new(self.clone())) + .serve_with_shutdown(addr, async { + let config = SETUP_CHANNEL.1.lock().await.recv().await; + if let Some(cfg) = config { + debug!("Received the passed Proxy configuration"); + server_config = cfg; + } else { + error!("Setup communication channel closed unexpectedly"); + } + }) + .await + .map_err(|err| { + error!("gRPC server error during setup: {err}"); + ApiError::Unexpected("gRPC server error during setup".into()) + })?; + + debug!("gRPC setup server on {addr} has shutdown after completing setup"); + + Ok(server_config.map_or_else( + || { + error!("No server configuration received after setup completion"); + Err(ApiError::Unexpected( + "No server configuration received after setup".into(), + )) + }, + |cfg| { + debug!("Returning received server configuration"); + Ok(cfg) + }, + )?) + } +} + +#[tonic::async_trait] +impl proxy_setup_server::ProxySetup for ProxySetupServer { + async fn start( + &self, + request: Request, + ) -> Result, Status> { + if self.setup_in_progress.load(Ordering::SeqCst) { + error!("Setup already in progress, rejecting new setup request"); + return Err(Status::resource_exhausted("Setup already in progress")); + } + + self.setup_in_progress.store(true, Ordering::SeqCst); + let setup_info = request.into_inner(); + + let key_pair = match defguard_certs::generate_key_pair() { + Ok(kp) => kp, + Err(err) => { + error!("Failed to generate key pair: {err}"); + self.setup_in_progress.store(false, Ordering::SeqCst); + return Err(Status::internal("Failed to generate key pair")); + } + }; + + let subject_alt_names = vec![setup_info.cert_hostname]; + + let csr = match defguard_certs::Csr::new( + &key_pair, + &subject_alt_names, + vec![ + // TODO: Change it? + (defguard_certs::DnType::CommonName, "Defguard Proxy"), + (defguard_certs::DnType::OrganizationName, "Defguard"), + ], + ) { + Ok(csr) => csr, + Err(err) => { + error!("Failed to generate CSR: {err}"); + self.setup_in_progress.store(false, Ordering::SeqCst); + return Err(Status::internal(format!("Failed to generate CSR: {err}"))); + } + }; + + self.key_pair.lock().unwrap().replace(key_pair); + + let csr_der = csr.to_der(); + let csr_request = DerPayload { + der_data: csr_der.to_vec(), + }; + + Ok(Response::new(csr_request)) + } + + async fn send_cert(&self, request: Request) -> Result, Status> { + let der_payload = request.into_inner(); + let cert_der = der_payload.der_data; + + let grpc_cert_pem = + match defguard_certs::der_to_pem(&cert_der, defguard_certs::PemLabel::Certificate) { + Ok(pem) => pem, + Err(err) => { + error!("Failed to convert certificate DER to PEM: {err}"); + self.setup_in_progress.store(false, Ordering::SeqCst); + return Err(Status::internal(format!( + "Failed to convert certificate DER to PEM: {err}" + ))); + } + }; + + let key_pair = { + let key_pair = self.key_pair.lock().unwrap().take(); + if let Some(kp) = key_pair { + kp + } else { + error!("Key pair not found during Proxy setup. Key pair generation step might have failed."); + self.setup_in_progress.store(false, Ordering::SeqCst); + return Err(Status::internal("Key pair not found during Proxy setup. Key pair generation step might have failed.")); + } + }; + + let configuration = Configuration { + grpc_key_pem: key_pair.serialize_pem(), + grpc_cert_pem, + }; + + match SETUP_CHANNEL.0.lock().await.send(Some(configuration)).await { + Ok(()) => info!("Configuration sent to gRPC server"), + Err(err) => { + error!("Failed to send configuration to gRPC server: {err}"); + self.setup_in_progress.store(false, Ordering::SeqCst); + return Err(Status::internal( + "Failed to send configuration to gRPC server", + )); + } + } + + self.setup_in_progress.store(false, Ordering::SeqCst); + + Ok(Response::new(())) + } +} From 8db349ae4aaf40b2507f66c2287b16a37582a68b Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Fri, 9 Jan 2026 14:14:37 +0100 Subject: [PATCH 9/9] suggestions --- src/grpc.rs | 48 ++++++++++++++++++++++++++++++++++++++---------- src/http.rs | 10 ++++++++-- src/setup.rs | 11 +++++++++-- 3 files changed, 55 insertions(+), 14 deletions(-) diff --git a/src/grpc.rs b/src/grpc.rs index 571ff1a0..3dd2c21d 100644 --- a/src/grpc.rs +++ b/src/grpc.rs @@ -64,7 +64,10 @@ impl ProxyServer { } pub(crate) fn set_tls_config(&self, cert_pem: String, key_pem: String) -> Result<(), ApiError> { - let mut lock = self.config.lock().unwrap(); + let mut lock = self + .config + .lock() + .expect("Failed to acquire lock on config mutex when updating TLS configuration"); let config = lock.get_or_insert_with(Configuration::default); config.grpc_cert_pem = cert_pem; config.grpc_key_pem = key_pem; @@ -72,12 +75,18 @@ impl ProxyServer { } pub(crate) fn configure(&self, config: Configuration) { - let mut lock = self.config.lock().unwrap(); + let mut lock = self + .config + .lock() + .expect("Failed to acquire lock on config mutex when applying proxy configuration"); *lock = Some(config); } pub(crate) fn get_configuration(&self) -> Option { - let lock = self.config.lock().unwrap(); + let lock = self + .config + .lock() + .expect("Failed to acquire lock on config mutex when retrieving proxy configuration"); lock.clone() } @@ -130,7 +139,13 @@ impl ProxyServer { payload: core_request::Payload, device_info: DeviceInfo, ) -> Result, ApiError> { - if let Some(client_tx) = self.clients.lock().unwrap().values().next() { + if let Some(client_tx) = self + .clients + .lock() + .expect("Failed to acquire lock on clients hashmap when sending message to core") + .values() + .next() + { let id = self.current_id.fetch_add(1, Ordering::Relaxed); let res = CoreRequest { id, @@ -142,7 +157,10 @@ impl ProxyServer { return Err(ApiError::Unexpected("Failed to send CoreRequest".into())); } let (tx, rx) = oneshot::channel(); - self.results.lock().unwrap().insert(id, tx); + self.results + .lock() + .expect("Failed to acquire lock on results hashmap when sending CoreRequest") + .insert(id, tx); self.connected.store(true, Ordering::Relaxed); Ok(rx) } else { @@ -155,7 +173,10 @@ impl ProxyServer { } pub(crate) fn setup_completed(&self) -> bool { - let lock = self.config.lock().unwrap(); + let lock = self + .config + .lock() + .expect("Failed to acquire lock on config mutex when checking setup status"); lock.is_some() } } @@ -197,7 +218,9 @@ impl proxy_server::Proxy for ProxyServer { }; let maybe_info = ComponentInfo::from_metadata(request.metadata()); let (version, info) = get_tracing_variables(&maybe_info); - *self.core_version.lock().unwrap() = Some(version.clone()); + *self.core_version.lock().expect( + "Failed to acquire lock on core_version mutex when storing version information", + ) = Some(version.clone()); let span = tracing::info_span!("core_bidi_stream", component = %DefguardComponent::Core, version = version.to_string(), info); @@ -206,7 +229,12 @@ impl proxy_server::Proxy for ProxyServer { info!("Defguard Core gRPC client connected from: {address}"); let (tx, rx) = mpsc::unbounded_channel(); - self.clients.lock().unwrap().insert(address, tx); + self.clients + .lock() + .expect( + "Failed to acquire lock on clients hashmap when registering new core connection", + ) + .insert(address, tx); self.connected.store(true, Ordering::Relaxed); let clients = Arc::clone(&self.clients); @@ -221,7 +249,7 @@ impl proxy_server::Proxy for ProxyServer { debug!("Received message from Defguard Core ID={}", response.id); connected.store(true, Ordering::Relaxed); if let Some(payload) = response.payload { - let maybe_rx = results.lock().unwrap().remove(&response.id); + let maybe_rx = results.lock().expect("Failed to acquire lock on results hashmap when processing response").remove(&response.id); if let Some(rx) = maybe_rx { if let Err(err) = rx.send(payload) { error!("Failed to send message to rx {:?}", err.type_id()); @@ -243,7 +271,7 @@ impl proxy_server::Proxy for ProxyServer { } info!("Defguard core client disconnected: {address}"); connected.store(false, Ordering::Relaxed); - clients.lock().unwrap().remove(&address); + clients.lock().expect("Failed to acquire lock on clients hashmap when removing disconnected client").remove(&address); } .instrument(tracing::Span::current()), ); diff --git a/src/http.rs b/src/http.rs index 855accc0..f18d52d3 100644 --- a/src/http.rs +++ b/src/http.rs @@ -211,11 +211,17 @@ pub async fn run_server(config: Config) -> anyhow::Result<()> { read_to_string(cert_dir.join(GRPC_CERT_NAME)).ok(), read_to_string(cert_dir.join(GRPC_KEY_NAME)).ok(), ) { - info!("Using existing gRPC TLS certificates from {cert_dir:?}"); + info!( + "Using existing gRPC TLS certificates from {}", + cert_dir.display() + ); server_clone.set_tls_config(cert, key)?; } else if !server_clone.setup_completed() { // Only attempt setup if not already configured - info!("No gRPC TLS certificates found at {cert_dir:?}, new certificates will be generated"); + info!( + "No gRPC TLS certificates found at {}, new certificates will be generated", + cert_dir.display() + ); let configuration = setup_server .await_setup(SocketAddr::new( config diff --git a/src/setup.rs b/src/setup.rs index a736b526..437ac81f 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -149,7 +149,10 @@ impl proxy_setup_server::ProxySetup for ProxySetupServer { } }; - self.key_pair.lock().unwrap().replace(key_pair); + self.key_pair + .lock() + .expect("Failed to acquire lock on key pair during proxy setup when trying to store generated key pair") + .replace(key_pair); let csr_der = csr.to_der(); let csr_request = DerPayload { @@ -176,7 +179,11 @@ impl proxy_setup_server::ProxySetup for ProxySetupServer { }; let key_pair = { - let key_pair = self.key_pair.lock().unwrap().take(); + let key_pair = self + .key_pair + .lock() + .expect("Failed to acquire lock on key pair during proxy setup when trying to receive certificate") + .take(); if let Some(kp) = key_pair { kp } else {