From 537a73f900ff65a4995f2615fc634c858e64d6cd Mon Sep 17 00:00:00 2001 From: Max van der Schee <26795741+mvdschee@users.noreply.github.com> Date: Sun, 7 Jun 2026 22:49:12 +0200 Subject: [PATCH 01/14] update dependencies --- Cargo.lock | 331 ++++++++++------------------------------------------- Cargo.toml | 7 +- 2 files changed, 66 insertions(+), 272 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 477111f..c080ed6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -119,12 +119,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "anyhow" -version = "1.0.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" - [[package]] name = "arc-swap" version = "1.9.1" @@ -401,27 +395,27 @@ dependencies = [ [[package]] name = "cf-rustracing" -version = "1.3.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6565523d8145e63e0cf1b397a5f1bd4e90d5652a7dffb2de8cec460ff23ef6b1" +checksum = "93f85c3824e4191621dec0551e3cef3d511f329da9a8990bf3e450a85651d97e" dependencies = [ "backtrace", - "rand 0.10.1", + "rand 0.8.6", "tokio", "trackable", ] [[package]] name = "cf-rustracing-jaeger" -version = "1.3.0" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16c0e4d8cce27f6a6eaff58d2b66f063a18b8ed0d6ef0947ae7a263afa3b7c08" +checksum = "a6a5f80d44c257c3300a7f45ada676c211e64bbbac591bbec19344a8f61fbcab" dependencies = [ "cf-rustracing", "hostname", "local-ip-address", "percent-encoding", - "rand 0.10.1", + "rand 0.9.4", "thrift_codec", "tokio", "trackable", @@ -433,22 +427,11 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" -[[package]] -name = "chacha20" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" -dependencies = [ - "cfg-if", - "cpufeatures", - "rand_core 0.10.1", -] - [[package]] name = "chrono" -version = "0.4.44" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", "js-sys", @@ -550,15 +533,6 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "cpufeatures" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" -dependencies = [ - "libc", -] - [[package]] name = "crc32fast" version = "1.5.0" @@ -796,12 +770,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "foldhash" version = "0.2.0" @@ -968,24 +936,10 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi 5.3.0", + "r-efi", "wasip2", ] -[[package]] -name = "getrandom" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" -dependencies = [ - "cfg-if", - "libc", - "r-efi 6.0.0", - "rand_core 0.10.1", - "wasip2", - "wasip3", -] - [[package]] name = "getset" version = "0.1.6" @@ -1035,15 +989,6 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash 0.1.5", -] - [[package]] name = "hashbrown" version = "0.16.1" @@ -1052,7 +997,7 @@ checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", - "foldhash 0.2.0", + "foldhash", ] [[package]] @@ -1063,7 +1008,7 @@ checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" dependencies = [ "allocator-api2", "equivalent", - "foldhash 0.2.0", + "foldhash", ] [[package]] @@ -1216,12 +1161,6 @@ dependencies = [ "cc", ] -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "ident_case" version = "1.0.1" @@ -1246,8 +1185,6 @@ checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", "hashbrown 0.17.0", - "serde", - "serde_core", ] [[package]] @@ -1374,12 +1311,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - [[package]] name = "libc" version = "0.2.186" @@ -1678,9 +1609,9 @@ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pingora" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "844a13b16e556293f4ea96dc5ac0923ac6f36855a9dfc13b640d0da183f6b5b7" +checksum = "54a75f2ff8e122aa80ab202dc865294fe59cd856c2a5dab2d3df6e122c93b941" dependencies = [ "pingora-cache", "pingora-core", @@ -1692,9 +1623,9 @@ dependencies = [ [[package]] name = "pingora-boringssl" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42c14f7d2002b24c809a0540e13c6bce1406549d527e99c2d6ced1f32a1d2b7b" +checksum = "d63a2d970f4c6ed9bd91025d89d8756a3ce1bb17969f4c83928445210cbf4940" dependencies = [ "boring", "boring-sys", @@ -1706,9 +1637,9 @@ dependencies = [ [[package]] name = "pingora-cache" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59d8c4c939a3a193a3da0e061aa7acf7432431f92ee62a26f5a9e5167a0ade2" +checksum = "527735ac204efb9fa3884bfd9224d016c5735fabe1d394ebed145b40e7545b99" dependencies = [ "ahash", "async-trait", @@ -1743,9 +1674,9 @@ dependencies = [ [[package]] name = "pingora-core" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08973c4853cef4c682f7a592907e81a32dcad69476c4846e5de079f16448b177" +checksum = "6a7ffe2f5acf9f94fd255cfd1438866bc9124f8f0c7d42562bd3f853df2094b7" dependencies = [ "ahash", "async-trait", @@ -1795,15 +1726,15 @@ dependencies = [ [[package]] name = "pingora-error" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9fa97a500e7e5c27a7b8609b9294c8922c9656322285268bfad9520f12feb38" +checksum = "b23f7bc013de67e44ed902a82843f6157460b89d11da882bcc6f09f8ae380af1" [[package]] name = "pingora-header-serde" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2705feb8b50d4e734e0c7d3879aa040e655a45656276323ff530e254585dd816" +checksum = "828c0e53e74160cbfe8e67dd3a811eb6a253c36acbaf7a39a01d9aacfb9ac139" dependencies = [ "bytes", "http", @@ -1817,9 +1748,9 @@ dependencies = [ [[package]] name = "pingora-http" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbb52d4651b687fab6abf669539cfd97b7cd94b301fde8f57c63354f9c9cc5e2" +checksum = "d553d310a15ec88107b9388a02885f798efc57764d8e9bdaaf32a76722927a10" dependencies = [ "bytes", "http", @@ -1828,18 +1759,18 @@ dependencies = [ [[package]] name = "pingora-ketama" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0286fb5a0359dca1e2e137dfe14ca4d94f676635a5eae4616bb3d8d4ce06d120" +checksum = "3e2a2e43a14f1d291fba7905542c7c1d1f89528f470b3cd48b6806e702ea772f" dependencies = [ "crc32fast", ] [[package]] name = "pingora-load-balancing" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2606e9e22e72927a69772cefe56b0d41d251c3ffdfcd548a6020fe157fb79ad" +checksum = "361b69af0234d2e4d10234e2efd106bb3b8147c575d52f45604a46aaf26def7a" dependencies = [ "arc-swap", "async-trait", @@ -1859,9 +1790,9 @@ dependencies = [ [[package]] name = "pingora-lru" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91bb5030596a3d442c0866ac68afe29c14ba558e77c726dcdf7016b0dbb359d9" +checksum = "6705a26ad89d241a989a5395641931ba37076f5ab5fbd19ee92402414a43af32" dependencies = [ "arrayvec", "hashbrown 0.17.0", @@ -1871,9 +1802,9 @@ dependencies = [ [[package]] name = "pingora-pool" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67f034be36772f318370d058913db43dbd22c3763ad974c995ba2e4afb2bb52a" +checksum = "feb1237893b15a9cf6b371bee8d7e2e1c10742e4be6eb00ed38cfe87fd1363f8" dependencies = [ "crossbeam-queue", "log", @@ -1886,9 +1817,9 @@ dependencies = [ [[package]] name = "pingora-proxy" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e1e070a98a70d0d05f2fdcfb706237e06a043b2fbc9261e8772a3459cc2175e" +checksum = "8a92ee756ecf6ecb6419864da651cad6cecd933b6d420a26877031efa16bef57" dependencies = [ "async-trait", "bytes", @@ -1909,9 +1840,9 @@ dependencies = [ [[package]] name = "pingora-runtime" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e371315b1c44c2e5a8788fdc61577527b785e121e6ff49144755f40d86511430" +checksum = "41815a13691a3e7d9ad0e34767d4140284132e31b95a4481f5e73ab6f407f834" dependencies = [ "once_cell", "rand 0.8.6", @@ -1921,9 +1852,9 @@ dependencies = [ [[package]] name = "pingora-timeout" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a853fee5ce510a7f5db2561f99c752724112ed13fc3820e70d462d278d704ea" +checksum = "8e3e321452eaa461e0b6c5aaa35b7e42527ee89df33710279f37fae7f066b68e" dependencies = [ "once_cell", "parking_lot", @@ -1953,16 +1884,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn 2.0.117", -] - [[package]] name = "proc-macro-error-attr2" version = "2.0.0" @@ -2019,6 +1940,7 @@ checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" name = "proxy" version = "0.1.0" dependencies = [ + "arc-swap", "async-trait", "chrono", "http", @@ -2046,12 +1968,6 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - [[package]] name = "rand" version = "0.8.6" @@ -2059,19 +1975,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", - "rand_chacha", + "rand_chacha 0.3.1", "rand_core 0.6.4", ] [[package]] name = "rand" -version = "0.10.1" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ - "chacha20", - "getrandom 0.4.2", - "rand_core 0.10.1", + "rand_chacha 0.9.0", + "rand_core 0.9.5", ] [[package]] @@ -2084,6 +1999,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + [[package]] name = "rand_core" version = "0.6.4" @@ -2095,9 +2020,12 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.10.1" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] [[package]] name = "rcgen" @@ -2837,12 +2765,6 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -2904,16 +2826,7 @@ version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen 0.57.1", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen 0.51.0", + "wit-bindgen", ] [[package]] @@ -2961,40 +2874,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap 2.14.0", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags 2.11.1", - "hashbrown 0.15.5", - "indexmap 2.14.0", - "semver", -] - [[package]] name = "webpki-root-certs" version = "1.0.7" @@ -3191,100 +3070,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - [[package]] name = "wit-bindgen" version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap 2.14.0", - "prettyplease", - "syn 2.0.117", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn 2.0.117", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.11.1", - "indexmap 2.14.0", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap 2.14.0", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - [[package]] name = "x509-parser" version = "0.18.1" diff --git a/Cargo.toml b/Cargo.toml index 4da94a9..6953af3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,9 @@ unused = { level = "allow", priority = -1 } async-trait = "0.1.89" tokio = { version = "1.52.3", features = ["full"] } +# Arc +arc-swap = "1.9.1" + # Certs # used Ring as backend for default rcgen = { version = "0.14.8", features = ["default"] } @@ -36,8 +39,8 @@ serde = { version = "1.0.228", features = ["derive"] } # Others thiserror = "2.0.18" -chrono = { version = "0.4.44", features = ["serde"] } +chrono = { version = "0.4.45", features = ["serde"] } # HTTP http = "1.4.1" -pingora = { version = "0.8.0", features = ["proxy", "boringssl"] } +pingora = { version = "0.8.1", features = ["proxy", "boringssl"] } From e17ef2d1e7a713f27bd2d8718297ce4e93e70f59 Mon Sep 17 00:00:00 2001 From: Max van der Schee <26795741+mvdschee@users.noreply.github.com> Date: Sun, 7 Jun 2026 22:50:16 +0200 Subject: [PATCH 02/14] wire up services for certs and scaffold background renewal --- README.md | 7 ++ src/core/handlers/certs.rs | 144 +++++++++++++++++++++--------- src/core/handlers/filesystem.rs | 12 ++- src/core/handlers/proxy.rs | 63 ++++++------- src/core/models/certs.rs | 8 +- src/core/models/proxy.rs | 3 - src/core/models/tasks.rs | 1 + src/core/setup.rs | 51 +++++------ src/main.rs | 8 +- src/services/certs/acme.rs | 7 ++ src/services/certs/mod.rs | 2 + src/services/certs/self_signed.rs | 34 +++++++ src/services/mod.rs | 1 + 13 files changed, 217 insertions(+), 124 deletions(-) create mode 100644 src/services/certs/acme.rs create mode 100644 src/services/certs/mod.rs create mode 100644 src/services/certs/self_signed.rs create mode 100644 src/services/mod.rs diff --git a/README.md b/README.md index c2c738a..140042e 100644 --- a/README.md +++ b/README.md @@ -166,3 +166,10 @@ I'll be upfront on every public project about what was done with AI. For this on - Cleanup of the README and other prose - Talking through code-level solutions - Generating the Docker image scaffolding from a spec + +# TODO: + +- finish create_acme_dns_challenge +- create a dns entry checker for the background task +- generate a staging certificate with instance_acme +- wire up the full flow in CertBackgroundRenewal diff --git a/src/core/handlers/certs.rs b/src/core/handlers/certs.rs index f9d088d..6139485 100644 --- a/src/core/handlers/certs.rs +++ b/src/core/handlers/certs.rs @@ -1,47 +1,42 @@ use crate::{ Error, Result, core::{ - handlers::filesystem::{safe_path, write_file}, + handlers::filesystem::{check_file_exists, read_file, safe_path, write_file}, models::{ - certs::{CertificateConfig, CertificateType}, + certs::{ + CertDir, CertPath, CertificateConfig, CertificateType, KeyPath, TlsMaterial, + TlsStore, + }, + routes::Host, tasks::TaskInterval, }, }, info, + services::certs::{ + acme::create_acme_dns_challenge, self_signed::create_self_signed_certificate_files, + }, + warn, }; +use arc_swap::ArcSwap; +use async_trait::async_trait; +use pingora::{server::ShutdownWatch, services::background::BackgroundService, tls}; use rcgen::{CertifiedKey, generate_simple_self_signed}; -use std::time::Duration; +use std::{collections::HashMap, sync::Arc, time::Duration}; use tokio::time; -pub fn generate_certs(certificate_configs: Vec) -> Result<()> { - for config in &certificate_configs { - // self signed certificates are good until the year 4096 - // this will be replace every restart so it's safe to keep using the default setting +pub fn create_initial_certs(certificate_configs: &Vec) -> Result<()> { + for config in certificate_configs { match config.cert_type { CertificateType::SelfSigned => { - info!("generating self-signed certificate for {}", config.host); - - let subject_alt_names = vec![config.host.to_string()]; - let pem_filename = format!("{}.pem", config.host); - let key_filename = format!("{}.key", config.host); - - let key_path = safe_path(&config.cert_dir, &key_filename)?; - let pem_path = safe_path(&config.cert_dir, &pem_filename)?; - - let CertifiedKey { - cert, - signing_key, - } = generate_simple_self_signed(subject_alt_names) - .map_err(|e| Error::Certificate(e.to_string()))?; - - let pem_serialized = cert.pem(); - let key_serialized = signing_key.serialize_pem(); - - write_file(pem_path, pem_serialized.as_bytes())?; - write_file(key_path, key_serialized.as_bytes())?; + // self signed certificates are good until the year 4096 + // this will be replace every restart so it's safe to keep using the default setting + // for selfsigned we will create the certs here right away + create_self_signed_certificate_files(config); }, CertificateType::Acme => { - info!("generating acme certificate for {}", config.host); + // we do not generate any certs here that is for the backend process, + // we are only displaying the DNS entry that needs to be included to make sure you are able to get a cert + create_acme_dns_challenge(config); }, CertificateType::None => {}, } @@ -50,24 +45,87 @@ pub fn generate_certs(certificate_configs: Vec) -> Result<()> Ok(()) } -pub async fn background_certs_task( - certificates: Vec, - task_interval: TaskInterval, -) -> Result<()> { - // only acme certificates need to be renewed - let certificates = certificates - .into_iter() - .filter(|cert| cert.cert_type == CertificateType::Acme) - .collect::>(); +pub fn load_tls_store(certificate_configs: &Vec) -> Result { + let mut tls_certs = HashMap::new(); - loop { - info!("certificates: {}", certificates.len()); + for config in certificate_configs { + if config.cert_type != CertificateType::None { + let (key_path, cert_path) = certificate_paths(&config.host, &config.cert_dir)?; - // for certificate in &certificates { - // } + let has_tls_files = check_file_exists(&key_path) && check_file_exists(&cert_path); - time::sleep(Duration::from_secs(*task_interval)).await; + // We only show a warning so its easier to debug once its running, + // but we are not stopping any traffic. + if !has_tls_files { + warn!("Certificate files not found for host `{}` but is expected", &config.host); + } + + let cert_bytes = read_file(&cert_path)?; + let cert = tls::x509::X509::from_pem(&cert_bytes) + .map_err(|e| Error::Certificate(format!("Failed to parse certificate: {}", e)))?; + + let key_bytes = read_file(&key_path)?; + let key = tls::pkey::PKey::private_key_from_pem(&key_bytes) + .map_err(|e| Error::Certificate(format!("Failed to parse private key: {}", e)))?; + + tls_certs.insert( + config.host.clone(), + TlsMaterial { + cert, + key, + }, + ); + } } - Ok(()) + let tls_store: TlsStore = Arc::new(ArcSwap::from_pointee(tls_certs)); + + Ok(tls_store) +} + +pub fn certificate_paths(host: &Host, cert_dir: &CertDir) -> Result<(KeyPath, CertPath)> { + let cert_filename = format!("{}.pem", host); + let key_filename = format!("{}.key", host); + + let key_path = safe_path(cert_dir, &key_filename)?; + let cert_path = safe_path(cert_dir, &cert_filename)?; + + Ok((key_path, cert_path)) +} + +pub struct CertBackgroundRenewal { + pub certificate_configs: Vec, + pub task_interval: TaskInterval, + pub tls_store: TlsStore, +} + +impl CertBackgroundRenewal { + pub fn new( + certificate_configs: Vec, + task_interval: TaskInterval, + tls_store: TlsStore, + ) -> Self { + Self { + certificate_configs, + task_interval, + tls_store, + } + } +} + +#[async_trait] +impl BackgroundService for CertBackgroundRenewal { + async fn start(&self, mut shutdown: ShutdownWatch) { + loop { + // TODO: couple things we will do in this background task + // - renew a certificate if its needed, and update the tls_store with the new certificate data + // - check if the dns has its text entry so we can start the process of generating certificates for that domein + // + + tokio::select! { + _ = tokio::time::sleep(Duration::from_secs(*self.task_interval)) => {} + _ = shutdown.changed() => break, + } + } + } } diff --git a/src/core/handlers/filesystem.rs b/src/core/handlers/filesystem.rs index bc77a13..f4648d8 100644 --- a/src/core/handlers/filesystem.rs +++ b/src/core/handlers/filesystem.rs @@ -8,10 +8,10 @@ pub fn write_file(file_path: SafePath, content: &[u8]) -> Result<()> { // foxguard: ignore[rs/no-path-traversal] // validated with SafePath let path = Path::new(file_path.as_str()); - if let Some(parent) = path.parent() { - if !parent.exists() { - return Err(Error::FileSystem(format!("Parent directory {:?} does not exist", parent))); - } + if let Some(parent) = path.parent() + && !parent.exists() + { + return Err(Error::FileSystem(format!("Parent directory {:?} does not exist", parent))); } fs::write(file_path.as_str(), content) @@ -30,9 +30,7 @@ pub fn read_file(file_path: &SafePath) -> Result> { pub fn check_file_exists(file_path: &SafePath) -> bool { // foxguard: ignore[rs/no-path-traversal] // validated with SafePath - let exists = Path::new(file_path.as_str()).exists(); - - exists + Path::new(file_path.as_str()).exists() } /// Constructs a safe path by joining `base` and `file_path` and normalizing it. diff --git a/src/core/handlers/proxy.rs b/src/core/handlers/proxy.rs index 62693f4..a257d6f 100644 --- a/src/core/handlers/proxy.rs +++ b/src/core/handlers/proxy.rs @@ -1,21 +1,22 @@ use crate::{ Error, Result, core::{ - handlers::filesystem::read_file, + handlers::{certs::CertBackgroundRenewal, filesystem::read_file}, models::{ - certs::{TlsCerts, TlsMaterial}, + certs::{TlsMaterial, TlsStore}, proxy::{ProxyConfig, ProxyRoute, ProxyRouteMap}, }, }, error, }; +use arc_swap::ArcSwap; use async_trait::async_trait; use http::header; use pingora::{ ErrorType, http::ResponseHeader, listeners::{TlsAccept, tls::TlsSettings}, - prelude::{Error as PingoraError, HttpPeer, Result as PingoraResult}, + prelude::{Error as PingoraError, HttpPeer, Result as PingoraResult, background_service}, proxy::{ProxyHttp, Session, http_proxy_service}, server::{Server, configuration::ServerConf}, services::Service, @@ -23,7 +24,12 @@ use pingora::{ }; use std::{collections::HashMap, sync::Arc}; -pub fn run_proxy(proxy_config: ProxyConfig, routes: Vec) -> Result<()> { +pub fn run_proxy( + proxy_config: ProxyConfig, + routes: Vec, + tls_store: TlsStore, + renewal: CertBackgroundRenewal, +) -> Result<()> { let mut server = Server::new(None).map_err(|e| Error::Proxy(e.to_string()))?; server.bootstrap(); @@ -31,34 +37,13 @@ pub fn run_proxy(proxy_config: ProxyConfig, routes: Vec) -> Result<( let server_conf = server.configuration.clone(); let http_addr = format!("{}:{}", proxy_config.input_address, *proxy_config.http_port); let https_addr = format!("{}:{}", proxy_config.input_address, *proxy_config.https_port); - let mut routes_map = HashMap::new(); - let mut tls_certs = HashMap::new(); for route in routes { - if *route.tls { - let cert_bytes = read_file(&route.cert_path)?; - let cert = tls::x509::X509::from_pem(&cert_bytes) - .map_err(|e| Error::Certificate(format!("Failed to parse certificate: {}", e)))?; - - let key_bytes = read_file(&route.key_path)?; - let key = tls::pkey::PKey::private_key_from_pem(&key_bytes) - .map_err(|e| Error::Certificate(format!("Failed to parse private key: {}", e)))?; - - tls_certs.insert( - route.host.clone(), - TlsMaterial { - cert, - key, - }, - ); - } - routes_map.insert(route.host.clone(), route); } let routes_map = Arc::new(routes_map); - let tls_certs = Arc::new(tls_certs); // plain proxies with redirect let plain_service = @@ -66,10 +51,12 @@ pub fn run_proxy(proxy_config: ProxyConfig, routes: Vec) -> Result<( server.add_service(plain_service); // tls proxies - let tls_service = - tls_routes_service(server_conf, https_addr.clone(), routes_map.clone(), tls_certs)?; + let tls_service = tls_routes_service(server_conf, https_addr, routes_map, tls_store)?; server.add_service(tls_service); + // background cert services + server.add_service(background_service("cert-renewal", renewal)); + server.run_forever(); } @@ -77,14 +64,14 @@ pub fn tls_routes_service( server_conf: Arc, listen_addr: String, routes_map: ProxyRouteMap, - tls_certs: TlsCerts, + tls_store: TlsStore, ) -> Result { let proxy_app = ProxyToUpstream::new(routes_map.clone(), false); let mut service = http_proxy_service(&server_conf, proxy_app); - let sni_resolver = SniResolver::new(tls_certs); - let callback = Box::new(sni_resolver); + let cert_resolver = CertResolver::new(tls_store); + let callback = Box::new(cert_resolver); let tls_settings = TlsSettings::with_callbacks(callback).map_err(|e| Error::Proxy(e.to_string()))?; service.add_tls_with_settings(&listen_addr, None, tls_settings); @@ -186,20 +173,20 @@ fn host_from_session(session: &Session) -> Option<&str> { session.get_header(header::HOST).and_then(|h| h.to_str().ok()) } -struct SniResolver { - certs: TlsCerts, +struct CertResolver { + tls_store: TlsStore, } -impl SniResolver { - fn new(tls_certs: TlsCerts) -> Self { +impl CertResolver { + fn new(tls_store: TlsStore) -> Self { Self { - certs: tls_certs, + tls_store, } } } #[async_trait] -impl TlsAccept for SniResolver { +impl TlsAccept for CertResolver { async fn certificate_callback(&self, ssl: &mut ssl::SslRef) -> () { let sni_provided = ssl.servername(ssl::NameType::HOST_NAME).map(str::to_owned); @@ -208,10 +195,12 @@ impl TlsAccept for SniResolver { return; }; + let certs = self.tls_store.load(); + let Some(TlsMaterial { cert, key, - }) = self.certs.get(sni_provided.as_str()) + }) = certs.get(sni_provided.as_str()) else { error!("No certificate found for SNI: {}", sni_provided); return; diff --git a/src/core/models/certs.rs b/src/core/models/certs.rs index 818149a..a0546b6 100644 --- a/src/core/models/certs.rs +++ b/src/core/models/certs.rs @@ -1,4 +1,5 @@ -use crate::core::models::routes::Host; +use crate::core::models::{filesystem::SafePath, routes::Host}; +use arc_swap::ArcSwap; use pingora::tls::{ pkey::{PKey, Private}, x509::X509, @@ -31,7 +32,10 @@ pub struct TlsMaterial { pub key: PKey, } -pub type TlsCerts = Arc>; +pub type KeyPath = SafePath; +pub type CertPath = SafePath; + +pub type TlsStore = Arc>>; // --- EMAIL --- #[derive(Debug, Clone, Deserialize)] diff --git a/src/core/models/proxy.rs b/src/core/models/proxy.rs index dab8d40..ca5438f 100644 --- a/src/core/models/proxy.rs +++ b/src/core/models/proxy.rs @@ -13,13 +13,10 @@ pub struct ProxyRoute { pub host: Host, pub upstream: Upstream, pub tls: ProxyTls, - pub cert_path: SafePath, - pub key_path: SafePath, } #[derive(Debug, Clone)] pub struct ProxyConfig { - pub cert_dir: CertDir, pub http_port: ProxyPort, pub https_port: ProxyPort, pub input_address: ProxyInputAddress, diff --git a/src/core/models/tasks.rs b/src/core/models/tasks.rs index 1cc2b46..8722ed1 100644 --- a/src/core/models/tasks.rs +++ b/src/core/models/tasks.rs @@ -2,6 +2,7 @@ use serde::Deserialize; use std::ops::Deref; // --- TASK INTERVAL --- +/// Interval for a task in seconds. #[derive(Debug, Clone, Deserialize)] pub struct TaskInterval(u64); diff --git a/src/core/setup.rs b/src/core/setup.rs index b133345..1ed4ab8 100644 --- a/src/core/setup.rs +++ b/src/core/setup.rs @@ -2,12 +2,14 @@ use crate::{ Error, Result, core::{ handlers::{ - certs::generate_certs, + certs::{ + CertBackgroundRenewal, certificate_paths, create_initial_certs, load_tls_store, + }, filesystem::{check_file_exists, safe_path}, proxy::run_proxy, }, models::{ - certs::{CertDir, CertificateConfig, CertificateType, Email}, + certs::{CertDir, CertificateConfig, CertificateType, Email, TlsStore}, proxy::{ProxyConfig, ProxyRoute, ProxyTls}, routes::Route, tasks::TaskInterval, @@ -72,61 +74,54 @@ impl HandleCertificates { } } - pub fn run(self) -> Result<()> { - generate_certs(self.certificate_configs.clone())?; + pub fn run(self) -> Result<(TlsStore, CertBackgroundRenewal)> { + create_initial_certs(&self.certificate_configs)?; - // spawn(async move { - // info!("starting certificates tasks..."); - // background_certs_task(self.certificates, self.task_interval).await; - // }); + let store = load_tls_store(&self.certificate_configs)?; + let renewal = CertBackgroundRenewal::new( + self.certificate_configs.clone(), + self.task_interval.clone(), + store.clone(), + ); - Ok(()) + Ok((store, renewal)) } } pub struct HandleProxy { proxy_config: ProxyConfig, proxy_routes: Vec, + tls_store: TlsStore, } impl HandleProxy { - pub fn new(proxy_config: ProxyConfig, routes: Vec) -> Result { + pub fn new(proxy_config: ProxyConfig, routes: Vec, tls_store: TlsStore) -> Result { let mut proxy_routes = Vec::new(); for route in routes { - let cert_filename = format!("{}.pem", route.host); - let key_filename = format!("{}.key", route.host); - - let key_path = safe_path(&proxy_config.cert_dir, &key_filename)?; - let cert_path = safe_path(&proxy_config.cert_dir, &cert_filename)?; - - let has_tls_files = check_file_exists(&key_path) && check_file_exists(&cert_path); - - // We only show a warning so its easier to debug once its running, - // but we are not stopping any traffic. - if !has_tls_files && route.cert_type != CertificateType::None { - warn!("Certificate files not found for host `{}` but is expected", route.host); - } - proxy_routes.push(ProxyRoute { host: route.host.clone(), upstream: route.upstream.clone(), tls: ProxyTls::from(route.cert_type != CertificateType::None), - cert_path, - key_path, }); } Ok(Self { proxy_config, proxy_routes, + tls_store, }) } - pub fn run(&self) -> Result<()> { + pub fn run(&self, renewal: CertBackgroundRenewal) -> Result<()> { info!("proxy running..."); - run_proxy(self.proxy_config.clone(), self.proxy_routes.clone())?; + run_proxy( + self.proxy_config.clone(), + self.proxy_routes.clone(), + self.tls_store.clone(), + renewal, + )?; Err(Error::Proxy("proxy exited".to_string())) } diff --git a/src/main.rs b/src/main.rs index ad61be0..bf0e8fd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ pub use error::{Error, Result}; mod config; mod core; pub mod error; +mod services; mod utils; // entry needs to be synchronous as pingora has there own @@ -29,17 +30,16 @@ fn main() -> Result<()> { config.routes.clone(), config.task_interval, ); - cert_handler.run()?; + let (store, renewal) = cert_handler.run()?; // start the proxy let proxy_config = ProxyConfig { - cert_dir: config.cert_dir.clone(), http_port: config.http_port, https_port: config.https_port, input_address: config.input_address, }; - let proxy_handler = HandleProxy::new(proxy_config, config.routes)?; - proxy_handler.run()?; + let proxy_handler = HandleProxy::new(proxy_config, config.routes, store)?; + proxy_handler.run(renewal)?; Err(Error::MainLoopClosed) } diff --git a/src/services/certs/acme.rs b/src/services/certs/acme.rs new file mode 100644 index 0000000..87a9742 --- /dev/null +++ b/src/services/certs/acme.rs @@ -0,0 +1,7 @@ +use crate::{Error, Result, core::models::certs::CertificateConfig, info}; + +pub fn create_acme_dns_challenge(config: &CertificateConfig) -> Result<()> { + info!("creating acme dns challenge for {}", config.host); + + Ok(()) +} diff --git a/src/services/certs/mod.rs b/src/services/certs/mod.rs new file mode 100644 index 0000000..7c4495a --- /dev/null +++ b/src/services/certs/mod.rs @@ -0,0 +1,2 @@ +pub mod acme; +pub mod self_signed; diff --git a/src/services/certs/self_signed.rs b/src/services/certs/self_signed.rs new file mode 100644 index 0000000..21099bf --- /dev/null +++ b/src/services/certs/self_signed.rs @@ -0,0 +1,34 @@ +use crate::{ + Error, Result, + core::{ + handlers::{ + certs::certificate_paths, + filesystem::{safe_path, write_file}, + }, + models::certs::CertificateConfig, + }, + info, +}; +use rcgen::{CertifiedKey, generate_simple_self_signed}; + +pub fn create_self_signed_certificate_files(config: &CertificateConfig) -> Result<()> { + info!("generating self-signed certificate for {}", config.host); + + let subject_alt_names = vec![config.host.to_string()]; + + let (key_path, cert_path) = certificate_paths(&config.host, &config.cert_dir)?; + + let CertifiedKey { + cert, + signing_key, + } = generate_simple_self_signed(subject_alt_names) + .map_err(|e| Error::Certificate(e.to_string()))?; + + let cert_serialized = cert.pem(); + let key_serialized = signing_key.serialize_pem(); + + write_file(cert_path, cert_serialized.as_bytes())?; + write_file(key_path, key_serialized.as_bytes())?; + + Ok(()) +} diff --git a/src/services/mod.rs b/src/services/mod.rs new file mode 100644 index 0000000..e410b93 --- /dev/null +++ b/src/services/mod.rs @@ -0,0 +1 @@ +pub mod certs; From aa27218e3eddd0873f4c407dd35ea03a62a67464 Mon Sep 17 00:00:00 2001 From: Max van der Schee <26795741+mvdschee@users.noreply.github.com> Date: Sun, 7 Jun 2026 22:56:20 +0200 Subject: [PATCH 03/14] clearify the port mapping in docker and how AI was used --- README.md | 4 +++- docker-compose.yml | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 140042e..42aff7c 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,8 @@ services: proxy: image: ghcr.io/mvdschee/reverse-proxy:latest restart: unless-stopped + # host ports 80/443 mapped to container ports 8080/8443 + # see ## Under the hood for explanation ports: - "80:8080" - "443:8443" @@ -164,7 +166,7 @@ I'll be upfront on every public project about what was done with AI. For this on - Research and tradeoff discussions - Cleanup of the README and other prose -- Talking through code-level solutions +- Discussing code-level solutions - Generating the Docker image scaffolding from a spec # TODO: diff --git a/docker-compose.yml b/docker-compose.yml index dec2d7b..a289748 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,6 +6,7 @@ services: context: . dockerfile: .docker/Dockerfile ports: + # nonroot user can't bind to ports < 1024, so map to 8080/8443 - "80:8080" - "443:8443" # Lets upstream = "host.docker.internal:" reach apps running From c208fd965edf43ff6147a9d69d401e87aa0d54b5 Mon Sep 17 00:00:00 2001 From: Max van der Schee <26795741+mvdschee@users.noreply.github.com> Date: Sun, 14 Jun 2026 21:59:33 +0200 Subject: [PATCH 04/14] cleanup for acme cert --- Cargo.lock | 50 +++++--------------------------------- Cargo.toml | 9 +++++-- src/core/handlers/certs.rs | 10 +++----- src/core/setup.rs | 4 +-- 4 files changed, 18 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c080ed6..33592fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -196,29 +196,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" -[[package]] -name = "aws-lc-rs" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" -dependencies = [ - "aws-lc-sys", - "untrusted 0.7.1", - "zeroize", -] - -[[package]] -name = "aws-lc-sys" -version = "0.41.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" -dependencies = [ - "cc", - "cmake", - "dunce", - "fs_extra", -] - [[package]] name = "backtrace" version = "0.3.76" @@ -713,12 +690,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - [[package]] name = "either" version = "1.15.0" @@ -1036,9 +1007,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", "itoa", @@ -1194,7 +1165,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f05ad37c421b962354c358d347d4a6130151df9407978372d3ad7f0c8f71a64" dependencies = [ "async-trait", - "aws-lc-rs", "base64", "bytes", "http", @@ -1205,6 +1175,7 @@ dependencies = [ "hyper-rustls", "hyper-util", "rcgen", + "ring", "rustls", "rustls-pki-types", "serde", @@ -2033,7 +2004,6 @@ version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57f6d249aad744e274e682777a50283a225a32705394ee6d5fcc01efa25e4055" dependencies = [ - "aws-lc-rs", "pem", "ring", "rustls-pki-types", @@ -2090,7 +2060,7 @@ dependencies = [ "cfg-if", "getrandom 0.2.17", "libc", - "untrusted 0.9.0", + "untrusted", "windows-sys 0.52.0", ] @@ -2159,8 +2129,8 @@ version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ - "aws-lc-rs", "once_cell", + "ring", "rustls-pki-types", "rustls-webpki", "subtle", @@ -2221,10 +2191,9 @@ version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ - "aws-lc-rs", "ring", "rustls-pki-types", - "untrusted 0.9.0", + "untrusted", ] [[package]] @@ -2771,12 +2740,6 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - [[package]] name = "untrusted" version = "0.9.0" @@ -3083,7 +3046,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" dependencies = [ "asn1-rs", - "aws-lc-rs", "data-encoding", "der-parser", "lazy_static", diff --git a/Cargo.toml b/Cargo.toml index 6953af3..7755965 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,12 @@ arc-swap = "1.9.1" # used Ring as backend for default rcgen = { version = "0.14.8", features = ["default"] } # match the backed from rcgen -instant-acme = { version = "0.8.5", features = ["rcgen"] } +instant-acme = { version = "0.8.5", default-features = false, features = [ + "ring", + "rcgen", + "hyper-rustls", +] } + # TOML toml = "1.1.2" @@ -42,5 +47,5 @@ thiserror = "2.0.18" chrono = { version = "0.4.45", features = ["serde"] } # HTTP -http = "1.4.1" +http = "1.4.2" pingora = { version = "0.8.1", features = ["proxy", "boringssl"] } diff --git a/src/core/handlers/certs.rs b/src/core/handlers/certs.rs index 6139485..7dd81ce 100644 --- a/src/core/handlers/certs.rs +++ b/src/core/handlers/certs.rs @@ -24,7 +24,7 @@ use rcgen::{CertifiedKey, generate_simple_self_signed}; use std::{collections::HashMap, sync::Arc, time::Duration}; use tokio::time; -pub fn create_initial_certs(certificate_configs: &Vec) -> Result<()> { +pub fn create_self_signed_certs(certificate_configs: &Vec) -> Result<()> { for config in certificate_configs { match config.cert_type { CertificateType::SelfSigned => { @@ -33,12 +33,7 @@ pub fn create_initial_certs(certificate_configs: &Vec) -> Res // for selfsigned we will create the certs here right away create_self_signed_certificate_files(config); }, - CertificateType::Acme => { - // we do not generate any certs here that is for the backend process, - // we are only displaying the DNS entry that needs to be included to make sure you are able to get a cert - create_acme_dns_challenge(config); - }, - CertificateType::None => {}, + _ => {}, } } @@ -121,6 +116,7 @@ impl BackgroundService for CertBackgroundRenewal { // - renew a certificate if its needed, and update the tls_store with the new certificate data // - check if the dns has its text entry so we can start the process of generating certificates for that domein // + info!("background thing"); tokio::select! { _ = tokio::time::sleep(Duration::from_secs(*self.task_interval)) => {} diff --git a/src/core/setup.rs b/src/core/setup.rs index 1ed4ab8..c8eebe1 100644 --- a/src/core/setup.rs +++ b/src/core/setup.rs @@ -3,7 +3,7 @@ use crate::{ core::{ handlers::{ certs::{ - CertBackgroundRenewal, certificate_paths, create_initial_certs, load_tls_store, + CertBackgroundRenewal, certificate_paths, create_self_signed_certs, load_tls_store, }, filesystem::{check_file_exists, safe_path}, proxy::run_proxy, @@ -75,7 +75,7 @@ impl HandleCertificates { } pub fn run(self) -> Result<(TlsStore, CertBackgroundRenewal)> { - create_initial_certs(&self.certificate_configs)?; + create_self_signed_certs(&self.certificate_configs)?; let store = load_tls_store(&self.certificate_configs)?; let renewal = CertBackgroundRenewal::new( From c4798ba6646e22b2f824fd0d76a0be94f7f048c3 Mon Sep 17 00:00:00 2001 From: Max van der Schee <26795741+mvdschee@users.noreply.github.com> Date: Mon, 15 Jun 2026 23:12:52 +0200 Subject: [PATCH 05/14] WIP background acme cert creation --- Cargo.lock | 36 ++++++++++++++++++++++++++++++++++-- Cargo.toml | 3 +++ example/example.toml | 2 +- src/config.rs | 4 ++-- src/core/handlers/certs.rs | 38 ++++++++++++++++++++++++++++++++------ src/core/models/certs.rs | 1 - src/core/setup.rs | 4 +++- src/services/certs/acme.rs | 28 +++++++++++++++++++++++++++- 8 files changed, 102 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 33592fb..672bcb7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -196,6 +196,28 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "backtrace" version = "0.3.76" @@ -690,6 +712,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "either" version = "1.15.0" @@ -1918,6 +1946,7 @@ dependencies = [ "instant-acme", "pingora", "rcgen", + "rustls", "serde", "thiserror 2.0.18", "tokio", @@ -2129,6 +2158,8 @@ version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ + "aws-lc-rs", + "log", "once_cell", "ring", "rustls-pki-types", @@ -2191,6 +2222,7 @@ version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -2293,9 +2325,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", diff --git a/Cargo.toml b/Cargo.toml index 7755965..d648627 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ instant-acme = { version = "0.8.5", default-features = false, features = [ "rcgen", "hyper-rustls", ] } +rustls = "0.23.40" # TOML @@ -41,6 +42,8 @@ toml = "1.1.2" # Json serde = { version = "1.0.228", features = ["derive"] } +# only needed for testing +# serde_json = { version = "1.0.150" } # Others thiserror = "2.0.18" diff --git a/example/example.toml b/example/example.toml index 69e6f8a..0da207d 100644 --- a/example/example.toml +++ b/example/example.toml @@ -1,5 +1,5 @@ [acme] -email = "max@example.com" +email = "max@example.com" # use a real email this will fail the even on local testing [[routes]] host = "git.example.com" diff --git a/src/config.rs b/src/config.rs index 6a6ddde..efe2013 100644 --- a/src/config.rs +++ b/src/config.rs @@ -16,8 +16,8 @@ const HTTP_PORT_ENV: &str = "HTTP_PORT"; const HTTPS_PORT_ENV: &str = "HTTPS_PORT"; const CERT_DIR_DEFAULT: &str = ".certs/"; -const HTTP_PORT_DEFAULT: u16 = 80; -const HTTPS_PORT_DEFAULT: u16 = 443; +const HTTP_PORT_DEFAULT: u16 = 880; +const HTTPS_PORT_DEFAULT: u16 = 8443; const INPUT_ADDRESS: &str = "0.0.0.0"; // in seconds diff --git a/src/core/handlers/certs.rs b/src/core/handlers/certs.rs index 7dd81ce..359fbd2 100644 --- a/src/core/handlers/certs.rs +++ b/src/core/handlers/certs.rs @@ -4,7 +4,7 @@ use crate::{ handlers::filesystem::{check_file_exists, read_file, safe_path, write_file}, models::{ certs::{ - CertDir, CertPath, CertificateConfig, CertificateType, KeyPath, TlsMaterial, + CertDir, CertPath, CertificateConfig, CertificateType, Email, KeyPath, TlsMaterial, TlsStore, }, routes::Host, @@ -13,7 +13,8 @@ use crate::{ }, info, services::certs::{ - acme::create_acme_dns_challenge, self_signed::create_self_signed_certificate_files, + acme::{create_account, create_acme_dns_challenge}, + self_signed::create_self_signed_certificate_files, }, warn, }; @@ -92,6 +93,7 @@ pub struct CertBackgroundRenewal { pub certificate_configs: Vec, pub task_interval: TaskInterval, pub tls_store: TlsStore, + pub email: Email, } impl CertBackgroundRenewal { @@ -99,11 +101,13 @@ impl CertBackgroundRenewal { certificate_configs: Vec, task_interval: TaskInterval, tls_store: TlsStore, + email: Email, ) -> Self { Self { certificate_configs, task_interval, tls_store, + email, } } } @@ -111,11 +115,33 @@ impl CertBackgroundRenewal { #[async_trait] impl BackgroundService for CertBackgroundRenewal { async fn start(&self, mut shutdown: ShutdownWatch) { + // TODO what to do when creating an account fails (acme endpoints is 500 etc..) + let account = create_account(&self.email).await; + + let configs = self + .certificate_configs + .clone() + .into_iter() + .filter(|c| c.cert_type == CertificateType::Acme); + loop { - // TODO: couple things we will do in this background task - // - renew a certificate if its needed, and update the tls_store with the new certificate data - // - check if the dns has its text entry so we can start the process of generating certificates for that domein - // + for config in configs.clone() { + // check if we have an order open or need to create a new one + // + // if so verify the dns records with cloudflare + // + // if its set allow the order to be proccessed + // + // write to the file system + // + // swap the file content in the store with the new values if any + // + // + // note: we write to the file system so we can pick the files up and load them in the store when we restart or bootup + // this so we don't have to deal here with loading if the files are there (so we only have to check here if the order is invalid or valid and swap when its time) + // so on boot we load all the tls certs from self-signed / acme and check in this flow it its valid or not and fix it with a swap. + } + info!("background thing"); tokio::select! { diff --git a/src/core/models/certs.rs b/src/core/models/certs.rs index a0546b6..e7eaa6d 100644 --- a/src/core/models/certs.rs +++ b/src/core/models/certs.rs @@ -23,7 +23,6 @@ pub enum CertificateType { pub struct CertificateConfig { pub host: Host, pub cert_dir: CertDir, - pub email: Email, pub cert_type: CertificateType, } diff --git a/src/core/setup.rs b/src/core/setup.rs index c8eebe1..6f74afd 100644 --- a/src/core/setup.rs +++ b/src/core/setup.rs @@ -49,6 +49,7 @@ impl HandleFileSystem { pub struct HandleCertificates { certificate_configs: Vec, task_interval: TaskInterval, + email: Email, } impl HandleCertificates { @@ -63,7 +64,6 @@ impl HandleCertificates { .map(|route| CertificateConfig { host: route.host.clone(), cert_dir: cert_dir.clone(), - email: email.clone(), cert_type: route.cert_type.clone(), }) .collect::>(); @@ -71,6 +71,7 @@ impl HandleCertificates { Self { certificate_configs, task_interval, + email, } } @@ -82,6 +83,7 @@ impl HandleCertificates { self.certificate_configs.clone(), self.task_interval.clone(), store.clone(), + self.email.clone(), ); Ok((store, renewal)) diff --git a/src/services/certs/acme.rs b/src/services/certs/acme.rs index 87a9742..83a45a6 100644 --- a/src/services/certs/acme.rs +++ b/src/services/certs/acme.rs @@ -1,7 +1,33 @@ -use crate::{Error, Result, core::models::certs::CertificateConfig, info}; +use crate::{ + Error, Result, + core::models::certs::{CertificateConfig, Email}, + info, +}; +use instant_acme::{Account, AccountCredentials, LetsEncrypt, NewAccount}; +use rustls::crypto::CryptoProvider; pub fn create_acme_dns_challenge(config: &CertificateConfig) -> Result<()> { info!("creating acme dns challenge for {}", config.host); Ok(()) } + +pub async fn create_account(email: &Email) -> Result<(Account, AccountCredentials)> { + CryptoProvider::install_default(rustls::crypto::ring::default_provider()); + + let (account, credentials) = Account::builder() + .map_err(|e| Error::Certificate(e.to_string()))? + .create( + &NewAccount { + contact: &[&format!("mailto:{}", email.as_str())], + terms_of_service_agreed: true, + only_return_existing: false, + }, + LetsEncrypt::Staging.url().to_owned(), + None, + ) + .await + .map_err(|e| Error::Certificate(e.to_string()))?; + + Ok((account, credentials)) +} From a880376782d14688b9afe1e2daa892f7a99dde16 Mon Sep 17 00:00:00 2001 From: Max van der Schee <26795741+mvdschee@users.noreply.github.com> Date: Tue, 16 Jun 2026 09:00:45 +0200 Subject: [PATCH 06/14] set back the port incase I forget --- src/config.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index efe2013..6a6ddde 100644 --- a/src/config.rs +++ b/src/config.rs @@ -16,8 +16,8 @@ const HTTP_PORT_ENV: &str = "HTTP_PORT"; const HTTPS_PORT_ENV: &str = "HTTPS_PORT"; const CERT_DIR_DEFAULT: &str = ".certs/"; -const HTTP_PORT_DEFAULT: u16 = 880; -const HTTPS_PORT_DEFAULT: u16 = 8443; +const HTTP_PORT_DEFAULT: u16 = 80; +const HTTPS_PORT_DEFAULT: u16 = 443; const INPUT_ADDRESS: &str = "0.0.0.0"; // in seconds From bd14719edb50442176dfabb62aa44442237c0d6f Mon Sep 17 00:00:00 2001 From: Max van der Schee <26795741+mvdschee@users.noreply.github.com> Date: Sat, 27 Jun 2026 23:44:55 +0200 Subject: [PATCH 07/14] added order creation and WIP on dns provider services --- .gitignore | 1 + Cargo.lock | 4 ++-- Cargo.toml | 2 +- makefile | 2 +- rust-toolchain.toml | 2 +- src/config.rs | 4 ++-- src/core/handlers/certs.rs | 42 +++++++++++++++++++++++++++++++++----- src/error.rs | 3 +++ src/services/certs/acme.rs | 35 +++++++++++++++++++++---------- src/services/dns/mod.rs | 0 10 files changed, 72 insertions(+), 23 deletions(-) create mode 100644 src/services/dns/mod.rs diff --git a/.gitignore b/.gitignore index fb68da6..dfb8b15 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ target/ .env .DS_Store .certs/ +example/local.toml diff --git a/Cargo.lock b/Cargo.lock index 672bcb7..834f359 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2154,9 +2154,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.40" +version = "0.23.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" dependencies = [ "aws-lc-rs", "log", diff --git a/Cargo.toml b/Cargo.toml index d648627..aca6c91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ instant-acme = { version = "0.8.5", default-features = false, features = [ "rcgen", "hyper-rustls", ] } -rustls = "0.23.40" +rustls = "0.23.41" # TOML diff --git a/makefile b/makefile index 3cd59f6..b0e05da 100644 --- a/makefile +++ b/makefile @@ -1,5 +1,5 @@ dev: - CONFIG_PATH=./example/example.toml watchexec -q -c -w src --exts rs --restart "cargo run" + CONFIG_PATH=./example/local.toml watchexec -q -c -w src --exts rs --restart "cargo run" scan: foxguard --config .foxguard.yml diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 554f234..03e6777 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.90.0" +channel = "1.96.0" components = ["clippy", "rustfmt", "rust-analyzer"] diff --git a/src/config.rs b/src/config.rs index 6a6ddde..3aa71ae 100644 --- a/src/config.rs +++ b/src/config.rs @@ -20,8 +20,8 @@ const HTTP_PORT_DEFAULT: u16 = 80; const HTTPS_PORT_DEFAULT: u16 = 443; const INPUT_ADDRESS: &str = "0.0.0.0"; -// in seconds -const CERT_BACKGROUND_TASK_INTERVAL: u64 = 120; +/// in seconds +const CERT_BACKGROUND_TASK_INTERVAL: u64 = 3600; // 1 hour #[derive(Debug, Clone)] pub struct Config { diff --git a/src/core/handlers/certs.rs b/src/core/handlers/certs.rs index 359fbd2..c28ffc9 100644 --- a/src/core/handlers/certs.rs +++ b/src/core/handlers/certs.rs @@ -11,15 +11,16 @@ use crate::{ tasks::TaskInterval, }, }, - info, + error, info, services::certs::{ - acme::{create_account, create_acme_dns_challenge}, + acme::{create_account, create_order}, self_signed::create_self_signed_certificate_files, }, warn, }; use arc_swap::ArcSwap; use async_trait::async_trait; +use instant_acme::{Identifier, NewOrder, OrderStatus}; use pingora::{server::ShutdownWatch, services::background::BackgroundService, tls}; use rcgen::{CertifiedKey, generate_simple_self_signed}; use std::{collections::HashMap, sync::Arc, time::Duration}; @@ -53,7 +54,8 @@ pub fn load_tls_store(certificate_configs: &Vec) -> Result pair, + Err(ref err) => { + error!("{err:?}"); + continue; + }, + }; + for config in configs.clone() { - // check if we have an order open or need to create a new one + let order_result = create_order(&account, &config.host).await; + + let mut order = match order_result { + Ok(order) => order, + Err(err) => { + error!("{err:?}"); + continue; + }, + }; + + let state = order.state(); + info!("order state: {:#?}", state); + + if !matches!(state.status, OrderStatus::Pending) { + warn!("Skipping non-Pending order: {:?}", state.status); + continue; + } + // // if so verify the dns records with cloudflare // diff --git a/src/error.rs b/src/error.rs index a9f79ac..e1e4da8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -19,6 +19,9 @@ pub enum Error { #[error("certificate error: {0}")] Certificate(String), + #[error("Acme error: {0}")] + Acme(String), + #[error("proxy error: {0}")] Proxy(String), diff --git a/src/services/certs/acme.rs b/src/services/certs/acme.rs index 83a45a6..f6aaea5 100644 --- a/src/services/certs/acme.rs +++ b/src/services/certs/acme.rs @@ -1,33 +1,46 @@ use crate::{ Error, Result, - core::models::certs::{CertificateConfig, Email}, - info, + core::models::{ + certs::{CertificateConfig, Email}, + routes::Host, + }, +}; +use instant_acme::{ + Account, AccountCredentials, Identifier, LetsEncrypt, NewAccount, NewOrder, Order, }; -use instant_acme::{Account, AccountCredentials, LetsEncrypt, NewAccount}; use rustls::crypto::CryptoProvider; -pub fn create_acme_dns_challenge(config: &CertificateConfig) -> Result<()> { - info!("creating acme dns challenge for {}", config.host); - - Ok(()) -} - pub async fn create_account(email: &Email) -> Result<(Account, AccountCredentials)> { CryptoProvider::install_default(rustls::crypto::ring::default_provider()); let (account, credentials) = Account::builder() - .map_err(|e| Error::Certificate(e.to_string()))? + .map_err(|e| Error::Acme(e.to_string()))? .create( &NewAccount { contact: &[&format!("mailto:{}", email.as_str())], terms_of_service_agreed: true, only_return_existing: false, }, + // TODO: switch to prod LetsEncrypt::Staging.url().to_owned(), None, ) .await - .map_err(|e| Error::Certificate(e.to_string()))?; + .map_err(|e| Error::Acme(e.to_string()))?; Ok((account, credentials)) } + +pub async fn create_order(account: &Account, host: &Host) -> Result { + let identifier = Identifier::Dns(host.to_string()); + // instant_acme support multiple host per order, + // but we need to know which dns needs to be updated + // which is why we split them up, this is maybe something we can refine in V2 + let identifiers = vec![identifier]; + let order_result = account + .new_order(&NewOrder::new(&identifiers)) + .await + .map_err(|e| Error::Acme(format!("Failed with new_order: {}", e))); + + order_result +} diff --git a/src/services/dns/mod.rs b/src/services/dns/mod.rs new file mode 100644 index 0000000..e69de29 From 91d0f5d425d0e9e8c956e895e66afffb0e6d90d8 Mon Sep 17 00:00:00 2001 From: Max van der Schee <26795741+mvdschee@users.noreply.github.com> Date: Mon, 10 Aug 2026 08:23:59 +0200 Subject: [PATCH 08/14] WIP DNS providers integration --- src/core/models/dns.rs | 26 ++++++++++++++++++++++++++ src/core/models/mod.rs | 1 + src/error.rs | 3 +++ src/services/dns/mod.rs | 15 +++++++++++++++ src/services/mod.rs | 1 + 5 files changed, 46 insertions(+) create mode 100644 src/core/models/dns.rs diff --git a/src/core/models/dns.rs b/src/core/models/dns.rs new file mode 100644 index 0000000..f5792e4 --- /dev/null +++ b/src/core/models/dns.rs @@ -0,0 +1,26 @@ +use serde::Deserialize; +use std::{fmt, ops::Deref}; + +// --- EMAIL --- +#[derive(Debug, Clone, Deserialize)] +pub struct Record(String); + +impl Deref for Record { + type Target = String; + + fn deref(&self) -> &String { + &self.0 + } +} + +impl fmt::Display for Record { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for Record { + fn from(s: String) -> Self { + Record(s) + } +} diff --git a/src/core/models/mod.rs b/src/core/models/mod.rs index 1cf9ecb..7052b71 100644 --- a/src/core/models/mod.rs +++ b/src/core/models/mod.rs @@ -1,4 +1,5 @@ pub mod certs; +pub mod dns; pub mod filesystem; pub mod proxy; pub mod routes; diff --git a/src/error.rs b/src/error.rs index e1e4da8..80712fa 100644 --- a/src/error.rs +++ b/src/error.rs @@ -25,6 +25,9 @@ pub enum Error { #[error("proxy error: {0}")] Proxy(String), + #[error("dns error: {0}")] + Dns(String), + #[error("config error: {0}")] Config(String), diff --git a/src/services/dns/mod.rs b/src/services/dns/mod.rs index e69de29..cf94684 100644 --- a/src/services/dns/mod.rs +++ b/src/services/dns/mod.rs @@ -0,0 +1,15 @@ +use crate::core::models::dns::Record; +use crate::core::models::routes::Host; +use crate::{Error, Result}; + +trait DnsProvider: Send + Sync + 'static { + /// Only returns single record + /// gets all records but internally filters out all records either, + /// after the call or if it supports it directly on the endpoint call. + fn get_challenge_record(&self, host: Host) -> Result; + /// Single fuction to set and update + /// will return the set record (if returned otherwise fake it) + fn update_challenge_record(&self, host: Host) -> Result; +} + +pub struct CloudflareDns {} diff --git a/src/services/mod.rs b/src/services/mod.rs index e410b93..ad80d1c 100644 --- a/src/services/mod.rs +++ b/src/services/mod.rs @@ -1 +1,2 @@ pub mod certs; +pub mod dns; From ddc5edd458493a9267eeee015a062cd8d6dc4ae6 Mon Sep 17 00:00:00 2001 From: Max van der Schee <26795741+mvdschee@users.noreply.github.com> Date: Tue, 11 Aug 2026 17:43:56 +0200 Subject: [PATCH 09/14] added macro for newtype string --- src/core/models/certs.rs | 53 ++++------------------------------- src/core/models/dns.rs | 38 +++++++++++++------------ src/core/models/filesystem.rs | 25 ++--------------- src/core/models/proxy.rs | 36 ++++++------------------ src/core/models/routes.rs | 53 +++++------------------------------ src/utils.rs | 27 ++++++++++++++++++ 6 files changed, 71 insertions(+), 161 deletions(-) diff --git a/src/core/models/certs.rs b/src/core/models/certs.rs index e7eaa6d..e8ddd57 100644 --- a/src/core/models/certs.rs +++ b/src/core/models/certs.rs @@ -1,4 +1,7 @@ -use crate::core::models::{filesystem::SafePath, routes::Host}; +use crate::{ + core::models::{filesystem::SafePath, routes::Host}, + string_newtype, +}; use arc_swap::ArcSwap; use pingora::tls::{ pkey::{PKey, Private}, @@ -6,8 +9,6 @@ use pingora::tls::{ }; use serde::Deserialize; use std::collections::HashMap; -use std::fmt; -use std::ops::Deref; use std::sync::Arc; #[derive(Debug, Clone, Deserialize, Default, PartialEq)] @@ -37,49 +38,7 @@ pub type CertPath = SafePath; pub type TlsStore = Arc>>; // --- EMAIL --- -#[derive(Debug, Clone, Deserialize)] -pub struct Email(String); - -impl Deref for Email { - type Target = String; - - fn deref(&self) -> &String { - &self.0 - } -} - -impl fmt::Display for Email { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -impl From for Email { - fn from(s: String) -> Self { - Email(s) - } -} +string_newtype!(Email, derive(Deserialize)); // --- CERT_DIR --- -#[derive(Debug, Clone, Deserialize)] -pub struct CertDir(String); - -impl Deref for CertDir { - type Target = String; - - fn deref(&self) -> &String { - &self.0 - } -} - -impl fmt::Display for CertDir { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -impl From for CertDir { - fn from(s: String) -> Self { - CertDir(s) - } -} +string_newtype!(CertDir, derive(Deserialize)); diff --git a/src/core/models/dns.rs b/src/core/models/dns.rs index f5792e4..1c28cfc 100644 --- a/src/core/models/dns.rs +++ b/src/core/models/dns.rs @@ -1,26 +1,28 @@ +use crate::string_newtype; use serde::Deserialize; -use std::{fmt, ops::Deref}; -// --- EMAIL --- -#[derive(Debug, Clone, Deserialize)] -pub struct Record(String); +// --- DNS Record --- +string_newtype!(Record, derive(Deserialize)); -impl Deref for Record { - type Target = String; +// --- DNS Provider credentials --- - fn deref(&self) -> &String { - &self.0 - } +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum ProviderCredentails { + Cloudflare(CloudflareProvider), } -impl fmt::Display for Record { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} +// --- add here any other providers credentials --- -impl From for Record { - fn from(s: String) -> Self { - Record(s) - } +// --- CloudFlare --- +#[derive(Debug, Clone, Deserialize)] +pub struct CloudflareProvider { + pub zone_id: ZoneId, + pub api_token: ApiToken, } + +// Zone ID +string_newtype!(ZoneId, derive(Deserialize)); + +// API token +string_newtype!(ApiToken, derive(Deserialize)); diff --git a/src/core/models/filesystem.rs b/src/core/models/filesystem.rs index 53c4570..3a70b54 100644 --- a/src/core/models/filesystem.rs +++ b/src/core/models/filesystem.rs @@ -1,25 +1,4 @@ -use std::{fmt, ops::Deref}; +use crate::string_newtype; // --- SAFEPATH --- -#[derive(Debug, Clone)] -pub struct SafePath(String); - -impl Deref for SafePath { - type Target = String; - - fn deref(&self) -> &String { - &self.0 - } -} - -impl fmt::Display for SafePath { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -impl From for SafePath { - fn from(s: String) -> Self { - SafePath(s) - } -} +string_newtype!(SafePath); diff --git a/src/core/models/proxy.rs b/src/core/models/proxy.rs index ca5438f..7db4914 100644 --- a/src/core/models/proxy.rs +++ b/src/core/models/proxy.rs @@ -1,10 +1,13 @@ -use crate::core::models::{ - certs::CertDir, - filesystem::SafePath, - routes::{Host, Upstream}, +use crate::{ + core::models::{ + certs::CertDir, + filesystem::SafePath, + routes::{Host, Upstream}, + }, + string_newtype, }; use http::{Response, StatusCode, header}; -use std::{collections::HashMap, fmt, ops::Deref, sync::Arc}; +use std::{collections::HashMap, ops::Deref, sync::Arc}; pub type ProxyRouteMap = Arc>; @@ -59,25 +62,4 @@ impl From for ProxyPort { } // --- PROXY INPUT ADDRESS --- -#[derive(Debug, Clone)] -pub struct ProxyInputAddress(String); - -impl Deref for ProxyInputAddress { - type Target = String; - - fn deref(&self) -> &String { - &self.0 - } -} - -impl fmt::Display for ProxyInputAddress { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -impl From for ProxyInputAddress { - fn from(s: String) -> Self { - ProxyInputAddress(s) - } -} +string_newtype!(ProxyInputAddress); diff --git a/src/core/models/routes.rs b/src/core/models/routes.rs index 17649ec..366d319 100644 --- a/src/core/models/routes.rs +++ b/src/core/models/routes.rs @@ -1,6 +1,8 @@ -use crate::core::models::certs::CertificateType; +use crate::{ + core::models::{certs::CertificateType, dns::ProviderCredentails}, + string_newtype, +}; use serde::Deserialize; -use std::{fmt, ops::Deref}; #[derive(Debug, Clone, Deserialize)] pub struct Route { @@ -8,19 +10,11 @@ pub struct Route { pub upstream: Upstream, #[serde(default)] pub cert_type: CertificateType, + pub dns_provider: Option, } // --- HOST --- -#[derive(Debug, Clone, Deserialize, Hash, Eq, PartialEq)] -pub struct Host(String); - -impl Deref for Host { - type Target = String; - - fn deref(&self) -> &String { - &self.0 - } -} +string_newtype!(Host, derive(Deserialize, Hash, Eq, PartialEq)); impl std::borrow::Borrow for Host { fn borrow(&self) -> &str { @@ -28,38 +22,5 @@ impl std::borrow::Borrow for Host { } } -impl fmt::Display for Host { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -impl From for Host { - fn from(s: String) -> Self { - Host(s) - } -} - // --- UPSTREAM --- -#[derive(Debug, Clone, Deserialize)] -pub struct Upstream(String); - -impl Deref for Upstream { - type Target = String; - - fn deref(&self) -> &String { - &self.0 - } -} - -impl fmt::Display for Upstream { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -impl From for Upstream { - fn from(s: String) -> Self { - Upstream(s) - } -} +string_newtype!(Upstream, derive(Deserialize)); diff --git a/src/utils.rs b/src/utils.rs index b253deb..4225ce5 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -18,3 +18,30 @@ macro_rules! error { println!("\x1b[90m{} \x1b[31m{} \x1b[0m{}", chrono::Local::now().format("%H:%M:%S%.3f %d-%m-%y"), "[ERROR]", format_args!($($arg)*)) }; } + +#[macro_export] +macro_rules! string_newtype { + ($name:ident $(, derive($($extra:path),+ $(,)?))?) => { + #[derive(Debug, Clone $(, $($extra),+)?)] + pub struct $name(String); + + impl ::std::ops::Deref for $name { + type Target = String; + fn deref(&self) -> &String { + &self.0 + } + } + + impl ::std::fmt::Display for $name { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + write!(f, "{}", self.0) + } + } + + impl From for $name { + fn from(s: String) -> Self { + $name(s) + } + } + }; +} From 20be828d9ed952f7857a56d428a6ba354f4af3d1 Mon Sep 17 00:00:00 2001 From: Max van der Schee <26795741+mvdschee@users.noreply.github.com> Date: Tue, 11 Aug 2026 17:51:29 +0200 Subject: [PATCH 10/14] BREAKING: set default cert type to None --- README.md | 2 +- example/example.toml | 4 ++-- src/core/models/certs.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7904482..d4d63d7 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ The full schema lives in [`example/example.toml`](example/example.toml). The fie | `acme.email` | yes | Contact email for Let's Encrypt (used once ACME lands; required today even if every route is `none`). | | `routes[].host` | yes | The `Host` header to match (e.g. `app.example.com`). | | `routes[].upstream` | yes | `host:port` to forward to. Use `host.docker.internal:` to reach the host machine from Docker. | -| `routes[].cert_type` | no | `self_signed` (default works on boot), `acme` (WIP), or `none` (HTTP only). Defaults to `acme`. | +| `routes[].cert_type` | no | `self_signed` (default works on boot), `acme` (WIP), or `none` (HTTP only). Defaults to `none`. | ## How it works diff --git a/example/example.toml b/example/example.toml index 0da207d..6eb0a62 100644 --- a/example/example.toml +++ b/example/example.toml @@ -4,9 +4,9 @@ email = "max@example.com" # use a real email this will fail the even on lo [[routes]] host = "git.example.com" upstream = "127.0.0.1:3000" # use "host.docker.internal:3000" when using docker -cert_type = "self_signed" # default is "acme" +cert_type = "self_signed" # default is "none" [[routes]] host = "test.example.com" upstream = "127.0.0.1:3000" # use "host.docker.internal:3000" when using docker -cert_type = "none" # default is "acme" +cert_type = "none" # default is "none" diff --git a/src/core/models/certs.rs b/src/core/models/certs.rs index e8ddd57..b5289cb 100644 --- a/src/core/models/certs.rs +++ b/src/core/models/certs.rs @@ -15,8 +15,8 @@ use std::sync::Arc; #[serde(rename_all = "snake_case")] pub enum CertificateType { SelfSigned, - #[default] Acme, + #[default] None, } From 447eff93953bb5cf439f754c275ccdfe8d85b489 Mon Sep 17 00:00:00 2001 From: Max van der Schee <26795741+mvdschee@users.noreply.github.com> Date: Tue, 11 Aug 2026 23:25:00 +0200 Subject: [PATCH 11/14] WIP cloudflare as first dns provider --- Cargo.lock | 632 +++++++++++++++++++++++++++++++-- Cargo.toml | 15 +- src/services/dns/cloudflare.rs | 13 + src/services/dns/mod.rs | 6 +- 4 files changed, 632 insertions(+), 34 deletions(-) create mode 100644 src/services/dns/cloudflare.rs diff --git a/Cargo.lock b/Cargo.lock index 48a0ffd..8706bdf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -121,9 +121,9 @@ dependencies = [ [[package]] name = "arc-swap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" dependencies = [ "rustversion", ] @@ -146,7 +146,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror 2.0.19", + "thiserror 2.0.20", "time", ] @@ -175,9 +175,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.91" +version = "0.1.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", @@ -426,6 +426,23 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core 0.10.1", +] + [[package]] name = "chrono" version = "0.4.45" @@ -516,6 +533,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -532,6 +559,15 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -724,6 +760,15 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -802,6 +847,15 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + [[package]] name = "fs_extra" version = "1.3.0" @@ -923,8 +977,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -935,10 +991,24 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", ] +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasm-bindgen", +] + [[package]] name = "getset" version = "0.1.6" @@ -1035,9 +1105,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" dependencies = [ "bytes", "itoa", @@ -1122,18 +1192,23 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ + "base64", "bytes", "futures-channel", "futures-util", "http", "http-body", "hyper", + "ipnet", "libc", + "percent-encoding", "pin-project-lite", "socket2", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -1160,12 +1235,115 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -1208,10 +1386,16 @@ dependencies = [ "rustls-pki-types", "serde", "serde_json", - "thiserror 2.0.19", + "thiserror 2.0.20", "tokio", ] +[[package]] +name = "ipnet" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -1245,7 +1429,7 @@ dependencies = [ "jni-sys", "log", "simd_cesu8", - "thiserror 2.0.19", + "thiserror 2.0.20", "walkdir", "windows-link", ] @@ -1336,6 +1520,12 @@ dependencies = [ "libc", ] +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + [[package]] name = "local-ip-address" version = "0.6.12" @@ -1371,6 +1561,12 @@ dependencies = [ "hashbrown 0.16.1", ] +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "memchr" version = "2.8.0" @@ -1386,6 +1582,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1868,6 +2070,15 @@ version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1946,13 +2157,71 @@ dependencies = [ "instant-acme", "pingora", "rcgen", + "reqwest", "rustls", "serde", - "thiserror 2.0.19", + "thiserror 2.0.20", "tokio", "toml", ] +[[package]] +name = "quinn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.20", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand 0.10.2", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.20", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + [[package]] name = "quote" version = "1.0.45" @@ -1968,6 +2237,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" version = "0.8.6" @@ -1989,6 +2264,17 @@ dependencies = [ "rand_core 0.9.5", ] +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -2027,6 +2313,21 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + [[package]] name = "rcgen" version = "0.14.8" @@ -2079,6 +2380,44 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +[[package]] +name = "reqwest" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "ring" version = "0.17.14" @@ -2154,9 +2493,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.41" +version = "0.23.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" dependencies = [ "aws-lc-rs", "log", @@ -2186,6 +2525,7 @@ version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ + "web-time", "zeroize", ] @@ -2195,7 +2535,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "jni", "log", @@ -2271,7 +2611,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ "bitflags 2.11.1", - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -2429,6 +2769,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + [[package]] name = "strsim" version = "0.11.1" @@ -2496,6 +2842,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -2507,6 +2862,27 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags 2.11.1", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -2518,11 +2894,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.19" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" dependencies = [ - "thiserror-impl 2.0.19", + "thiserror-impl 2.0.20", ] [[package]] @@ -2538,9 +2914,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.19" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", @@ -2597,6 +2973,31 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.53.1" @@ -2672,9 +3073,9 @@ dependencies = [ [[package]] name = "toml" -version = "1.1.3+spec-1.1.0" +version = "1.1.4+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53c96ecdfa941c8fc4fcaed14f99ada8ebed502eef533015095a07e3301d4c3c" +checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5" dependencies = [ "indexmap 2.14.0", "serde_core", @@ -2696,9 +3097,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.1.2+spec-1.1.0" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" dependencies = [ "winnow", ] @@ -2709,6 +3110,45 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags 2.11.1", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" version = "0.3.3" @@ -2789,6 +3229,24 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -2848,6 +3306,16 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.121" @@ -2880,6 +3348,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "web-sys" +version = "0.3.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webpki-root-certs" version = "1.0.7" @@ -2961,6 +3449,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + [[package]] name = "windows-result" version = "0.4.1" @@ -3082,6 +3581,12 @@ version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + [[package]] name = "x509-parser" version = "0.18.1" @@ -3096,7 +3601,7 @@ dependencies = [ "oid-registry", "ring", "rusticata-macros", - "thiserror 2.0.19", + "thiserror 2.0.20", "time", ] @@ -3110,6 +3615,29 @@ dependencies = [ "time", ] +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.8.48" @@ -3130,12 +3658,66 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + [[package]] name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "zmij" version = "1.0.21" diff --git a/Cargo.toml b/Cargo.toml index f04283e..607b0af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,11 +19,11 @@ unused = { level = "allow", priority = -1 } [dependencies] # Async -async-trait = "0.1.91" +async-trait = "0.1.92" tokio = { version = "1.53.1", features = ["full"] } # Arc -arc-swap = "1.9.1" +arc-swap = "1.9.2" # Certs # used Ring as backend for default @@ -34,19 +34,22 @@ instant-acme = { version = "0.8.5", default-features = false, features = [ "rcgen", "hyper-rustls", ] } -rustls = "0.23.41" +rustls = "0.23.43" # TOML -toml = "1.1.3" +toml = "1.1.4" # Json serde = { version = "1.0.229", features = ["derive"] } # Others -thiserror = "2.0.19" +thiserror = "2.0.20" chrono = { version = "0.4.45", features = ["serde"] } # HTTP -http = "1.4.2" +http = "1.5.0" pingora = { version = "0.8.1", features = ["proxy", "boringssl"] } + +# HTTP Client +reqwest = "0.13.4" diff --git a/src/services/dns/cloudflare.rs b/src/services/dns/cloudflare.rs new file mode 100644 index 0000000..074dfbf --- /dev/null +++ b/src/services/dns/cloudflare.rs @@ -0,0 +1,13 @@ +use crate::core::models::dns::{CloudflareProvider, Record}; +use crate::core::models::routes::Host; +use crate::services::dns::DnsProvider; +use crate::{Error, Result}; + +impl DnsProvider for CloudflareProvider { + fn get_challenge_record(&self, host: Host) -> Result { + Err(Error::Dns("not implemented!".to_string())) + } + fn update_challenge_record(&self, host: Host) -> Result { + Err(Error::Dns("not implemented!".to_string())) + } +} diff --git a/src/services/dns/mod.rs b/src/services/dns/mod.rs index cf94684..eb889d7 100644 --- a/src/services/dns/mod.rs +++ b/src/services/dns/mod.rs @@ -1,7 +1,9 @@ -use crate::core::models::dns::Record; +use crate::core::models::dns::{CloudflareProvider, Record}; use crate::core::models::routes::Host; use crate::{Error, Result}; +mod cloudflare; + trait DnsProvider: Send + Sync + 'static { /// Only returns single record /// gets all records but internally filters out all records either, @@ -11,5 +13,3 @@ trait DnsProvider: Send + Sync + 'static { /// will return the set record (if returned otherwise fake it) fn update_challenge_record(&self, host: Host) -> Result; } - -pub struct CloudflareDns {} From 7607e526e2ebbe0afeda1b891eafa12bac20f708 Mon Sep 17 00:00:00 2001 From: Max van der Schee <26795741+mvdschee@users.noreply.github.com> Date: Thu, 27 Aug 2026 08:39:05 +0200 Subject: [PATCH 12/14] reworking acme account creation --- Cargo.lock | 2 + Cargo.toml | 2 +- src/config.rs | 10 +++- src/core/handlers/certs.rs | 92 +++++++++++++++++++++++++++++++--- src/core/models/certs.rs | 6 ++- src/core/models/dns.rs | 24 ++++++++- src/core/models/routes.rs | 4 +- src/core/setup.rs | 1 + src/error.rs | 9 ++++ src/services/certs/acme.rs | 13 +++++ src/services/dns/cloudflare.rs | 9 ++-- src/services/dns/mod.rs | 14 ------ src/services/http/mod.rs | 15 ++++++ src/services/mod.rs | 1 + 14 files changed, 170 insertions(+), 32 deletions(-) create mode 100644 src/services/http/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 8706bdf..ef5bd9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2406,6 +2406,8 @@ dependencies = [ "rustls", "rustls-pki-types", "rustls-platform-verifier", + "serde", + "serde_json", "sync_wrapper", "tokio", "tokio-rustls", diff --git a/Cargo.toml b/Cargo.toml index 607b0af..03ab67c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,4 +52,4 @@ http = "1.5.0" pingora = { version = "0.8.1", features = ["proxy", "boringssl"] } # HTTP Client -reqwest = "0.13.4" +reqwest = { version = "0.13.4", features = ["json"] } diff --git a/src/config.rs b/src/config.rs index 3aa71ae..9f33640 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,12 +1,13 @@ use crate::{ Error, Result, core::models::{ - certs::{CertDir, Email}, + certs::{CertAccountPath, CertDir, Email}, proxy::{ProxyInputAddress, ProxyPort}, routes::Route, tasks::TaskInterval, }, }; +use instant_acme::AccountCredentials; use serde::Deserialize; use std::{env, fs}; @@ -16,6 +17,8 @@ const HTTP_PORT_ENV: &str = "HTTP_PORT"; const HTTPS_PORT_ENV: &str = "HTTPS_PORT"; const CERT_DIR_DEFAULT: &str = ".certs/"; +// this will be stored in the .certs/ or depending on where the user wants to store it +const CERT_CREDENTIAL_FILE: &str = "acme_account"; const HTTP_PORT_DEFAULT: u16 = 80; const HTTPS_PORT_DEFAULT: u16 = 443; const INPUT_ADDRESS: &str = "0.0.0.0"; @@ -27,6 +30,8 @@ const CERT_BACKGROUND_TASK_INTERVAL: u64 = 3600; // 1 hour pub struct Config { pub email: Email, pub cert_dir: CertDir, + // opague string type as it can't be cloned when its in AccountCredentials type + pub cert_account_path: CertAccountPath, pub routes: Vec, pub task_interval: TaskInterval, pub http_port: ProxyPort, @@ -51,6 +56,8 @@ impl Config { let config_file = parse_toml_config(config_path)?; let cert_dir = load_env(CERT_DIR_ENV).unwrap_or_else(|_| CERT_DIR_DEFAULT.to_string()); + let cert_account_path = format!("{}/{}", cert_dir, CERT_CREDENTIAL_FILE); + let http_port = load_env(HTTP_PORT_ENV).ok().and_then(|v| v.parse().ok()).unwrap_or(HTTP_PORT_DEFAULT); let https_port = load_env(HTTPS_PORT_ENV) @@ -61,6 +68,7 @@ impl Config { Ok(Config { email: config_file.acme.email.clone(), cert_dir: CertDir::from(cert_dir), + cert_account_path: CertAccountPath::from(cert_account_path), routes: config_file.routes.clone(), task_interval: TaskInterval::from(CERT_BACKGROUND_TASK_INTERVAL), http_port: ProxyPort::from(http_port), diff --git a/src/core/handlers/certs.rs b/src/core/handlers/certs.rs index c28ffc9..624e1d3 100644 --- a/src/core/handlers/certs.rs +++ b/src/core/handlers/certs.rs @@ -7,14 +7,18 @@ use crate::{ CertDir, CertPath, CertificateConfig, CertificateType, Email, KeyPath, TlsMaterial, TlsStore, }, + dns::{Cloudflare, CloudflareProvider, DnsProvider, ProviderCredentail}, routes::Host, tasks::TaskInterval, }, }, error, info, - services::certs::{ - acme::{create_account, create_order}, - self_signed::create_self_signed_certificate_files, + services::{ + certs::{ + acme::{create_account, create_order}, + self_signed::create_self_signed_certificate_files, + }, + http::create_client, }, warn, }; @@ -120,6 +124,14 @@ impl BackgroundService for CertBackgroundRenewal { // this means we have to be a little more verbose with our error handeling. // TLDR; just continue on any error :D, problem for the next loop :') async fn start(&self, mut shutdown: ShutdownWatch) { + let http_client = match create_client() { + Ok(client) => client, + Err(e) => { + error!("Can't start cert renewal loop: {}", e); + return; + }, + }; + // TODO what to do when creating an account fails (acme endpoints is 500 etc..) let account_result = create_account(&self.email) .await @@ -143,6 +155,18 @@ impl BackgroundService for CertBackgroundRenewal { for config in configs.clone() { let order_result = create_order(&account, &config.host).await; + // if no DNS credentials are provided there is DNS validation. + // we should terminate early on. + let dns_service_config = match config.provider_config { + Some(config) => config, + None => { + warn!("No DNS credentials provided for {}", config.host); + continue; + }, + }; + + let dns_service = get_dns_services(dns_service_config, "_acme-challenge."); + let mut order = match order_result { Ok(order) => order, Err(err) => { @@ -154,16 +178,58 @@ impl BackgroundService for CertBackgroundRenewal { let state = order.state(); info!("order state: {:#?}", state); + // TODO what does pending means? can we use this to gate the refresh on it. like this will tell us its time to refresh the dns record. if !matches!(state.status, OrderStatus::Pending) { warn!("Skipping non-Pending order: {:?}", state.status); continue; } - // - // if so verify the dns records with cloudflare - // - // if its set allow the order to be proccessed - // + // TODO dont verify the value go straigh to update we are going to get get_challenge_record once we have put it and use it as a input check before finalizing + + // Pick the desired challenge type and prepare the response. + + // let mut authorizations = order.authorizations(); + // while let Some(result) = authorizations.next().await { + // let mut authz = result?; + // match authz.status { + // AuthorizationStatus::Pending => {}, + // AuthorizationStatus::Valid => continue, + // _ => todo!(), + // } + + // // We'll use the DNS challenges for this example, but you could + // // pick something else to use here. + + // let mut challenge = authz + // .challenge(ChallengeType::Dns01) + // .ok_or_else(|| anyhow::anyhow!("no dns01 challenge found"))?; + + // println!("Please set the following DNS record then press the Return key:"); + // println!( + // "_acme-challenge.{} IN TXT {}", + // challenge.identifier(), + // challenge.key_authorization()?.dns_value() + // ); + // io::stdin().read_line(&mut String::new())?; + + // challenge.set_ready().await?; + // } + + // // Exponentially back off until the order becomes ready or invalid. + + // let status = order.poll_ready(&RetryPolicy::default()).await?; + // if status != OrderStatus::Ready { + // return Err(anyhow::anyhow!("unexpected order status: {status:?}")); + // } + + // // Finalize the order and print certificate chain, private key and account credentials. + + // let private_key_pem = order.finalize().await?; + // let cert_chain_pem = order.poll_certificate(&RetryPolicy::default()).await?; + + // info!("certificate chain:\n\n{cert_chain_pem}"); + // info!("private key:\n\n{private_key_pem}"); + // write to the file system // // swap the file content in the store with the new values if any @@ -183,3 +249,13 @@ impl BackgroundService for CertBackgroundRenewal { } } } + +fn get_dns_services(config: ProviderCredentail, challenge_prefix: &str) -> impl DnsProvider { + match config { + ProviderCredentail::Cloudflare(config) => CloudflareProvider { + zone_id: config.zone_id, + api_token: config.api_token, + challenge_prefix: challenge_prefix.to_string(), + }, + } +} diff --git a/src/core/models/certs.rs b/src/core/models/certs.rs index b5289cb..a498a1a 100644 --- a/src/core/models/certs.rs +++ b/src/core/models/certs.rs @@ -1,5 +1,5 @@ use crate::{ - core::models::{filesystem::SafePath, routes::Host}, + core::models::{dns::ProviderCredentail, filesystem::SafePath, routes::Host}, string_newtype, }; use arc_swap::ArcSwap; @@ -25,6 +25,7 @@ pub struct CertificateConfig { pub host: Host, pub cert_dir: CertDir, pub cert_type: CertificateType, + pub provider_config: Option, } pub struct TlsMaterial { @@ -42,3 +43,6 @@ string_newtype!(Email, derive(Deserialize)); // --- CERT_DIR --- string_newtype!(CertDir, derive(Deserialize)); + +// --- CERT_ACCOUNT_PATH --- +string_newtype!(CertAccountPath, derive(Deserialize)); diff --git a/src/core/models/dns.rs b/src/core/models/dns.rs index 1c28cfc..75c4ebe 100644 --- a/src/core/models/dns.rs +++ b/src/core/models/dns.rs @@ -1,6 +1,19 @@ +use crate::core::models::routes::Host; use crate::string_newtype; +use crate::{Error, Result}; +use reqwest::Client; use serde::Deserialize; +pub trait DnsProvider: Send + Sync + 'static { + /// Only returns single record + /// gets all records but internally filters out all records either, + /// after the call or if it supports it directly on the endpoint call. + fn get_challenge_record(&self, host: &Host) -> Result; + /// Single fuction to set and update + /// will return the set record (if returned otherwise fake it) + fn update_challenge_record(&self, host: &Host, value: &str) -> Result; +} + // --- DNS Record --- string_newtype!(Record, derive(Deserialize)); @@ -8,17 +21,26 @@ string_newtype!(Record, derive(Deserialize)); #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "snake_case")] -pub enum ProviderCredentails { +pub enum ProviderCredentail { Cloudflare(CloudflareProvider), } // --- add here any other providers credentials --- // --- CloudFlare --- + +// config coming from TOML file #[derive(Debug, Clone, Deserialize)] pub struct CloudflareProvider { pub zone_id: ZoneId, pub api_token: ApiToken, + pub challenge_prefix: String, +} + +// struct that hold client +pub struct Cloudflare { + client: Client, + config: CloudflareProvider, } // Zone ID diff --git a/src/core/models/routes.rs b/src/core/models/routes.rs index 366d319..b44a2e3 100644 --- a/src/core/models/routes.rs +++ b/src/core/models/routes.rs @@ -1,5 +1,5 @@ use crate::{ - core::models::{certs::CertificateType, dns::ProviderCredentails}, + core::models::{certs::CertificateType, dns::ProviderCredentail}, string_newtype, }; use serde::Deserialize; @@ -10,7 +10,7 @@ pub struct Route { pub upstream: Upstream, #[serde(default)] pub cert_type: CertificateType, - pub dns_provider: Option, + pub dns_provider: Option, } // --- HOST --- diff --git a/src/core/setup.rs b/src/core/setup.rs index 6f74afd..2da6167 100644 --- a/src/core/setup.rs +++ b/src/core/setup.rs @@ -65,6 +65,7 @@ impl HandleCertificates { host: route.host.clone(), cert_dir: cert_dir.clone(), cert_type: route.cert_type.clone(), + provider_config: route.dns_provider.clone(), }) .collect::>(); diff --git a/src/error.rs b/src/error.rs index 80712fa..10da973 100644 --- a/src/error.rs +++ b/src/error.rs @@ -13,6 +13,9 @@ pub enum Error { #[error("IO error: {0}")] Io(#[from] std::io::Error), + #[error("request error: {0}")] + Request(reqwest::Error), + #[error("file system error: {0}")] FileSystem(String), @@ -34,3 +37,9 @@ pub enum Error { #[error("unknown error")] Unknown, } + +impl From for Error { + fn from(err: reqwest::Error) -> Self { + Error::Request(err) + } +} diff --git a/src/services/certs/acme.rs b/src/services/certs/acme.rs index f6aaea5..99c7c05 100644 --- a/src/services/certs/acme.rs +++ b/src/services/certs/acme.rs @@ -10,6 +10,19 @@ use instant_acme::{ }; use rustls::crypto::CryptoProvider; +// w +pub async fn load_account(credentials: AccountCredentials) -> Result { + let account = Account::builder() + .map_err(|e| Error::Acme(e.to_string()))? + .from_credentials(credentials) + .await + .map_err(|e| Error::Acme(e.to_string()))?; + + Ok(account) +} + +// this one returns credentials, because we are going to save it to the file +// for later usage to start the process with the same credentials pub async fn create_account(email: &Email) -> Result<(Account, AccountCredentials)> { CryptoProvider::install_default(rustls::crypto::ring::default_provider()); diff --git a/src/services/dns/cloudflare.rs b/src/services/dns/cloudflare.rs index 074dfbf..7bb4ee8 100644 --- a/src/services/dns/cloudflare.rs +++ b/src/services/dns/cloudflare.rs @@ -1,13 +1,14 @@ -use crate::core::models::dns::{CloudflareProvider, Record}; +use serde::de::value; + +use crate::core::models::dns::{CloudflareProvider, DnsProvider, Record}; use crate::core::models::routes::Host; -use crate::services::dns::DnsProvider; use crate::{Error, Result}; impl DnsProvider for CloudflareProvider { - fn get_challenge_record(&self, host: Host) -> Result { + fn get_challenge_record(&self, host: &Host) -> Result { Err(Error::Dns("not implemented!".to_string())) } - fn update_challenge_record(&self, host: Host) -> Result { + fn update_challenge_record(&self, host: &Host, value: &str) -> Result { Err(Error::Dns("not implemented!".to_string())) } } diff --git a/src/services/dns/mod.rs b/src/services/dns/mod.rs index eb889d7..c727ae5 100644 --- a/src/services/dns/mod.rs +++ b/src/services/dns/mod.rs @@ -1,15 +1 @@ -use crate::core::models::dns::{CloudflareProvider, Record}; -use crate::core::models::routes::Host; -use crate::{Error, Result}; - mod cloudflare; - -trait DnsProvider: Send + Sync + 'static { - /// Only returns single record - /// gets all records but internally filters out all records either, - /// after the call or if it supports it directly on the endpoint call. - fn get_challenge_record(&self, host: Host) -> Result; - /// Single fuction to set and update - /// will return the set record (if returned otherwise fake it) - fn update_challenge_record(&self, host: Host) -> Result; -} diff --git a/src/services/http/mod.rs b/src/services/http/mod.rs new file mode 100644 index 0000000..731029c --- /dev/null +++ b/src/services/http/mod.rs @@ -0,0 +1,15 @@ +use crate::Result; +use reqwest::Client; +use std::time::Duration; + +// simply return client no need for struct, +// or extras +pub fn create_client() -> Result { + let client = Client::builder() + .timeout(Duration::from_secs(30)) + .pool_idle_timeout(Duration::from_secs(90)) + .pool_max_idle_per_host(10) + .build()?; + + Ok(client) +} diff --git a/src/services/mod.rs b/src/services/mod.rs index ad80d1c..cc7959b 100644 --- a/src/services/mod.rs +++ b/src/services/mod.rs @@ -1,2 +1,3 @@ pub mod certs; pub mod dns; +pub mod http; From a12aecf6ee49ac6f4ed4c7e2094789b3a43e0e0d Mon Sep 17 00:00:00 2001 From: Max van der Schee <26795741+mvdschee@users.noreply.github.com> Date: Mon, 31 Aug 2026 15:25:44 +0200 Subject: [PATCH 13/14] fix loading acme account to prevent duplicated accounts creation --- .rustfmt.toml | 3 ++ Cargo.lock | 5 +-- Cargo.toml | 1 + src/config.rs | 5 +-- src/core/handlers/certs.rs | 65 ++++++++++++++++++++++++++------------ src/core/models/certs.rs | 2 +- src/core/models/dns.rs | 10 ++++++ src/core/setup.rs | 8 ++++- src/main.rs | 1 + src/services/certs/acme.rs | 10 +++++- 10 files changed, 83 insertions(+), 27 deletions(-) diff --git a/.rustfmt.toml b/.rustfmt.toml index 750ac8a..d1afa00 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -6,3 +6,6 @@ reorder_modules = true match_block_trailing_comma = true use_field_init_shorthand = true use_small_heuristics = "Off" +# clean up multiple use crate:: that share the same root import +# run with cargo +nightly fmt +imports_granularity = "Crate" diff --git a/Cargo.lock b/Cargo.lock index ef5bd9b..5cd9b62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2160,6 +2160,7 @@ dependencies = [ "reqwest", "rustls", "serde", + "serde_json", "thiserror 2.0.20", "tokio", "toml", @@ -2667,9 +2668,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ "itoa", "memchr", diff --git a/Cargo.toml b/Cargo.toml index 03ab67c..2a8f7d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ toml = "1.1.4" # Json serde = { version = "1.0.229", features = ["derive"] } +serde_json = "1.0.151" # Others thiserror = "2.0.20" diff --git a/src/config.rs b/src/config.rs index 9f33640..3669513 100644 --- a/src/config.rs +++ b/src/config.rs @@ -15,12 +15,13 @@ const CONFIG_PATH_ENV: &str = "CONFIG_PATH"; const CERT_DIR_ENV: &str = "CERT_DIR"; const HTTP_PORT_ENV: &str = "HTTP_PORT"; const HTTPS_PORT_ENV: &str = "HTTPS_PORT"; +pub const ACME_CHALLENGE_PREFIX: &str = "_acme-challenge."; const CERT_DIR_DEFAULT: &str = ".certs/"; // this will be stored in the .certs/ or depending on where the user wants to store it const CERT_CREDENTIAL_FILE: &str = "acme_account"; -const HTTP_PORT_DEFAULT: u16 = 80; -const HTTPS_PORT_DEFAULT: u16 = 443; +const HTTP_PORT_DEFAULT: u16 = 880; +const HTTPS_PORT_DEFAULT: u16 = 8443; const INPUT_ADDRESS: &str = "0.0.0.0"; /// in seconds diff --git a/src/core/handlers/certs.rs b/src/core/handlers/certs.rs index 624e1d3..ec2980f 100644 --- a/src/core/handlers/certs.rs +++ b/src/core/handlers/certs.rs @@ -4,8 +4,8 @@ use crate::{ handlers::filesystem::{check_file_exists, read_file, safe_path, write_file}, models::{ certs::{ - CertDir, CertPath, CertificateConfig, CertificateType, Email, KeyPath, TlsMaterial, - TlsStore, + CertAccountPath, CertDir, CertPath, CertificateConfig, CertificateType, Email, + KeyPath, TlsMaterial, TlsStore, }, dns::{Cloudflare, CloudflareProvider, DnsProvider, ProviderCredentail}, routes::Host, @@ -15,7 +15,7 @@ use crate::{ error, info, services::{ certs::{ - acme::{create_account, create_order}, + acme::{create_account, create_order, init_account, load_account}, self_signed::create_self_signed_certificate_files, }, http::create_client, @@ -24,10 +24,10 @@ use crate::{ }; use arc_swap::ArcSwap; use async_trait::async_trait; -use instant_acme::{Identifier, NewOrder, OrderStatus}; +use instant_acme::{Account, AccountCredentials, Identifier, NewOrder, OrderStatus}; use pingora::{server::ShutdownWatch, services::background::BackgroundService, tls}; use rcgen::{CertifiedKey, generate_simple_self_signed}; -use std::{collections::HashMap, sync::Arc, time::Duration}; +use std::{collections::HashMap, fs, sync::Arc, time::Duration}; use tokio::time; pub fn create_self_signed_certs(certificate_configs: &Vec) -> Result<()> { @@ -97,6 +97,7 @@ pub fn certificate_paths(host: &Host, cert_dir: &CertDir) -> Result<(KeyPath, Ce pub struct CertBackgroundRenewal { pub certificate_configs: Vec, + pub cert_account_path: CertAccountPath, pub task_interval: TaskInterval, pub tls_store: TlsStore, pub email: Email, @@ -105,12 +106,14 @@ pub struct CertBackgroundRenewal { impl CertBackgroundRenewal { pub fn new( certificate_configs: Vec, + cert_account_path: CertAccountPath, task_interval: TaskInterval, tls_store: TlsStore, email: Email, ) -> Self { Self { certificate_configs, + cert_account_path, task_interval, tls_store, email, @@ -132,10 +135,13 @@ impl BackgroundService for CertBackgroundRenewal { }, }; - // TODO what to do when creating an account fails (acme endpoints is 500 etc..) - let account_result = create_account(&self.email) - .await - .map_err(|e| Error::Certificate(format!("Failed with create_account: {}", e))); + let account = match resolve_acme_account(&self.cert_account_path, &self.email).await { + Ok(account) => account, + Err(err) => { + error!("Failed to resolve ACME account: {err:?}"); + return; + }, + }; let configs = self .certificate_configs @@ -144,14 +150,6 @@ impl BackgroundService for CertBackgroundRenewal { .filter(|c| c.cert_type == CertificateType::Acme); loop { - let (account, credentials) = match account_result { - Ok(ref pair) => pair, - Err(ref err) => { - error!("{err:?}"); - continue; - }, - }; - for config in configs.clone() { let order_result = create_order(&account, &config.host).await; @@ -165,7 +163,7 @@ impl BackgroundService for CertBackgroundRenewal { }, }; - let dns_service = get_dns_services(dns_service_config, "_acme-challenge."); + let dns_service = get_dns_services(dns_service_config); let mut order = match order_result { Ok(order) => order, @@ -250,12 +248,39 @@ impl BackgroundService for CertBackgroundRenewal { } } -fn get_dns_services(config: ProviderCredentail, challenge_prefix: &str) -> impl DnsProvider { +fn get_dns_services(config: ProviderCredentail) -> impl DnsProvider { match config { ProviderCredentail::Cloudflare(config) => CloudflareProvider { zone_id: config.zone_id, api_token: config.api_token, - challenge_prefix: challenge_prefix.to_string(), + challenge_prefix: config.challenge_prefix, }, } } + +async fn resolve_acme_account( + cert_account_path: &CertAccountPath, + email: &Email, +) -> Result { + // set crypto lib to load/create the account + init_account(); + + if let Ok(credentials) = get_acme_account(cert_account_path) { + return load_account(credentials).await; + } + + let (account, credentials) = create_account(email).await?; + let content = serde_json::to_vec(&credentials) + .map_err(|e| Error::Acme(format!("Failed to serialize ACME account: {}", e)))?; + write_file(cert_account_path.clone(), &content)?; + + Ok(account) +} + +// any erorr one this part we will simply create a new account +fn get_acme_account(cert_account_path: &CertAccountPath) -> Result { + let raw_content = read_file(cert_account_path)?; + + serde_json::from_slice(&raw_content) + .map_err(|e| Error::Acme(format!("Failed to parse ACME account: {}", e))) +} diff --git a/src/core/models/certs.rs b/src/core/models/certs.rs index a498a1a..cfbe9e7 100644 --- a/src/core/models/certs.rs +++ b/src/core/models/certs.rs @@ -45,4 +45,4 @@ string_newtype!(Email, derive(Deserialize)); string_newtype!(CertDir, derive(Deserialize)); // --- CERT_ACCOUNT_PATH --- -string_newtype!(CertAccountPath, derive(Deserialize)); +pub type CertAccountPath = SafePath; diff --git a/src/core/models/dns.rs b/src/core/models/dns.rs index 75c4ebe..143246e 100644 --- a/src/core/models/dns.rs +++ b/src/core/models/dns.rs @@ -1,3 +1,4 @@ +use crate::config::ACME_CHALLENGE_PREFIX; use crate::core::models::routes::Host; use crate::string_newtype; use crate::{Error, Result}; @@ -14,6 +15,10 @@ pub trait DnsProvider: Send + Sync + 'static { fn update_challenge_record(&self, host: &Host, value: &str) -> Result; } +fn default_challenge_prefix() -> String { + ACME_CHALLENGE_PREFIX.to_string() +} + // --- DNS Record --- string_newtype!(Record, derive(Deserialize)); @@ -34,6 +39,11 @@ pub enum ProviderCredentail { pub struct CloudflareProvider { pub zone_id: ZoneId, pub api_token: ApiToken, + // challenge_prefix is a default value from ACME, + // this can not be set or changed from the userside + // but its here to keep keep it as a single config + // per provider + #[serde(skip, default = "default_challenge_prefix")] pub challenge_prefix: String, } diff --git a/src/core/setup.rs b/src/core/setup.rs index 2da6167..4be3f61 100644 --- a/src/core/setup.rs +++ b/src/core/setup.rs @@ -9,7 +9,9 @@ use crate::{ proxy::run_proxy, }, models::{ - certs::{CertDir, CertificateConfig, CertificateType, Email, TlsStore}, + certs::{ + CertAccountPath, CertDir, CertificateConfig, CertificateType, Email, TlsStore, + }, proxy::{ProxyConfig, ProxyRoute, ProxyTls}, routes::Route, tasks::TaskInterval, @@ -48,6 +50,7 @@ impl HandleFileSystem { pub struct HandleCertificates { certificate_configs: Vec, + cert_account_path: CertAccountPath, task_interval: TaskInterval, email: Email, } @@ -55,6 +58,7 @@ pub struct HandleCertificates { impl HandleCertificates { pub fn new( cert_dir: CertDir, + cert_account_path: CertAccountPath, email: Email, routes: Vec, task_interval: TaskInterval, @@ -71,6 +75,7 @@ impl HandleCertificates { Self { certificate_configs, + cert_account_path, task_interval, email, } @@ -82,6 +87,7 @@ impl HandleCertificates { let store = load_tls_store(&self.certificate_configs)?; let renewal = CertBackgroundRenewal::new( self.certificate_configs.clone(), + self.cert_account_path.clone(), self.task_interval.clone(), store.clone(), self.email.clone(), diff --git a/src/main.rs b/src/main.rs index bf0e8fd..1a54adc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,6 +26,7 @@ fn main() -> Result<()> { // and spin up background tasks to refresh certificates let cert_handler = HandleCertificates::new( config.cert_dir.clone(), + config.cert_account_path.clone(), config.email, config.routes.clone(), config.task_interval, diff --git a/src/services/certs/acme.rs b/src/services/certs/acme.rs index 99c7c05..4aadaf4 100644 --- a/src/services/certs/acme.rs +++ b/src/services/certs/acme.rs @@ -4,13 +4,17 @@ use crate::{ certs::{CertificateConfig, Email}, routes::Host, }, + info, }; use instant_acme::{ Account, AccountCredentials, Identifier, LetsEncrypt, NewAccount, NewOrder, Order, }; use rustls::crypto::CryptoProvider; -// w +pub fn init_account() { + CryptoProvider::install_default(rustls::crypto::ring::default_provider()); +} + pub async fn load_account(credentials: AccountCredentials) -> Result { let account = Account::builder() .map_err(|e| Error::Acme(e.to_string()))? @@ -18,6 +22,8 @@ pub async fn load_account(credentials: AccountCredentials) -> Result { .await .map_err(|e| Error::Acme(e.to_string()))?; + info!("Reusing ACME account"); + Ok(account) } @@ -41,6 +47,8 @@ pub async fn create_account(email: &Email) -> Result<(Account, AccountCredential .await .map_err(|e| Error::Acme(e.to_string()))?; + info!("Created new ACME account"); + Ok((account, credentials)) } From db7d99d28ed249fc441f61fdf492a3b3366675f0 Mon Sep 17 00:00:00 2001 From: Max van der Schee <26795741+mvdschee@users.noreply.github.com> Date: Mon, 31 Aug 2026 15:26:29 +0200 Subject: [PATCH 14/14] merge imports --- src/core/models/certs.rs | 3 +-- src/core/models/dns.rs | 7 +++---- src/services/dns/cloudflare.rs | 10 +++++++--- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/core/models/certs.rs b/src/core/models/certs.rs index cfbe9e7..4ad5470 100644 --- a/src/core/models/certs.rs +++ b/src/core/models/certs.rs @@ -8,8 +8,7 @@ use pingora::tls::{ x509::X509, }; use serde::Deserialize; -use std::collections::HashMap; -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; #[derive(Debug, Clone, Deserialize, Default, PartialEq)] #[serde(rename_all = "snake_case")] diff --git a/src/core/models/dns.rs b/src/core/models/dns.rs index 143246e..e16a458 100644 --- a/src/core/models/dns.rs +++ b/src/core/models/dns.rs @@ -1,7 +1,6 @@ -use crate::config::ACME_CHALLENGE_PREFIX; -use crate::core::models::routes::Host; -use crate::string_newtype; -use crate::{Error, Result}; +use crate::{ + Error, Result, config::ACME_CHALLENGE_PREFIX, core::models::routes::Host, string_newtype, +}; use reqwest::Client; use serde::Deserialize; diff --git a/src/services/dns/cloudflare.rs b/src/services/dns/cloudflare.rs index 7bb4ee8..b41363a 100644 --- a/src/services/dns/cloudflare.rs +++ b/src/services/dns/cloudflare.rs @@ -1,8 +1,12 @@ use serde::de::value; -use crate::core::models::dns::{CloudflareProvider, DnsProvider, Record}; -use crate::core::models::routes::Host; -use crate::{Error, Result}; +use crate::{ + Error, Result, + core::models::{ + dns::{CloudflareProvider, DnsProvider, Record}, + routes::Host, + }, +}; impl DnsProvider for CloudflareProvider { fn get_challenge_record(&self, host: &Host) -> Result {