diff --git a/Cargo.lock b/Cargo.lock index 61133284..d4564c2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,9 +94,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" [[package]] name = "argon2" @@ -384,6 +384,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.13.0" @@ -449,9 +455,9 @@ checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" [[package]] name = "camino" -version = "1.2.3" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ce8d3bd5823c7504d3f579f13e7b2f3da252fcb938c594d5680ee508bf846f" +checksum = "5f2d30e4173c4026932d51d31d6b0613b1fd3014bf3f9f8943d4ba139c437ba0" dependencies = [ "serde_core", ] @@ -481,9 +487,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.64" +version = "1.2.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" dependencies = [ "find-msvc-tools", "jobserver", @@ -835,7 +841,7 @@ checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] name = "defguard-gateway" -version = "2.0.1" +version = "2.0.2" dependencies = [ "axum", "base64 0.22.1", @@ -851,7 +857,6 @@ dependencies = [ "log", "mnl", "nftnl", - "nix 0.31.3", "prost", "prost-types", "rustls", @@ -888,7 +893,7 @@ dependencies = [ "ip_network", "ip_network_table", "libc", - "nix 0.31.3", + "nix", "parking_lot", "ring", "socket2", @@ -902,7 +907,7 @@ dependencies = [ [[package]] name = "defguard_certs" version = "0.0.0" -source = "git+https://github.com/DefGuard/defguard.git?rev=dc0b70e69335c4be98d5b17615003922c9464994#dc0b70e69335c4be98d5b17615003922c9464994" +source = "git+https://github.com/DefGuard/defguard.git?rev=f5ae25dc6810b92ce9bd8dafddefc03f336a69bf#f5ae25dc6810b92ce9bd8dafddefc03f336a69bf" dependencies = [ "base64 0.22.1", "chrono", @@ -915,8 +920,8 @@ dependencies = [ [[package]] name = "defguard_common" -version = "2.0.0" -source = "git+https://github.com/DefGuard/defguard.git?rev=dc0b70e69335c4be98d5b17615003922c9464994#dc0b70e69335c4be98d5b17615003922c9464994" +version = "2.0.1" +source = "git+https://github.com/DefGuard/defguard.git?rev=f5ae25dc6810b92ce9bd8dafddefc03f336a69bf#f5ae25dc6810b92ce9bd8dafddefc03f336a69bf" dependencies = [ "anyhow", "argon2", @@ -957,7 +962,7 @@ dependencies = [ [[package]] name = "defguard_grpc_tls" version = "0.0.0" -source = "git+https://github.com/DefGuard/defguard.git?rev=dc0b70e69335c4be98d5b17615003922c9464994#dc0b70e69335c4be98d5b17615003922c9464994" +source = "git+https://github.com/DefGuard/defguard.git?rev=f5ae25dc6810b92ce9bd8dafddefc03f336a69bf#f5ae25dc6810b92ce9bd8dafddefc03f336a69bf" dependencies = [ "defguard_common", "http", @@ -974,7 +979,7 @@ dependencies = [ [[package]] name = "defguard_version" version = "0.0.0" -source = "git+https://github.com/DefGuard/defguard.git?rev=dc0b70e69335c4be98d5b17615003922c9464994#dc0b70e69335c4be98d5b17615003922c9464994" +source = "git+https://github.com/DefGuard/defguard.git?rev=f5ae25dc6810b92ce9bd8dafddefc03f336a69bf#f5ae25dc6810b92ce9bd8dafddefc03f336a69bf" dependencies = [ "axum", "http", @@ -990,9 +995,9 @@ dependencies = [ [[package]] name = "defguard_wireguard_rs" -version = "0.9.7" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5657c83576553019b0a473b5417fe9faeba6128819025ad8969cb846be96b50d" +checksum = "9f4188d2edddeff6c87f01746ddd869a34ea4a5b0005909b9f8b89c806bfdd0d" dependencies = [ "base64 0.22.1", "defguard_boringtun", @@ -1005,7 +1010,6 @@ dependencies = [ "netlink-packet-utils", "netlink-packet-wireguard", "netlink-sys", - "nix 0.31.3", "regex", "serde", "thiserror 2.0.18", @@ -1014,6 +1018,38 @@ dependencies = [ "x25519-dalek", ] +[[package]] +name = "defmt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e524506490a1953d237cb87b1cfc1e46f88c18f10a22dfe0f507dc6bfc7f7f" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0a27770e9c8f719a79d8b638281f4d828f77d8fd61e0bd94451b9b85e576a0b" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.18", +] + [[package]] name = "der" version = "0.7.10" @@ -1111,7 +1147,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags", + "bitflags 2.13.0", "objc2", ] @@ -1224,9 +1260,9 @@ dependencies = [ [[package]] name = "env_filter" -version = "1.0.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" dependencies = [ "log", "regex", @@ -1234,9 +1270,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.10" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" dependencies = [ "anstream", "anstyle", @@ -1513,7 +1549,7 @@ version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b" dependencies = [ - "bitflags", + "bitflags 2.13.0", "libc", "libgit2-sys", "log", @@ -2044,10 +2080,11 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.28" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4603d3033e49e2b0e31229fcab20a5d40089c607d975cd9c80551dc69eed9102" +checksum = "ccfe6121cbe750cf81efa362d85c0bde7ea298ec43092d3a193baca59cdbd634" dependencies = [ + "defmt", "jiff-static", "log", "portable-atomic", @@ -2057,9 +2094,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.28" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "782d32378dddf207193ac91cefb848ad41abb58195c95168e1291227a0832b47" +checksum = "e165e897f662d428f3cd3828a919dbe067c2d42bb1031eede74ef9d27ecdedd2" dependencies = [ "proc-macro2", "quote", @@ -2078,9 +2115,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.102" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ "cfg-if", "futures-util", @@ -2160,7 +2197,7 @@ version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" dependencies = [ - "bitflags", + "bitflags 2.13.0", "libc", "plain", "redox_syscall 0.8.1", @@ -2211,9 +2248,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.32" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "matchers" @@ -2246,15 +2283,6 @@ version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - [[package]] name = "mime" version = "0.3.17" @@ -2312,7 +2340,7 @@ dependencies = [ [[package]] name = "model_derive" version = "0.0.0" -source = "git+https://github.com/DefGuard/defguard.git?rev=dc0b70e69335c4be98d5b17615003922c9464994#dc0b70e69335c4be98d5b17615003922c9464994" +source = "git+https://github.com/DefGuard/defguard.git?rev=f5ae25dc6810b92ce9bd8dafddefc03f336a69bf#f5ae25dc6810b92ce9bd8dafddefc03f336a69bf" dependencies = [ "quote", "syn", @@ -2365,7 +2393,7 @@ version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be8919612f6028ab4eacbbfe1234a9a43e3722c6e0915e7ff519066991905092" dependencies = [ - "bitflags", + "bitflags 2.13.0", "libc", "log", "netlink-packet-core", @@ -2384,10 +2412,11 @@ dependencies = [ [[package]] name = "netlink-packet-wireguard" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "205d2bad950c9cbbbf08cc5432d6501edfe02d3a34ecad822a3e91c98e97dbf6" +checksum = "cb217ca08c02978c3ea5ef0f013d20c602f2a17a9f00d9e4b1518a3500c2f332" dependencies = [ + "bitflags 2.13.0", "libc", "log", "netlink-packet-core", @@ -2407,19 +2436,19 @@ dependencies = [ [[package]] name = "nftnl" -version = "0.9.1" -source = "git+https://github.com/DefGuard/nftnl-rs.git?rev=a5cf4c9965118e27795f4ad94ea5fbbde2687ff7#a5cf4c9965118e27795f4ad94ea5fbbde2687ff7" +version = "0.9.2" +source = "git+https://github.com/DefGuard/nftnl-rs.git?rev=abd34b08c53b88a47346da6362e360d522cf8ca2#abd34b08c53b88a47346da6362e360d522cf8ca2" dependencies = [ - "bitflags", + "bitflags 2.13.0", "log", "nftnl-sys", - "nix 0.30.1", + "nix", ] [[package]] name = "nftnl-sys" version = "0.6.4" -source = "git+https://github.com/DefGuard/nftnl-rs.git?rev=a5cf4c9965118e27795f4ad94ea5fbbde2687ff7#a5cf4c9965118e27795f4ad94ea5fbbde2687ff7" +source = "git+https://github.com/DefGuard/nftnl-rs.git?rev=abd34b08c53b88a47346da6362e360d522cf8ca2#abd34b08c53b88a47346da6362e360d522cf8ca2" dependencies = [ "cfg-if", "libc", @@ -2427,29 +2456,16 @@ dependencies = [ "pkg-config", ] -[[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 = "nix" version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" dependencies = [ - "bitflags", + "bitflags 2.13.0", "cfg-if", "cfg_aliases", "libc", - "memoffset", ] [[package]] @@ -2577,7 +2593,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" dependencies = [ - "bitflags", + "bitflags 2.13.0", "objc2", "objc2-foundation", ] @@ -2598,7 +2614,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags", + "bitflags 2.13.0", "dispatch2", "objc2", ] @@ -2609,7 +2625,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "bitflags", + "bitflags 2.13.0", "dispatch2", "objc2", "objc2-core-foundation", @@ -2642,7 +2658,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" dependencies = [ - "bitflags", + "bitflags 2.13.0", "objc2", "objc2-core-foundation", "objc2-core-graphics", @@ -2660,7 +2676,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags", + "bitflags 2.13.0", "block2", "libc", "objc2", @@ -2673,7 +2689,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ - "bitflags", + "bitflags 2.13.0", "objc2", "objc2-core-foundation", ] @@ -2684,7 +2700,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ - "bitflags", + "bitflags 2.13.0", "objc2", "objc2-core-foundation", "objc2-foundation", @@ -2696,7 +2712,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ - "bitflags", + "bitflags 2.13.0", "block2", "objc2", "objc2-cloud-kit", @@ -2794,7 +2810,7 @@ version = "0.10.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" dependencies = [ - "bitflags", + "bitflags 2.13.0", "cfg-if", "foreign-types", "libc", @@ -2848,7 +2864,7 @@ checksum = "9cf20a545b305cf1da722b236b5155c9bb35f1d5ceb28c048bd96ca842f41b5b" dependencies = [ "android_system_properties", "log", - "nix 0.31.3", + "nix", "objc2", "objc2-foundation", "objc2-ui-kit", @@ -3096,6 +3112,28 @@ dependencies = [ "elliptic-curve", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -3164,7 +3202,7 @@ version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e" dependencies = [ - "bitflags", + "bitflags 2.13.0", "memchr", "unicase", ] @@ -3180,9 +3218,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.45" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] @@ -3278,7 +3316,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.13.0", ] [[package]] @@ -3287,7 +3325,7 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b44b894f2a6e36457d665d1e08c3866add6ed5e70050c1b4ba8a8ddedb02ce7" dependencies = [ - "bitflags", + "bitflags 2.13.0", ] [[package]] @@ -3453,7 +3491,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys", @@ -3462,9 +3500,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.40" +version = "0.23.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" dependencies = [ "aws-lc-rs", "log", @@ -3610,7 +3648,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags", + "bitflags 2.13.0", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -4001,7 +4039,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64 0.22.1", - "bitflags", + "bitflags 2.13.0", "byteorder", "bytes", "chrono", @@ -4045,7 +4083,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64 0.22.1", - "bitflags", + "bitflags 2.13.0", "byteorder", "chrono", "crc", @@ -4207,7 +4245,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags", + "bitflags 2.13.0", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -4295,9 +4333,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.49" +version = "0.3.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711a53c2d47bbd818258c498c8dbfe186a2526c631495cfe7e078567f86b8469" +checksum = "0e48db7b415311b615f910b3dcaa4557bcd4bf1982379c95c223fd8c2a20e210" dependencies = [ "deranged", "libc", @@ -4317,9 +4355,9 @@ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.29" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71c652a3727a9cbb9a02f707f530b618ce00d0ccd762009c8c23bd191df3c17d" +checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" dependencies = [ "num-conv", "time-core", @@ -4591,7 +4629,7 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags", + "bitflags 2.13.0", "bytes", "futures-util", "http", @@ -4923,9 +4961,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.23.3" +version = "1.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7" +checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" dependencies = [ "getrandom 0.4.3", "js-sys", @@ -5021,9 +5059,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.125" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -5034,9 +5072,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.75" +version = "0.4.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "503b14d284f2c8dac03b819967e155ea753f573586193b2b2c95990cb5d69280" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" dependencies = [ "js-sys", "wasm-bindgen", @@ -5044,9 +5082,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.125" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5054,9 +5092,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.125" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", @@ -5067,18 +5105,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.125" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.102" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6430a72df5eb332242960fe84b3002a241163998241eb596d4f739b9757061d" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" dependencies = [ "js-sys", "wasm-bindgen", @@ -5476,7 +5514,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22b4dbcc6c93786cf22e420ef96e8976bfb92a455070282302b74de5848191f4" dependencies = [ - "bitflags", + "bitflags 2.13.0", "getrandom 0.2.17", "ipnet", "libloading", diff --git a/Cargo.toml b/Cargo.toml index b6da3642..7d9777c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "defguard-gateway" -version = "2.0.1" +version = "2.0.2" edition = "2024" [dependencies] @@ -8,13 +8,13 @@ axum = "0.8" base64 = "0.22" chrono = "0.4" clap = { version = "4.6", features = ["derive", "env"] } -defguard_certs = { git = "https://github.com/DefGuard/defguard.git", rev = "dc0b70e69335c4be98d5b17615003922c9464994" } -defguard_grpc_tls = { git = "https://github.com/DefGuard/defguard.git", rev = "dc0b70e69335c4be98d5b17615003922c9464994" } -defguard_version = { git = "https://github.com/DefGuard/defguard.git", rev = "dc0b70e69335c4be98d5b17615003922c9464994" } +defguard_certs = { git = "https://github.com/DefGuard/defguard.git", rev = "f5ae25dc6810b92ce9bd8dafddefc03f336a69bf" } +defguard_grpc_tls = { git = "https://github.com/DefGuard/defguard.git", rev = "f5ae25dc6810b92ce9bd8dafddefc03f336a69bf" } +defguard_version = { git = "https://github.com/DefGuard/defguard.git", rev = "f5ae25dc6810b92ce9bd8dafddefc03f336a69bf" } +defguard_wireguard_rs = "0.10" rustls = { version = "0.23", default-features = false, features = ["ring"] } rustls-pki-types = "1" rustls-webpki = { version = "0.103", features = ["ring", "std"] } -defguard_wireguard_rs = "0.9" env_logger = "0.11" ipnetwork = "0.21" libc = { version = "0.2", default-features = false } @@ -43,12 +43,9 @@ tracing = "0.1" tracing-subscriber = "0.3" [target.'cfg(target_os = "linux")'.dependencies] -nftnl = { git = "https://github.com/DefGuard/nftnl-rs.git", rev = "a5cf4c9965118e27795f4ad94ea5fbbde2687ff7" } +nftnl = { git = "https://github.com/DefGuard/nftnl-rs.git", rev = "abd34b08c53b88a47346da6362e360d522cf8ca2" } mnl = "0.3" -[target.'cfg(any(target_os = "freebsd", target_os = "macos", target_os = "netbsd"))'.dependencies] -nix = { version = "0.31", default-features = false, features = ["ioctl"] } - [dev-dependencies] tokio = { version = "1", features = ["net"] } tonic = { version = "0.14", default-features = false, features = [ diff --git a/example-config.toml b/example-config.toml index d635c010..66433bf6 100644 --- a/example-config.toml +++ b/example-config.toml @@ -45,3 +45,6 @@ syslog_socket = "/var/run/log" # Optional: Set the priority of the Defguard forward chain #fw_priority = 0 + +# Optional: Clean the network interface and VPN configuration on quit. +#clean_on_quit = true diff --git a/src/main.rs b/src/bin/defguard-gateway.rs similarity index 93% rename from src/main.rs rename to src/bin/defguard-gateway.rs index 1ecadec0..b1c95942 100644 --- a/src/main.rs +++ b/src/bin/defguard-gateway.rs @@ -68,11 +68,7 @@ async fn main() -> Result<(), GatewayError> { } let cert_dir = &config.cert_dir; - if !cert_dir.exists() { - tokio::fs::create_dir_all(cert_dir).await?; - #[cfg(unix)] - tokio::fs::set_permissions(cert_dir, Permissions::from_mode(0o700)).await?; - } else { + if cert_dir.exists() { // Probe write access let probe = cert_dir.join(".write_test"); match tokio::fs::OpenOptions::new() @@ -94,6 +90,10 @@ async fn main() -> Result<(), GatewayError> { } Err(e) => return Err(e.into()), } + } else { + tokio::fs::create_dir_all(cert_dir).await?; + #[cfg(unix)] + tokio::fs::set_permissions(cert_dir, Permissions::from_mode(0o700)).await?; } let maybe_tls_config = load_tls_config(cert_dir)?; @@ -161,7 +161,13 @@ async fn main() -> Result<(), GatewayError> { "gRPC TLS certificates not found in {}. They will be generated during setup.", config.cert_dir.display() ); - run_setup(&config, Arc::clone(&logs_rx)).await? + match run_setup(&config, Arc::clone(&logs_rx)).await { + Ok(tls_config) => tls_config, + Err(err) => { + log::error!("Failed to run setup: {err}"); + return; + } + } } Some(tls_config) => { log::info!( @@ -173,12 +179,11 @@ async fn main() -> Result<(), GatewayError> { }; // Launch gRPC server (with purge-triggered setup loop). - run_gateway_loop(config, gateway, Arc::clone(&logs_rx), tls_config).await + run_gateway_loop(config, gateway, Arc::clone(&logs_rx), tls_config).await; }); - while let Some(Ok(result)) = tasks.join_next().await { - result?; - } + // Wait for the first task to finish. + let _result = tasks.join_next().await; if let Some(post_down) = &post_down_clone { log::info!("Executing specified POST_DOWN command: {post_down}"); diff --git a/src/config.rs b/src/config.rs index a81722c5..bbc7941d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -152,6 +152,11 @@ pub struct Config { )] #[serde(default = "default_adoption_timeout")] pub adoption_timeout: u64, + + /// On quit, clean the network interface and VPN configurations. + #[arg(long, env = "DEFGUARD_CLEAN_ON_QUIT")] + #[serde(default)] + pub clean_on_quit: bool, } impl Config { @@ -196,6 +201,7 @@ impl Default for Config { http_bind_address: None, cert_dir: PathBuf::from("/etc/defguard/certs"), adoption_timeout: 5, + clean_on_quit: false, } } } diff --git a/src/enterprise/firewall/mod.rs b/src/enterprise/firewall/mod.rs index 87c7dc84..befb71a0 100644 --- a/src/enterprise/firewall/mod.rs +++ b/src/enterprise/firewall/mod.rs @@ -357,9 +357,6 @@ pub enum FirewallError { IpAddrRange(#[from] IpAddrRangeError), #[error("Io error: {0}")] Io(#[from] std::io::Error), - #[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "netbsd"))] - #[error("Errno:{0}")] - Errno(#[from] nix::errno::Errno), #[error("Type conversion error: {0}")] TypeConversionError(String), #[error("Out of memory: {0}")] diff --git a/src/enterprise/firewall/packetfilter/api.rs b/src/enterprise/firewall/packetfilter/api.rs index d5d928e7..96f7b78e 100644 --- a/src/enterprise/firewall/packetfilter/api.rs +++ b/src/enterprise/firewall/packetfilter/api.rs @@ -1,8 +1,11 @@ use std::os::fd::AsRawFd; +use defguard_wireguard_rs::bsd::c_int_to_error; +use libc::ioctl; + use super::{ FirewallRule, - calls::{IocTrans, IocTransElement, pf_begin, pf_commit, pf_rollback}, + calls::{DIOCXBEGIN, DIOCXCOMMIT, DIOCXROLLBACK, IocTrans, IocTransElement}, rule::RuleSet, }; use crate::enterprise::firewall::{ @@ -33,9 +36,8 @@ impl FirewallManagementApi for FirewallApi { let mut elements = [IocTransElement::new(RuleSet::Filter, anchor)]; let mut ioc_trans = IocTrans::new(elements.as_mut_slice()); // This will create an anchor if it doesn't exist. - unsafe { - pf_begin(self.fd(), &raw mut ioc_trans)?; - } + let result = unsafe { ioctl(self.fd(), DIOCXBEGIN, &raw mut ioc_trans) }; + c_int_to_error(result)?; let ticket = elements[0].ticket; let pool_ticket = self.get_pool_ticket(anchor)?; @@ -45,10 +47,10 @@ impl FirewallManagementApi for FirewallApi { error!("Default policy rule can't be added"); debug!("Rollback pf transaction"); // Rule cannot be added, so rollback. - unsafe { - pf_rollback(self.fd(), &raw mut ioc_trans)?; - return Err(FirewallError::TransactionFailed(err.to_string())); - } + let result = unsafe { ioctl(self.fd(), DIOCXROLLBACK, &raw mut ioc_trans) }; + c_int_to_error(result)?; + + return Err(FirewallError::TransactionFailed(err.to_string())); } for rule in rules { @@ -56,18 +58,17 @@ impl FirewallManagementApi for FirewallApi { error!("Firewall rule {} can't be added", &rule.id); debug!("Rollback pf transaction"); // Rule cannot be added, so rollback. - unsafe { - pf_rollback(self.fd(), &raw mut ioc_trans)?; - return Err(FirewallError::TransactionFailed(err.to_string())); - } + let result = unsafe { ioctl(self.fd(), DIOCXROLLBACK, &raw mut ioc_trans) }; + c_int_to_error(result)?; + + return Err(FirewallError::TransactionFailed(err.to_string())); } } // Commit transaction. debug!("Commit pf transaction"); - unsafe { - pf_commit(self.file.as_raw_fd(), &raw mut ioc_trans).unwrap(); - } + let result = unsafe { ioctl(self.file.as_raw_fd(), DIOCXCOMMIT, &raw mut ioc_trans) }; + c_int_to_error(result)?; Ok(()) } diff --git a/src/enterprise/firewall/packetfilter/calls.rs b/src/enterprise/firewall/packetfilter/calls.rs index e0c85a41..8c5d6c17 100644 --- a/src/enterprise/firewall/packetfilter/calls.rs +++ b/src/enterprise/firewall/packetfilter/calls.rs @@ -7,9 +7,9 @@ use std::{ ptr, }; +use defguard_wireguard_rs::bsd::ioctl::iowr; use ipnetwork::IpNetwork; -use libc::{IFNAMSIZ, pid_t, uid_t}; -use nix::{ioctl_none, ioctl_readwrite}; +use libc::{IFNAMSIZ, c_ulong, pid_t, uid_t}; use super::rule::{Action, AddressFamily, Direction, PacketFilterRule, RuleSet, State}; use crate::enterprise::firewall::Port; @@ -762,89 +762,42 @@ impl IocTrans { } } -// DIOCSTART // Start the packet filter. -ioctl_none!(pf_start, b'D', 1); - -// DIOCSTOP +// pub(super) const DIOCSTART: c_ulong = io(b'D', 1); // Stop the packet filter. -ioctl_none!(pf_stop, b'D', 2); +// pub(super) const DIOCSTOP: c_ulong = io(b'D', 2); -// DIOCADDRULE // Add rule at the end of the inactive ruleset. This call requires a ticket obtained through // a preceding DIOCXBEGIN call and a pool_ticket obtained through a DIOCBEGINADDRS call. // DIOCADDADDR must also be called if any pool addresses are required. The optional anchor name // indicates the anchor in which to append the rule. `nr` and `action` are ignored. -ioctl_readwrite!(pf_add_rule, b'D', 4, IocRule); - -// DIOCGETRULES -ioctl_readwrite!(pf_get_rules, b'D', 6, IocRule); - -// DIOCGETRULE -ioctl_readwrite!(pf_get_rule, b'D', 7, IocRule); - -// DIOCCLRSTATES -// ioctl_readwrite!(pf_clear_states, b'D', 18, pfioc_state_kill); - -// DIOCGETSTATUS -// ioctl_readwrite!(pf_get_status, b'D', 21, pf_status); - -// DIOCGETSTATES (COMPAT_FREEBSD14) -// ioctl_readwrite!(pf_get_states, b'D', 25, pfioc_states); - -// DIOCCHANGERULE -ioctl_readwrite!(pf_change_rule, b'D', 26, IocRule); - -// DIOCINSERTRULE +pub(super) const DIOCADDRULE: c_ulong = iowr::(b'D', 4); +// pub(super) const DIOCGETRULES: c_ulong = iowr::(b'D', 6); +// pub(super) const DIOCGETRULE: c_ulong = iowr::(b'D', 7); +// pub(super) const DIOCCHANGERULE: c_ulong = iowr::(b'D', 26); // Substituted on FreeBSD, NetBSD, and OpenBSD by DIOCCHANGERULE with rule.action = PF_CHANGE_REMOVE -#[cfg(target_os = "macos")] -ioctl_readwrite!(pf_insert_rule, b'D', 27, IocRule); - -// DIOCDELETERULE +// #[cfg(target_os = "macos")] +// pub(super) const DIOCINSERTRULE: c_ulong = iowr::(b'D', 27); // Substituted on FreeBSD, NetBSD, and OpenBSD by DIOCCHANGERULE with rule.action = PF_CHANGE_REMOVE -#[cfg(target_os = "macos")] -ioctl_readwrite!(pf_delete_rule, b'D', 28, IocRule); +// #[cfg(target_os = "macos")] +// pub(super) const DIOCDELETERULE: c_ulong = iowr::(b'D', 28); -// DIOCKILLSTATES -// ioctl_readwrite!(pf_kill_states, b'D', 41, pfioc_state_kill); - -// DIOCBEGINADDRS // Clear the buffer address pool and get a ticket for subsequent DIOCADDADDR, DIOCADDRULE, and // DIOCCHANGERULE calls. -ioctl_readwrite!(pf_begin_addrs, b'D', 51, IocPoolAddr); - -// DIOCADDADDR +pub(super) const DIOCBEGINADDRS: c_ulong = iowr::(b'D', 51); // Add the pool address `addr` to the buffer address pool to be used in the following DIOCADDRULE // or DIOCCHANGERULE call. All other members of the structure are ignored. -ioctl_readwrite!(pf_add_addr, b'D', 52, IocPoolAddr); - -// DIOCGETADDRS +// pub(super) const DIOCADDADDR: c_ulong = iowr::(b'D', 52); // Get a ticket for subsequent DIOCGETADDR calls and the number nr of pool addresses in the rule // specified with r_action, r_num, and anchor. -ioctl_readwrite!(pf_get_addrs, b'D', 53, IocPoolAddr); - -// DIOCGETADDR +// pub(super) const DIOCGETADDRS: c_ulong = iowr::(b'D', 53); // Get the pool address addr by its number nr from the rule specified with r_action, r_num, and // anchor using the ticket obtained through a preceding DIOCGETADDRS call. -ioctl_readwrite!(pf_get_addr, b'D', 54, IocPoolAddr); - -// DIOCCHANGEADDR -// ioctl_readwrite!(pf_change_addr, b'D', 55, IocPoolAddr); - -// DIOCGETRULESETS -// ioctl_readwrite!(pf_get_rulesets, b'D', 58, PFRuleset); - -// DIOCGETRULESET -// ioctl_readwrite!(pf_get_ruleset, b'D', 59, PFRuleset); - -// DIOCXBEGIN -ioctl_readwrite!(pf_begin, b'D', 81, IocTrans); - -// DIOCXCOMMIT -ioctl_readwrite!(pf_commit, b'D', 82, IocTrans); +// pub(super) const DIOCGETADDR: c_ulong = iowr::(b'D', 54); -// DIOCXROLLBACK -ioctl_readwrite!(pf_rollback, b'D', 83, IocTrans); +pub(super) const DIOCXBEGIN: c_ulong = iowr::(b'D', 81); +pub(super) const DIOCXCOMMIT: c_ulong = iowr::(b'D', 82); +pub(super) const DIOCXROLLBACK: c_ulong = iowr::(b'D', 83); #[cfg(test)] mod tests { diff --git a/src/enterprise/firewall/packetfilter/mod.rs b/src/enterprise/firewall/packetfilter/mod.rs index 3286c8b7..325b7804 100644 --- a/src/enterprise/firewall/packetfilter/mod.rs +++ b/src/enterprise/firewall/packetfilter/mod.rs @@ -18,10 +18,13 @@ mod rule; use std::os::fd::{AsRawFd, RawFd}; -use calls::{IocPoolAddr, pf_begin_addrs}; -use rule::PacketFilterRule; +use defguard_wireguard_rs::bsd::c_int_to_error; +use libc::ioctl; -use self::calls::{Change, IocRule, Rule, pf_add_rule}; +use self::{ + calls::{Change, DIOCADDRULE, DIOCBEGINADDRS, IocPoolAddr, IocRule, Rule}, + rule::PacketFilterRule, +}; use super::{FirewallError, FirewallRule, api::FirewallApi}; use crate::enterprise::firewall::Port; @@ -41,9 +44,8 @@ impl FirewallApi { fn get_pool_ticket(&self, anchor: &str) -> Result { let mut ioc = IocPoolAddr::new(anchor); - unsafe { - pf_begin_addrs(self.fd(), &raw mut ioc)?; - } + let result = unsafe { ioctl(self.fd(), DIOCBEGINADDRS, &raw mut ioc) }; + c_int_to_error(result)?; Ok(ioc.ticket) } @@ -58,7 +60,8 @@ impl FirewallApi { let mut ioc = IocRule::with_rule(anchor, Rule::from_pf_rule(&rule)); ioc.ticket = ticket; ioc.pool_ticket = pool_ticket; - if let Err(err) = unsafe { pf_add_rule(self.fd(), &raw mut ioc) } { + let result = unsafe { ioctl(self.fd(), DIOCADDRULE, &raw mut ioc) }; + if let Err(err) = c_int_to_error(result) { error!("Packet filter rule {rule} can't be added."); return Err(err.into()); } @@ -82,7 +85,8 @@ impl FirewallApi { ioc.action = Change::None; ioc.ticket = ticket; ioc.pool_ticket = pool_ticket; - if let Err(err) = unsafe { pf_add_rule(self.fd(), &raw mut ioc) } { + let result = unsafe { ioctl(self.fd(), DIOCADDRULE, &raw mut ioc) }; + if let Err(err) = c_int_to_error(result) { error!("Packet filter rule {rule} can't be added."); return Err(err.into()); } diff --git a/src/error.rs b/src/error.rs index 2bd97d8d..fe7ff3a5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -18,18 +18,12 @@ pub enum GatewayError { #[error("Tonic error")] Tonic(#[from] tonic::transport::Error), - #[error("Uri error")] - Uri(#[from] tonic::codegen::http::uri::InvalidUri), - #[error("Invalid config file. Error: {0}")] InvalidConfigFile(String), #[error("WireGuard error: {0}")] WireguardError(#[from] WireguardInterfaceError), - #[error("Invalid CA file. Error")] - InvalidCaFile, - #[error(transparent)] IoError(#[from] std::io::Error), diff --git a/src/gateway.rs b/src/gateway.rs index 13eb3db3..cd33e919 100644 --- a/src/gateway.rs +++ b/src/gateway.rs @@ -1,48 +1,39 @@ +#[cfg(test)] +mod tests; + use std::{ collections::HashMap, - path::PathBuf, str::FromStr, sync::{ Arc, Mutex, - atomic::{AtomicBool, AtomicU64, Ordering}, + atomic::{AtomicBool, Ordering}, }, time::{Duration, SystemTime}, }; -use defguard_certs::{CertificateError, CertificateInfo}; -use defguard_grpc_tls::{certs::server_tls_config, server::certificate_serial_interceptor}; -use defguard_version::{ - ComponentInfo, DefguardComponent, Version, get_tracing_variables, server::DefguardVersionLayer, -}; use defguard_wireguard_rs::{WireguardInterfaceApi, net::IpAddrMask}; use tokio::{ - fs::remove_file, + signal, sync::{mpsc, oneshot}, time::interval, }; -use tokio_stream::wrappers::UnboundedReceiverStream; -use tonic::{Request, Response, Status, Streaming, service::InterceptorLayer, transport::Server}; -use tower::ServiceBuilder; +use tonic::Status; use tracing::instrument; use crate::{ - CORE_CLIENT_CERT_NAME, GRPC_CA_CERT_NAME, GRPC_CERT_NAME, GRPC_KEY_NAME, VERSION, config::Config, enterprise::firewall::{ FirewallConfig, FirewallError, FirewallRule, SnatBinding, api::{FirewallApi, FirewallManagementApi}, }, error::GatewayError, - execute_command, mask, + gateway_server::GatewayServer, + mask, proto::{ common::LogEntry, - gateway::{ - Configuration, CoreRequest, CoreResponse, Peer, Update, core_request, core_response, - gateway_server, update, - }, + gateway::{Configuration, CoreRequest, Peer, Update, core_request, update}, }, setup::run_setup, - version::is_core_version_supported, }; /// Keeps the gRPC server running, but allows it to be stopped and @@ -52,7 +43,7 @@ pub async fn run_gateway_loop( gateway: Arc>, logs_rx: Arc>>, mut tls_config: TlsConfig, -) -> Result<(), GatewayError> { +) { loop { // Channel used by the gRPC service to request entering setup mode. // The purge RPC sends on this channel. @@ -65,17 +56,26 @@ pub async fn run_gateway_loop( tokio::select! { biased; + // Handle Ctrl-C + _ = signal::ctrl_c() => { + if config.clean_on_quit { + gateway.lock().unwrap().purge(); + } + break; + } // The purge RPC requested setup mode. signal = &mut reset_rx => { // Handle channel closed event. if signal.is_err() { match server_handle.await { Ok(Ok(())) => (), - Ok(Err(err)) => return Err(err), + Ok(Err(err)) => { + error!("gRPC server task failed: {err}"); + return; + } Err(err) => { - return Err(GatewayError::SetupError( - format!("gRPC server task failed: {err}"), - )); + error!("gRPC server task failed: {err}"); + return; } } break; @@ -87,25 +87,31 @@ pub async fn run_gateway_loop( // Run setup server to obtain new TLS certs, then loop to restart gRPC. log::info!("Restarting setup server after purge request"); - tls_config = run_setup(&config, Arc::clone(&logs_rx)).await?; + tls_config = match run_setup(&config, Arc::clone(&logs_rx)).await { + Ok(config) => config, + Err(err) => { + error!("Failed to run setup {err}"); + return; + } + } } // Server exited on its own (error or normal shutdown). result = &mut server_handle => { match result { Ok(Ok(())) => (), - Ok(Err(err)) => return Err(err), + Ok(Err(err)) => { + error!("gRPC server task failed: {err}"); + return; + } Err(err) => { - return Err(GatewayError::SetupError( - format!("gRPC server task failed: {err}"), - )); + error!("gRPC server task failed: {err}"); + return; } } break; } } } - - Ok(()) } // Helper struct which stores just the interface config without peers. @@ -154,11 +160,11 @@ pub struct Gateway { config: Config, interface_configuration: Option, peers: HashMap, - wgapi: Arc>, + pub(crate) wgapi: Arc>, firewall_config: Option, pub connected: Arc, // Transmission channel. Important: allows only one connected client. - client_tx: Option>>, + pub(crate) client_tx: Option>>, pub(crate) tls_config: Option, } @@ -383,7 +389,10 @@ impl Gateway { /// Performs complete interface reconfiguration based on `configuration` object. /// Called when gateway (re)connects to gRPC endpoint and retrieves complete /// network and peers data. - fn configure(&mut self, new_configuration: Configuration) -> Result<(), GatewayError> { + pub(crate) fn configure( + &mut self, + new_configuration: Configuration, + ) -> Result<(), GatewayError> { debug!( "Received configuration, reconfiguring WireGuard interface {} (addresses: {:?})", new_configuration.name, new_configuration.addresses @@ -450,7 +459,7 @@ impl Gateway { } #[instrument(skip_all)] - fn handle_updates(&mut self, update: Update) { + pub(crate) fn handle_updates(&mut self, update: Update) { debug!("Received update: {update:?}"); match update.update { Some(update::Update::Network(configuration)) => { @@ -539,287 +548,8 @@ impl Gateway { } } -pub struct GatewayServer { - message_id: AtomicU64, - gateway: Arc>, - cert_dir: PathBuf, - reset_tx: Arc>>>, -} - -impl GatewayServer { - #[must_use] - pub fn new( - gateway: Arc>, - cert_dir: PathBuf, - reset_tx: oneshot::Sender<()>, - ) -> Self { - Self { - message_id: AtomicU64::new(0), - gateway, - cert_dir, - reset_tx: Arc::new(tokio::sync::Mutex::new(Some(reset_tx))), - } - } - - /// Starts the gateway process. - /// * Requires a valid mTLS configuration to be set (via `set_tls_config`) before starting; - /// returns an error if TLS configuration is absent - the gRPC server never starts in plain-text mode - /// * Retrieves configuration and configuration updates from Defguard core via a mTLS-secured gRPC server - /// * Manages the WireGuard interface according to configuration and updates - /// * Sends interface statistics to Defguard core periodically - pub async fn start(self, config: Config) -> Result<(), GatewayError> { - info!("Starting Defguard Gateway version {VERSION} with configuration: {config:?}"); - - // Try to create network interface for WireGuard. - // FIXME: check if the interface already exists, or somehow be more clever. - { - #[allow(unused)] - let mut gateway = &mut self.gateway.lock().expect("gateway mutex poison"); - if let Err(err) = gateway - .wgapi - .lock() - .expect("wgapi mutex poison") - .create_interface() - { - warn!( - "Couldn't create network interface {}: {err}. Proceeding anyway.", - config.ifname - ); - } else { - #[cfg(target_os = "linux")] - if !config.disable_firewall_management && config.masquerade { - let mut firewall_api = FirewallApi::new(&config.ifname)?; - firewall_api.setup_nat(config.masquerade, &[])?; - } - } - } - - if let Some(post_up) = &config.post_up { - debug!("Executing specified POST_UP command: {post_up}"); - execute_command(post_up)?; - } - - let tls_config = self - .gateway - .lock() - .expect("gateway mutex poison") - .tls_config - .clone(); - - // Build gRPC server. - let addr = config.grpc_socket(); - info!("gRPC server is listening on {addr}"); - - let tls = tls_config.ok_or_else(|| { - GatewayError::SetupError( - "TLS configuration is required; gateway gRPC server cannot start without mTLS" - .into(), - ) - })?; - - let tls_config = - server_tls_config(&tls.grpc_cert_pem, &tls.grpc_key_pem, &tls.grpc_ca_cert_pem) - .map_err(|e| GatewayError::SetupError(e.to_string()))?; - let mut builder = Server::builder().tls_config(tls_config)?; - - // Extract Core client cert serial for pinning. - let expected_serial = CertificateInfo::from_der(&tls.core_client_cert_der) - .map_err(|e: CertificateError| GatewayError::SetupError(e.to_string()))? - .serial; - - // Start gRPC server. This should run indefinitely. - debug!("Serving gRPC"); - builder - .add_service( - ServiceBuilder::new() - .layer(InterceptorLayer::new(certificate_serial_interceptor( - expected_serial, - ))) - .layer(DefguardVersionLayer::new(Version::parse(VERSION)?)) - .service(gateway_server::GatewayServer::new(self)), - ) - .serve(addr) - .await?; - - Ok(()) - } - - pub(crate) fn set_tls_config(&mut self, tls_config: TlsConfig) { - if let Ok(mut gateway) = self.gateway.lock() { - gateway.tls_config = Some(tls_config); - } - } -} - -#[tonic::async_trait] -impl gateway_server::Gateway for GatewayServer { - type BidiStream = UnboundedReceiverStream>; - - /// Handle bidirectional communication with Defguard Core. - async fn bidi( - &self, - request: Request>, - ) -> Result, Status> { - let Some(address) = request.remote_addr() else { - error!("Failed to determine Defguard Core's address for request: {request:?}"); - return Err(Status::internal( - "Failed to determine Defguard Core's address", - )); - }; - info!("Defguard Core gRPC client connected from {address}"); - - let core_info = ComponentInfo::from_metadata(request.metadata()); - let (version, info) = get_tracing_variables(&core_info); - - // Tracing span. - let span = tracing::info_span!( - "core_communication", - component = %DefguardComponent::Core, - version = version.to_string(), - info - ); - let _guard = span.enter(); - - // Check Defguard Core's version and exit if it's not supported. - let version = core_info.as_ref().map(|info| &info.version); - if !is_core_version_supported(version) { - return Err(Status::internal("Unsupported Defguard Core version")); - } - - // Drop new connections if another Core has already been connected. - if self - .gateway - .lock() - .expect("Gateway lock poison") - .client_tx - .is_some() - { - error!("Only one client connection is allowed."); - return Err(Status::internal("Client already connected")); - } - - let (tx, rx) = mpsc::unbounded_channel(); - - // First, send configuration request. - let req = CoreRequest { - id: self.message_id.fetch_add(1, Ordering::Relaxed), - payload: Some(core_request::Payload::ConfigRequest(())), - }; - - match tx.send(Ok(req)) { - Ok(()) => info!("Requesting network configuration from {address}"), - Err(err) => { - error!("Unable to send network configuration request to {address}: {err}"); - return Err(Status::internal("failed to send configuration request")); - } - } - - self.gateway.lock().expect("Gateway lock poison").client_tx = Some(tx); - - let gateway = Arc::clone(&self.gateway); - let mut stream = request.into_inner(); - tokio::spawn(async move { - loop { - match stream.message().await { - Ok(Some(response)) => { - debug!("Received message from Defguard Core: {response:?}"); - // Discard empty payloads. - if let Some(payload) = response.payload { - match payload { - core_response::Payload::Config(configuration) => { - match gateway.lock() { - Ok(mut gw) => { - gw.connected.store(true, Ordering::Relaxed); - if let Err(err) = gw.configure(configuration) { - error!("Failed to configure: {err}"); - } - } - Err(err) => error!("Lock failed: {err}"), - } - } - core_response::Payload::Update(update) => match gateway.lock() { - Ok(mut gw) => { - gw.handle_updates(update); - } - Err(err) => error!("Lock failed: {err}"), - }, - core_response::Payload::Empty(()) => (), - } - } - } - Ok(None) => { - info!("gRPC stream from Defguard Core has been closed"); - break; - } - Err(err) => { - error!("gRPC stream from Defguard Core failed with error: {err}"); - break; - } - } - } - info!("Defguard Core gRPC stream has been disconnected: {address}"); - if let Ok(mut gateway) = gateway.lock() { - gateway.connected.store(false, Ordering::Relaxed); - gateway.client_tx = None; - } - }); - - Ok(Response::new(UnboundedReceiverStream::new(rx))) - } - - #[instrument(skip(self, _request))] - async fn purge(&self, _request: Request<()>) -> Result, Status> { - debug!("Received purge request, removing gRPC certificate files"); - let cert_path = self.cert_dir.join(GRPC_CERT_NAME); - let key_path = self.cert_dir.join(GRPC_KEY_NAME); - let ca_cert_path = self.cert_dir.join(GRPC_CA_CERT_NAME); - let core_client_cert_path = self.cert_dir.join(CORE_CLIENT_CERT_NAME); - - let remove_cert_file = async |path: &std::path::Path, label: &str| -> Result<(), Status> { - match remove_file(path).await { - Ok(()) => { - info!("Removed {label} at {}", path.display()); - Ok(()) - } - Err(err) if err.kind() == std::io::ErrorKind::NotFound => { - debug!("{label} not found at {}, skipping removal", path.display()); - Ok(()) - } - Err(err) => { - error!("Failed to remove {label} at {}: {err}", path.display()); - Err(Status::internal(format!("Failed to remove {label}"))) - } - } - }; - - remove_cert_file(&cert_path, "gRPC certificate").await?; - remove_cert_file(&key_path, "gRPC key").await?; - remove_cert_file(&ca_cert_path, "CA certificate").await?; - remove_cert_file(&core_client_cert_path, "Core client certificate").await?; - - // Prepare underlying `Gateway` to enter setup mode. - self.gateway - .lock() - .expect("Failed to lock GatewayServer::gateway") - .purge(); - - let Some(sender) = self.reset_tx.lock().await.take() else { - error!("Reset channel sender not found"); - return Err(Status::internal("Failed to enter setup mode")); - }; - - if sender.send(()).is_err() { - error!("Failed to notify setup handler"); - return Err(Status::internal("Failed to enter setup mode")); - } - - info!("Removed gRPC certificate files; entering setup mode"); - Ok(Response::new(())) - } -} - /// Gather WireGuard statistics and send them to Core through gRPC. -pub async fn run_stats(gateway: Arc>, period: Duration) -> Result<(), GatewayError> { +pub async fn run_stats(gateway: Arc>, period: Duration) { // Helper map to track if peer data is actually changing to avoid sending duplicate stats. let mut peer_map = HashMap::new(); let mut interval = interval(period); @@ -889,369 +619,3 @@ pub async fn run_stats(gateway: Arc>, period: Duration) -> Result } } } - -#[cfg(test)] -mod tests { - use std::{ - net::{IpAddr, Ipv4Addr}, - slice::from_ref, - }; - - #[cfg(not(any(target_os = "macos", target_os = "netbsd")))] - use defguard_wireguard_rs::Kernel; - #[cfg(any(target_os = "macos", target_os = "netbsd"))] - use defguard_wireguard_rs::Userspace; - use defguard_wireguard_rs::WGApi; - use ipnetwork::IpNetwork; - - use super::*; - use crate::enterprise::firewall::{Address, FirewallRule, Policy, Port, Protocol}; - - #[cfg(any(target_os = "macos", target_os = "netbsd"))] - type WG = WGApi; - #[cfg(not(any(target_os = "macos", target_os = "netbsd")))] - type WG = WGApi; - - #[tokio::test] - async fn test_configuration_comparison() { - let old_config = InterfaceConfiguration { - name: "gateway".to_string(), - private_key: "FGqcPuaSlGWC2j50TBA4jHgiefPgQQcgTNLwzKUzBS8=".to_string(), - addresses: vec!["10.6.1.1/24".parse().unwrap()], - port: 50051, - mtu: 1420, - fwmark: 0, - }; - - let old_peers = vec![ - Peer { - pubkey: "+Oj0nZZ3iVH9WvKU9gM2eajJqY0hnzN5PkI4bvblgWo=".to_string(), - allowed_ips: vec!["10.6.1.2/24".to_string()], - preshared_key: None, - keepalive_interval: None, - }, - Peer { - pubkey: "m7ZxDjk4sjpzgowerQqycBvOz2n/nkswCdv24MEYVGA=".to_string(), - allowed_ips: vec!["10.6.1.3/24".to_string()], - preshared_key: None, - keepalive_interval: None, - }, - ]; - let old_peers_map = old_peers - .clone() - .into_iter() - .map(|peer| (peer.pubkey.clone(), peer)) - .collect(); - - let wgapi = WG::new("wg0").unwrap(); - let config = Config::default(); - let gateway = Gateway { - config, - interface_configuration: Some(old_config.clone()), - peers: old_peers_map, - wgapi: Arc::new(Mutex::new(wgapi)), - firewall_config: None, - connected: Arc::new(AtomicBool::new(false)), - client_tx: None, - tls_config: None, - }; - - // new config is the same - let new_config = old_config.clone(); - let new_peers = old_peers.clone(); - assert!(!gateway.is_interface_config_changed(&new_config, &new_peers)); - - // only interface config is different - let new_config = InterfaceConfiguration { - name: "gateway".to_string(), - private_key: "FGqcPuaSlGWC2j50TBA4jHgiefPgQQcgTNLwzKUzBS8=".to_string(), - addresses: vec!["10.6.1.2/24".parse().unwrap()], - port: 50051, - mtu: 1420, - fwmark: 0, - }; - let new_peers = old_peers.clone(); - assert!(gateway.is_interface_config_changed(&new_config, &new_peers)); - - // peer was removed - let new_config = old_config.clone(); - let mut new_peers = old_peers.clone(); - new_peers.pop(); - - assert!(gateway.is_interface_config_changed(&new_config, &new_peers)); - - // peer was added - let new_config = old_config.clone(); - let mut new_peers = old_peers.clone(); - new_peers.push(Peer { - pubkey: "VOCXuGWKz3PcdFba8pl7bFO/W4OG8sPet+w9Eb1LECk=".to_string(), - allowed_ips: vec!["10.6.1.4/24".to_string()], - preshared_key: None, - keepalive_interval: None, - }); - - assert!(gateway.is_interface_config_changed(&new_config, &new_peers)); - - // peer pubkey changed - let new_config = old_config.clone(); - let new_peers = vec![ - Peer { - pubkey: "VOCXuGWKz3PcdFba8pl7bFO/W4OG8sPet+w9Eb1LECk=".to_string(), - allowed_ips: vec!["10.6.1.2/24".to_string()], - preshared_key: None, - keepalive_interval: None, - }, - Peer { - pubkey: "m7ZxDjk4sjpzgowerQqycBvOz2n/nkswCdv24MEYVGA=".to_string(), - allowed_ips: vec!["10.6.1.3/24".to_string()], - preshared_key: None, - keepalive_interval: None, - }, - ]; - - assert!(gateway.is_interface_config_changed(&new_config, &new_peers)); - - // peer IP changed - let new_config = old_config.clone(); - let new_peers = vec![ - Peer { - pubkey: "+Oj0nZZ3iVH9WvKU9gM2eajJqY0hnzN5PkI4bvblgWo=".to_string(), - allowed_ips: vec!["10.6.1.2/24".to_string()], - preshared_key: None, - keepalive_interval: None, - }, - Peer { - pubkey: "m7ZxDjk4sjpzgowerQqycBvOz2n/nkswCdv24MEYVGA=".to_string(), - allowed_ips: vec!["10.6.1.4/24".to_string()], - preshared_key: None, - keepalive_interval: None, - }, - ]; - - assert!(gateway.is_interface_config_changed(&new_config, &new_peers)); - - // peer preshared key changed - let new_config = old_config.clone(); - let new_peers = vec![ - Peer { - pubkey: "+Oj0nZZ3iVH9WvKU9gM2eajJqY0hnzN5PkI4bvblgWo=".to_string(), - allowed_ips: vec!["10.6.1.2/24".to_string()], - preshared_key: Some("VGhpc2lzdGhlcGFzc3dvcmQzMWNoYXJhY3RlcnNsbwo=".into()), - keepalive_interval: None, - }, - Peer { - pubkey: "m7ZxDjk4sjpzgowerQqycBvOz2n/nkswCdv24MEYVGA=".to_string(), - allowed_ips: vec!["10.6.1.4/24".to_string()], - preshared_key: None, - keepalive_interval: None, - }, - ]; - - assert!(gateway.is_interface_config_changed(&new_config, &new_peers)); - - // peer keepalive interval changed - let new_config = old_config.clone(); - let new_peers = vec![ - Peer { - pubkey: "+Oj0nZZ3iVH9WvKU9gM2eajJqY0hnzN5PkI4bvblgWo=".to_string(), - allowed_ips: vec!["10.6.1.2/24".to_string()], - preshared_key: Some("VGhpc2lzdGhlcGFzc3dvcmQzMWNoYXJhY3RlcnNsbwo=".into()), - keepalive_interval: Some(15), - }, - Peer { - pubkey: "m7ZxDjk4sjpzgowerQqycBvOz2n/nkswCdv24MEYVGA=".to_string(), - allowed_ips: vec!["10.6.1.4/24".to_string()], - preshared_key: None, - keepalive_interval: None, - }, - ]; - - assert!(gateway.is_interface_config_changed(&new_config, &new_peers)); - } - - #[tokio::test] - async fn test_firewall_rules_comparison() { - let rule1 = FirewallRule { - comment: Some("Rule 1".to_string()), - destination_addrs: vec![Address::Network( - IpNetwork::new(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)), 32).unwrap(), - )], - destination_ports: vec![Port::Single(80)], - id: 1, - verdict: Policy::Allow, - protocols: vec![Protocol::Tcp], - source_addrs: vec![Address::Network( - IpNetwork::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)), 32).unwrap(), - )], - ipv4: true, - }; - - let rule2 = FirewallRule { - comment: Some("Rule 2".to_string()), - destination_addrs: vec![Address::Network( - IpNetwork::new(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2)), 32).unwrap(), - )], - destination_ports: vec![Port::Single(443)], - id: 2, - verdict: Policy::Allow, - protocols: vec![Protocol::Tcp], - source_addrs: vec![Address::Network( - IpNetwork::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 2)), 32).unwrap(), - )], - ipv4: true, - }; - - let rule3 = FirewallRule { - comment: Some("Rule 3".to_string()), - destination_addrs: vec![Address::Network( - IpNetwork::from_str("10.0.1.0/24").unwrap(), - )], - destination_ports: vec![Port::Range(1000, 2000)], - id: 3, - verdict: Policy::Deny, - protocols: vec![Protocol::Udp], - source_addrs: vec![Address::Network( - IpNetwork::from_str("192.168.0.0/16").unwrap(), - )], - ipv4: true, - }; - - let config1 = FirewallConfig { - rules: vec![rule1.clone(), rule2.clone()], - default_policy: Policy::Allow, - snat_bindings: Vec::new(), - }; - - let config_empty = FirewallConfig { - rules: Vec::new(), - default_policy: Policy::Allow, - snat_bindings: Vec::new(), - }; - - let wgapi = WG::new("wg0").unwrap(); - let config = Config::default(); - let mut gateway = Gateway { - config, - interface_configuration: None, - peers: HashMap::new(), - wgapi: Arc::new(Mutex::new(wgapi)), - firewall_config: None, - connected: Arc::new(AtomicBool::new(false)), - client_tx: None, - tls_config: None, - }; - - // Gateway has no firewall config, new rules are empty - gateway.firewall_config = None; - assert!(gateway.have_firewall_rules_changed(&[])); - - // Gateway has no firewall config, but new rules exist - gateway.firewall_config = None; - assert!(gateway.have_firewall_rules_changed(from_ref(&rule1))); - - // Gateway has firewall config, with empty rules list - gateway.firewall_config = Some(config1.clone()); - assert!(gateway.have_firewall_rules_changed(&[])); - - // Gateway has firewall config, new rules have different length - gateway.firewall_config = Some(config1.clone()); - assert!(gateway.have_firewall_rules_changed(from_ref(&rule1))); - - // Gateway has firewall config, new rules have different content - gateway.firewall_config = Some(config1.clone()); - assert!(gateway.have_firewall_rules_changed(&[rule1.clone(), rule3.clone()])); - - // Gateway has firewall config, new rules are identical - gateway.firewall_config = Some(config1.clone()); - assert!(!gateway.have_firewall_rules_changed(&[rule1.clone(), rule2.clone()])); - - // Gateway has empty firewall config, new rules exist - gateway.firewall_config = Some(config_empty.clone()); - assert!(gateway.have_firewall_rules_changed(from_ref(&rule1))); - - // Both configs are empty - gateway.firewall_config = Some(config_empty); - assert!(!gateway.have_firewall_rules_changed(&[])); - } - - #[tokio::test] - async fn test_firewall_config_comparison() { - let config1 = FirewallConfig { - rules: Vec::new(), - default_policy: Policy::Allow, - snat_bindings: Vec::new(), - }; - - let config2 = FirewallConfig { - rules: Vec::new(), - default_policy: Policy::Deny, - snat_bindings: Vec::new(), - }; - - let config3 = FirewallConfig { - rules: Vec::new(), - default_policy: Policy::Allow, - snat_bindings: Vec::new(), - }; - - let wgapi = WG::new("wg0").unwrap(); - let config = Config::default(); - let mut gateway = Gateway { - config, - interface_configuration: None, - peers: HashMap::new(), - wgapi: Arc::new(Mutex::new(wgapi)), - firewall_config: None, - connected: Arc::new(AtomicBool::new(false)), - client_tx: None, - tls_config: None, - }; - // Gateway has no config - gateway.firewall_config = None; - assert!(gateway.has_firewall_config_changed(&config1)); - - // Gateway has config, new config has different default_policy - gateway.firewall_config = Some(config1.clone()); - assert!(gateway.has_firewall_config_changed(&config2)); - - // Gateway has config, new config is identical - gateway.firewall_config = Some(config1.clone()); - assert!(!gateway.has_firewall_config_changed(&config3)); - - // Rules are not being ignored - let config4 = FirewallConfig { - rules: vec![FirewallRule { - comment: None, - destination_addrs: Vec::new(), - destination_ports: Vec::new(), - id: 0, - verdict: Policy::Allow, - protocols: Vec::new(), - source_addrs: Vec::new(), - ipv4: true, - }], - default_policy: Policy::Allow, - snat_bindings: Vec::new(), - }; - gateway.firewall_config = Some(config1); - assert!(gateway.has_firewall_config_changed(&config4)); - - // Rule IP versions are not being ignored - let config5 = FirewallConfig { - rules: vec![FirewallRule { - comment: None, - destination_addrs: Vec::new(), - destination_ports: Vec::new(), - id: 0, - verdict: Policy::Allow, - protocols: Vec::new(), - source_addrs: Vec::new(), - ipv4: false, - }], - default_policy: Policy::Allow, - snat_bindings: Vec::new(), - }; - gateway.firewall_config = Some(config4); - assert!(gateway.has_firewall_config_changed(&config5)); - } -} diff --git a/src/gateway/tests.rs b/src/gateway/tests.rs new file mode 100644 index 00000000..eb4359e0 --- /dev/null +++ b/src/gateway/tests.rs @@ -0,0 +1,362 @@ +use std::{ + net::{IpAddr, Ipv4Addr}, + slice::from_ref, +}; + +#[cfg(not(any(target_os = "macos", target_os = "netbsd")))] +use defguard_wireguard_rs::Kernel; +#[cfg(any(target_os = "macos", target_os = "netbsd"))] +use defguard_wireguard_rs::Userspace; +use defguard_wireguard_rs::WGApi; +use ipnetwork::IpNetwork; + +use super::*; +use crate::enterprise::firewall::{Address, FirewallRule, Policy, Port, Protocol}; + +#[cfg(any(target_os = "macos", target_os = "netbsd"))] +type WG = WGApi; +#[cfg(not(any(target_os = "macos", target_os = "netbsd")))] +type WG = WGApi; + +#[tokio::test] +async fn test_configuration_comparison() { + let old_config = InterfaceConfiguration { + name: "gateway".to_string(), + private_key: "FGqcPuaSlGWC2j50TBA4jHgiefPgQQcgTNLwzKUzBS8=".to_string(), + addresses: vec!["10.6.1.1/24".parse().unwrap()], + port: 50051, + mtu: 1420, + fwmark: 0, + }; + + let old_peers = vec![ + Peer { + pubkey: "+Oj0nZZ3iVH9WvKU9gM2eajJqY0hnzN5PkI4bvblgWo=".to_string(), + allowed_ips: vec!["10.6.1.2/24".to_string()], + preshared_key: None, + keepalive_interval: None, + }, + Peer { + pubkey: "m7ZxDjk4sjpzgowerQqycBvOz2n/nkswCdv24MEYVGA=".to_string(), + allowed_ips: vec!["10.6.1.3/24".to_string()], + preshared_key: None, + keepalive_interval: None, + }, + ]; + let old_peers_map = old_peers + .clone() + .into_iter() + .map(|peer| (peer.pubkey.clone(), peer)) + .collect(); + + let wgapi = WG::new("wg0").unwrap(); + let config = Config::default(); + let gateway = Gateway { + config, + interface_configuration: Some(old_config.clone()), + peers: old_peers_map, + wgapi: Arc::new(Mutex::new(wgapi)), + firewall_config: None, + connected: Arc::new(AtomicBool::new(false)), + client_tx: None, + tls_config: None, + }; + + // new config is the same + let new_config = old_config.clone(); + let new_peers = old_peers.clone(); + assert!(!gateway.is_interface_config_changed(&new_config, &new_peers)); + + // only interface config is different + let new_config = InterfaceConfiguration { + name: "gateway".to_string(), + private_key: "FGqcPuaSlGWC2j50TBA4jHgiefPgQQcgTNLwzKUzBS8=".to_string(), + addresses: vec!["10.6.1.2/24".parse().unwrap()], + port: 50051, + mtu: 1420, + fwmark: 0, + }; + let new_peers = old_peers.clone(); + assert!(gateway.is_interface_config_changed(&new_config, &new_peers)); + + // peer was removed + let new_config = old_config.clone(); + let mut new_peers = old_peers.clone(); + new_peers.pop(); + + assert!(gateway.is_interface_config_changed(&new_config, &new_peers)); + + // peer was added + let new_config = old_config.clone(); + let mut new_peers = old_peers.clone(); + new_peers.push(Peer { + pubkey: "VOCXuGWKz3PcdFba8pl7bFO/W4OG8sPet+w9Eb1LECk=".to_string(), + allowed_ips: vec!["10.6.1.4/24".to_string()], + preshared_key: None, + keepalive_interval: None, + }); + + assert!(gateway.is_interface_config_changed(&new_config, &new_peers)); + + // peer pubkey changed + let new_config = old_config.clone(); + let new_peers = vec![ + Peer { + pubkey: "VOCXuGWKz3PcdFba8pl7bFO/W4OG8sPet+w9Eb1LECk=".to_string(), + allowed_ips: vec!["10.6.1.2/24".to_string()], + preshared_key: None, + keepalive_interval: None, + }, + Peer { + pubkey: "m7ZxDjk4sjpzgowerQqycBvOz2n/nkswCdv24MEYVGA=".to_string(), + allowed_ips: vec!["10.6.1.3/24".to_string()], + preshared_key: None, + keepalive_interval: None, + }, + ]; + + assert!(gateway.is_interface_config_changed(&new_config, &new_peers)); + + // peer IP changed + let new_config = old_config.clone(); + let new_peers = vec![ + Peer { + pubkey: "+Oj0nZZ3iVH9WvKU9gM2eajJqY0hnzN5PkI4bvblgWo=".to_string(), + allowed_ips: vec!["10.6.1.2/24".to_string()], + preshared_key: None, + keepalive_interval: None, + }, + Peer { + pubkey: "m7ZxDjk4sjpzgowerQqycBvOz2n/nkswCdv24MEYVGA=".to_string(), + allowed_ips: vec!["10.6.1.4/24".to_string()], + preshared_key: None, + keepalive_interval: None, + }, + ]; + + assert!(gateway.is_interface_config_changed(&new_config, &new_peers)); + + // peer preshared key changed + let new_config = old_config.clone(); + let new_peers = vec![ + Peer { + pubkey: "+Oj0nZZ3iVH9WvKU9gM2eajJqY0hnzN5PkI4bvblgWo=".to_string(), + allowed_ips: vec!["10.6.1.2/24".to_string()], + preshared_key: Some("VGhpc2lzdGhlcGFzc3dvcmQzMWNoYXJhY3RlcnNsbwo=".into()), + keepalive_interval: None, + }, + Peer { + pubkey: "m7ZxDjk4sjpzgowerQqycBvOz2n/nkswCdv24MEYVGA=".to_string(), + allowed_ips: vec!["10.6.1.4/24".to_string()], + preshared_key: None, + keepalive_interval: None, + }, + ]; + + assert!(gateway.is_interface_config_changed(&new_config, &new_peers)); + + // peer keepalive interval changed + let new_config = old_config.clone(); + let new_peers = vec![ + Peer { + pubkey: "+Oj0nZZ3iVH9WvKU9gM2eajJqY0hnzN5PkI4bvblgWo=".to_string(), + allowed_ips: vec!["10.6.1.2/24".to_string()], + preshared_key: Some("VGhpc2lzdGhlcGFzc3dvcmQzMWNoYXJhY3RlcnNsbwo=".into()), + keepalive_interval: Some(15), + }, + Peer { + pubkey: "m7ZxDjk4sjpzgowerQqycBvOz2n/nkswCdv24MEYVGA=".to_string(), + allowed_ips: vec!["10.6.1.4/24".to_string()], + preshared_key: None, + keepalive_interval: None, + }, + ]; + + assert!(gateway.is_interface_config_changed(&new_config, &new_peers)); +} + +#[tokio::test] +async fn test_firewall_rules_comparison() { + let rule1 = FirewallRule { + comment: Some("Rule 1".to_string()), + destination_addrs: vec![Address::Network( + IpNetwork::new(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)), 32).unwrap(), + )], + destination_ports: vec![Port::Single(80)], + id: 1, + verdict: Policy::Allow, + protocols: vec![Protocol::Tcp], + source_addrs: vec![Address::Network( + IpNetwork::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)), 32).unwrap(), + )], + ipv4: true, + }; + + let rule2 = FirewallRule { + comment: Some("Rule 2".to_string()), + destination_addrs: vec![Address::Network( + IpNetwork::new(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2)), 32).unwrap(), + )], + destination_ports: vec![Port::Single(443)], + id: 2, + verdict: Policy::Allow, + protocols: vec![Protocol::Tcp], + source_addrs: vec![Address::Network( + IpNetwork::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 2)), 32).unwrap(), + )], + ipv4: true, + }; + + let rule3 = FirewallRule { + comment: Some("Rule 3".to_string()), + destination_addrs: vec![Address::Network( + IpNetwork::from_str("10.0.1.0/24").unwrap(), + )], + destination_ports: vec![Port::Range(1000, 2000)], + id: 3, + verdict: Policy::Deny, + protocols: vec![Protocol::Udp], + source_addrs: vec![Address::Network( + IpNetwork::from_str("192.168.0.0/16").unwrap(), + )], + ipv4: true, + }; + + let config1 = FirewallConfig { + rules: vec![rule1.clone(), rule2.clone()], + default_policy: Policy::Allow, + snat_bindings: Vec::new(), + }; + + let config_empty = FirewallConfig { + rules: Vec::new(), + default_policy: Policy::Allow, + snat_bindings: Vec::new(), + }; + + let wgapi = WG::new("wg0").unwrap(); + let config = Config::default(); + let mut gateway = Gateway { + config, + interface_configuration: None, + peers: HashMap::new(), + wgapi: Arc::new(Mutex::new(wgapi)), + firewall_config: None, + connected: Arc::new(AtomicBool::new(false)), + client_tx: None, + tls_config: None, + }; + + // Gateway has no firewall config, new rules are empty + gateway.firewall_config = None; + assert!(gateway.have_firewall_rules_changed(&[])); + + // Gateway has no firewall config, but new rules exist + gateway.firewall_config = None; + assert!(gateway.have_firewall_rules_changed(from_ref(&rule1))); + + // Gateway has firewall config, with empty rules list + gateway.firewall_config = Some(config1.clone()); + assert!(gateway.have_firewall_rules_changed(&[])); + + // Gateway has firewall config, new rules have different length + gateway.firewall_config = Some(config1.clone()); + assert!(gateway.have_firewall_rules_changed(from_ref(&rule1))); + + // Gateway has firewall config, new rules have different content + gateway.firewall_config = Some(config1.clone()); + assert!(gateway.have_firewall_rules_changed(&[rule1.clone(), rule3.clone()])); + + // Gateway has firewall config, new rules are identical + gateway.firewall_config = Some(config1.clone()); + assert!(!gateway.have_firewall_rules_changed(&[rule1.clone(), rule2.clone()])); + + // Gateway has empty firewall config, new rules exist + gateway.firewall_config = Some(config_empty.clone()); + assert!(gateway.have_firewall_rules_changed(from_ref(&rule1))); + + // Both configs are empty + gateway.firewall_config = Some(config_empty); + assert!(!gateway.have_firewall_rules_changed(&[])); +} + +#[tokio::test] +async fn test_firewall_config_comparison() { + let config1 = FirewallConfig { + rules: Vec::new(), + default_policy: Policy::Allow, + snat_bindings: Vec::new(), + }; + + let config2 = FirewallConfig { + rules: Vec::new(), + default_policy: Policy::Deny, + snat_bindings: Vec::new(), + }; + + let config3 = FirewallConfig { + rules: Vec::new(), + default_policy: Policy::Allow, + snat_bindings: Vec::new(), + }; + + let wgapi = WG::new("wg0").unwrap(); + let config = Config::default(); + let mut gateway = Gateway { + config, + interface_configuration: None, + peers: HashMap::new(), + wgapi: Arc::new(Mutex::new(wgapi)), + firewall_config: None, + connected: Arc::new(AtomicBool::new(false)), + client_tx: None, + tls_config: None, + }; + // Gateway has no config + gateway.firewall_config = None; + assert!(gateway.has_firewall_config_changed(&config1)); + + // Gateway has config, new config has different default_policy + gateway.firewall_config = Some(config1.clone()); + assert!(gateway.has_firewall_config_changed(&config2)); + + // Gateway has config, new config is identical + gateway.firewall_config = Some(config1.clone()); + assert!(!gateway.has_firewall_config_changed(&config3)); + + // Rules are not being ignored + let config4 = FirewallConfig { + rules: vec![FirewallRule { + comment: None, + destination_addrs: Vec::new(), + destination_ports: Vec::new(), + id: 0, + verdict: Policy::Allow, + protocols: Vec::new(), + source_addrs: Vec::new(), + ipv4: true, + }], + default_policy: Policy::Allow, + snat_bindings: Vec::new(), + }; + gateway.firewall_config = Some(config1); + assert!(gateway.has_firewall_config_changed(&config4)); + + // Rule IP versions are not being ignored + let config5 = FirewallConfig { + rules: vec![FirewallRule { + comment: None, + destination_addrs: Vec::new(), + destination_ports: Vec::new(), + id: 0, + verdict: Policy::Allow, + protocols: Vec::new(), + source_addrs: Vec::new(), + ipv4: false, + }], + default_policy: Policy::Allow, + snat_bindings: Vec::new(), + }; + gateway.firewall_config = Some(config4); + assert!(gateway.has_firewall_config_changed(&config5)); +} diff --git a/src/gateway_server.rs b/src/gateway_server.rs new file mode 100644 index 00000000..5e0dec7d --- /dev/null +++ b/src/gateway_server.rs @@ -0,0 +1,312 @@ +use std::{ + path::PathBuf, + sync::{ + Arc, Mutex, + atomic::{AtomicU64, Ordering}, + }, +}; + +use defguard_certs::{CertificateError, CertificateInfo}; +use defguard_grpc_tls::{certs::server_tls_config, server::certificate_serial_interceptor}; +use defguard_version::{ + ComponentInfo, DefguardComponent, Version, get_tracing_variables, server::DefguardVersionLayer, +}; +use tokio::{ + fs::remove_file, + sync::{mpsc, oneshot}, +}; +use tokio_stream::wrappers::UnboundedReceiverStream; +use tonic::{Request, Response, Status, Streaming, service::InterceptorLayer, transport::Server}; +use tower::ServiceBuilder; +use tracing::instrument; + +#[cfg(target_os = "linux")] +use crate::enterprise::firewall::api::{FirewallApi, FirewallManagementApi}; +use crate::{ + CORE_CLIENT_CERT_NAME, GRPC_CA_CERT_NAME, GRPC_CERT_NAME, GRPC_KEY_NAME, VERSION, + config::Config, + error::GatewayError, + execute_command, + gateway::{Gateway, TlsConfig}, + proto::gateway::{CoreRequest, CoreResponse, core_request, core_response, gateway_server}, + version::is_core_version_supported, +}; + +pub(crate) struct GatewayServer { + message_id: AtomicU64, + gateway: Arc>, + cert_dir: PathBuf, + reset_tx: Arc>>>, +} + +impl GatewayServer { + #[must_use] + pub(crate) fn new( + gateway: Arc>, + cert_dir: PathBuf, + reset_tx: oneshot::Sender<()>, + ) -> Self { + Self { + message_id: AtomicU64::new(0), + gateway, + cert_dir, + reset_tx: Arc::new(tokio::sync::Mutex::new(Some(reset_tx))), + } + } + + /// Starts the gateway process. + /// * Requires a valid mTLS configuration to be set (via `set_tls_config`) before starting; + /// returns an error if TLS configuration is absent - the gRPC server never starts in plain-text mode + /// * Retrieves configuration and configuration updates from Defguard core via a mTLS-secured gRPC server + /// * Manages the WireGuard interface according to configuration and updates + /// * Sends interface statistics to Defguard core periodically + pub(crate) async fn start(self, config: Config) -> Result<(), GatewayError> { + info!("Starting Defguard Gateway version {VERSION} with configuration: {config:?}"); + + // Try to create network interface for WireGuard. + // FIXME: check if the interface already exists, or somehow be more clever. + { + #[allow(unused)] + let mut gateway = &mut self.gateway.lock().expect("gateway mutex poison"); + if let Err(err) = gateway + .wgapi + .lock() + .expect("wgapi mutex poison") + .create_interface() + { + warn!( + "Couldn't create network interface {}: {err}. Proceeding anyway.", + config.ifname + ); + } else { + #[cfg(target_os = "linux")] + if !config.disable_firewall_management && config.masquerade { + let mut firewall_api = FirewallApi::new(&config.ifname)?; + firewall_api.setup_nat(config.masquerade, &[])?; + } + } + } + + if let Some(post_up) = &config.post_up { + debug!("Executing specified POST_UP command: {post_up}"); + execute_command(post_up)?; + } + + let tls_config = self + .gateway + .lock() + .expect("gateway mutex poison") + .tls_config + .clone(); + + // Build gRPC server. + let addr = config.grpc_socket(); + info!("gRPC server is listening on {addr}"); + + let tls = tls_config.ok_or_else(|| { + GatewayError::SetupError( + "TLS configuration is required; gateway gRPC server cannot start without mTLS" + .into(), + ) + })?; + + let tls_config = + server_tls_config(&tls.grpc_cert_pem, &tls.grpc_key_pem, &tls.grpc_ca_cert_pem) + .map_err(|e| GatewayError::SetupError(e.to_string()))?; + let mut builder = Server::builder().tls_config(tls_config)?; + + // Extract Core client cert serial for pinning. + let expected_serial = CertificateInfo::from_der(&tls.core_client_cert_der) + .map_err(|e: CertificateError| GatewayError::SetupError(e.to_string()))? + .serial; + + // Start gRPC server. This should run indefinitely. + debug!("Serving gRPC"); + builder + .add_service( + ServiceBuilder::new() + .layer(InterceptorLayer::new(certificate_serial_interceptor( + expected_serial, + ))) + .layer(DefguardVersionLayer::new(Version::parse(VERSION)?)) + .service(gateway_server::GatewayServer::new(self)), + ) + .serve(addr) + .await?; + + Ok(()) + } + + pub(crate) fn set_tls_config(&mut self, tls_config: TlsConfig) { + if let Ok(mut gateway) = self.gateway.lock() { + gateway.tls_config = Some(tls_config); + } + } +} + +#[tonic::async_trait] +impl gateway_server::Gateway for GatewayServer { + type BidiStream = UnboundedReceiverStream>; + + /// Handle bidirectional communication with Defguard Core. + async fn bidi( + &self, + request: Request>, + ) -> Result, Status> { + let Some(address) = request.remote_addr() else { + error!("Failed to determine Defguard Core's address for request: {request:?}"); + return Err(Status::internal( + "Failed to determine Defguard Core's address", + )); + }; + info!("Defguard Core gRPC client connected from {address}"); + + let core_info = ComponentInfo::from_metadata(request.metadata()); + let (version, info) = get_tracing_variables(&core_info); + + // Tracing span. + let span = tracing::info_span!( + "core_communication", + component = %DefguardComponent::Core, + version = version.to_string(), + info + ); + let _guard = span.enter(); + + // Check Defguard Core's version and exit if it's not supported. + let version = core_info.as_ref().map(|info| &info.version); + if !is_core_version_supported(version) { + return Err(Status::internal("Unsupported Defguard Core version")); + } + + // Drop new connections if another Core has already been connected. + if self + .gateway + .lock() + .expect("Gateway lock poison") + .client_tx + .is_some() + { + error!("Only one client connection is allowed."); + return Err(Status::internal("Client already connected")); + } + + let (tx, rx) = mpsc::unbounded_channel(); + + // First, send configuration request. + let req = CoreRequest { + id: self.message_id.fetch_add(1, Ordering::Relaxed), + payload: Some(core_request::Payload::ConfigRequest(())), + }; + + match tx.send(Ok(req)) { + Ok(()) => info!("Requesting network configuration from {address}"), + Err(err) => { + error!("Unable to send network configuration request to {address}: {err}"); + return Err(Status::internal("failed to send configuration request")); + } + } + + self.gateway.lock().expect("Gateway lock poison").client_tx = Some(tx); + + let gateway = Arc::clone(&self.gateway); + let mut stream = request.into_inner(); + tokio::spawn(async move { + loop { + match stream.message().await { + Ok(Some(response)) => { + debug!("Received message from Defguard Core: {response:?}"); + // Discard empty payloads. + if let Some(payload) = response.payload { + match payload { + core_response::Payload::Config(configuration) => { + match gateway.lock() { + Ok(mut gw) => { + gw.connected.store(true, Ordering::Relaxed); + if let Err(err) = gw.configure(configuration) { + error!("Failed to configure: {err}"); + } + } + Err(err) => error!("Lock failed: {err}"), + } + } + core_response::Payload::Update(update) => match gateway.lock() { + Ok(mut gw) => { + gw.handle_updates(update); + } + Err(err) => error!("Lock failed: {err}"), + }, + core_response::Payload::Empty(()) => (), + } + } + } + Ok(None) => { + info!("gRPC stream from Defguard Core has been closed"); + break; + } + Err(err) => { + error!("gRPC stream from Defguard Core failed with error: {err}"); + break; + } + } + } + info!("Defguard Core gRPC stream has been disconnected: {address}"); + if let Ok(mut gateway) = gateway.lock() { + gateway.connected.store(false, Ordering::Relaxed); + gateway.client_tx = None; + } + }); + + Ok(Response::new(UnboundedReceiverStream::new(rx))) + } + + #[instrument(skip(self, _request))] + async fn purge(&self, _request: Request<()>) -> Result, Status> { + debug!("Received purge request, removing gRPC certificate files"); + let cert_path = self.cert_dir.join(GRPC_CERT_NAME); + let key_path = self.cert_dir.join(GRPC_KEY_NAME); + let ca_cert_path = self.cert_dir.join(GRPC_CA_CERT_NAME); + let core_client_cert_path = self.cert_dir.join(CORE_CLIENT_CERT_NAME); + + let remove_cert_file = async |path: &std::path::Path, label: &str| -> Result<(), Status> { + match remove_file(path).await { + Ok(()) => { + info!("Removed {label} at {}", path.display()); + Ok(()) + } + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + debug!("{label} not found at {}, skipping removal", path.display()); + Ok(()) + } + Err(err) => { + error!("Failed to remove {label} at {}: {err}", path.display()); + Err(Status::internal(format!("Failed to remove {label}"))) + } + } + }; + + remove_cert_file(&cert_path, "gRPC certificate").await?; + remove_cert_file(&key_path, "gRPC key").await?; + remove_cert_file(&ca_cert_path, "CA certificate").await?; + remove_cert_file(&core_client_cert_path, "Core client certificate").await?; + + // Prepare underlying `Gateway` to enter setup mode. + self.gateway + .lock() + .expect("Failed to lock GatewayServer::gateway") + .purge(); + + let Some(sender) = self.reset_tx.lock().await.take() else { + error!("Reset channel sender not found"); + return Err(Status::internal("Failed to enter setup mode")); + }; + + if sender.send(()).is_err() { + error!("Failed to notify setup handler"); + return Err(Status::internal("Failed to enter setup mode")); + } + + info!("Removed gRPC certificate files; entering setup mode"); + Ok(Response::new(())) + } +} diff --git a/src/lib.rs b/src/lib.rs index 2351e8bf..2729cbb5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ pub mod config; pub mod error; pub mod gateway; +mod gateway_server; pub mod server; mod version; @@ -124,7 +125,7 @@ pub fn execute_command(command: &str) -> Result<(), GatewayError> { let stdout = String::from_utf8_lossy(&output.stdout); let stderr = String::from_utf8_lossy(&output.stderr); - info!("Command {command} executed successfully. Stdout: {stdout}",); + info!("Command {command} executed successfully. Stdout: {stdout}"); if !stderr.is_empty() { error!("Stderr:\n{stderr}"); } diff --git a/src/logging.rs b/src/logging.rs index ac912c08..5d900b1b 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -1,3 +1,5 @@ +use std::{collections::HashMap, fmt}; + use defguard_version::Version; use tokio::sync::mpsc::Sender; use tracing::{Event, Subscriber}; @@ -45,7 +47,7 @@ where event.record(&mut visitor); let entry = LogEntry { - level: format!("{:?}", event.metadata().level()), + level: event.metadata().level().to_string(), target: event.metadata().target().to_string(), message: visitor.message, timestamp: chrono::Utc::now().to_rfc3339(), @@ -60,16 +62,16 @@ where #[derive(Default)] struct LogVisitor { message: String, - fields: std::collections::HashMap, + fields: HashMap, } impl tracing::field::Visit for LogVisitor { - fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) { + fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn fmt::Debug) { + let value_str = format!("{value:?}"); if field.name() == "message" { - self.message = format!("{value:?}"); + self.message = value_str; } else { - self.fields - .insert(field.name().to_string(), format!("{value:?}")); + self.fields.insert(field.name().to_string(), value_str); } } } diff --git a/src/server.rs b/src/server.rs index e96012f7..f857ffe0 100644 --- a/src/server.rs +++ b/src/server.rs @@ -9,8 +9,6 @@ use std::{ use axum::{Router, extract::Extension, http::StatusCode, routing::get, serve}; use tokio::net::TcpListener; -use crate::error::GatewayError; - async fn healthcheck<'a>( Extension(connected): Extension>, ) -> (StatusCode, &'a str) { @@ -26,7 +24,7 @@ pub async fn run_http_server( http_port: u16, http_bind_address: Option, connected: Arc, -) -> Result<(), GatewayError> { +) { let app = Router::new() .route("/health", get(healthcheck)) .layer(Extension(connected)); @@ -36,13 +34,15 @@ pub async fn run_http_server( http_bind_address.unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)), http_port, ); - let listener = TcpListener::bind(&addr).await.inspect_err(|err| { - error!("Failed to bind to {addr}: {err}"); - })?; + let listener = match TcpListener::bind(&addr).await { + Ok(socket) => socket, + Err(err) => { + error!("Failed to bind to {addr}: {err}"); + return; + } + }; info!("Health check listening on {addr}"); // From axum docs: this future will never actually complete or return an error. let _ = serve(listener, app.into_make_service()).await; - - Ok(()) } diff --git a/src/setup.rs b/src/setup.rs index 2864c039..0a79780b 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -206,7 +206,7 @@ impl GatewaySetupServer { let (cancel_tx, cancel_rx) = oneshot::channel::<()>(); tokio::spawn(async move { tokio::select! { - _ = tokio::time::sleep(adoption_timeout) => { + () = tokio::time::sleep(adoption_timeout) => { adoption_expired.store(true, Ordering::Relaxed); error!( "Gateway adoption expired and is now blocked. Restart the Gateway to enable adoption." @@ -391,7 +391,7 @@ impl gateway_setup_server::GatewaySetup for GatewaySetupServer { debug!("Key pair created"); let subject_alt_names = [setup_info.cert_hostname]; - debug!("Preparing Certificate Signing Request for hostname: {subject_alt_names:?}",); + debug!("Preparing Certificate Signing Request for hostname: {subject_alt_names:?}"); let csr = match defguard_certs::Csr::new( &key_pair, diff --git a/src/tests/mtls.rs b/src/tests/mtls.rs index e88b7801..a85104fa 100644 --- a/src/tests/mtls.rs +++ b/src/tests/mtls.rs @@ -24,7 +24,8 @@ use tonic::{ use super::mock_wgapi::NullWgApi; use crate::{ config::Config, - gateway::{Gateway, GatewayServer, TlsConfig}, + gateway::{Gateway, TlsConfig}, + gateway_server::GatewayServer, proto::gateway::gateway_client::GatewayClient, };