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 1/8] 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 2/8] 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 3/8] 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 4/8] 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 5/8] 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 6/8] 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 7/8] 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 8/8] 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;