diff options
33 files changed, 935 insertions, 747 deletions
@@ -3,21 +3,6 @@ version = 4 [[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] name = "aead" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -55,9 +40,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -114,15 +99,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.99" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "argon2" -version = "0.6.0-rc.1" +version = "0.6.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d911686206fdd816a61ed5226535997149b0fc7726e37fee46f407c9ff82ed87" +checksum = "e1a213fe583d472f454ae47407edc78848bebd950493528b1d4f7327a7dc335f" dependencies = [ "base64ct", "blake2", @@ -188,21 +173,6 @@ dependencies = [ ] [[package]] -name = "backtrace" -version = "0.3.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - -[[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -236,17 +206,17 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.9.4" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "blake2" -version = "0.11.0-rc.2" +version = "0.11.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edac47499deef695d9431bf241c75ea29f4cf3dcb78d39e19b31515e4ad3b08" +checksum = "679065eb2b85a078ace42411e657bef3a6afe93a40d1b9cb04e39ca303cc3f36" dependencies = [ - "digest 0.11.0-rc.1", + "digest 0.11.0-rc.4", ] [[package]] @@ -281,9 +251,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.37" +version = "1.2.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65193589c6404eb80b450d618eaf9a2cafaaafd57ecce47370519ef674a7bd44" +checksum = "35900b6c8d709fb1d854671ae27aeaa9eec2f8b01b364e1619a40da3e6fe2afe" dependencies = [ "find-msvc-tools", "jobserver", @@ -308,9 +278,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -340,6 +310,46 @@ dependencies = [ ] [[package]] +name = "clap" +version = "4.5.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" + +[[package]] name = "cmake" version = "0.1.54" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -408,9 +418,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.2.0-rc.4" +version = "0.2.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8235645834fbc6832939736ce2f2d08192652269e11010a6240f61b908a1c6" +checksum = "919bd05924682a5480aec713596b9e2aabed3a0a6022fab6847f85a99e5f190a" dependencies = [ "hybrid-array", ] @@ -436,13 +446,13 @@ dependencies = [ [[package]] name = "digest" -version = "0.11.0-rc.1" +version = "0.11.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a4aae35a0fcbe22ff1be50fe96df72002d5a4a6fb4aae9193cf2da0daa36da2" +checksum = "ea390c940e465846d64775e55e3115d5dc934acb953de6f6e6360bc232fe2bf7" dependencies = [ "block-buffer 0.11.0-rc.5", "const-oid", - "crypto-common 0.2.0-rc.4", + "crypto-common 0.2.0-rc.5", "subtle", ] @@ -493,7 +503,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18c1ddb9231d8554c2d6bdf4cfaabf0c59251658c68b6c95cd52dd0c513a912a" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "libm", "rand", "siphasher", @@ -507,9 +517,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "find-msvc-tools" -version = "0.1.1" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" [[package]] name = "fnv" @@ -631,31 +641,25 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi", - "wasi 0.14.5+wasi-0.2.4", + "wasip2", "wasm-bindgen", ] [[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - -[[package]] name = "glob" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -671,6 +675,7 @@ dependencies = [ "async-stream", "base64", "bytes", + "clap", "env_logger", "futures", "futures-util", @@ -702,7 +707,7 @@ dependencies = [ "serde_json", "serde_yml", "sha2", - "thiserror 2.0.16", + "thiserror 2.0.17", "tokio", "tokio-rustls", "tokio-util", @@ -759,9 +764,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.5" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" [[package]] name = "headers" @@ -788,6 +793,12 @@ dependencies = [ ] [[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -859,9 +870,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +checksum = "1744436df46f0bde35af3eda22aeaba453aada65d8f1c171cd8a5f59030bd69f" dependencies = [ "atomic-waker", "bytes", @@ -882,9 +893,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" dependencies = [ "bytes", "futures-core", @@ -897,9 +908,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.1" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206a8042aec68fa4a62e8d3f7aa4ceb508177d9324faf261e1959e495b7a1921" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", "hashbrown", @@ -937,17 +948,6 @@ dependencies = [ ] [[package]] -name = "io-uring" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" -dependencies = [ - "bitflags", - "cfg-if", - "libc", -] - -[[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1020,15 +1020,15 @@ version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "libc", ] [[package]] name = "js-sys" -version = "0.3.78" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" dependencies = [ "once_cell", "wasm-bindgen", @@ -1036,18 +1036,18 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.175" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libloading" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-targets 0.53.3", + "windows-link", ] [[package]] @@ -1068,11 +1068,10 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] @@ -1110,9 +1109,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "mime" @@ -1137,23 +1136,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", -] - -[[package]] name = "mio" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", + "wasi", + "windows-sys 0.61.2", ] [[package]] @@ -1177,15 +1167,6 @@ dependencies = [ ] [[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - -[[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1211,9 +1192,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -1221,25 +1202,25 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-link", ] [[package]] name = "password-hash" -version = "0.6.0-rc.1" +version = "0.6.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee14c44aa1c04c22c4d4532c4fa2cdd5b6d31c2514a5898530d889fc2fc2737" +checksum = "a7d47a2d1aee5a339aa6c740d9128211a8a3d2bdf06a13e01b3f8a0b5c49b9db" dependencies = [ "base64ct", - "rand_core 0.9.3", + "rand_core 0.10.0-rc-2", "subtle", ] @@ -1329,9 +1310,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] @@ -1351,7 +1332,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror 2.0.16", + "thiserror 2.0.17", "tokio", "tracing", "web-time", @@ -1365,7 +1346,7 @@ checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "bytes", "fastbloom", - "getrandom 0.3.3", + "getrandom 0.3.4", "lru-slab", "rand", "ring", @@ -1374,7 +1355,7 @@ dependencies = [ "rustls-pki-types", "rustls-platform-verifier", "slab", - "thiserror 2.0.16", + "thiserror 2.0.17", "tinyvec", "tracing", "web-time", @@ -1396,9 +1377,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -1444,10 +1425,16 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", ] [[package]] +name = "rand_core" +version = "0.10.0-rc-2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "104a23e4e8b77312a823b6b5613edbac78397e2f34320bc7ac4277013ec4478e" + +[[package]] name = "rand_distr" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1459,18 +1446,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.11.2" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -1480,9 +1467,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.10" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -1491,9 +1478,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "ring" @@ -1510,12 +1497,6 @@ dependencies = [ ] [[package]] -name = "rustc-demangle" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" - -[[package]] name = "rustc-hash" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1523,9 +1504,9 @@ checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustls" -version = "0.23.31" +version = "0.23.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" +checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" dependencies = [ "aws-lc-rs", "log", @@ -1560,9 +1541,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" dependencies = [ "web-time", "zeroize", @@ -1597,9 +1578,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.5" +version = "0.103.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a37813727b78798e53c2bec3f5e8fe12a6d6f8389bf9ca7802add4c9905ad8" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ "aws-lc-rs", "ring", @@ -1608,6 +1589,12 @@ dependencies = [ ] [[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1628,7 +1615,7 @@ version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -1662,9 +1649,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.220" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceecad4c782e936ac90ecfd6b56532322e3262b14320abf30ce89a92ffdbfe22" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", @@ -1672,18 +1659,18 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.220" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddba47394f3b862d6ff6efdbd26ca4673e3566a307880a0ffb98f274bbe0ec32" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.220" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60e1f3b1761e96def5ec6d04a6e7421c0404fa3cf5c0155f1e2848fae3d8cc08" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -1692,14 +1679,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.143" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", + "serde_core", ] [[package]] @@ -1730,13 +1718,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.11.0-rc.2" +version = "0.11.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1e3878ab0f98e35b2df35fe53201d088299b41a6bb63e3e34dada2ac4abd924" +checksum = "19d43dc0354d88b791216bb5c1bfbb60c0814460cc653ae0ebd71f286d0bd927" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.11.0-rc.1", + "digest 0.11.0-rc.4", ] [[package]] @@ -1774,15 +1762,21 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1790,9 +1784,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.106" +version = "2.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" dependencies = [ "proc-macro2", "quote", @@ -1810,11 +1804,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.16", + "thiserror-impl 2.0.17", ] [[package]] @@ -1830,9 +1824,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -1856,29 +1850,26 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.47.1" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ - "backtrace", "bytes", - "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", - "slab", "socket2", "tokio-macros", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", @@ -1887,9 +1878,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ "rustls", "tokio", @@ -1897,9 +1888,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.16" +version = "0.7.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" dependencies = [ "bytes", "futures-core", @@ -1936,9 +1927,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicase" @@ -1948,9 +1939,9 @@ checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "universal-hash" @@ -2016,54 +2007,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.14.5+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4494f6290a82f5fe584817a676a34b9d6763e8d9d18204009fb31dceca98fd4" -dependencies = [ - "wasip2", -] - -[[package]] name = "wasip2" -version = "1.0.0+wasi-0.2.4" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03fa2761397e5bd52002cd7e73110c71af2109aca4e521a9f40473fe685b0a24" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.101" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.101" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] name = "wasm-bindgen-macro" -version = "0.2.101" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2071,22 +2040,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.101" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.101" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" dependencies = [ "unicode-ident", ] @@ -2112,9 +2081,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" +checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" dependencies = [ "rustls-pki-types", ] @@ -2125,20 +2094,14 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.0", + "windows-sys 0.60.2", ] [[package]] name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-link" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" @@ -2173,16 +2136,16 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.3", + "windows-targets 0.53.5", ] [[package]] name = "windows-sys" -version = "0.61.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.0", + "windows-link", ] [[package]] @@ -2218,19 +2181,19 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.3" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.1.3", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -2247,9 +2210,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -2265,9 +2228,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -2283,9 +2246,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" @@ -2295,9 +2258,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -2313,9 +2276,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -2331,9 +2294,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -2349,9 +2312,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -2367,15 +2330,15 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "wit-bindgen" -version = "0.45.1" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "zerocopy" @@ -2399,6 +2362,6 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" @@ -5,11 +5,11 @@ edition = "2021" [dependencies] # HTTP -hyper = { version = "1.7.0", features = ["server", "client", "http1", "http2"] } +hyper = { version = "1.8.0", features = ["server", "client", "http1", "http2"] } quinn = "0.11.9" h3 = "0.0.8" h3-quinn = "0.0.10" -hyper-util = { version = "0.1.16", features = [ +hyper-util = { version = "0.1.17", features = [ "server-auto", "server", "http1", @@ -26,25 +26,26 @@ hex = "0.4.3" # TLS rustls-pemfile = "2.2.0" -rustls = { version = "0.23.31", default-features = false, features = ["ring"] } -tokio-rustls = "0.26.2" -rustls-webpki = "0.103.5" -webpki-roots = "1.0.2" +rustls = { version = "0.23.35", default-features = false, features = ["ring"] } +tokio-rustls = "0.26.4" +rustls-webpki = "0.103.8" +webpki-roots = "1.0.4" ring = "0.17.14" # Async stuff -tokio = { version = "1.47.1", features = ["full"] } -tokio-util = { version = "0.7.16", features = ["io"] } +tokio = { version = "1.48.0", features = ["full"] } +tokio-util = { version = "0.7.17", features = ["io"] } futures-util = "0.3.31" futures = "0.3.31" async-stream = "0.3.6" pin-project = "1.1.10" # Config -serde = { version = "1.0.220", features = ["derive"] } +serde = { version = "1.0.228", features = ["derive"] } serde_yml = "0.0.12" -serde_json = "1.0.143" +serde_json = "1.0.145" inotify = "0.11.0" +clap = { version = "4.5.51", features = ["derive"] } # Logging env_logger = "0.11.8" @@ -57,14 +58,14 @@ mime_guess = "2.0.5" # Crypto for authentificating clients aes-gcm-siv = "0.11.1" -argon2 = "0.6.0-rc.1" -sha2 = "0.11.0-rc.2" +argon2 = "0.6.0-rc.2" +sha2 = "0.11.0-rc.3" rand = "0.9.2" rand_distr = "0.5.1" # Other helpers and stuff bytes = "1.10.1" -anyhow = "1.0.99" -thiserror = "2.0.16" -regex = "1.11.2" +anyhow = "1.0.100" +thiserror = "2.0.17" +regex = "1.12.2" users = "0.11.0" diff --git a/src/certs.rs b/src/certs.rs index e75d5e0..950bc88 100644 --- a/src/certs.rs +++ b/src/certs.rs @@ -7,14 +7,13 @@ use anyhow::{anyhow, Context, Result}; use log::debug; use rustls::{ crypto::CryptoProvider, - pki_types::{CertificateDer, PrivateKeyDer}, server::{ClientHello, ResolvesServerCert}, sign::CertifiedKey, }; use std::{ collections::HashMap, - fs::File, - io::BufReader, + fs::read_to_string, + io::Cursor, path::{Path, PathBuf}, sync::Arc, }; @@ -28,6 +27,12 @@ pub struct CertPool { fallback: Option<Arc<CertifiedKey>>, } +pub struct CertPackage { + pub name: String, + pub cert: String, + pub key: String, +} + impl Default for CertPool { fn default() -> Self { Self { @@ -39,59 +44,68 @@ impl Default for CertPool { } } -impl CertPool { - pub fn load(roots: &[PathBuf], fallback: Option<PathBuf>) -> Result<Self> { - let mut s = Self::default(); +impl CertPackage { + fn create_from(path: &Path) -> Result<Option<CertPackage>> { + let keypath = path.join("privkey.pem"); + let certpath = if path.join("fullchain.pem").exists() { + path.join("fullchain.pem") + } else { + path.join("cert.pem") + }; + Ok(if keypath.exists() && certpath.exists() { + debug!("creating cert package at {path:?}"); + Some(CertPackage { + name: path.to_string_lossy().to_string(), + cert: read_to_string(certpath)?, + key: read_to_string(keypath)?, + }) + } else { + None + }) + } + + pub fn create_from_recursive(roots: &[PathBuf]) -> Result<Vec<CertPackage>> { + let mut out = Vec::new(); for r in roots { - s.load_recursive(r)?; - } - if let Some(path) = fallback { - let keypath = path.join("privkey.pem"); - let certpath = if path.join("fullchain.pem").exists() { - path.join("fullchain.pem") - } else { - path.join("cert.pem") - }; - let certs = load_certs(&certpath)?; - let key = load_private_key(&keypath)?; - let skey = s.provider.key_provider.load_private_key(key)?; - let ck = CertifiedKey::new(certs.clone(), skey.clone()); - s.fallback = Some(Arc::new(ck)) + Self::create_package_recursion(r, &mut out)?; } - Ok(s) + Ok(out) } - fn load_recursive(&mut self, path: &Path) -> Result<()> { + fn create_package_recursion(path: &Path, out: &mut Vec<CertPackage>) -> Result<()> { if !path.is_dir() { return Ok(()); } for e in path.read_dir()? { let p = e?.path(); if p.is_dir() { - self.load_recursive(&p)?; + Self::create_package_recursion(&p, out)?; } } - let keypath = path.join("privkey.pem"); - let certpath = if path.join("fullchain.pem").exists() { - path.join("fullchain.pem") - } else { - path.join("cert.pem") - }; - if keypath.exists() && certpath.exists() { - let certs = load_certs(&certpath)?; - let key = load_private_key(&keypath)?; - let skey = self.provider.key_provider.load_private_key(key)?; - for c in &certs { - let eec = EndEntityCert::try_from(c).unwrap(); - for name in eec.valid_dns_names() { - let ck = CertifiedKey::new(certs.clone(), skey.clone()); - if let Some(name) = name.strip_prefix("*.") { - debug!("loaded wildcard key for {name:?}"); - self.wildcards.insert(name.to_owned(), Arc::new(ck)); - } else { - debug!("loaded key for {name:?}"); - self.domains.insert(name.to_owned(), Arc::new(ck)); - } + out.extend(Self::create_from(path)?); + Ok(()) + } +} + +impl CertPool { + pub fn add_package(&mut self, cp: CertPackage) -> Result<()> { + let certs = rustls_pemfile::certs(&mut Cursor::new(cp.cert)) + .try_collect::<Vec<_>>() + .context("parsing tls certs")?; + let key = rustls_pemfile::private_key(&mut Cursor::new(cp.key)) + .context("parsing tls private key")? + .ok_or(anyhow!("private key missing"))?; + let skey = self.provider.key_provider.load_private_key(key)?; + for c in &certs { + let eec = EndEntityCert::try_from(c).unwrap(); + for name in eec.valid_dns_names() { + let ck = CertifiedKey::new(certs.clone(), skey.clone()); + if let Some(name) = name.strip_prefix("*.") { + debug!("loaded wildcard key for {name:?}"); + self.wildcards.insert(name.to_owned(), Arc::new(ck)); + } else { + debug!("loaded key for {name:?}"); + self.domains.insert(name.to_owned(), Arc::new(ck)); } } } @@ -116,16 +130,3 @@ impl ResolvesServerCert for CertPool { ) } } - -fn load_certs(path: &Path) -> Result<Vec<CertificateDer<'static>>> { - let mut reader = BufReader::new(File::open(path).context("reading tls certs")?); - let certs = rustls_pemfile::certs(&mut reader) - .try_collect::<Vec<_>>() - .context("parsing tls certs")?; - Ok(certs) -} -fn load_private_key(path: &Path) -> Result<PrivateKeyDer<'static>> { - let mut reader = BufReader::new(File::open(path).context("reading tls private key")?); - let keys = rustls_pemfile::private_key(&mut reader).context("parsing tls private key")?; - keys.ok_or(anyhow!("no private key found")) -} diff --git a/src/config.rs b/src/config.rs index 112e86f..13a8019 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,27 +4,19 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ use crate::{ - modules::{Node, NodeKind}, - State, + certs::CertPackage, + deser_helpers::{seq_or_not, string_or_seq}, + modules::{Node, MODULES}, }; -use anyhow::Context; -use inotify::{Inotify, WatchMask}; -use log::{error, info}; +use anyhow::{anyhow, bail, Context, Result}; use rand::random; -use serde::{ - de::{value, Error, SeqAccess, Visitor}, - Deserialize, Deserializer, Serialize, -}; -use serde_yml::value::TaggedValue; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde_yml::Value; use std::{ - collections::BTreeMap, - fmt, fs::read_to_string, - marker::PhantomData, net::SocketAddr, - ops::Deref, path::{Path, PathBuf}, - sync::{Arc, RwLock}, + sync::Arc, }; #[derive(Deserialize)] @@ -39,7 +31,7 @@ pub struct Config { pub limits: Limits, #[serde(default)] pub source_ip_from_header: bool, - pub handler: DynNode, + pub handler: DynNodeConfig, #[serde(default)] pub disable_server_header: bool, } @@ -81,124 +73,70 @@ pub struct HttpsConfig { pub disable_h1: bool, } -// try deser Vec<T> but fall back to deser T and putting that in Vec -pub fn seq_or_not<'de, D, V: Deserialize<'de>>(des: D) -> Result<Vec<V>, D::Error> -where - D: Deserializer<'de>, -{ - struct SeqOrNot<V>(PhantomData<V>); - impl<'de, V: Deserialize<'de>> Visitor<'de> for SeqOrNot<V> { - type Value = Vec<V>; +pub struct ConfigPackage { + pub config: String, + pub certificates: Vec<CertPackage>, // name, fullchain, key +} - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a sequence or not a sequence") - } - fn visit_enum<A>(self, data: A) -> Result<Self::Value, A::Error> - where - A: serde::de::EnumAccess<'de>, - { - Ok(vec![V::deserialize(value::EnumAccessDeserializer::new( - data, - ))?]) - } - fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error> - where - A: serde::de::MapAccess<'de>, - { - Ok(vec![V::deserialize(value::MapAccessDeserializer::new( - map, - ))?]) - } - fn visit_str<E>(self, val: &str) -> Result<Vec<V>, E> - where - E: Error, - { - Ok(vec![V::deserialize(value::StrDeserializer::new(val))?]) - } +pub type DynNode = Arc<dyn Node>; - fn visit_seq<A>(self, val: A) -> Result<Vec<V>, A::Error> - where - A: SeqAccess<'de>, - { - Vec::<V>::deserialize(value::SeqAccessDeserializer::new(val)) - } +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct DynNodeConfig(serde_yml::Value); +impl DynNodeConfig { + pub fn parse<T: DeserializeOwned>(self) -> serde_yml::Result<T> { + serde_yml::from_value(self.0) } - des.deserialize_any(SeqOrNot::<V>(PhantomData)) } -// fall back to expecting a single string and putting that in a 1-length vector -fn string_or_seq<'de, D>(des: D) -> Result<Vec<SocketAddr>, D::Error> -where - D: Deserializer<'de>, -{ - struct StringOrList; - impl<'de> Visitor<'de> for StringOrList { - type Value = Vec<SocketAddr>; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("sequence or list") - } - - fn visit_str<E>(self, val: &str) -> Result<Vec<SocketAddr>, E> - where - E: Error, - { - let addr = SocketAddr::deserialize(value::StrDeserializer::new(val))?; - Ok(vec![addr]) - } - - fn visit_seq<A>(self, val: A) -> Result<Vec<SocketAddr>, A::Error> - where - A: SeqAccess<'de>, - { - Vec::<SocketAddr>::deserialize(value::SeqAccessDeserializer::new(val)) - } +pub struct InstContext { + config: DynNodeConfig, +} +impl InstContext { + pub fn config(&self) -> DynNodeConfig { + self.config.clone() + } + pub fn instanciate_child(&self, config: DynNodeConfig) -> Result<DynNode> { + instanciate_handler(config) } - - des.deserialize_any(StringOrList) } -pub static NODE_KINDS: RwLock<BTreeMap<String, &'static dyn NodeKind>> = - RwLock::new(BTreeMap::new()); - -#[derive(Clone)] -pub struct DynNode(Arc<dyn Node>); - -impl<'de> Deserialize<'de> for DynNode { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: Deserializer<'de>, - { - let tv = TaggedValue::deserialize(deserializer)?; - let s = tv.tag.to_string(); - let s = s.strip_prefix("!").unwrap_or(s.as_str()); - let inst = NODE_KINDS - .read() - .unwrap() - .get(s) - .ok_or(serde::de::Error::unknown_variant(s, &[]))? - .instanciate(tv.value) - .map_err(|e| { - let x = format!("instanciating module {s:?}: {e:?}"); - serde::de::Error::custom(e.context(x)) - })?; - - Ok(Self(inst)) +impl Config { + pub fn new(raw: &str) -> Result<Self> { + Ok(serde_yml::from_str(&raw).context("parsing config YAML")?) } -} -impl Deref for DynNode { - type Target = dyn Node; - fn deref(&self) -> &Self::Target { - self.0.as_ref() + pub fn create_handler(&self) -> Result<DynNode> { + instanciate_handler(self.handler.clone()) } } -impl Config { - pub fn load(path: &Path) -> anyhow::Result<Config> { - info!("loading config from {path:?}"); - let raw = read_to_string(path).context("reading config file")?; - let config: Config = serde_yml::from_str(&raw).context("during parsing")?; - Ok(config) +fn instanciate_handler(config: DynNodeConfig) -> Result<DynNode> { + let Value::Tagged(tv) = config.0 else { + bail!("handler is not a tagged value") + }; + let kind = tv.tag.string; + let cons = MODULES + .iter() + .find(|m| m.name() == kind) + .ok_or(anyhow!("unknown module name {kind:?}"))?; + + cons.instanciate(InstContext { + config: DynNodeConfig(tv.value), + }) +} + +impl ConfigPackage { + pub fn new(entry: &Path) -> Result<Self> { + let config = read_to_string(entry).context("reading main config file")?; + let config_parsed: Config = serde_yml::from_str(&config).context("parsing config YAML")?; + let certificates = config_parsed + .https + .map(|h| CertPackage::create_from_recursive(&h.cert_path)) + .transpose()? + .unwrap_or_default(); + Ok(Self { + config, + certificates, + }) } } @@ -213,36 +151,36 @@ impl Default for Limits { } } -pub fn setup_file_watch(config_path: PathBuf, state: Arc<State>) { - std::thread::spawn(move || { - let mut inotify = Inotify::init().unwrap(); - inotify - .watches() - .add( - config_path.parent().unwrap(), - WatchMask::MODIFY | WatchMask::CREATE | WatchMask::DELETE | WatchMask::MOVED_TO, - ) - .unwrap(); - let mut buffer = [0u8; 4096]; - loop { - let events = inotify - .read_events_blocking(&mut buffer) - .expect("Failed to read inotify events"); +// pub fn setup_file_watch(config_path: PathBuf, state: Arc<State>) { +// std::thread::spawn(move || { +// let mut inotify = Inotify::init().unwrap(); +// inotify +// .watches() +// .add( +// config_path.parent().unwrap(), +// WatchMask::MODIFY | WatchMask::CREATE | WatchMask::DELETE | WatchMask::MOVED_TO, +// ) +// .unwrap(); +// let mut buffer = [0u8; 4096]; +// loop { +// let events = inotify +// .read_events_blocking(&mut buffer) +// .expect("Failed to read inotify events"); - for event in events { - if event.name == config_path.file_name() { - if config_path.metadata().map(|m| m.len()).unwrap_or_default() == 0 { - continue; - } - match Config::load(&config_path) { - Ok(conf) => { - let mut r = state.config.blocking_write(); - *r = Arc::new(conf) - } - Err(e) => error!("config has errors: {e:?}"), - } - } - } - } - }); -} +// for event in events { +// if event.name == config_path.file_name() { +// if config_path.metadata().map(|m| m.len()).unwrap_or_default() == 0 { +// continue; +// } +// match Config::load(&config_path) { +// Ok(conf) => { +// let mut r = state.config.blocking_write(); +// *r = Arc::new(conf) +// } +// Err(e) => error!("config has errors: {e:?}"), +// } +// } +// } +// } +// }); +// } diff --git a/src/deser_helpers.rs b/src/deser_helpers.rs new file mode 100644 index 0000000..693d67f --- /dev/null +++ b/src/deser_helpers.rs @@ -0,0 +1,87 @@ +/* + This file is part of gnix (https://codeberg.org/metamuffin/gnix) + which is licensed under the GNU Affero General Public License (version 3); see /COPYING. + Copyright (C) 2025 metamuffin <metamuffin.org> +*/ +use serde::{ + de::{value, Error, SeqAccess, Visitor}, + Deserialize, Deserializer, +}; +use std::{fmt, marker::PhantomData, net::SocketAddr}; + +// try deser Vec<T> but fall back to deser T and putting that in Vec +pub fn seq_or_not<'de, D, V: Deserialize<'de>>(des: D) -> Result<Vec<V>, D::Error> +where + D: Deserializer<'de>, +{ + struct SeqOrNot<V>(PhantomData<V>); + impl<'de, V: Deserialize<'de>> Visitor<'de> for SeqOrNot<V> { + type Value = Vec<V>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a sequence or not a sequence") + } + fn visit_enum<A>(self, data: A) -> Result<Self::Value, A::Error> + where + A: serde::de::EnumAccess<'de>, + { + Ok(vec![V::deserialize(value::EnumAccessDeserializer::new( + data, + ))?]) + } + fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error> + where + A: serde::de::MapAccess<'de>, + { + Ok(vec![V::deserialize(value::MapAccessDeserializer::new( + map, + ))?]) + } + fn visit_str<E>(self, val: &str) -> Result<Vec<V>, E> + where + E: Error, + { + Ok(vec![V::deserialize(value::StrDeserializer::new(val))?]) + } + + fn visit_seq<A>(self, val: A) -> Result<Vec<V>, A::Error> + where + A: SeqAccess<'de>, + { + Vec::<V>::deserialize(value::SeqAccessDeserializer::new(val)) + } + } + des.deserialize_any(SeqOrNot::<V>(PhantomData)) +} + +// fall back to expecting a single string and putting that in a 1-length vector +pub fn string_or_seq<'de, D>(des: D) -> Result<Vec<SocketAddr>, D::Error> +where + D: Deserializer<'de>, +{ + struct StringOrList; + impl<'de> Visitor<'de> for StringOrList { + type Value = Vec<SocketAddr>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("sequence or list") + } + + fn visit_str<E>(self, val: &str) -> Result<Vec<SocketAddr>, E> + where + E: Error, + { + let addr = SocketAddr::deserialize(value::StrDeserializer::new(val))?; + Ok(vec![addr]) + } + + fn visit_seq<A>(self, val: A) -> Result<Vec<SocketAddr>, A::Error> + where + A: SeqAccess<'de>, + { + Vec::<SocketAddr>::deserialize(value::SeqAccessDeserializer::new(val)) + } + } + + des.deserialize_any(StringOrList) +} diff --git a/src/generation.rs b/src/generation.rs new file mode 100644 index 0000000..73874c5 --- /dev/null +++ b/src/generation.rs @@ -0,0 +1,71 @@ +/* + This file is part of gnix (https://codeberg.org/metamuffin/gnix) + which is licensed under the GNU Affero General Public License (version 3); see /COPYING. + Copyright (C) 2025 metamuffin <metamuffin.org> +*/ +use crate::{ + certs::{CertPackage, CertPool}, + config::{Config, ConfigPackage, DynNode}, +}; +use anyhow::{anyhow, Context, Result}; +use quinn::crypto::rustls::QuicServerConfig; +use std::sync::Arc; +use tokio_rustls::TlsAcceptor; + +pub struct Generation { + pub config: Config, + pub handler: DynNode, + pub tls_acceptor: Option<Arc<TlsAcceptor>>, + pub quic_config: Option<quinn::ServerConfig>, +} + +impl Generation { + pub fn new(cp: ConfigPackage) -> Result<Self> { + let config = Config::new(&cp.config)?; + let handler = config.create_handler()?; + let (tls_acceptor, quic_config) = setup_tls(&config, cp.certificates)?; + Ok(Generation { + config, + handler, + tls_acceptor, + quic_config, + }) + } +} + +fn setup_tls( + config: &Config, + certs: Vec<CertPackage>, +) -> Result<(Option<Arc<TlsAcceptor>>, Option<quinn::ServerConfig>)> { + let Some(https_config) = &config.https else { + return Ok((None, None)); + }; + let mut resolver = CertPool::default(); + for cert in certs { + let err = anyhow!("loading cert {:?}", cert.name); + resolver.add_package(cert).context(err)?; + } + let resolver = Arc::new(resolver); + + let mut h12_config = rustls::ServerConfig::builder() + .with_no_client_auth() + .with_cert_resolver(resolver.clone()); + let mut h3_config = rustls::ServerConfig::builder() + .with_no_client_auth() + .with_cert_resolver(resolver.clone()); + + if !https_config.disable_h1 { + h12_config.alpn_protocols.push(b"http/1.1".to_vec()); + } + if !https_config.disable_h2 { + h12_config.alpn_protocols.push(b"h2".to_vec()); + } + h3_config.alpn_protocols.push(b"h3".to_vec()); + + let h3_config = Arc::new(QuicServerConfig::try_from(h3_config)?); + let quic_config = quinn::ServerConfig::with_crypto(h3_config.clone()); + + let tls_acceptor = Arc::new(TlsAcceptor::from(Arc::new(h12_config))); + + Ok((Some(tls_acceptor), Some(quic_config))) +} diff --git a/src/main.rs b/src/main.rs index 00e4e5b..3787527 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,22 +7,23 @@ try_trait_v2, slice_split_once, iterator_try_collect, - path_add_extension, never_type, string_from_utf8_lossy_owned )] pub mod certs; pub mod config; +pub mod deser_helpers; pub mod error; +pub mod generation; pub mod h3_support; pub mod modules; +use crate::{config::ConfigPackage, generation::Generation}; use aes_gcm_siv::{aead::generic_array::GenericArray, Aes256GcmSiv, KeyInit}; use anyhow::{Context, Result}; use bytes::Bytes; -use certs::CertPool; -use config::{setup_file_watch, Config, NODE_KINDS}; +use clap::Parser; use error::ServiceError; use futures::future::try_join_all; use h3::server::RequestStream; @@ -39,10 +40,8 @@ use hyper::{ }; use hyper_util::rt::{TokioExecutor, TokioIo}; use log::{debug, error, info, warn, LevelFilter}; -use modules::{NodeContext, MODULES}; -use quinn::crypto::rustls::QuicServerConfig; +use modules::NodeContext; use std::{ - collections::HashMap, net::{IpAddr, SocketAddr}, path::PathBuf, process::exit, @@ -50,26 +49,32 @@ use std::{ sync::Arc, }; use tokio::{ - fs::File, - io::BufWriter, net::TcpListener, signal::ctrl_c, spawn, sync::{RwLock, Semaphore}, }; -use tokio_rustls::TlsAcceptor; pub struct State { pub crypto_key: Aes256GcmSiv, - pub config: RwLock<Arc<Config>>, - pub access_logs: RwLock<HashMap<String, BufWriter<File>>>, + pub generation: RwLock<Arc<Generation>>, pub l_incoming: Semaphore, pub l_outgoing: Semaphore, pub l_incoming_h3: Semaphore, } +#[derive(Parser)] +struct Args { + #[arg(long)] + setuid: Option<String>, + /// YAML config file + config: PathBuf, +} + #[tokio::main] async fn main() -> anyhow::Result<()> { + let args = Args::parse(); + env_logger::Builder::new() .filter_level(LevelFilter::Info) .parse_env("LOG") @@ -79,39 +84,21 @@ async fn main() -> anyhow::Result<()> { .install_default() .unwrap(); - NODE_KINDS - .write() - .unwrap() - .extend(MODULES.iter().map(|m| (m.name().to_owned(), *m))); - - let Some(config_path) = std::env::args().nth(1) else { - eprintln!("error: first argument is expected to be the configuration file"); - exit(1) - }; - let config_path = PathBuf::from_str(&config_path) - .unwrap() - .canonicalize() - .unwrap(); - - let config = match Config::load(&config_path) { - Ok(c) => c, - Err(e) => { - eprintln!("error {e:?}"); - exit(1); - } - }; + let config_package = ConfigPackage::new(&args.config)?; + let generation = Generation::new(config_package)?; let state = Arc::new(State { - crypto_key: aes_gcm_siv::Aes256GcmSiv::new(GenericArray::from_slice(&config.private_key)), - l_incoming: Semaphore::new(config.limits.max_incoming_connections), - l_incoming_h3: Semaphore::new(config.limits.max_incoming_connections_h3), - l_outgoing: Semaphore::new(config.limits.max_outgoing_connections), - config: RwLock::new(Arc::new(config)), - access_logs: Default::default(), + crypto_key: aes_gcm_siv::Aes256GcmSiv::new(GenericArray::from_slice( + &generation.config.private_key, + )), + l_incoming: Semaphore::new(generation.config.limits.max_incoming_connections), + l_incoming_h3: Semaphore::new(generation.config.limits.max_incoming_connections_h3), + l_outgoing: Semaphore::new(generation.config.limits.max_outgoing_connections), + generation: RwLock::new(Arc::new(generation)), }); - if state.config.read().await.watch_config { - setup_file_watch(config_path, state.clone()); - } + // if state.config.read().await.watch_config { + // setup_file_watch(config_path, state.clone()); + // } { let state = state.clone(); @@ -146,18 +133,27 @@ async fn main() -> anyhow::Result<()> { } async fn serve_http(state: Arc<State>) -> Result<()> { - let config = state.config.read().await.clone(); - let http_config = match &config.http { - Some(n) => n, - None => return Ok(()), + let Some(bind_addrs) = state + .generation + .read() + .await + .config + .http + .as_ref() + .map(|h| h.bind.clone()) + else { + return Ok(()); // http disabled }; - let listen_futs: Result<Vec<()>> = try_join_all(http_config.bind.iter().map(|e| async { - let l = TcpListener::bind(*e).await?; - let listen_addr = l.local_addr()?; - info!("HTTP listener bound to {}/tcp", l.local_addr().unwrap()); + let listen_futs: Result<Vec<()>> = try_join_all(bind_addrs.iter().map(|e| async { + let listener = TcpListener::bind(*e).await?; + let listen_addr = listener.local_addr()?; + info!( + "HTTP listener bound to {}/tcp", + listener.local_addr().unwrap() + ); loop { - let (stream, addr) = l.accept().await.context("accepting connection")?; + let (stream, addr) = listener.accept().await.context("accepting connection")?; debug!("connection from {addr}"); let stream = TokioIo::new(stream); let state = state.clone(); @@ -172,36 +168,33 @@ async fn serve_http(state: Arc<State>) -> Result<()> { } async fn serve_https(state: Arc<State>) -> Result<()> { - let config = state.config.read().await.clone(); - let https_config = match &config.https { - Some(n) => n, - None => return Ok(()), - }; - let tls_config = { - let certs = CertPool::load(&https_config.cert_path, https_config.cert_fallback.clone())?; - let mut cfg = rustls::ServerConfig::builder() - .with_no_client_auth() - .with_cert_resolver(Arc::new(certs)); - if !https_config.disable_h1 { - cfg.alpn_protocols.push(b"http/1.1".to_vec()); - } - if !https_config.disable_h2 { - cfg.alpn_protocols.push(b"h2".to_vec()); - } - Arc::new(cfg) + let Some(bind_addrs) = state + .generation + .read() + .await + .config + .https + .as_ref() + .map(|h| h.bind.clone()) + else { + return Ok(()); // https disabled }; - let tls_acceptor = Arc::new(TlsAcceptor::from(tls_config)); - let listen_futs: Result<Vec<()>> = try_join_all(https_config.bind.iter().map(|e| async { - let l = TcpListener::bind(*e).await?; - let listen_addr = l.local_addr()?; + + let listen_futs: Result<Vec<()>> = try_join_all(bind_addrs.iter().map(|e| async { + let listener = TcpListener::bind(*e).await?; + let listen_addr = listener.local_addr()?; info!( "HTTPS (h1+h2) listener bound to {}/tcp", - l.local_addr().unwrap() + listener.local_addr().unwrap() ); loop { - let (stream, addr) = l.accept().await.context("accepting connection")?; + let (stream, addr) = listener.accept().await.context("accepting connection")?; + let generation = state.generation.read().await.clone(); + let Some(tls_acceptor) = generation.tls_acceptor.clone() else { + warn!("HTTPS (h1+h2) listener is missing TLS configuration"); + break; + }; let state = state.clone(); - let tls_acceptor = tls_acceptor.clone(); tokio::task::spawn(async move { debug!("connection from {addr}"); match tls_acceptor.accept(stream).await { @@ -212,6 +205,11 @@ async fn serve_https(state: Arc<State>) -> Result<()> { }; }); } + info!( + "HTTPS (h1+h2) listener for {} shutting down", + listener.local_addr().unwrap() + ); + Ok(()) })) .await; listen_futs?; @@ -250,8 +248,8 @@ pub async fn serve_stream<T: Unpin + Send + 'static + hyper::rt::Read + hyper::r } async fn serve_h3(state: Arc<State>) -> Result<()> { - let config = state.config.read().await.clone(); - let https_config = match &config.https { + let generation = state.generation.read().await.clone(); + let https_config = match &generation.config.https { Some(n) => n, None => return Ok(()), }; @@ -259,22 +257,19 @@ async fn serve_h3(state: Arc<State>) -> Result<()> { return Ok(()); } let bind_addrs = https_config.bind.clone(); - let certs = CertPool::load(&https_config.cert_path, https_config.cert_fallback.clone())?; - let mut cfg = rustls::ServerConfig::builder() - .with_no_client_auth() - .with_cert_resolver(Arc::new(certs)); - cfg.alpn_protocols = vec![b"h3".to_vec()]; - let cfg = Arc::new(QuicServerConfig::try_from(cfg)?); try_join_all(bind_addrs.iter().map(|listen_addr| async { - let cfg = quinn::ServerConfig::with_crypto(cfg.clone()); - let endpoint = quinn::Endpoint::server(cfg, *listen_addr)?; + let Some(quic_config) = generation.quic_config.clone() else { + warn!("HTTPS (h3) listener is missing TLS configuration"); + return Ok(()); + }; + let endpoint = quinn::Endpoint::server(quic_config, *listen_addr)?; let listen_addr = *listen_addr; info!("HTTPS (h3) listener bound to {listen_addr}/udp"); while let Some(conn) = endpoint.accept().await { let state = state.clone(); - let config = config.clone(); - tokio::spawn(serve_h3_stream(conn, config, state, listen_addr)); + let generation = generation.clone(); + tokio::spawn(serve_h3_stream(conn, generation, state, listen_addr)); } Ok::<_, anyhow::Error>(()) })) @@ -284,7 +279,7 @@ async fn serve_h3(state: Arc<State>) -> Result<()> { async fn serve_h3_stream( conn: quinn::Incoming, - config: Arc<Config>, + generation: Arc<Generation>, state: Arc<State>, listen_addr: SocketAddr, ) { @@ -308,7 +303,7 @@ async fn serve_h3_stream( Err(e) => return warn!("h3 accept failed {e}"), }; debug!("h3 stream from {addr}"); - let max_par_requests = Semaphore::new(config.limits.max_requests_per_connnection); + let max_par_requests = Semaphore::new(generation.config.limits.max_requests_per_connnection); loop { match conn.accept().await { Ok(Some(x)) => { @@ -405,7 +400,7 @@ async fn service( secure: bool, listen_addr: SocketAddr, ) -> Result<hyper::Response<BoxBody<bytes::Bytes, ServiceError>>, ServiceError> { - let config = state.config.read().await.clone(); + let generation = state.generation.read().await.clone(); // move uri authority used in HTTP/2 to Host header field { let uri = request.uri_mut(); @@ -420,7 +415,7 @@ async fn service( } } - if config.source_ip_from_header { + if generation.config.source_ip_from_header { if let Some(x) = request.headers_mut().remove("x-real-ip") { addr = SocketAddr::new( IpAddr::from_str(x.to_str()?).map_err(|_| ServiceError::InvalidHeader)?, @@ -443,9 +438,9 @@ async fn service( secure, listen_addr, }; - let mut resp = config.handler.handle(&mut context, request).await?; + let mut resp = generation.handler.handle(&mut context, request).await?; - if !config.disable_server_header { + if !generation.config.disable_server_header { let server_header = resp.headers().get(SERVER).cloned(); resp.headers_mut().insert( SERVER, diff --git a/src/modules/accesslog.rs b/src/modules/accesslog.rs index c41d4fb..5b91949 100644 --- a/src/modules/accesslog.rs +++ b/src/modules/accesslog.rs @@ -4,7 +4,12 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ use super::{Node, NodeContext, NodeKind, NodeRequest, NodeResponse}; -use crate::{config::DynNode, error::ServiceError}; +use crate::{ + config::{DynNode, DynNodeConfig}, + error::ServiceError, + modules::InstContext, +}; +use anyhow::Result; use futures::Future; use log::error; use serde::Deserialize; @@ -24,11 +29,12 @@ struct AccessLogConfig { flush: bool, #[serde(default)] reject_on_fail: bool, - next: DynNode, + next: DynNodeConfig, } struct AccessLog { config: AccessLogConfig, + next: DynNode, file: RwLock<Option<BufWriter<File>>>, } @@ -36,9 +42,11 @@ impl NodeKind for AccessLogKind { fn name(&self) -> &'static str { "access_log" } - fn instanciate(&self, config: serde_yml::Value) -> anyhow::Result<Arc<dyn Node>> { + fn instanciate(&self, ic: InstContext) -> Result<DynNode> { + let config: AccessLogConfig = ic.config().parse()?; Ok(Arc::new(AccessLog { - config: serde_yml::from_value::<AccessLogConfig>(config)?, + next: ic.instanciate_child(config.next.clone())?, + config, file: Default::default(), })) } @@ -81,7 +89,7 @@ impl Node for AccessLog { error!("failed to write log: {e:?}") } - self.config.next.handle(context, request).await + self.next.handle(context, request).await }) } } diff --git a/src/modules/auth/basic.rs b/src/modules/auth/basic.rs index 4b10a47..b8b3e52 100644 --- a/src/modules/auth/basic.rs +++ b/src/modules/auth/basic.rs @@ -4,10 +4,11 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ use crate::{ - config::DynNode, + config::{DynNode, DynNodeConfig}, error::ServiceError, - modules::{Node, NodeContext, NodeKind, NodeRequest, NodeResponse}, + modules::{InstContext, Node, NodeContext, NodeKind, NodeRequest, NodeResponse}, }; +use anyhow::Result; use base64::Engine; use futures::Future; use http_body_util::{combinators::BoxBody, BodyExt}; @@ -17,7 +18,6 @@ use hyper::{ }; use log::debug; use serde::Deserialize; -use serde_yml::Value; use std::{pin::Pin, sync::Arc}; use super::Credentials; @@ -27,19 +27,24 @@ impl NodeKind for HttpBasicAuthKind { fn name(&self) -> &'static str { "http_basic_auth" } - fn instanciate(&self, config: Value) -> anyhow::Result<Arc<dyn Node>> { - Ok(Arc::new(serde_yml::from_value::<HttpBasicAuth>(config)?)) + fn instanciate(&self, ic: InstContext) -> Result<DynNode> { + let config: HttpBasicAuth<DynNodeConfig> = ic.config().parse()?; + Ok(Arc::new(HttpBasicAuth { + next: ic.instanciate_child(config.next)?, + realm: config.realm, + users: config.users, + })) } } #[derive(Deserialize)] -pub struct HttpBasicAuth { +pub struct HttpBasicAuth<N> { realm: String, users: Credentials, - next: DynNode, + next: N, } -impl Node for HttpBasicAuth { +impl Node for HttpBasicAuth<DynNode> { fn handle<'a>( &'a self, context: &'a mut NodeContext, diff --git a/src/modules/auth/cookie.rs b/src/modules/auth/cookie.rs index c0935f7..81fc50a 100644 --- a/src/modules/auth/cookie.rs +++ b/src/modules/auth/cookie.rs @@ -3,15 +3,17 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2025 metamuffin <metamuffin.org> */ +use super::Credentials; use crate::{ - config::{return_true, DynNode}, + config::{return_true, DynNode, DynNodeConfig}, error::ServiceError, - modules::{Node, NodeContext, NodeKind, NodeRequest, NodeResponse}, + modules::{InstContext, Node, NodeContext, NodeKind, NodeRequest, NodeResponse}, }; use aes_gcm_siv::{ aead::{Aead, Payload}, Nonce, }; +use anyhow::Result; use base64::Engine; use bytes::Bytes; use futures::Future; @@ -25,33 +27,37 @@ use log::debug; use percent_encoding::{percent_decode_str, percent_encode, NON_ALPHANUMERIC}; use rand::random; use serde::Deserialize; -use serde_yml::Value; use std::fmt::Write; use std::{pin::Pin, sync::Arc, time::SystemTime}; -use super::Credentials; - pub struct CookieAuthKind; impl NodeKind for CookieAuthKind { fn name(&self) -> &'static str { "cookie_auth" } - fn instanciate(&self, config: Value) -> anyhow::Result<Arc<dyn Node>> { - Ok(Arc::new(serde_yml::from_value::<CookieAuth>(config)?)) + fn instanciate(&self, ic: InstContext) -> Result<Arc<dyn Node>> { + let config: CookieAuth<DynNodeConfig> = ic.config().parse()?; + Ok(Arc::new(CookieAuth { + expire: config.expire, + secure: config.secure, + users: config.users, + fail: ic.instanciate_child(config.fail)?, + next: ic.instanciate_child(config.next)?, + })) } } #[derive(Deserialize)] -pub struct CookieAuth { +pub struct CookieAuth<N> { users: Credentials, expire: Option<u64>, #[serde(default = "return_true")] secure: bool, - next: DynNode, - fail: DynNode, + next: N, + fail: N, } -impl Node for CookieAuth { +impl Node for CookieAuth<DynNode> { fn handle<'a>( &'a self, context: &'a mut NodeContext, @@ -160,7 +166,7 @@ impl Node for CookieAuth { fn apply_login_success_headers( context: &mut NodeContext, - node: &CookieAuth, + node: &CookieAuth<DynNode>, username: &str, r: &mut Response<BoxBody<Bytes, ServiceError>>, ) { @@ -205,7 +211,7 @@ fn apply_login_success_headers( fn login_success_response( context: &mut NodeContext, - node: &CookieAuth, + node: &CookieAuth<DynNode>, referrer: Option<HeaderValue>, username: &str, ) -> Response<BoxBody<Bytes, ServiceError>> { diff --git a/src/modules/auth/openid.rs b/src/modules/auth/openid.rs index 0b5aea7..175fa22 100644 --- a/src/modules/auth/openid.rs +++ b/src/modules/auth/openid.rs @@ -4,14 +4,15 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ use crate::{ - config::DynNode, + config::{DynNode, DynNodeConfig}, error::ServiceError, - modules::{Node, NodeContext, NodeKind, NodeRequest, NodeResponse}, + modules::{InstContext, Node, NodeContext, NodeKind, NodeRequest, NodeResponse}, }; use aes_gcm_siv::{ aead::{Aead, Payload}, Nonce, }; +use anyhow::Result; use base64::{prelude::BASE64_URL_SAFE_NO_PAD, Engine}; use bytes::Buf; use futures::Future; @@ -31,7 +32,6 @@ use percent_encoding::{ use rand::random; use rustls::{pki_types::ServerName, RootCertStore}; use serde::Deserialize; -use serde_yml::Value; use sha2::{Digest, Sha256}; use std::{collections::HashSet, io::Read, pin::Pin, str::FromStr, sync::Arc, time::SystemTime}; use tokio::net::TcpStream; @@ -41,13 +41,23 @@ impl NodeKind for OpenIDAuthKind { fn name(&self) -> &'static str { "openid_auth" } - fn instanciate(&self, config: Value) -> anyhow::Result<Arc<dyn Node>> { - Ok(Arc::new(serde_yml::from_value::<OpenIDAuth>(config)?)) + fn instanciate(&self, ic: InstContext) -> Result<DynNode> { + let config: OpenIDAuth<DynNodeConfig> = ic.config().parse()?; + Ok(Arc::new(OpenIDAuth { + authorize_endpoint: config.authorize_endpoint, + authorized_emails: config.authorized_emails, + client_id: config.client_id, + client_secret: config.client_secret, + next: ic.instanciate_child(config.next)?, + salt: config.salt, + scope: config.scope, + token_endpoint: config.token_endpoint, + })) } } #[derive(Deserialize)] -pub struct OpenIDAuth { +pub struct OpenIDAuth<N> { salt: String, client_id: String, client_secret: String, @@ -55,10 +65,10 @@ pub struct OpenIDAuth { token_endpoint: String, scope: String, authorized_emails: HashSet<String>, - next: DynNode, + next: N, } -impl Node for OpenIDAuth { +impl Node for OpenIDAuth<DynNode> { fn handle<'a>( &'a self, context: &'a mut NodeContext, diff --git a/src/modules/cache.rs b/src/modules/cache.rs index 34db902..548c076 100644 --- a/src/modules/cache.rs +++ b/src/modules/cache.rs @@ -15,7 +15,11 @@ //! - on disk (redb? filesystem?) //! - external db? use super::{Node, NodeContext, NodeKind, NodeRequest, NodeResponse}; -use crate::{config::DynNode, error::ServiceError}; +use crate::{ + config::{DynNode, DynNodeConfig}, + error::ServiceError, + modules::InstContext, +}; use anyhow::Result; use bytes::Bytes; use headers::{CacheControl, HeaderMapExt}; @@ -23,7 +27,6 @@ use http::Response; use http_body_util::{BodyExt, Full}; use log::debug; use serde::Deserialize; -use serde_yml::Value; use sha2::{Digest, Sha256}; use std::{collections::HashMap, future::Future, pin::Pin, sync::Arc, time::Duration}; use tokio::sync::RwLock; @@ -32,21 +35,23 @@ pub struct CacheKind; #[derive(Deserialize)] struct CacheConfig { - next: DynNode, + next: DynNodeConfig, } struct Cache { - entries: RwLock<HashMap<[u8; 32], Response<Bytes>>>, config: CacheConfig, + next: DynNode, + entries: RwLock<HashMap<[u8; 32], Response<Bytes>>>, } impl NodeKind for CacheKind { fn name(&self) -> &'static str { "cache" } - fn instanciate(&self, config: Value) -> Result<Arc<dyn Node>> { - let config = serde_yml::from_value::<CacheConfig>(config)?; + fn instanciate(&self, ic: InstContext) -> Result<DynNode> { + let config: CacheConfig = ic.config().parse()?; Ok(Arc::new(Cache { + next: ic.instanciate_child(config.next.clone())?, config, entries: HashMap::new().into(), })) @@ -62,7 +67,7 @@ impl Node for Cache { let allow_cache = request.method().is_safe(); if !allow_cache { - return self.config.next.handle(context, request).await; + return self.next.handle(context, request).await; } // not very fast @@ -94,7 +99,7 @@ impl Node for Cache { } debug!("miss"); - let response = self.config.next.handle(context, request).await?; + let response = self.next.handle(context, request).await?; let cache_control = response .headers() diff --git a/src/modules/cgi.rs b/src/modules/cgi.rs index 121bcac..b687398 100644 --- a/src/modules/cgi.rs +++ b/src/modules/cgi.rs @@ -4,7 +4,7 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ use super::{Node, NodeContext, NodeKind, NodeRequest, NodeResponse}; -use crate::error::ServiceError; +use crate::{error::ServiceError, modules::InstContext}; use anyhow::{anyhow, Result}; use futures::TryStreamExt; use http_body_util::{combinators::BoxBody, BodyExt, StreamBody}; @@ -14,7 +14,6 @@ use hyper::{ Response, StatusCode, }; use serde::Deserialize; -use serde_yml::Value; use std::{ collections::BTreeMap, future::Future, io::ErrorKind, path::PathBuf, pin::Pin, process::Stdio, str::FromStr, sync::Arc, @@ -48,10 +47,8 @@ impl NodeKind for CgiKind { fn name(&self) -> &'static str { "cgi" } - fn instanciate(&self, config: Value) -> Result<Arc<dyn Node>> { - Ok(Arc::new(Cgi::new(serde_yml::from_value::<CgiConfig>( - config, - )?)?)) + fn instanciate(&self, ic: InstContext) -> Result<Arc<dyn Node>> { + Ok(Arc::new(Cgi::new(ic.config().parse()?)?)) } } impl Cgi { diff --git a/src/modules/debug.rs b/src/modules/debug.rs index 5802881..f9cad9a 100644 --- a/src/modules/debug.rs +++ b/src/modules/debug.rs @@ -4,12 +4,11 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ use super::{Node, NodeContext, NodeKind, NodeRequest, NodeResponse}; -use crate::error::ServiceError; +use crate::{error::ServiceError, modules::InstContext}; use futures::Future; use http::{header::CONTENT_TYPE, HeaderValue, Response}; use http_body_util::BodyExt; use serde::Deserialize; -use serde_yml::Value; use std::{pin::Pin, sync::Arc}; pub struct DebugKind; @@ -21,8 +20,8 @@ impl NodeKind for DebugKind { fn name(&self) -> &'static str { "debug" } - fn instanciate(&self, config: Value) -> anyhow::Result<Arc<dyn Node>> { - Ok(Arc::new(serde_yml::from_value::<Debug>(config)?)) + fn instanciate(&self, _ic: InstContext) -> anyhow::Result<Arc<dyn Node>> { + Ok(Arc::new(Debug)) } } diff --git a/src/modules/delay.rs b/src/modules/delay.rs index 31036fe..59f6a71 100644 --- a/src/modules/delay.rs +++ b/src/modules/delay.rs @@ -4,32 +4,40 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ use super::{Node, NodeContext, NodeKind, NodeRequest, NodeResponse}; -use crate::{config::DynNode, error::ServiceError}; +use crate::{ + config::{DynNode, DynNodeConfig}, + error::ServiceError, + modules::InstContext, +}; use anyhow::Result; use rand_distr::Distribution; use serde::Deserialize; -use serde_yml::Value; use std::{future::Future, pin::Pin, sync::Arc, time::Duration}; use tokio::time::sleep; pub struct DelayKind; #[derive(Deserialize)] -struct Delay { +struct Delay<N> { duration: u64, stdev: Option<u64>, - next: DynNode, + next: N, } impl NodeKind for DelayKind { fn name(&self) -> &'static str { "delay" } - fn instanciate(&self, config: Value) -> Result<Arc<dyn Node>> { - Ok(Arc::new(serde_yml::from_value::<Delay>(config)?)) + fn instanciate(&self, ic: InstContext) -> Result<DynNode> { + let config: Delay<DynNodeConfig> = ic.config().parse()?; + Ok(Arc::new(Delay { + duration: config.duration, + stdev: config.stdev, + next: ic.instanciate_child(config.next)?, + })) } } -impl Node for Delay { +impl Node for Delay<DynNode> { fn handle<'a>( &'a self, context: &'a mut NodeContext, diff --git a/src/modules/error.rs b/src/modules/error.rs index 6e895d0..4ec5c6b 100644 --- a/src/modules/error.rs +++ b/src/modules/error.rs @@ -4,10 +4,10 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ use super::{Node, NodeContext, NodeKind, NodeRequest, NodeResponse}; -use crate::error::ServiceError; +use crate::{config::DynNode, error::ServiceError, modules::InstContext}; +use anyhow::Result; use futures::Future; use serde::Deserialize; -use serde_yml::Value; use std::{pin::Pin, sync::Arc}; pub struct ErrorKind; @@ -20,8 +20,8 @@ impl NodeKind for ErrorKind { fn name(&self) -> &'static str { "error" } - fn instanciate(&self, config: Value) -> anyhow::Result<Arc<dyn Node>> { - Ok(Arc::new(serde_yml::from_value::<Error>(config)?)) + fn instanciate(&self, ic: InstContext) -> Result<DynNode> { + Ok(Arc::new(ic.config().parse::<Error>()?)) } } diff --git a/src/modules/fallback.rs b/src/modules/fallback.rs index ef3d741..5b518bd 100644 --- a/src/modules/fallback.rs +++ b/src/modules/fallback.rs @@ -4,25 +4,32 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ use super::{Node, NodeContext, NodeKind, NodeRequest, NodeResponse}; -use crate::{config::DynNode, error::ServiceError}; +use crate::{ + config::{DynNode, DynNodeConfig}, + error::ServiceError, + modules::InstContext, +}; use anyhow::Result; use http::Request; use http_body_util::{combinators::BoxBody, BodyExt, Full}; -use serde::Deserialize; -use serde_yml::Value; use std::{future::Future, pin::Pin, sync::Arc}; pub struct FallbackKind; -#[derive(Deserialize)] struct Fallback(Vec<DynNode>); impl NodeKind for FallbackKind { fn name(&self) -> &'static str { "fallback" } - fn instanciate(&self, config: Value) -> Result<Arc<dyn Node>> { - Ok(Arc::new(serde_yml::from_value::<Fallback>(config)?)) + fn instanciate(&self, ic: InstContext) -> Result<DynNode> { + let config: Vec<DynNodeConfig> = ic.config().parse()?; + Ok(Arc::new(Fallback( + config + .into_iter() + .map(|c| ic.instanciate_child(c)) + .collect::<Result<_>>()?, + ))) } } diff --git a/src/modules/file.rs b/src/modules/file.rs index 0a03179..d6273aa 100644 --- a/src/modules/file.rs +++ b/src/modules/file.rs @@ -4,7 +4,8 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ use super::{Node, NodeContext, NodeKind, NodeRequest, NodeResponse}; -use crate::error::ServiceError; +use crate::{config::DynNode, error::ServiceError, modules::InstContext}; +use anyhow::Result; use futures::Future; use http_body_util::{combinators::BoxBody, BodyExt}; use hyper::{ @@ -12,7 +13,6 @@ use hyper::{ Response, }; use serde::Deserialize; -use serde_yml::Value; use std::{fs::read_to_string, path::PathBuf, pin::Pin, sync::Arc}; pub struct FileKind; @@ -34,8 +34,8 @@ impl NodeKind for FileKind { fn name(&self) -> &'static str { "file" } - fn instanciate(&self, config: Value) -> anyhow::Result<Arc<dyn Node>> { - let conf = serde_yml::from_value::<FileConfig>(config)?; + fn instanciate(&self, ic: InstContext) -> Result<DynNode> { + let conf = ic.config().parse::<FileConfig>()?; Ok(Arc::new(File { content: conf .content diff --git a/src/modules/files.rs b/src/modules/files.rs index 94dab8c..1d2706c 100644 --- a/src/modules/files.rs +++ b/src/modules/files.rs @@ -4,7 +4,12 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ use super::{Node, NodeContext, NodeKind, NodeRequest, NodeResponse}; -use crate::{config::return_true, ServiceError}; +use crate::{ + config::{return_true, DynNode}, + modules::InstContext, + ServiceError, +}; +use anyhow::Result; use bytes::{Bytes, BytesMut}; use futures::Future; use futures_util::{future, future::Either, ready, stream, FutureExt, Stream, StreamExt}; @@ -24,7 +29,6 @@ use hyper::{ use log::debug; use percent_encoding::percent_decode_str; use serde::Deserialize; -use serde_yml::Value; use std::{ fs::Metadata, io, @@ -69,8 +73,8 @@ impl NodeKind for FilesKind { fn name(&self) -> &'static str { "files" } - fn instanciate(&self, config: Value) -> anyhow::Result<Arc<dyn Node>> { - Ok(Arc::new(serde_yml::from_value::<Files>(config)?)) + fn instanciate(&self, ic: InstContext) -> Result<DynNode> { + Ok(Arc::new(ic.config().parse::<Files>()?)) } } diff --git a/src/modules/headers.rs b/src/modules/headers.rs index f5b081f..5ca0663 100644 --- a/src/modules/headers.rs +++ b/src/modules/headers.rs @@ -4,7 +4,11 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ use super::{Node, NodeContext, NodeKind, NodeRequest, NodeResponse}; -use crate::{config::DynNode, error::ServiceError}; +use crate::{ + config::{DynNode, DynNodeConfig}, + error::ServiceError, + modules::InstContext, +}; use anyhow::Result; use futures::Future; use hyper::{ @@ -17,24 +21,29 @@ use std::{collections::BTreeMap, pin::Pin, str::FromStr, sync::Arc}; pub struct HeadersKind; #[derive(Deserialize)] -pub struct Headers { +pub struct Headers<N> { #[serde(default)] request: HeaderMapWrap, #[serde(default)] response: HeaderMapWrap, - next: DynNode, + next: N, } impl NodeKind for HeadersKind { fn name(&self) -> &'static str { "headers" } - fn instanciate(&self, config: serde_yml::Value) -> Result<Arc<dyn Node>> { - Ok(Arc::new(serde_yml::from_value::<Headers>(config)?)) + fn instanciate(&self, ic: InstContext) -> Result<Arc<dyn Node>> { + let config: Headers<DynNodeConfig> = ic.config().parse()?; + Ok(Arc::new(Headers { + request: config.request, + response: config.response, + next: ic.instanciate_child(config.next)?, + })) } } -impl Node for Headers { +impl Node for Headers<DynNode> { fn handle<'a>( &'a self, context: &'a mut NodeContext, diff --git a/src/modules/hosts.rs b/src/modules/hosts.rs index e747e64..f486738 100644 --- a/src/modules/hosts.rs +++ b/src/modules/hosts.rs @@ -4,27 +4,38 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ use super::{Node, NodeContext, NodeKind, NodeRequest, NodeResponse}; -use crate::{config::DynNode, error::ServiceError}; +use crate::{ + config::{DynNode, DynNodeConfig}, + error::ServiceError, + modules::InstContext, +}; +use anyhow::Result; use futures::Future; use hyper::header::HOST; use serde::Deserialize; -use serde_yml::Value; use std::{collections::HashMap, pin::Pin, sync::Arc}; #[derive(Deserialize)] #[serde(transparent)] -struct Hosts(HashMap<String, DynNode>); +struct Hosts<N>(HashMap<String, N>); pub struct HostsKind; impl NodeKind for HostsKind { fn name(&self) -> &'static str { "hosts" } - fn instanciate(&self, config: Value) -> anyhow::Result<Arc<dyn Node>> { - Ok(Arc::new(serde_yml::from_value::<Hosts>(config)?)) + fn instanciate(&self, ic: InstContext) -> Result<DynNode> { + let config: Hosts<DynNodeConfig> = ic.config().parse()?; + Ok(Arc::new(Hosts( + config + .0 + .into_iter() + .map(|(k, v)| anyhow::Ok((k, ic.instanciate_child(v)?))) + .collect::<Result<_>>()?, + ))) } } -impl Node for Hosts { +impl Node for Hosts<DynNode> { fn handle<'a>( &'a self, context: &'a mut NodeContext, diff --git a/src/modules/inspect.rs b/src/modules/inspect.rs index c9b3abc..a0c68e6 100644 --- a/src/modules/inspect.rs +++ b/src/modules/inspect.rs @@ -4,7 +4,11 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ use super::{Node, NodeContext, NodeKind, NodeRequest, NodeResponse}; -use crate::{config::DynNode, error::ServiceError}; +use crate::{ + config::{DynNode, DynNodeConfig}, + error::ServiceError, + modules::InstContext, +}; use anyhow::Result; use futures::Future; use log::debug; @@ -14,20 +18,23 @@ use std::{pin::Pin, sync::Arc}; pub struct InspectKind; #[derive(Deserialize)] -pub struct Inspect { - next: DynNode, +pub struct Inspect<N> { + next: N, } impl NodeKind for InspectKind { fn name(&self) -> &'static str { "inspect" } - fn instanciate(&self, config: serde_yml::Value) -> Result<Arc<dyn Node>> { - Ok(Arc::new(serde_yml::from_value::<Inspect>(config)?)) + fn instanciate(&self, ic: InstContext) -> Result<Arc<dyn Node>> { + let config: Inspect<DynNodeConfig> = ic.config().parse()?; + Ok(Arc::new(Inspect { + next: ic.instanciate_child(config.next)?, + })) } } -impl Node for Inspect { +impl Node for Inspect<DynNode> { fn handle<'a>( &'a self, context: &'a mut NodeContext, diff --git a/src/modules/limits.rs b/src/modules/limits.rs index dcfc508..4f8e85e 100644 --- a/src/modules/limits.rs +++ b/src/modules/limits.rs @@ -4,7 +4,11 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ use super::{Node, NodeContext, NodeKind, NodeRequest, NodeResponse}; -use crate::{config::DynNode, error::ServiceError}; +use crate::{ + config::{DynNode, DynNodeConfig}, + error::ServiceError, + modules::InstContext, +}; use anyhow::Result; use bytes::Bytes; use futures::{Future, StreamExt}; @@ -21,12 +25,12 @@ use tokio::time::sleep_until; pub struct LimitsKind; #[derive(Deserialize)] -pub struct Limits { +pub struct Limits<N> { #[serde(default)] request: LimitParam, #[serde(default)] response: LimitParam, - next: DynNode, + next: N, } #[derive(Debug, Clone, Deserialize, Default)] @@ -40,12 +44,17 @@ impl NodeKind for LimitsKind { fn name(&self) -> &'static str { "limits" } - fn instanciate(&self, config: serde_yml::Value) -> Result<Arc<dyn Node>> { - Ok(Arc::new(serde_yml::from_value::<Limits>(config)?)) + fn instanciate(&self, ic: InstContext) -> Result<DynNode> { + let config: Limits<DynNodeConfig> = ic.config().parse()?; + Ok(Arc::new(Limits { + request: config.request, + response: config.response, + next: ic.instanciate_child(config.next)?, + })) } } -impl Node for Limits { +impl Node for Limits<DynNode> { fn handle<'a>( &'a self, context: &'a mut NodeContext, diff --git a/src/modules/loadbalance.rs b/src/modules/loadbalance.rs index 8a93e69..1b09c86 100644 --- a/src/modules/loadbalance.rs +++ b/src/modules/loadbalance.rs @@ -8,10 +8,13 @@ //! Given a set of handlers, the handler that is the least busy will handle the next request. //! Current implementation does not scale well for many handlers. use super::{Node, NodeContext, NodeKind, NodeRequest, NodeResponse}; -use crate::{config::DynNode, error::ServiceError}; +use crate::{ + config::{DynNode, DynNodeConfig}, + error::ServiceError, + modules::InstContext, +}; use anyhow::Result; use serde::Deserialize; -use serde_yml::Value; use std::{ future::Future, pin::Pin, @@ -24,22 +27,26 @@ use std::{ pub struct LoadBalanceKind; #[derive(Deserialize)] -struct LoadBalanceConfig(Vec<DynNode>); +struct LoadBalanceConfig(Vec<DynNodeConfig>); struct LoadBalance { load: Vec<AtomicUsize>, - config: LoadBalanceConfig, + nodes: Vec<DynNode>, } impl NodeKind for LoadBalanceKind { fn name(&self) -> &'static str { "loadbalance" } - fn instanciate(&self, config: Value) -> Result<Arc<dyn Node>> { - let config = serde_yml::from_value::<LoadBalanceConfig>(config)?; + fn instanciate(&self, ic: InstContext) -> Result<DynNode> { + let config: LoadBalanceConfig = ic.config().parse()?; Ok(Arc::new(LoadBalance { load: config.0.iter().map(|_| AtomicUsize::new(0)).collect(), - config, + nodes: config + .0 + .into_iter() + .map(|c| ic.instanciate_child(c)) + .collect::<Result<_>>()?, })) } } @@ -59,7 +66,7 @@ impl Node for LoadBalance { .ok_or(ServiceError::CustomStatic("zero routes to balance load"))?; self.load[index].fetch_add(1, Ordering::Relaxed); - let resp = self.config.0[index].handle(context, request).await; + let resp = self.nodes[index].handle(context, request).await; self.load[index].fetch_sub(1, Ordering::Relaxed); resp }) diff --git a/src/modules/log.rs b/src/modules/log.rs index 9efa3ad..e1a6205 100644 --- a/src/modules/log.rs +++ b/src/modules/log.rs @@ -4,7 +4,12 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ use super::{Node, NodeContext, NodeKind, NodeRequest, NodeResponse}; -use crate::{config::DynNode, error::ServiceError}; +use crate::{ + config::{DynNode, DynNodeConfig}, + error::ServiceError, + modules::InstContext, +}; +use anyhow::Result; use futures::Future; use http::{ header::{HOST, USER_AGENT}, @@ -39,11 +44,12 @@ struct LogConfig { format: Format, #[serde(default)] flush: bool, - next: DynNode, + next: DynNodeConfig, } struct Log { config: LogConfig, + next: DynNode, file: RwLock<Option<(BufWriter<File>, Instant)>>, } #[derive(Deserialize, Default)] @@ -71,9 +77,11 @@ impl NodeKind for LogKind { fn name(&self) -> &'static str { "log" } - fn instanciate(&self, config: serde_yml::Value) -> anyhow::Result<Arc<dyn Node>> { + fn instanciate(&self, ic: InstContext) -> Result<DynNode> { + let config: LogConfig = ic.config().parse()?; Ok(Arc::new(Log { - config: serde_yml::from_value::<LogConfig>(config)?, + next: ic.instanciate_child(config.next.clone())?, + config, file: Default::default(), })) } @@ -257,7 +265,7 @@ impl Node for Log { } } - self.config.next.handle(context, request).await + self.next.handle(context, request).await }) } } diff --git a/src/modules/mod.rs b/src/modules/mod.rs index b267a04..308fe4f 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -3,13 +3,13 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2025 metamuffin <metamuffin.org> */ -use crate::error::ServiceError; use crate::State; +use crate::{config::DynNode, error::ServiceError}; +use anyhow::Result; use bytes::Bytes; use futures::Future; use http_body_util::combinators::BoxBody; use hyper::{Request, Response}; -use serde_yml::Value; use std::{net::SocketAddr, pin::Pin, sync::Arc}; mod accesslog; @@ -36,6 +36,7 @@ mod semaphore; mod switch; mod upgrade_insecure; +pub use crate::config::InstContext; pub type NodeRequest = Request<BoxBody<Bytes, ServiceError>>; pub type NodeResponse = Response<BoxBody<Bytes, ServiceError>>; @@ -76,7 +77,7 @@ pub struct NodeContext { pub trait NodeKind: Send + Sync + 'static { fn name(&self) -> &'static str; - fn instanciate(&self, config: Value) -> anyhow::Result<Arc<dyn Node>>; + fn instanciate(&self, context: InstContext) -> Result<DynNode>; } pub trait Node: Send + Sync + 'static { fn handle<'a>( diff --git a/src/modules/paths.rs b/src/modules/paths.rs index 7131664..8818502 100644 --- a/src/modules/paths.rs +++ b/src/modules/paths.rs @@ -4,7 +4,11 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ use super::{Node, NodeContext, NodeKind, NodeRequest, NodeResponse}; -use crate::{config::DynNode, error::ServiceError}; +use crate::{ + config::{DynNode, DynNodeConfig}, + error::ServiceError, + modules::InstContext, +}; use futures::Future; use http::Uri; use regex::{Regex, RegexSet}; @@ -15,7 +19,6 @@ use serde::{ }, Deserialize, }; -use serde_yml::Value; use std::{collections::BTreeMap, marker::PhantomData, pin::Pin, sync::Arc}; pub struct PathsKind; @@ -30,14 +33,14 @@ impl NodeKind for PathsKind { fn name(&self) -> &'static str { "paths" } - fn instanciate(&self, config: Value) -> anyhow::Result<Arc<dyn Node>> { - let routes = serde_yml::from_value::<SeqOrMap<DynNode>>(config)?.0; + fn instanciate(&self, ic: InstContext) -> anyhow::Result<Arc<dyn Node>> { + let routes = ic.config().parse::<SeqOrMap<DynNodeConfig>>()?.0; let mut handlers = Vec::new(); let mut patterns = Vec::new(); let mut extractors = Vec::new(); - for (k, v) in routes { - let pattern = format!("^{k}$"); - handlers.push(v); + for (pattern, config) in routes { + let pattern = format!("^{pattern}$"); + handlers.push(ic.instanciate_child(config)?); extractors.push(Regex::new(&pattern)?); patterns.push(pattern); } diff --git a/src/modules/proxy.rs b/src/modules/proxy.rs index aecd775..252e482 100644 --- a/src/modules/proxy.rs +++ b/src/modules/proxy.rs @@ -4,7 +4,8 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ use super::{Node, NodeContext, NodeKind, NodeRequest, NodeResponse}; -use crate::ServiceError; +use crate::{config::DynNode, modules::InstContext, ServiceError}; +use anyhow::Result; use futures::Future; use http::{Response, Version}; use http_body_util::BodyExt; @@ -12,7 +13,6 @@ use hyper::{body::Incoming, http::HeaderValue, upgrade::OnUpgrade, StatusCode}; use hyper_util::rt::TokioIo; use log::{debug, warn}; use serde::Deserialize; -use serde_yml::Value; use std::{ fmt::Display, net::{Ipv4Addr, SocketAddr, SocketAddrV4}, @@ -53,8 +53,8 @@ impl NodeKind for ProxyKind { fn name(&self) -> &'static str { "proxy" } - fn instanciate(&self, config: Value) -> anyhow::Result<Arc<dyn Node>> { - Ok(Arc::new(serde_yml::from_value::<Proxy>(config)?)) + fn instanciate(&self, ic: InstContext) -> Result<DynNode> { + Ok(Arc::new(ic.config().parse::<Proxy>()?)) } } diff --git a/src/modules/ratelimit.rs b/src/modules/ratelimit.rs index ce5ac68..34a8bca 100644 --- a/src/modules/ratelimit.rs +++ b/src/modules/ratelimit.rs @@ -4,7 +4,11 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ use super::{cgi::set_cgi_variables, Node, NodeContext, NodeKind, NodeRequest, NodeResponse}; -use crate::{config::DynNode, error::ServiceError}; +use crate::{ + config::{DynNode, DynNodeConfig}, + error::ServiceError, + modules::InstContext, +}; use anyhow::Result; use futures::Future; use http::{header::RETRY_AFTER, HeaderValue, Response, StatusCode}; @@ -26,7 +30,7 @@ pub struct RatelimitKind; #[derive(Deserialize)] pub struct RatelimitConfig { - next: DynNode, + next: DynNodeConfig, #[serde(default)] identity: IdentityMode, #[serde(default = "default_max_identities")] @@ -62,6 +66,7 @@ enum LimitMode { pub struct Ratelimit { state: Arc<Mutex<HashMap<u64, IdentityState>>>, config: RatelimitConfig, + next: DynNode, } struct IdentityState { @@ -73,10 +78,12 @@ impl NodeKind for RatelimitKind { fn name(&self) -> &'static str { "ratelimit" } - fn instanciate(&self, config: serde_yml::Value) -> Result<Arc<dyn Node>> { + fn instanciate(&self, ic: InstContext) -> Result<Arc<dyn Node>> { + let config: RatelimitConfig = ic.config().parse()?; Ok(Arc::new(Ratelimit { state: Arc::new(Mutex::new(HashMap::new())), - config: serde_yml::from_value::<RatelimitConfig>(config)?, + next: ic.instanciate_child(config.next.clone())?, + config, })) } } @@ -164,7 +171,7 @@ impl Node for Ratelimit { } } - self.config.next.handle(context, request).await + self.next.handle(context, request).await }) } } diff --git a/src/modules/redirect.rs b/src/modules/redirect.rs index ef9115b..5163013 100644 --- a/src/modules/redirect.rs +++ b/src/modules/redirect.rs @@ -4,7 +4,7 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ use super::{Node, NodeContext, NodeKind, NodeRequest, NodeResponse}; -use crate::error::ServiceError; +use crate::{error::ServiceError, modules::InstContext}; use anyhow::Result; use futures::Future; use http_body_util::BodyExt; @@ -21,8 +21,8 @@ impl NodeKind for RedirectKind { fn name(&self) -> &'static str { "redirect" } - fn instanciate(&self, config: serde_yml::Value) -> Result<Arc<dyn Node>> { - Ok(Arc::new(serde_yml::from_value::<Redirect>(config)?)) + fn instanciate(&self, ic: InstContext) -> Result<Arc<dyn Node>> { + Ok(Arc::new(ic.config().parse::<Redirect>()?)) } } diff --git a/src/modules/semaphore.rs b/src/modules/semaphore.rs index 7aa8283..8fed518 100644 --- a/src/modules/semaphore.rs +++ b/src/modules/semaphore.rs @@ -4,10 +4,13 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ use super::{Node, NodeContext, NodeKind, NodeRequest, NodeResponse}; -use crate::{config::DynNode, error::ServiceError}; +use crate::{ + config::{DynNode, DynNodeConfig}, + error::ServiceError, + modules::InstContext, +}; use anyhow::Result; use serde::Deserialize; -use serde_yml::Value; use std::{future::Future, pin::Pin, sync::Arc}; use tokio::sync::Semaphore; @@ -16,23 +19,23 @@ pub struct SemaphoreKind; #[derive(Deserialize)] struct SemaphoreConfig { permits: usize, - next: DynNode, + next: DynNodeConfig, } struct SemaphoreState { - config: SemaphoreConfig, semaphore: Semaphore, + next: DynNode, } impl NodeKind for SemaphoreKind { fn name(&self) -> &'static str { "semaphore" } - fn instanciate(&self, config: Value) -> Result<Arc<dyn Node>> { - let config = serde_yml::from_value::<SemaphoreConfig>(config)?; + fn instanciate(&self, ic: InstContext) -> Result<Arc<dyn Node>> { + let config = ic.config().parse::<SemaphoreConfig>()?; Ok(Arc::new(SemaphoreState { semaphore: Semaphore::new(config.permits), - config, + next: ic.instanciate_child(config.next)?, })) } } @@ -44,7 +47,7 @@ impl Node for SemaphoreState { ) -> Pin<Box<dyn Future<Output = Result<NodeResponse, ServiceError>> + Send + Sync + 'a>> { Box::pin(async move { let _permit = self.semaphore.acquire().await; - let resp = self.config.next.handle(context, request).await?; + let resp = self.next.handle(context, request).await?; drop(_permit); Ok(resp) }) diff --git a/src/modules/switch.rs b/src/modules/switch.rs index 0cc013c..c42216e 100644 --- a/src/modules/switch.rs +++ b/src/modules/switch.rs @@ -4,7 +4,11 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ use super::{Node, NodeContext, NodeKind, NodeRequest, NodeResponse}; -use crate::{config::DynNode, error::ServiceError}; +use crate::{ + config::{DynNode, DynNodeConfig}, + error::ServiceError, + modules::InstContext, +}; use anyhow::Result; use futures::Future; use headers::{HeaderMapExt, Upgrade}; @@ -16,22 +20,27 @@ use std::{pin::Pin, sync::Arc}; pub struct SwitchKind; #[derive(Deserialize)] -pub struct Switch { +pub struct Switch<N> { condition: Condition, - case_true: DynNode, - case_false: DynNode, + case_true: N, + case_false: N, } impl NodeKind for SwitchKind { fn name(&self) -> &'static str { "switch" } - fn instanciate(&self, config: serde_yml::Value) -> Result<Arc<dyn Node>> { - Ok(Arc::new(serde_yml::from_value::<Switch>(config)?)) + fn instanciate(&self, ic: InstContext) -> Result<DynNode> { + let config: Switch<DynNodeConfig> = ic.config().parse()?; + Ok(Arc::new(Switch { + condition: config.condition, + case_true: ic.instanciate_child(config.case_true)?, + case_false: ic.instanciate_child(config.case_false)?, + })) } } -impl Node for Switch { +impl Node for Switch<DynNode> { fn handle<'a>( &'a self, context: &'a mut NodeContext, diff --git a/src/modules/upgrade_insecure.rs b/src/modules/upgrade_insecure.rs index 37fc04e..c06caa7 100644 --- a/src/modules/upgrade_insecure.rs +++ b/src/modules/upgrade_insecure.rs @@ -4,7 +4,11 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ use super::{Node, NodeContext, NodeKind, NodeRequest, NodeResponse}; -use crate::{config::DynNode, error::ServiceError}; +use crate::{ + config::{DynNode, DynNodeConfig}, + error::ServiceError, + modules::InstContext, +}; use anyhow::Result; use futures::Future; use http::{ @@ -18,18 +22,23 @@ use std::{pin::Pin, str::FromStr, sync::Arc}; pub struct UpgradeInsecureKind; #[derive(Deserialize)] -pub struct UpgradeInsecure([DynNode; 1]); +pub struct UpgradeInsecure<N> { + next: N, +} impl NodeKind for UpgradeInsecureKind { fn name(&self) -> &'static str { "upgrade_insecure" } - fn instanciate(&self, config: serde_yml::Value) -> Result<Arc<dyn Node>> { - Ok(Arc::new(serde_yml::from_value::<UpgradeInsecure>(config)?)) + fn instanciate(&self, ic: InstContext) -> Result<DynNode> { + let config = ic.config().parse::<UpgradeInsecure<DynNodeConfig>>()?; + Ok(Arc::new(UpgradeInsecure::<DynNode> { + next: ic.instanciate_child(config.next)?, + })) } } -impl Node for UpgradeInsecure { +impl Node for UpgradeInsecure<DynNode> { fn handle<'a>( &'a self, context: &'a mut NodeContext, @@ -67,7 +76,7 @@ impl Node for UpgradeInsecure { ); return Ok(resp); } - self.0[0].handle(context, request).await + self.next.handle(context, request).await }) } } |