diff --git a/Cargo.lock b/Cargo.lock index 7f8f932..3ede8bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -131,6 +131,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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core", +] + [[package]] name = "ciborium" version = "0.2.2" @@ -218,31 +235,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[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" +name = "cpufeatures" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" dependencies = [ - "core-foundation-sys", "libc", ] -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - [[package]] name = "criterion" version = "0.5.1" @@ -348,70 +348,24 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - [[package]] name = "find-msvc-tools" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" -[[package]] -name = "fnv" -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 = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.2" @@ -477,8 +431,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -488,29 +444,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", + "rand_core", "wasip2", "wasip3", -] - -[[package]] -name = "h2" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", + "wasm-bindgen", ] [[package]] @@ -600,7 +540,6 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2", "http", "http-body", "httparse", @@ -626,22 +565,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", -] - -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", + "webpki-roots", ] [[package]] @@ -662,11 +586,9 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2", - "system-configuration", "tokio", "tower-service", "tracing", - "windows-registry", ] [[package]] @@ -868,12 +790,6 @@ dependencies = [ "libc", ] -[[package]] -name = "linux-raw-sys" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" - [[package]] name = "litemap" version = "0.8.1" @@ -887,16 +803,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] -name = "memchr" -version = "2.8.0" +name = "lru-slab" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] -name = "mime" -version = "0.3.17" +name = "memchr" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "mio" @@ -909,23 +825,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "native-tls" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -953,49 +852,6 @@ version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" -[[package]] -name = "openssl" -version = "0.10.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" - -[[package]] -name = "openssl-sys" -version = "0.9.116" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "option-ext" version = "0.2.0" @@ -1020,12 +876,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - [[package]] name = "plotters" version = "0.3.7" @@ -1082,6 +932,62 @@ dependencies = [ "unicode-ident", ] +[[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", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04759210543be93709136e28212294a659ef5001836ff4eab4d663e4529bba83" +dependencies = [ + "bytes", + "getrandom 0.4.1", + "lru-slab", + "rand", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror", + "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.52.0", +] + [[package]] name = "quote" version = "1.0.44" @@ -1097,6 +1003,32 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.1", + "rand_core", +] + +[[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", +] + [[package]] name = "rayon" version = "1.12.0" @@ -1165,31 +1097,28 @@ checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64", "bytes", - "encoding_rs", "futures-channel", "futures-core", "futures-util", - "h2", "http", "http-body", "http-body-util", "hyper", "hyper-rustls", - "hyper-tls", "hyper-util", "js-sys", "log", - "mime", - "native-tls", "percent-encoding", "pin-project-lite", + "quinn", + "rustls", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", - "tokio-native-tls", + "tokio-rustls", "tower", "tower-http", "tower-service", @@ -1197,6 +1126,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots", ] [[package]] @@ -1214,17 +1144,10 @@ dependencies = [ ] [[package]] -name = "rustix" -version = "1.1.4" +name = "rustc-hash" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustls" @@ -1233,6 +1156,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ "once_cell", + "ring", "rustls-pki-types", "rustls-webpki", "subtle", @@ -1245,6 +1169,7 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ + "web-time", "zeroize", ] @@ -1280,38 +1205,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "schannel" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "security-framework" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" -dependencies = [ - "bitflags", - "core-foundation 0.10.1", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "semver" version = "1.0.27" @@ -1450,40 +1343,6 @@ dependencies = [ "syn", ] -[[package]] -name = "system-configuration" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" -dependencies = [ - "bitflags", - "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 = "tempfile" -version = "3.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" -dependencies = [ - "fastrand", - "getrandom 0.4.1", - "once_cell", - "rustix", - "windows-sys 0.61.2", -] - [[package]] name = "thiserror" version = "2.0.18" @@ -1524,6 +1383,21 @@ dependencies = [ "serde_json", ] +[[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.49.0" @@ -1538,16 +1412,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.4" @@ -1558,19 +1422,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-util" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - [[package]] name = "tower" version = "0.5.3" @@ -1683,12 +1534,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "walkdir" version = "2.5.0" @@ -1836,48 +1681,38 @@ dependencies = [ ] [[package]] -name = "winapi-util" -version = "0.1.11" +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ - "windows-sys 0.61.2", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "windows-link" -version = "0.2.1" +name = "webpki-roots" +version = "1.0.9" 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" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" dependencies = [ - "windows-link", - "windows-result", - "windows-strings", + "rustls-pki-types", ] [[package]] -name = "windows-result" -version = "0.4.1" +name = "winapi-util" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-link", + "windows-sys 0.61.2", ] [[package]] -name = "windows-strings" -version = "0.5.1" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link", -] +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" diff --git a/Cargo.toml b/Cargo.toml index 9687583..d31da16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ readme = "README.adoc" [dependencies] clap = { version = "4", features = ["derive"] } -reqwest = { version = "0.12", features = ["blocking", "json"] } +reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] } serde = { version = "1", features = ["derive"] } serde_json = "1" dirs = "6" diff --git a/benches/cloudguard_bench.rs b/benches/cloudguard_bench.rs index a411987..d672b5a 100644 --- a/benches/cloudguard_bench.rs +++ b/benches/cloudguard_bench.rs @@ -8,8 +8,8 @@ //! - `audit_settings()` — full compliance scan over a mock settings payload //! - Policy table iteration throughput at varying setting counts -use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; use cloudguard_cli::api::{audit_settings, hardening_policy, AuditFinding, CfSetting}; +use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; // ============================================================================ // Helpers — construct representative Cloudflare setting payloads @@ -77,7 +77,12 @@ fn bench_hardening_policy(c: &mut Criterion) { fn bench_audit_all_pass(c: &mut Criterion) { let settings = make_compliant_settings(); c.bench_function("audit_settings_all_pass", |b| { - b.iter(|| black_box(audit_settings(black_box("example.com"), black_box(&settings)))) + b.iter(|| { + black_box(audit_settings( + black_box("example.com"), + black_box(&settings), + )) + }) }); } @@ -87,7 +92,12 @@ fn bench_audit_all_pass(c: &mut Criterion) { fn bench_audit_all_fail(c: &mut Criterion) { let settings = make_noncompliant_settings(); c.bench_function("audit_settings_all_fail", |b| { - b.iter(|| black_box(audit_settings(black_box("example.com"), black_box(&settings)))) + b.iter(|| { + black_box(audit_settings( + black_box("example.com"), + black_box(&settings), + )) + }) }); } @@ -101,7 +111,12 @@ fn bench_audit_scaling(c: &mut Criterion) { for n in [4, 8, 16, policy_size] { let settings = make_settings_n(n); group.bench_with_input(BenchmarkId::from_parameter(n), &n, |b, _| { - b.iter(|| black_box(audit_settings(black_box("bench.example.com"), black_box(&settings)))) + b.iter(|| { + black_box(audit_settings( + black_box("bench.example.com"), + black_box(&settings), + )) + }) }); } group.finish(); diff --git a/src/api/mod.rs b/src/api/mod.rs index ecf2434..ac7dd2f 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -17,7 +17,8 @@ use serde::{Deserialize, Serialize}; const RATE_LIMIT_MS: u64 = 333; /// Last request timestamp for rate limiting. -static LAST_REQUEST: Lazy> = Lazy::new(|| Mutex::new(Instant::now() - Duration::from_secs(1))); +static LAST_REQUEST: Lazy> = + Lazy::new(|| Mutex::new(Instant::now() - Duration::from_secs(1))); /// CF API base URL. const CF_API: &str = "https://api.cloudflare.com/client/v4"; @@ -105,7 +106,9 @@ pub struct CfDnsRecord { pub comment: Option, } -fn default_ttl() -> u32 { 1 } +fn default_ttl() -> u32 { + 1 +} // ============================================================================ // Pages project types @@ -162,7 +165,6 @@ const HARDENING_POLICY: &[(&str, &str, &str)] = &[ ("ip_geolocation", "on", "LOW"), ]; - // ============================================================================ // Client // ============================================================================ @@ -206,22 +208,28 @@ impl CloudflareClient { fn get(&self, path: &str) -> Result { self.rate_limit(); let url = format!("{}{}", CF_API, path); - let resp = self.client.get(&url) + let resp = self + .client + .get(&url) .bearer_auth(&self.token) .send() .map_err(|e| format!("HTTP error: {}", e))?; let status = resp.status(); - let body: serde_json::Value = resp.json() + let body: serde_json::Value = resp + .json() .map_err(|e| format!("JSON parse error: {}", e))?; if !status.is_success() { - let errors = body.get("errors") + let errors = body + .get("errors") .and_then(|e| e.as_array()) - .map(|arr| arr.iter() - .filter_map(|e| e.get("message").and_then(|m| m.as_str())) - .collect::>() - .join(", ")) + .map(|arr| { + arr.iter() + .filter_map(|e| e.get("message").and_then(|m| m.as_str())) + .collect::>() + .join(", ") + }) .unwrap_or_else(|| format!("HTTP {}", status)); return Err(errors); } @@ -233,23 +241,29 @@ impl CloudflareClient { fn post(&self, path: &str, body: &serde_json::Value) -> Result { self.rate_limit(); let url = format!("{}{}", CF_API, path); - let resp = self.client.post(&url) + let resp = self + .client + .post(&url) .bearer_auth(&self.token) .json(body) .send() .map_err(|e| format!("HTTP error: {}", e))?; let status = resp.status(); - let response_body: serde_json::Value = resp.json() + let response_body: serde_json::Value = resp + .json() .map_err(|e| format!("JSON parse error: {}", e))?; if !status.is_success() { - let errors = response_body.get("errors") + let errors = response_body + .get("errors") .and_then(|e| e.as_array()) - .map(|arr| arr.iter() - .filter_map(|e| e.get("message").and_then(|m| m.as_str())) - .collect::>() - .join(", ")) + .map(|arr| { + arr.iter() + .filter_map(|e| e.get("message").and_then(|m| m.as_str())) + .collect::>() + .join(", ") + }) .unwrap_or_else(|| format!("HTTP {}", status)); return Err(errors); } @@ -261,23 +275,29 @@ impl CloudflareClient { fn patch(&self, path: &str, body: &serde_json::Value) -> Result { self.rate_limit(); let url = format!("{}{}", CF_API, path); - let resp = self.client.patch(&url) + let resp = self + .client + .patch(&url) .bearer_auth(&self.token) .json(body) .send() .map_err(|e| format!("HTTP error: {}", e))?; let status = resp.status(); - let response_body: serde_json::Value = resp.json() + let response_body: serde_json::Value = resp + .json() .map_err(|e| format!("JSON parse error: {}", e))?; if !status.is_success() { - let errors = response_body.get("errors") + let errors = response_body + .get("errors") .and_then(|e| e.as_array()) - .map(|arr| arr.iter() - .filter_map(|e| e.get("message").and_then(|m| m.as_str())) - .collect::>() - .join(", ")) + .map(|arr| { + arr.iter() + .filter_map(|e| e.get("message").and_then(|m| m.as_str())) + .collect::>() + .join(", ") + }) .unwrap_or_else(|| format!("HTTP {}", status)); return Err(errors); } @@ -289,19 +309,24 @@ impl CloudflareClient { fn delete(&self, path: &str) -> Result<(), String> { self.rate_limit(); let url = format!("{}{}", CF_API, path); - let resp = self.client.delete(&url) + let resp = self + .client + .delete(&url) .bearer_auth(&self.token) .send() .map_err(|e| format!("HTTP error: {}", e))?; if !resp.status().is_success() { let body: serde_json::Value = resp.json().unwrap_or_default(); - let errors = body.get("errors") + let errors = body + .get("errors") .and_then(|e| e.as_array()) - .map(|arr| arr.iter() - .filter_map(|e| e.get("message").and_then(|m| m.as_str())) - .collect::>() - .join(", ")) + .map(|arr| { + arr.iter() + .filter_map(|e| e.get("message").and_then(|m| m.as_str())) + .collect::>() + .join(", ") + }) .unwrap_or_else(|| "Delete failed".to_string()); return Err(errors); } @@ -332,20 +357,22 @@ impl CloudflareClient { loop { let body = self.get(&format!("/zones?page={}&per_page=50", page))?; - let resp: CfResponse> = serde_json::from_value(body) - .map_err(|e| format!("Parse error: {}", e))?; + let resp: CfResponse> = + serde_json::from_value(body).map_err(|e| format!("Parse error: {}", e))?; if let Some(zones) = resp.result { - if zones.is_empty() { break; } + if zones.is_empty() { + break; + } all_zones.extend(zones); } else { break; } - let total_pages = resp.result_info - .and_then(|ri| ri.total_pages) - .unwrap_or(1); - if page >= total_pages { break; } + let total_pages = resp.result_info.and_then(|ri| ri.total_pages).unwrap_or(1); + if page >= total_pages { + break; + } page += 1; } @@ -356,8 +383,8 @@ impl CloudflareClient { /// Find a zone by domain name. pub fn find_zone_by_name(&self, name: &str) -> Result { let body = self.get(&format!("/zones?name={}", name))?; - let resp: CfResponse> = serde_json::from_value(body) - .map_err(|e| format!("Parse error: {}", e))?; + let resp: CfResponse> = + serde_json::from_value(body).map_err(|e| format!("Parse error: {}", e))?; resp.result .and_then(|zones| zones.into_iter().next()) @@ -371,10 +398,11 @@ impl CloudflareClient { /// Get all settings for a zone. pub fn get_zone_settings(&self, zone_id: &str) -> Result, String> { let body = self.get(&format!("/zones/{}/settings", zone_id))?; - let resp: CfResponse> = serde_json::from_value(body) - .map_err(|e| format!("Parse error: {}", e))?; + let resp: CfResponse> = + serde_json::from_value(body).map_err(|e| format!("Parse error: {}", e))?; - resp.result.ok_or_else(|| "No settings in response".to_string()) + resp.result + .ok_or_else(|| "No settings in response".to_string()) } /// Apply hardening settings to a zone. Returns number of settings updated. @@ -423,21 +451,26 @@ impl CloudflareClient { let mut page = 1u32; loop { - let body = self.get(&format!("/zones/{}/dns_records?page={}&per_page=100", zone_id, page))?; - let resp: CfResponse> = serde_json::from_value(body) - .map_err(|e| format!("Parse error: {}", e))?; + let body = self.get(&format!( + "/zones/{}/dns_records?page={}&per_page=100", + zone_id, page + ))?; + let resp: CfResponse> = + serde_json::from_value(body).map_err(|e| format!("Parse error: {}", e))?; if let Some(records) = resp.result { - if records.is_empty() { break; } + if records.is_empty() { + break; + } all_records.extend(records); } else { break; } - let total_pages = resp.result_info - .and_then(|ri| ri.total_pages) - .unwrap_or(1); - if page >= total_pages { break; } + let total_pages = resp.result_info.and_then(|ri| ri.total_pages).unwrap_or(1); + if page >= total_pages { + break; + } page += 1; } @@ -463,10 +496,11 @@ impl CloudflareClient { }); let resp_body = self.post(&format!("/zones/{}/dns_records", zone_id), &body)?; - let resp: CfResponse = serde_json::from_value(resp_body) - .map_err(|e| format!("Parse error: {}", e))?; + let resp: CfResponse = + serde_json::from_value(resp_body).map_err(|e| format!("Parse error: {}", e))?; - resp.result.ok_or_else(|| "No record in response".to_string()) + resp.result + .ok_or_else(|| "No record in response".to_string()) } /// Delete a DNS record. @@ -479,7 +513,11 @@ impl CloudflareClient { // ======================================================================== /// Patch multiple zone settings at once. - pub fn patch_zone_settings(&self, zone_id: &str, body: &serde_json::Value) -> Result<(), String> { + pub fn patch_zone_settings( + &self, + zone_id: &str, + body: &serde_json::Value, + ) -> Result<(), String> { self.patch(&format!("/zones/{}/settings", zone_id), body)?; Ok(()) } @@ -493,8 +531,8 @@ impl CloudflareClient { // Try shorthand first, then fall back to looking up account ID. match self.get("/accounts/_/pages/projects") { Ok(body) => { - let resp: CfResponse> = serde_json::from_value(body) - .map_err(|e| format!("Parse error: {}", e))?; + let resp: CfResponse> = + serde_json::from_value(body).map_err(|e| format!("Parse error: {}", e))?; Ok(resp.result.unwrap_or_default()) } Err(_) => { @@ -540,15 +578,18 @@ impl CloudflareClient { .join("cloudguard") .join("configs"); - std::fs::create_dir_all(&dir) - .map_err(|e| format!("Failed to create config dir: {}", e))?; + std::fs::create_dir_all(&dir).map_err(|e| format!("Failed to create config dir: {}", e))?; let filename = domain.replace('.', "_"); let path = dir.join(format!("{}.json", filename)); - std::fs::write(&path, serde_json::to_string_pretty(&config) - .expect("serializing a typed Rust value to JSON is infallible (no Serialize impl can fail)")) - .map_err(|e| format!("Failed to write config: {}", e))?; + std::fs::write( + &path, + serde_json::to_string_pretty(&config).expect( + "serializing a typed Rust value to JSON is infallible (no Serialize impl can fail)", + ), + ) + .map_err(|e| format!("Failed to write config: {}", e))?; Ok(path.to_string_lossy().to_string()) } @@ -560,10 +601,7 @@ impl CloudflareClient { /// Audit a zone's settings against the hardening policy. /// Returns (passed_count, failed_count, findings). -pub fn audit_settings( - domain: &str, - settings: &[CfSetting], -) -> (usize, usize, Vec) { +pub fn audit_settings(domain: &str, settings: &[CfSetting]) -> (usize, usize, Vec) { let mut passed = 0; let mut failed = 0; let mut findings = Vec::new(); @@ -574,7 +612,13 @@ pub fn audit_settings( Some(s) => { let actual = match &s.value { serde_json::Value::String(v) => v.clone(), - serde_json::Value::Bool(b) => if *b { "on".to_string() } else { "off".to_string() }, + serde_json::Value::Bool(b) => { + if *b { + "on".to_string() + } else { + "off".to_string() + } + } serde_json::Value::Number(n) => n.to_string(), other => other.to_string(), }; diff --git a/src/main.rs b/src/main.rs index a0f5e4c..a5d7af0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -197,19 +197,39 @@ fn main() { let result = match cli.command { Commands::Audit { domain, output } => cmd_audit(&client, domain, output, json_output), - Commands::Harden { domain, apply, dry_run } => { - cmd_harden(&client, domain, apply || !dry_run, json_output) - } + Commands::Harden { + domain, + apply, + dry_run, + } => cmd_harden(&client, domain, apply || !dry_run, json_output), Commands::Sync { action } => match action { - SyncAction::Download { dir, domain } => cmd_sync_download(&client, dir, domain, json_output), - SyncAction::Upload { path, dry_run } => cmd_sync_upload(&client, &path, dry_run, json_output), + SyncAction::Download { dir, domain } => { + cmd_sync_download(&client, dir, domain, json_output) + } + SyncAction::Upload { path, dry_run } => { + cmd_sync_upload(&client, &path, dry_run, json_output) + } }, Commands::Diff { domain } => cmd_diff(&client, domain, json_output), Commands::Dns { action } => match action { DnsAction::List { domain } => cmd_dns_list(&client, &domain, json_output), - DnsAction::Add { domain, record_type, name, content, ttl, proxied } => { - cmd_dns_add(&client, &domain, &record_type, &name, &content, ttl, proxied, json_output) - } + DnsAction::Add { + domain, + record_type, + name, + content, + ttl, + proxied, + } => cmd_dns_add( + &client, + &domain, + &record_type, + &name, + &content, + ttl, + proxied, + json_output, + ), DnsAction::Delete { domain, record_id } => { cmd_dns_delete(&client, &domain, &record_id, json_output) } @@ -273,17 +293,34 @@ fn cmd_audit( "score": format!("{:.1}%", score), "findings": all_findings, }); - println!("{}", serde_json::to_string_pretty(&report).expect("serializing a typed Rust value to JSON is infallible (no Serialize impl can fail)")); + println!( + "{}", + serde_json::to_string_pretty(&report).expect( + "serializing a typed Rust value to JSON is infallible (no Serialize impl can fail)" + ) + ); } else { println!("CloudGuard Audit Report"); println!("======================="); - println!("Domains: {}", zones.iter().map(|z| z.name.as_str()).collect::>().join(", ")); - println!("Score: {:.1}% ({} passed, {} failed)", score, total_passed, total_failed); + println!( + "Domains: {}", + zones + .iter() + .map(|z| z.name.as_str()) + .collect::>() + .join(", ") + ); + println!( + "Score: {:.1}% ({} passed, {} failed)", + score, total_passed, total_failed + ); if !all_findings.is_empty() { println!("\nFindings:"); for f in &all_findings { - println!(" [{}] {}: {} (expected {}, got {})", - f.severity, f.domain, f.setting_id, f.expected, f.actual); + println!( + " [{}] {}: {} (expected {}, got {})", + f.severity, f.domain, f.setting_id, f.expected, f.actual + ); } } else { println!("\nAll settings match policy. No findings."); @@ -298,8 +335,13 @@ fn cmd_audit( "score": format!("{:.1}%", score), "findings": all_findings, }); - std::fs::write(&path, serde_json::to_string_pretty(&report).expect("serializing a typed Rust value to JSON is infallible (no Serialize impl can fail)")) - .map_err(|e| format!("Failed to write report to {}: {}", path, e))?; + std::fs::write( + &path, + serde_json::to_string_pretty(&report).expect( + "serializing a typed Rust value to JSON is infallible (no Serialize impl can fail)", + ), + ) + .map_err(|e| format!("Failed to write report to {}: {}", path, e))?; eprintln!("Report written to {}", path); } @@ -326,11 +368,14 @@ fn cmd_harden( if apply { let count = client.harden_zone(&zone.id)?; if json_output { - println!("{}", serde_json::json!({ - "domain": zone.name, - "status": "hardened", - "settings_updated": count, - })); + println!( + "{}", + serde_json::json!({ + "domain": zone.name, + "status": "hardened", + "settings_updated": count, + }) + ); } else { println!(" {} settings applied.", count); } @@ -339,11 +384,14 @@ fn cmd_harden( println!(" [DRY RUN] Would apply 17 hardening settings."); println!(" Use --apply to actually apply changes."); } else { - println!("{}", serde_json::json!({ - "domain": zone.name, - "status": "dry_run", - "settings_would_update": 17, - })); + println!( + "{}", + serde_json::json!({ + "domain": zone.name, + "status": "dry_run", + "settings_would_update": 17, + }) + ); } } } @@ -366,11 +414,14 @@ fn cmd_sync_download( for zone in &zones { let path = client.download_config(&zone.id, &zone.name)?; if json_output { - println!("{}", serde_json::json!({ - "domain": zone.name, - "path": path, - "status": "downloaded", - })); + println!( + "{}", + serde_json::json!({ + "domain": zone.name, + "path": path, + "status": "downloaded", + }) + ); } else { println!("Downloaded: {} -> {}", zone.name, path); } @@ -389,22 +440,25 @@ fn cmd_sync_upload( dry_run: bool, json_output: bool, ) -> Result<(), String> { - let file_content = std::fs::read_to_string(path) - .map_err(|e| format!("Failed to read {}: {}", path, e))?; + let file_content = + std::fs::read_to_string(path).map_err(|e| format!("Failed to read {}: {}", path, e))?; let config: serde_json::Value = serde_json::from_str(&file_content) .map_err(|e| format!("Failed to parse JSON from {}: {}", path, e))?; - let zone_id = config.get("zone_id") + let zone_id = config + .get("zone_id") .and_then(|v| v.as_str()) .ok_or_else(|| "Config file missing 'zone_id' field".to_string())?; - let domain = config.get("domain") + let domain = config + .get("domain") .and_then(|v| v.as_str()) .unwrap_or("unknown"); // Get live settings for comparison. let live_settings = client.get_zone_settings(zone_id)?; - let offline_settings = config.get("settings") + let offline_settings = config + .get("settings") .and_then(|v| v.as_array()) .ok_or_else(|| "Config file missing 'settings' array".to_string())?; @@ -423,22 +477,28 @@ fn cmd_sync_upload( } if json_output { - let diff_json: Vec<_> = diffs.iter().map(|(id, offline, live)| { + let diff_json: Vec<_> = diffs + .iter() + .map(|(id, offline, live)| { + serde_json::json!({ + "setting_id": id, + "offline": offline, + "live": live, + }) + }) + .collect(); + + println!( + "{}", serde_json::json!({ - "setting_id": id, - "offline": offline, - "live": live, + "domain": domain, + "zone_id": zone_id, + "diffs": diff_json, + "total_diffs": diffs.len(), + "dry_run": dry_run, + "applied": !dry_run && !diffs.is_empty(), }) - }).collect(); - - println!("{}", serde_json::json!({ - "domain": domain, - "zone_id": zone_id, - "diffs": diff_json, - "total_diffs": diffs.len(), - "dry_run": dry_run, - "applied": !dry_run && !diffs.is_empty(), - })); + ); } else { println!("Config upload: {} ({})", domain, zone_id); if diffs.is_empty() { @@ -452,9 +512,10 @@ fn cmd_sync_upload( } if !dry_run && !diffs.is_empty() { - let items: Vec<_> = diffs.iter().map(|(id, val, _)| { - serde_json::json!({"id": id, "value": val}) - }).collect(); + let items: Vec<_> = diffs + .iter() + .map(|(id, val, _)| serde_json::json!({"id": id, "value": val})) + .collect(); let patch_body = serde_json::json!({"items": items}); client.patch_zone_settings(zone_id, &patch_body)?; @@ -491,15 +552,20 @@ fn cmd_diff( let mut entries = Vec::new(); for &(setting_id, expected, _severity) in api::hardening_policy() { - let live_val = live_settings.iter() + let live_val = live_settings + .iter() .find(|s| s.id == setting_id) .map(|s| setting_value_to_string(&s.value)) .unwrap_or_else(|| "".to_string()); - let offline_val = offline_config.as_ref() + let offline_val = offline_config + .as_ref() .and_then(|cfg| cfg.get("settings")) .and_then(|s| s.as_array()) - .and_then(|arr| arr.iter().find(|s| s.get("id").and_then(|v| v.as_str()) == Some(setting_id))) + .and_then(|arr| { + arr.iter() + .find(|s| s.get("id").and_then(|v| v.as_str()) == Some(setting_id)) + }) .and_then(|s| s.get("value")) .map(|v| setting_value_to_string(v)) .unwrap_or_else(|| "".to_string()); @@ -520,13 +586,19 @@ fn cmd_diff( } if json_output { - println!("{}", serde_json::json!({ - "domain": zone.name, - "diffs": entries, - })); + println!( + "{}", + serde_json::json!({ + "domain": zone.name, + "diffs": entries, + }) + ); } else { println!("Three-way diff for: {}", zone.name); - println!("{:<25} {:<15} {:<15} {:<15} {}", "Setting", "Live", "Offline", "Policy", "Status"); + println!( + "{:<25} {:<15} {:<15} {:<15} {}", + "Setting", "Live", "Offline", "Policy", "Status" + ); println!("{}", "-".repeat(80)); for entry in &entries { @@ -547,7 +619,10 @@ fn cmd_diff( "CHANGED" }; - println!("{:<25} {:<15} {:<15} {:<15} {}", setting, live, offline, policy, status); + println!( + "{:<25} {:<15} {:<15} {:<15} {}", + setting, live, offline, policy, status + ); } println!(); } @@ -569,7 +644,13 @@ fn load_offline_config(domain: &str) -> Option { fn setting_value_to_string(value: &serde_json::Value) -> String { match value { serde_json::Value::String(v) => v.clone(), - serde_json::Value::Bool(b) => if *b { "on".to_string() } else { "off".to_string() }, + serde_json::Value::Bool(b) => { + if *b { + "on".to_string() + } else { + "off".to_string() + } + } serde_json::Value::Number(n) => n.to_string(), other => other.to_string(), } @@ -585,20 +666,39 @@ fn cmd_dns_list( let records = client.list_dns_records(&zone.id)?; if json_output { - println!("{}", serde_json::to_string_pretty(&records).expect("serializing a typed Rust value to JSON is infallible (no Serialize impl can fail)")); + println!( + "{}", + serde_json::to_string_pretty(&records).expect( + "serializing a typed Rust value to JSON is infallible (no Serialize impl can fail)" + ) + ); } else { println!("DNS Records for {} ({} records)", domain, records.len()); - println!("{:<8} {:<30} {:<50} {:<6} {}", "Type", "Name", "Content", "TTL", "Proxy"); + println!( + "{:<8} {:<30} {:<50} {:<6} {}", + "Type", "Name", "Content", "TTL", "Proxy" + ); println!("{}", "-".repeat(100)); for r in &records { - let ttl_str = if r.ttl == 1 { "Auto".to_string() } else { r.ttl.to_string() }; - let proxy_str = if r.proxied.unwrap_or(false) { "ON" } else { "--" }; + let ttl_str = if r.ttl == 1 { + "Auto".to_string() + } else { + r.ttl.to_string() + }; + let proxy_str = if r.proxied.unwrap_or(false) { + "ON" + } else { + "--" + }; let content_display = if r.content.len() > 48 { format!("{}...", &r.content[..48]) } else { r.content.clone() }; - println!("{:<8} {:<30} {:<50} {:<6} {}", r.record_type, r.name, content_display, ttl_str, proxy_str); + println!( + "{:<8} {:<30} {:<50} {:<6} {}", + r.record_type, r.name, content_display, ttl_str, proxy_str + ); } } @@ -620,7 +720,12 @@ fn cmd_dns_add( let record = client.create_dns_record(&zone.id, record_type, name, content, ttl, proxied)?; if json_output { - println!("{}", serde_json::to_string_pretty(&record).expect("serializing a typed Rust value to JSON is infallible (no Serialize impl can fail)")); + println!( + "{}", + serde_json::to_string_pretty(&record).expect( + "serializing a typed Rust value to JSON is infallible (no Serialize impl can fail)" + ) + ); } else { println!("Created {} record: {} -> {}", record_type, name, content); } @@ -639,11 +744,14 @@ fn cmd_dns_delete( client.delete_dns_record(&zone.id, record_id)?; if json_output { - println!("{}", serde_json::json!({ - "domain": domain, - "record_id": record_id, - "status": "deleted", - })); + println!( + "{}", + serde_json::json!({ + "domain": domain, + "record_id": record_id, + "status": "deleted", + }) + ); } else { println!("Deleted record {} from {}", record_id, domain); } @@ -666,9 +774,19 @@ fn cmd_dns_bulk_add( let templates: Vec<(&str, &str, &str, &str)> = vec![ ("TXT", domain, "v=spf1 -all", "SPF deny-all"), - ("TXT", &dmarc_name, "v=DMARC1; p=reject; sp=reject; adkim=s; aspf=s; pct=100; fo=1", "DMARC reject"), + ( + "TXT", + &dmarc_name, + "v=DMARC1; p=reject; sp=reject; adkim=s; aspf=s; pct=100; fo=1", + "DMARC reject", + ), ("TXT", &dkim_name, "v=DKIM1; p=", "DKIM revocation"), - ("CAA", domain, "0 issue \"letsencrypt.org\"", "CAA Let's Encrypt"), + ( + "CAA", + domain, + "0 issue \"letsencrypt.org\"", + "CAA Let's Encrypt", + ), ("TXT", &tlsrpt_name, &tlsrpt_content, "TLS-RPT"), ]; @@ -690,33 +808,46 @@ fn cmd_dns_bulk_add( } if json_output { - println!("{}", serde_json::json!({ - "domain": domain, - "created": created, - "total": templates.len(), - })); + println!( + "{}", + serde_json::json!({ + "domain": domain, + "created": created, + "total": templates.len(), + }) + ); } else { - println!("Created {}/{} security records for {}", created, templates.len(), domain); + println!( + "Created {}/{} security records for {}", + created, + templates.len(), + domain + ); } Ok(()) } /// List all zones. -fn cmd_zones_list( - client: &api::CloudflareClient, - json_output: bool, -) -> Result<(), String> { +fn cmd_zones_list(client: &api::CloudflareClient, json_output: bool) -> Result<(), String> { let zones = client.list_zones()?; if json_output { - println!("{}", serde_json::to_string_pretty(&zones).expect("serializing a typed Rust value to JSON is infallible (no Serialize impl can fail)")); + println!( + "{}", + serde_json::to_string_pretty(&zones).expect( + "serializing a typed Rust value to JSON is infallible (no Serialize impl can fail)" + ) + ); } else { println!("Zones ({} total)", zones.len()); println!("{:<30} {:<10} {:<12} {}", "Domain", "Status", "Plan", "ID"); println!("{}", "-".repeat(80)); for z in &zones { - println!("{:<30} {:<10} {:<12} {}", z.name, z.status, z.plan.name, z.id); + println!( + "{:<30} {:<10} {:<12} {}", + z.name, z.status, z.plan.name, z.id + ); } } @@ -732,7 +863,12 @@ fn cmd_zones_status( let zone = client.find_zone_by_name(domain)?; if json_output { - println!("{}", serde_json::to_string_pretty(&zone).expect("serializing a typed Rust value to JSON is infallible (no Serialize impl can fail)")); + println!( + "{}", + serde_json::to_string_pretty(&zone).expect( + "serializing a typed Rust value to JSON is infallible (no Serialize impl can fail)" + ) + ); } else { println!("Zone: {}", zone.name); println!(" ID: {}", zone.id); @@ -745,17 +881,22 @@ fn cmd_zones_status( } /// List Cloudflare Pages projects. -fn cmd_pages_list( - client: &api::CloudflareClient, - json_output: bool, -) -> Result<(), String> { +fn cmd_pages_list(client: &api::CloudflareClient, json_output: bool) -> Result<(), String> { let projects = client.list_pages_projects()?; if json_output { - println!("{}", serde_json::to_string_pretty(&projects).expect("serializing a typed Rust value to JSON is infallible (no Serialize impl can fail)")); + println!( + "{}", + serde_json::to_string_pretty(&projects).expect( + "serializing a typed Rust value to JSON is infallible (no Serialize impl can fail)" + ) + ); } else { println!("Pages Projects ({} total)", projects.len()); - println!("{:<30} {:<40} {:<15} {}", "Name", "Subdomain", "Branch", "Domains"); + println!( + "{:<30} {:<40} {:<15} {}", + "Name", "Subdomain", "Branch", "Domains" + ); println!("{}", "-".repeat(100)); for p in &projects { let domains = if p.domains.is_empty() { @@ -763,7 +904,10 @@ fn cmd_pages_list( } else { p.domains.join(", ") }; - println!("{:<30} {:<40} {:<15} {}", p.name, p.subdomain, p.production_branch, domains); + println!( + "{:<30} {:<40} {:<15} {}", + p.name, p.subdomain, p.production_branch, domains + ); } } diff --git a/tests/smoke_test.rs b/tests/smoke_test.rs index 922318f..2448418 100644 --- a/tests/smoke_test.rs +++ b/tests/smoke_test.rs @@ -135,7 +135,10 @@ fn audit_settings_all_pass_returns_zero_failures() { let (passed, failed, findings) = audit_settings("example.com", &settings); - assert_eq!(failed, 0, "expected no failures when all settings match policy"); + assert_eq!( + failed, 0, + "expected no failures when all settings match policy" + ); assert!(findings.is_empty(), "expected no findings"); assert_eq!(passed, hardening_policy().len()); } @@ -157,7 +160,10 @@ fn audit_settings_all_fail_returns_full_finding_list() { let (passed, failed, findings) = audit_settings("bad.example", &settings); - assert_eq!(passed, 0, "expected zero passes when all settings are wrong"); + assert_eq!( + passed, 0, + "expected zero passes when all settings are wrong" + ); assert_eq!(failed, hardening_policy().len()); assert_eq!(findings.len(), hardening_policy().len()); @@ -231,7 +237,10 @@ fn hardening_policy_entries_are_well_formed() { for &(id, expected, severity) in policy { assert!(!id.is_empty(), "policy setting id must not be empty"); - assert!(!expected.is_empty(), "policy expected value must not be empty"); + assert!( + !expected.is_empty(), + "policy expected value must not be empty" + ); assert!( matches!(severity, "CRITICAL" | "HIGH" | "MEDIUM" | "LOW"), "severity must be one of CRITICAL/HIGH/MEDIUM/LOW, got: {}", @@ -246,15 +255,14 @@ fn hardening_policy_entries_are_well_formed() { fn hardening_policy_includes_critical_tls_settings() { let policy = hardening_policy(); - let has_ssl = policy.iter().any(|&(id, _, sev)| id == "ssl" && sev == "CRITICAL"); + let has_ssl = policy + .iter() + .any(|&(id, _, sev)| id == "ssl" && sev == "CRITICAL"); let has_always_https = policy .iter() .any(|&(id, _, sev)| id == "always_use_https" && sev == "CRITICAL"); - assert!( - has_ssl, - "policy must include a CRITICAL ssl setting" - ); + assert!(has_ssl, "policy must include a CRITICAL ssl setting"); assert!( has_always_https, "policy must include a CRITICAL always_use_https setting"