diff --git a/Cargo.lock b/Cargo.lock index 4cf204c3..04173914 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" @@ -170,9 +170,9 @@ dependencies = [ [[package]] name = "asn1-rs" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" +checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8" dependencies = [ "asn1-rs-derive 0.6.0", "asn1-rs-impl", @@ -247,9 +247,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" @@ -386,9 +386,15 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.11.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" dependencies = [ "serde_core", ] @@ -431,9 +437,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "byteorder" @@ -443,15 +449,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" [[package]] name = "camino" -version = "1.2.2" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +checksum = "5f2d30e4173c4026932d51d31d6b0613b1fd3014bf3f9f8943d4ba139c437ba0" dependencies = [ "serde_core", ] @@ -481,9 +487,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.62" +version = "1.2.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" dependencies = [ "find-msvc-tools", "jobserver", @@ -529,9 +535,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.44" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", "js-sys", @@ -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.6" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c25476f197cec498e72de55dcaa34f966720ae000eda3171f5144f0433256acc" +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" @@ -1045,7 +1081,7 @@ version = "10.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" dependencies = [ - "asn1-rs 0.7.1", + "asn1-rs 0.7.2", "displaydoc", "nom", "num-bigint", @@ -1059,7 +1095,6 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ - "powerfmt", "serde_core", ] @@ -1112,15 +1147,15 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags", + "bitflags 2.13.0", "objc2", ] [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", @@ -1186,9 +1221,9 @@ dependencies = [ [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" dependencies = [ "serde", ] @@ -1225,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", @@ -1235,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", @@ -1499,15 +1534,13 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", - "wasip2", - "wasip3", ] [[package]] @@ -1516,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", @@ -1553,9 +1586,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" +checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" dependencies = [ "atomic-waker", "bytes", @@ -1665,9 +1698,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", "itoa", @@ -1716,9 +1749,9 @@ checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" [[package]] name = "hyper" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" dependencies = [ "atomic-waker", "bytes", @@ -1913,12 +1946,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "ident_case" version = "1.0.1" @@ -2053,10 +2080,11 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.24" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d" +checksum = "ccfe6121cbe750cf81efa362d85c0bde7ea298ec43092d3a193baca59cdbd634" dependencies = [ + "defmt", "jiff-static", "log", "portable-atomic", @@ -2066,9 +2094,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.24" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7" +checksum = "e165e897f662d428f3cd3828a919dbe067c2d42bb1031eede74ef9d27ecdedd2" dependencies = [ "proc-macro2", "quote", @@ -2087,13 +2115,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.98" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ "cfg-if", "futures-util", - "once_cell", "wasm-bindgen", ] @@ -2130,12 +2157,6 @@ dependencies = [ "spin", ] -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - [[package]] name = "libc" version = "0.2.186" @@ -2144,9 +2165,9 @@ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libgit2-sys" -version = "0.18.4+1.9.3" +version = "0.18.5+1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b26f66f35e1871b22efcf7191564123d2a446ca0538cde63c23adfefa9b15b7" +checksum = "005d6ae6eac1912906073e069f7db60b1fa98e052a68227824afe3e3a1c59ca2" dependencies = [ "cc", "libc", @@ -2172,14 +2193,14 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" dependencies = [ - "bitflags", + "bitflags 2.13.0", "libc", "plain", - "redox_syscall 0.7.5", + "redox_syscall 0.8.1", ] [[package]] @@ -2194,9 +2215,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.28" +version = "1.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc3a226e576f50782b3305c5ccf458698f92798987f551c6a02efe8276721e22" +checksum = "85bc9657773828b90eeb625adff10eeac83cc21bbfd8e23a03eaa8a33c9e28d9" dependencies = [ "cc", "libc", @@ -2227,9 +2248,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "matchers" @@ -2258,18 +2279,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "mime" @@ -2295,9 +2307,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", "wasi", @@ -2328,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", @@ -2381,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", @@ -2400,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", @@ -2423,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", @@ -2443,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]] @@ -2515,9 +2515,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" @@ -2593,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", ] @@ -2614,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", ] @@ -2625,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", @@ -2658,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", @@ -2676,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", @@ -2689,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", ] @@ -2700,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", @@ -2712,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", @@ -2752,7 +2752,7 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" dependencies = [ - "asn1-rs 0.7.1", + "asn1-rs 0.7.2", ] [[package]] @@ -2806,11 +2806,11 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.79" +version = "0.10.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf0b434746ee2832f4f0baf10137e1cabb18cbe6912c69e2e33263c45250f542" +checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" dependencies = [ - "bitflags", + "bitflags 2.13.0", "cfg-if", "foreign-types", "libc", @@ -2837,9 +2837,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.115" +version = "0.9.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "158fe5b292746440aa6e7a7e690e55aeb72d41505e2804c23c6973ad0e9c9781" +checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" dependencies = [ "cc", "libc", @@ -2858,13 +2858,13 @@ dependencies = [ [[package]] name = "os_info" -version = "3.14.0" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224" +checksum = "9cf20a545b305cf1da722b236b5155c9bb35f1d5ceb28c048bd96ca842f41b5b" dependencies = [ "android_system_properties", "log", - "nix 0.30.1", + "nix", "objc2", "objc2-foundation", "objc2-ui-kit", @@ -2986,18 +2986,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.12" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf0d9e68100b3a7989b4901972f265cd542e560a3a8a724e1e20322f4d06ce9" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.12" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a990e22f43e84855daf260dded30524ef4a9021cc7541c26540500a50b624389" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", @@ -3112,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" @@ -3123,9 +3145,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" dependencies = [ "bytes", "prost-derive", @@ -3133,9 +3155,9 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" +checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042" dependencies = [ "heck", "itertools 0.14.0", @@ -3154,9 +3176,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" dependencies = [ "anyhow", "itertools 0.14.0", @@ -3167,20 +3189,20 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" +checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a" dependencies = [ "prost", ] [[package]] name = "pulldown-cmark" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3a14896dfa883796f1cb410461aef38810ea05f2b2c33c5aded3649095fdad" +checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e" dependencies = [ - "bitflags", + "bitflags 2.13.0", "memchr", "unicase", ] @@ -3196,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", ] @@ -3294,16 +3316,16 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.13.0", ] [[package]] name = "redox_syscall" -version = "0.7.5" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4666a1a60d8412eab19d94f6d13dcc9cea0a5ef4fdf6a5db306537413c661b1b" +checksum = "5b44b894f2a6e36457d665d1e08c3866add6ed5e70050c1b4ba8a8ddedb02ce7" dependencies = [ - "bitflags", + "bitflags 2.13.0", ] [[package]] @@ -3328,9 +3350,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -3351,9 +3373,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "reqwest" @@ -3469,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", @@ -3478,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", @@ -3494,9 +3516,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" dependencies = [ "openssl-probe", "rustls-pki-types", @@ -3626,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", @@ -3705,9 +3727,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -3759,9 +3781,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.20.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e72c1c2cb7b223fafb600a619537a871c2818583d619401b785e7c0b746ccde2" +checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" dependencies = [ "base64 0.22.1", "bs58", @@ -3779,9 +3801,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.20.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b90c488738ecb4fb0262f41f43bc40efc5868d9fb744319ddf5f5317f417bfac" +checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -3822,9 +3844,9 @@ dependencies = [ [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "signal-hook-registry" @@ -3878,24 +3900,24 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" dependencies = [ "serde", ] [[package]] name = "smawk" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" +checksum = "e8e2fb0f499abb4d162f2bedad68f5ef91a1682b5a03596ddb67efd37768d100" [[package]] name = "socket2" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", "windows-sys 0.61.2", @@ -4017,7 +4039,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64 0.22.1", - "bitflags", + "bitflags 2.13.0", "byteorder", "bytes", "chrono", @@ -4061,7 +4083,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64 0.22.1", - "bitflags", + "bitflags 2.13.0", "byteorder", "chrono", "crc", @@ -4176,9 +4198,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.117" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", @@ -4223,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", ] @@ -4245,7 +4267,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.2", + "getrandom 0.4.3", "once_cell", "rustix", "windows-sys 0.61.2", @@ -4311,12 +4333,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.47" +version = "0.3.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "0e48db7b415311b615f910b3dcaa4557bcd4bf1982379c95c223fd8c2a20e210" dependencies = [ "deranged", - "itoa", "libc", "num-conv", "num_threads", @@ -4328,15 +4349,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" dependencies = [ "num-conv", "time-core", @@ -4464,7 +4485,7 @@ dependencies = [ "serde_spanned", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 1.0.2", + "winnow 1.0.3", ] [[package]] @@ -4491,7 +4512,7 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.2", + "winnow 1.0.3", ] [[package]] @@ -4604,11 +4625,11 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.10" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags", + "bitflags 2.13.0", "bytes", "futures-util", "http", @@ -4702,9 +4723,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.20.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unicase" @@ -4739,17 +4760,11 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "uniffi" -version = "0.31.1" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc5f2297ee5b893405bed1a6929faec4713a061df158ecf5198089f23910d470" +checksum = "46eefd5468602930da46b1f49d3448c6dfc2e81295f93120f23f8174fd70267f" dependencies = [ "anyhow", "camino", @@ -4764,9 +4779,9 @@ dependencies = [ [[package]] name = "uniffi_bindgen" -version = "0.31.1" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bc0c60a9607e7ab77a2ad47ec5530178015014839db25af7512447d2238016c" +checksum = "c4a0c9b375d32e1365cdb2bdd7cb495eecf6fac851ddbad077412b4ee1888514" dependencies = [ "anyhow", "askama", @@ -4790,9 +4805,9 @@ dependencies = [ [[package]] name = "uniffi_build" -version = "0.31.1" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c39413c43b955e4aa8a4e2b34bbd1b6b5ff6bd85532b52f9eb92fbe88c14458" +checksum = "744fe15bcd3e2b1712a4573a45ce749af19cf28d69027ca5789619014955668c" dependencies = [ "anyhow", "camino", @@ -4801,9 +4816,9 @@ dependencies = [ [[package]] name = "uniffi_core" -version = "0.31.1" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77baf5d539fe2e1ad6805e942dbc5dbdeb2b83eb5f2b3a6535d422ca4b02a12f" +checksum = "eec017b112701681f6fbbe5d92014b5c468eb0b177a94389de03ceec40665095" dependencies = [ "anyhow", "bytes", @@ -4813,9 +4828,9 @@ dependencies = [ [[package]] name = "uniffi_internal_macros" -version = "0.31.1" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b42137524f4be6400fcaca9d02c1d4ecb6ad917e4013c0b93235526d8396e5" +checksum = "4641669b48fefbc5e80ff08c5004d9c7617fb91232131a6734ab6712779cb04c" dependencies = [ "anyhow", "indexmap 2.14.0", @@ -4826,9 +4841,9 @@ dependencies = [ [[package]] name = "uniffi_macros" -version = "0.31.1" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9273ec45330d8fe9a3701b7b983cea7a4e218503359831967cb95d26b873561" +checksum = "eeb8617ee814de22caf7417bf514715ba0b3f46bd9d5a5d794413fd8282cb737" dependencies = [ "camino", "fs-err", @@ -4843,9 +4858,9 @@ dependencies = [ [[package]] name = "uniffi_meta" -version = "0.31.1" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "431d2f443e7828a6c29d188de98b6771a6491ee98bba2d4372643bf93f988a18" +checksum = "58d5b94fc92803d21b2928bd15c6f06e57609b95caf98ea561c99cda1b6d2a25" dependencies = [ "anyhow", "siphasher", @@ -4855,9 +4870,9 @@ dependencies = [ [[package]] name = "uniffi_pipeline" -version = "0.31.1" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "761ef74f6175e15603d0424cc5f98854c5baccfe7bf4ccb08e5816f9ab8af689" +checksum = "032739b3ec725576914c15899dedaf080163ced86b6934566c20ec2b20ce90ca" dependencies = [ "anyhow", "heck", @@ -4868,9 +4883,9 @@ dependencies = [ [[package]] name = "uniffi_udl" -version = "0.31.1" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68773ec0e1c067b6505a73bbf6a5782f31a7f9209333a0df97b87565c46bf370" +checksum = "fc0a1d0a0252ce1af9e8ce78ba67ac0d8937fb2bedaf10cbddd43d3614d06ec6" dependencies = [ "anyhow", "textwrap", @@ -4946,11 +4961,11 @@ dependencies = [ [[package]] name = "uuid" -version = "1.23.1" +version = "1.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" dependencies = [ - "getrandom 0.4.2", + "getrandom 0.4.3", "js-sys", "serde_core", "wasm-bindgen", @@ -5029,20 +5044,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.3+wasi-0.2.9" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ - "wit-bindgen 0.57.1", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen 0.51.0", + "wit-bindgen", ] [[package]] @@ -5053,9 +5059,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.121" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -5066,9 +5072,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.71" +version = "0.4.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" dependencies = [ "js-sys", "wasm-bindgen", @@ -5076,9 +5082,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.121" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5086,9 +5092,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.121" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", @@ -5099,52 +5105,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.121" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap 2.14.0", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags", - "hashbrown 0.15.5", - "indexmap 2.14.0", - "semver", -] - [[package]] name = "web-sys" -version = "0.3.98" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" dependencies = [ "js-sys", "wasm-bindgen", @@ -5532,9 +5504,9 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" [[package]] name = "wireguard-nt" @@ -5542,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", @@ -5552,100 +5524,12 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - [[package]] name = "wit-bindgen" version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap 2.14.0", - "prettyplease", - "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags", - "indexmap 2.14.0", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap 2.14.0", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - [[package]] name = "writeable" version = "0.6.3" @@ -5687,7 +5571,7 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" dependencies = [ - "asn1-rs 0.7.1", + "asn1-rs 0.7.2", "data-encoding", "der-parser 10.0.0", "lazy_static", @@ -5711,9 +5595,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -5734,18 +5618,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", @@ -5775,18 +5659,18 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 6b8e4cb6..4e9eb774 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 5e2e652c..8864e501 100644 --- a/example-config.toml +++ b/example-config.toml @@ -48,3 +48,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, };