From b1f648f31080be044be9cc833d256b82cf011520 Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Tue, 24 Jun 2025 09:55:22 -0400 Subject: [PATCH 01/31] RUST-2235: Initial Devin code --- Cargo.lock | 437 ++++++++++++++++++++++++++++++-------- Cargo.toml | 5 + src/client/auth.rs | 41 +++- src/client/auth/gssapi.rs | 294 +++++++++++++++++++++++++ src/client/options.rs | 20 +- src/test/spec/auth.rs | 9 - 6 files changed, 706 insertions(+), 100 deletions(-) create mode 100644 src/client/auth/gssapi.rs diff --git a/Cargo.lock b/Cargo.lock index c7824f756..7ab280239 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 [[package]] name = "addr2line" @@ -35,7 +35,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.3.2", + "getrandom 0.3.3", "once_cell", "version_check", "zerocopy", @@ -111,7 +111,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -122,7 +122,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -170,6 +170,26 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +[[package]] +name = "bindgen" +version = "0.71.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" +dependencies = [ + "bitflags 2.9.1", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.104", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -178,9 +198,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "bitvec" @@ -221,9 +241,9 @@ dependencies = [ "base64 0.22.1", "bitvec", "getrandom 0.2.16", - "getrandom 0.3.2", + "getrandom 0.3.3", "hex", - "indexmap 2.9.0", + "indexmap 2.10.0", "js-sys", "once_cell", "rand 0.9.1", @@ -237,15 +257,15 @@ dependencies = [ [[package]] name = "bson" version = "3.0.0" -source = "git+https://github.com/mongodb/bson-rust?branch=main#431d4483856b18d1b8885d0b46a60be7f2eb2dee" +source = "git+https://github.com/mongodb/bson-rust?branch=main#7703fee7e296ddf045d1dafe61359fccac41368a" dependencies = [ "ahash", "base64 0.22.1", "bitvec", "getrandom 0.2.16", - "getrandom 0.3.2", + "getrandom 0.3.3", "hex", - "indexmap 2.9.0", + "indexmap 2.10.0", "js-sys", "once_cell", "rand 0.9.1", @@ -290,6 +310,15 @@ dependencies = [ "shlex", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.1" @@ -325,6 +354,17 @@ dependencies = [ "inout", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -391,6 +431,19 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "cross-krb5" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d4ddf7139e64dc916b11d434421031bcc5ba02e521a49a011652a0f68775188" +dependencies = [ + "anyhow", + "bitflags 2.9.1", + "bytes", + "libgssapi", + "windows", +] + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -463,7 +516,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -474,7 +527,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -511,7 +564,7 @@ checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -522,7 +575,7 @@ checksum = "510c292c8cf384b1a340b816a9a6cf2599eb8f566a44949024af88418000c50b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -535,7 +588,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -572,7 +625,19 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", +] + +[[package]] +name = "dns-lookup" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5766087c2235fec47fafa4cfecc81e494ee679d0fd4a59887ea0919bfb0e4fc" +dependencies = [ + "cfg-if", + "libc", + "socket2", + "windows-sys 0.48.0", ] [[package]] @@ -605,7 +670,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -621,7 +686,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -752,7 +817,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -810,9 +875,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "js-sys", @@ -828,6 +893,12 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + [[package]] name = "h2" version = "0.4.11" @@ -840,7 +911,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.3.1", - "indexmap 2.9.0", + "indexmap 2.10.0", "slab", "tokio", "tokio-util", @@ -855,9 +926,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.15.3" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" [[package]] name = "heck" @@ -916,7 +987,7 @@ dependencies = [ "parking_lot", "rand 0.8.5", "resolv-conf", - "smallvec 1.15.0", + "smallvec 1.15.1", "thiserror 1.0.69", "tokio", "tracing", @@ -1046,7 +1117,7 @@ dependencies = [ "httparse", "itoa", "pin-project-lite", - "smallvec 1.15.0", + "smallvec 1.15.1", "tokio", "want", ] @@ -1065,7 +1136,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots 1.0.0", + "webpki-roots 1.0.1", ] [[package]] @@ -1086,9 +1157,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" +checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" dependencies = [ "base64 0.22.1", "bytes", @@ -1171,7 +1242,7 @@ dependencies = [ "icu_normalizer_data", "icu_properties", "icu_provider", - "smallvec 1.15.0", + "smallvec 1.15.1", "zerovec", ] @@ -1233,7 +1304,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ "idna_adapter", - "smallvec 1.15.0", + "smallvec 1.15.1", "utf8_iter", ] @@ -1260,12 +1331,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", - "hashbrown 0.15.3", + "hashbrown 0.15.4", "serde", ] @@ -1285,7 +1356,7 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cfg-if", "libc", ] @@ -1318,6 +1389,15 @@ dependencies = [ "serde", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" @@ -1330,7 +1410,7 @@ version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", "libc", ] @@ -1388,6 +1468,38 @@ version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +[[package]] +name = "libgssapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "834339e86b2561169d45d3b01741967fee3e5716c7d0b6e33cd4e3b34c9558cd" +dependencies = [ + "bitflags 2.9.1", + "bytes", + "lazy_static", + "libgssapi-sys", +] + +[[package]] +name = "libgssapi-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7518e6902e94f92e7c7271232684b60988b4bd813529b4ef9d97aead96956ae8" +dependencies = [ + "bindgen", + "pkg-config", +] + +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets 0.53.2", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -1446,7 +1558,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1460,7 +1572,7 @@ dependencies = [ "macro_magic_core_macros", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1471,7 +1583,7 @@ checksum = "b02abfe41815b5bd98dbd4260173db2c116dda171dc0fe7838cb206333b83308" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1482,7 +1594,7 @@ checksum = "73ea28ee64b88876bf45277ed9a5817c1817df061a74f2b988971a12570e5869" dependencies = [ "macro_magic_core", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1513,6 +1625,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -1563,9 +1681,11 @@ dependencies = [ "bson 2.15.0", "bson 3.0.0", "chrono", + "cross-krb5", "ctrlc", "derive-where", "derive_more", + "dns-lookup", "flate2", "function_name", "futures", @@ -1635,7 +1755,7 @@ dependencies = [ "macro_magic", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1661,7 +1781,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cfg-if", "cfg_aliases", "libc", @@ -1673,6 +1793,16 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1729,7 +1859,7 @@ version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cfg-if", "foreign-types", "libc", @@ -1746,7 +1876,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1792,7 +1922,7 @@ dependencies = [ "cfg-if", "libc", "redox_syscall", - "smallvec 1.15.0", + "smallvec 1.15.1", "windows-targets 0.52.6", ] @@ -1848,7 +1978,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1932,6 +2062,16 @@ dependencies = [ "yansi", ] +[[package]] +name = "prettyplease" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" +dependencies = [ + "proc-macro2", + "syn 2.0.104", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -1977,7 +2117,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" dependencies = [ "bytes", - "getrandom 0.3.2", + "getrandom 0.3.3", "lru-slab", "rand 0.9.1", "ring", @@ -2082,7 +2222,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", ] [[package]] @@ -2111,7 +2251,7 @@ version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -2131,7 +2271,7 @@ checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -2204,7 +2344,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 1.0.0", + "webpki-roots 1.0.1", ] [[package]] @@ -2264,7 +2404,7 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys", @@ -2384,7 +2524,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "core-foundation", "core-foundation-sys", "libc", @@ -2444,7 +2584,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -2453,7 +2593,7 @@ version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ - "indexmap 2.9.0", + "indexmap 2.10.0", "itoa", "memchr", "ryu", @@ -2492,7 +2632,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.9.0", + "indexmap 2.10.0", "schemars 0.9.0", "schemars 1.0.4", "serde", @@ -2511,7 +2651,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -2583,9 +2723,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "snap" @@ -2655,9 +2795,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.101" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -2681,7 +2821,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -2690,7 +2830,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "core-foundation", "system-configuration-sys", ] @@ -2724,7 +2864,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ "fastrand", - "getrandom 0.3.2", + "getrandom 0.3.3", "once_cell", "rustix", "windows-sys 0.59.0", @@ -2756,7 +2896,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -2767,7 +2907,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -2872,7 +3012,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -2976,7 +3116,7 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "bytes", "futures-util", "http 1.3.1", @@ -3020,7 +3160,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -3052,7 +3192,7 @@ checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "nu-ansi-term", "sharded-slab", - "smallvec 1.15.0", + "smallvec 1.15.1", "thread_local", "tracing-core", "tracing-log", @@ -3081,7 +3221,7 @@ checksum = "3c36781cc0e46a83726d9879608e4cf6c2505237e263a8eb8c24502989cfdb28" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -3146,7 +3286,7 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", "js-sys", "serde", "wasm-bindgen", @@ -3216,7 +3356,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", "wasm-bindgen-shared", ] @@ -3251,7 +3391,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3291,14 +3431,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.0", + "webpki-roots 1.0.1", ] [[package]] name = "webpki-roots" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb" +checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502" dependencies = [ "rustls-pki-types", ] @@ -3331,6 +3471,28 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + [[package]] name = "windows-core" version = "0.61.2" @@ -3344,6 +3506,17 @@ dependencies = [ "windows-strings", ] +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + [[package]] name = "windows-implement" version = "0.60.0" @@ -3352,7 +3525,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -3363,7 +3536,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -3372,6 +3545,16 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link", +] + [[package]] name = "windows-registry" version = "0.5.3" @@ -3428,6 +3611,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -3452,13 +3644,38 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -3471,6 +3688,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -3483,6 +3706,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -3495,12 +3724,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -3513,6 +3754,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -3525,6 +3772,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -3537,6 +3790,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -3549,6 +3808,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winreg" version = "0.50.0" @@ -3565,7 +3830,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -3609,7 +3874,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", "synstructure", ] @@ -3630,7 +3895,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -3650,7 +3915,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", "synstructure", ] @@ -3690,7 +3955,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c8e8d8733..ad3219b4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,9 @@ gcp-oidc = ["dep:reqwest"] # This can only be used with the tokio-runtime feature flag. gcp-kms = ["dep:reqwest"] +# Enable support for GSSAPI (Kerberos) authentication. +gssapi-auth = ["dep:cross-krb5", "dep:dns-lookup"] + zstd-compression = ["dep:zstd"] zlib-compression = ["dep:flate2"] snappy-compression = ["dep:snap"] @@ -82,6 +85,7 @@ chrono = { version = "0.4.7", default-features = false, features = [ ] } derive_more = "0.99.17" derive-where = "1.2.7" +dns-lookup = { version = "2.0", optional = true } flate2 = { version = "1.0", optional = true } futures-io = "0.3.21" futures-core = "0.3.14" @@ -109,6 +113,7 @@ sha1 = "0.10.0" sha2 = "0.10.2" snap = { version = "1.0.5", optional = true } socket2 = "0.5.5" +cross-krb5 = { version = "0.4.1", optional = true } stringprep = "0.1.2" strsim = "0.11.1" take_mut = "0.2.2" diff --git a/src/client/auth.rs b/src/client/auth.rs index 3c53c1a29..5c946c6f0 100644 --- a/src/client/auth.rs +++ b/src/client/auth.rs @@ -3,6 +3,8 @@ #[cfg(feature = "aws-auth")] pub(crate) mod aws; +#[cfg(feature = "gssapi-auth")] +mod gssapi; /// Contains the functionality for [`OIDC`](https://openid.net/developers/how-connect-works/) authorization and authentication. pub mod oidc; mod plain; @@ -67,8 +69,7 @@ pub enum AuthMechanism { /// Kerberos authentication mechanism as defined in [RFC 4752](http://tools.ietf.org/html/rfc4752). /// /// See the [MongoDB documentation](https://www.mongodb.com/docs/manual/core/kerberos/) for more information. - /// - /// Note: This mechanism is not currently supported by this driver but will be in the future. + #[cfg(feature = "gssapi-auth")] Gssapi, /// The SASL PLAIN mechanism, as defined in [RFC 4616](), is used in MongoDB to perform LDAP @@ -186,6 +187,25 @@ impl AuthMechanism { Ok(()) } AuthMechanism::MongoDbOidc => oidc::validate_credential(credential), + #[cfg(feature = "gssapi-auth")] + AuthMechanism::Gssapi => { + if credential.username.is_none() { + return Err(ErrorKind::InvalidArgument { + message: "No username provided for GSSAPI authentication".to_string(), + } + .into()); + } + + if credential.source.as_deref().unwrap_or("$external") != "$external" { + return Err(ErrorKind::InvalidArgument { + message: "only $external may be specified as an auth source for GSSAPI" + .to_string(), + } + .into()); + } + + Ok(()) + } _ => Ok(()), } } @@ -197,6 +217,7 @@ impl AuthMechanism { AuthMechanism::ScramSha256 => SCRAM_SHA_256_STR, AuthMechanism::MongoDbCr => MONGODB_CR_STR, AuthMechanism::MongoDbX509 => MONGODB_X509_STR, + #[cfg(feature = "gssapi-auth")] AuthMechanism::Gssapi => GSSAPI_STR, AuthMechanism::Plain => PLAIN_STR, #[cfg(feature = "aws-auth")] @@ -217,7 +238,8 @@ impl AuthMechanism { AuthMechanism::MongoDbOidc => "$external", #[cfg(feature = "aws-auth")] AuthMechanism::MongoDbAws => "$external", - AuthMechanism::Gssapi => "", + #[cfg(feature = "gssapi-auth")] + AuthMechanism::Gssapi => "$external", } } @@ -248,6 +270,8 @@ impl AuthMechanism { .map(|comm| ClientFirst::Oidc(Box::new(comm)))), #[cfg(feature = "aws-auth")] AuthMechanism::MongoDbAws => Ok(None), + #[cfg(feature = "gssapi-auth")] + AuthMechanism::Gssapi => Ok(None), AuthMechanism::MongoDbCr => Err(ErrorKind::Authentication { message: "MONGODB-CR is deprecated and not supported by this driver. Use SCRAM \ for password-based authentication instead" @@ -300,6 +324,10 @@ impl AuthMechanism { AuthMechanism::MongoDbOidc => { oidc::authenticate_stream(stream, credential, server_api, None).await } + #[cfg(feature = "gssapi-auth")] + AuthMechanism::Gssapi => { + gssapi::authenticate_stream(stream, credential, server_api, None).await + } _ => Err(ErrorKind::Authentication { message: format!("Authentication mechanism {:?} not yet implemented.", self), } @@ -355,7 +383,13 @@ impl FromStr for AuthMechanism { SCRAM_SHA_256_STR => Ok(AuthMechanism::ScramSha256), MONGODB_CR_STR => Ok(AuthMechanism::MongoDbCr), MONGODB_X509_STR => Ok(AuthMechanism::MongoDbX509), + #[cfg(feature = "gssapi-auth")] GSSAPI_STR => Ok(AuthMechanism::Gssapi), + #[cfg(not(feature = "gssapi-auth"))] + GSSAPI_STR => Err(ErrorKind::InvalidArgument { + message: "GSSAPI auth is only supported with the gssapi-auth feature flag".into(), + } + .into()), PLAIN_STR => Ok(AuthMechanism::Plain), MONGODB_OIDC_STR => Ok(AuthMechanism::MongoDbOidc), #[cfg(feature = "aws-auth")] @@ -499,7 +533,6 @@ impl Credential { None => Cow::Owned(AuthMechanism::from_stream_description(stream_description)), Some(ref m) => Cow::Borrowed(m), }; - // Authenticate according to the chosen mechanism. mechanism .authenticate_stream( diff --git a/src/client/auth/gssapi.rs b/src/client/auth/gssapi.rs new file mode 100644 index 000000000..6955ffd29 --- /dev/null +++ b/src/client/auth/gssapi.rs @@ -0,0 +1,294 @@ +#![cfg(feature = "gssapi-auth")] + +use cross_krb5::{ClientCtx, InitiateFlags, Step}; +use dns_lookup::getnameinfo; +use std::net::SocketAddr; + +use crate::{ + bson::{Bson, Document}, + client::{ + auth::{ + sasl::{SaslContinue, SaslResponse, SaslStart}, + Credential, + }, + options::ServerApi, + }, + cmap::{Command, Connection}, + error::{Error, ErrorKind, Result}, +}; +#[derive(Debug, Clone)] +pub(crate) struct GssapiProperties { + pub service_name: String, + pub canonicalize_host_name: CanonicalizeHostName, + pub service_realm: Option, + pub service_host: Option, + pub kdc_url: Option, +} + +#[derive(Debug, Clone)] +pub(crate) enum CanonicalizeHostName { + None, + Forward, + ForwardAndReverse, +} + +impl Default for CanonicalizeHostName { + fn default() -> Self { + CanonicalizeHostName::None + } +} + +impl GssapiProperties { + pub fn from_credential(credential: &Credential) -> Result { + let mut properties = GssapiProperties { + service_name: "mongodb".to_string(), + canonicalize_host_name: CanonicalizeHostName::None, + service_realm: None, + service_host: None, + kdc_url: None, + }; + + if let Some(mechanism_properties) = &credential.mechanism_properties { + if let Some(service_name) = mechanism_properties.get("SERVICE_NAME") { + if let Bson::String(name) = service_name { + properties.service_name = name.clone(); + } + } + + if let Some(canonicalize) = mechanism_properties.get("CANONICALIZE_HOST_NAME") { + properties.canonicalize_host_name = match canonicalize { + Bson::String(s) => match s.as_str() { + "none" => CanonicalizeHostName::None, + "forward" => CanonicalizeHostName::Forward, + "forwardAndReverse" => CanonicalizeHostName::ForwardAndReverse, + _ => return Err(ErrorKind::InvalidArgument { + message: format!("Invalid CANONICALIZE_HOST_NAME value: {}. Valid values are 'none', 'forward', 'forwardAndReverse'", s), + }.into()), + }, + Bson::Boolean(true) => CanonicalizeHostName::ForwardAndReverse, + Bson::Boolean(false) => CanonicalizeHostName::None, + _ => return Err(ErrorKind::InvalidArgument { + message: "CANONICALIZE_HOST_NAME must be a string or boolean".to_string(), + }.into()), + }; + } + + if let Some(service_realm) = mechanism_properties.get("SERVICE_REALM") { + if let Bson::String(realm) = service_realm { + properties.service_realm = Some(realm.clone()); + } + } + + if let Some(service_host) = mechanism_properties.get("SERVICE_HOST") { + if let Bson::String(host) = service_host { + properties.service_host = Some(host.clone()); + } + } + + if let Some(kdc_url) = mechanism_properties.get("KDC_URL") { + if let Bson::String(url) = kdc_url { + properties.kdc_url = Some(url.clone()); + } + } + } + + Ok(properties) + } +} + +struct GssapiAuthenticator { + pending_ctx: Option, + client_ctx: Option, + service_principal: String, + is_complete: bool, + properties: GssapiProperties, + user_principal: Option, +} + +impl GssapiAuthenticator { + async fn new( + credential: &Credential, + properties: GssapiProperties, + hostname: &str, + ) -> Result { + let service_name: &str = properties.service_name.as_ref(); + let service_principal = format!("{}/{}", service_name, hostname); + + Ok(Self { + pending_ctx: None, + client_ctx: None, + service_principal, + is_complete: false, + properties, + user_principal: credential.username.clone(), + }) + } + + async fn step(&mut self, challenge: Option<&[u8]>) -> Result>> { + if self.pending_ctx.is_none() && self.client_ctx.is_none() { + let (pending_ctx, initial_token) = ClientCtx::new( + InitiateFlags::empty(), + self.user_principal.as_deref(), // Use provided credentials + &self.service_principal, + None, // No channel bindings + ) + .map_err(|e| { + Error::authentication_error( + "GSSAPI", + &format!("Failed to initialize GSSAPI context: {}", e), + ) + })?; + + self.pending_ctx = Some(pending_ctx); + return Ok(Some(initial_token.to_vec())); + } + + if let Some(challenge_data) = challenge { + if let Some(pending_ctx) = self.pending_ctx.take() { + match pending_ctx.step(challenge_data).map_err(|e| { + Error::authentication_error("GSSAPI", &format!("GSSAPI step failed: {}", e)) + })? { + Step::Finished((ctx, token)) => { + self.client_ctx = Some(ctx); + self.is_complete = true; + Ok(token.map(|t| t.to_vec())) + } + Step::Continue((ctx, token)) => { + self.pending_ctx = Some(ctx); + Ok(Some(token.to_vec())) + } + } + } else { + Err(Error::authentication_error( + "GSSAPI", + "Authentication context not initialized", + )) + } + } else { + Err(Error::authentication_error( + "GSSAPI", + "Expected challenge data for GSSAPI continuation", + )) + } + } + + fn is_complete(&self) -> bool { + self.is_complete + } +} + +async fn canonicalize_hostname(hostname: &str, mode: &CanonicalizeHostName) -> Result { + match mode { + CanonicalizeHostName::None => Ok(hostname.to_string()), + CanonicalizeHostName::Forward => { + let (canonical_host, _address) = resolve_hostname_with_canonical(hostname).await?; + Ok(canonical_host.to_lowercase()) + } + CanonicalizeHostName::ForwardAndReverse => { + let (canonical_host, address) = resolve_hostname_with_canonical(hostname).await?; + match perform_reverse_dns_lookup(address).await { + Ok(reversed_hostname) => Ok(reversed_hostname.to_lowercase()), + Err(_) => Ok(canonical_host.to_lowercase()), + } + } + } +} + +async fn resolve_hostname_with_canonical(hostname: &str) -> Result<(String, std::net::IpAddr)> { + match tokio::net::lookup_host((hostname, 0)).await { + Ok(mut addrs) => { + if let Some(addr) = addrs.next() { + Ok((hostname.to_string(), addr.ip())) + } else { + return Err(Error::authentication_error( + "GSSAPI", + &format!("No addresses found for hostname '{}'", hostname), + )); + } + } + Err(e) => Err(Error::authentication_error( + "GSSAPI", + &format!("DNS resolution failed for hostname '{}': {}", hostname, e), + )), + } +} + +async fn perform_reverse_dns_lookup(ip: std::net::IpAddr) -> Result { + let sockaddr = SocketAddr::new(ip, 0); + match tokio::task::spawn_blocking(move || getnameinfo(&sockaddr, 0)).await { + Ok(Ok((hostname, _))) => Ok(hostname), + Ok(Err(_)) | Err(_) => Err(Error::authentication_error( + "GSSAPI", + "Reverse DNS lookup failed", + )), + } +} + +pub(crate) async fn authenticate_stream( + conn: &mut Connection, + credential: &Credential, + server_api: Option<&ServerApi>, + _server_first: impl Into>, +) -> Result<()> { + let properties = GssapiProperties::from_credential(credential)?; + let hostname = + canonicalize_hostname(&conn.address().host(), &properties.canonicalize_host_name).await?; + let _service_host = properties.service_host.as_ref().unwrap_or(&hostname); + + let mut authenticator = + GssapiAuthenticator::new(credential, properties.clone(), &hostname).await?; + + let source = credential.source.as_deref().unwrap_or("$external"); + let mut conversation_id = None; + let mut payload = Vec::new(); + + for _ in 0..10 { + let challenge = if payload.is_empty() { + None + } else { + Some(payload.as_slice()) + }; + let output_token = authenticator.step(challenge).await?; + + if let Some(token) = output_token { + let command = if conversation_id.is_none() { + SaslStart::new( + source.to_string(), + crate::client::auth::AuthMechanism::Gssapi, + token, + server_api.cloned(), + ) + .into_command() + } else { + SaslContinue::new( + source.to_string(), + conversation_id.clone().unwrap(), + token, + server_api.cloned(), + ) + .into_command() + }; + + let response_doc = conn.send_message(command).await?.body()?; + let sasl_response = SaslResponse::parse("GSSAPI", response_doc)?; + + conversation_id = Some(sasl_response.conversation_id); + payload = sasl_response.payload; + + if sasl_response.done && authenticator.is_complete() { + return Ok(()); + } + } else if authenticator.is_complete() { + return Ok(()); + } + } + + Err(Error::authentication_error( + "GSSAPI", + "GSSAPI authentication failed after 10 attempts", + )) +} + +pub(crate) fn build_speculative_client_first(_credential: &Credential) -> Option { + None +} diff --git a/src/client/options.rs b/src/client/options.rs index ea6a7e18c..4f54e5997 100644 --- a/src/client/options.rs +++ b/src/client/options.rs @@ -1583,7 +1583,15 @@ impl ConnectionString { let val = match &s.to_lowercase()[..] { "true" => Bson::Boolean(true), "false" => Bson::Boolean(false), - _ => Bson::String(s), + "none" | "forward" | "forwardandreverse" => Bson::String(s), + _ => { + return Err(ErrorKind::InvalidArgument { + message: format!( + "Invalid CANONICALIZE_HOST_NAME value: {}. Valid values are 'none', 'forward', 'forwardAndReverse', 'true', 'false'", + s + ), + }.into()); + } }; doc.insert("CANONICALIZE_HOST_NAME", val); } @@ -1596,6 +1604,16 @@ impl ConnectionString { credential.mechanism_properties = Some(doc); } + #[cfg(feature = "gssapi-auth")] + if mechanism == &crate::options::AuthMechanism::Gssapi + && credential.mechanism_properties.is_none() + { + // If no auth_mechanism_properties are provided, set mongodb as the default SERVICE_NAME + let mut doc = crate::bson::Document::new(); + doc.insert("SERVICE_NAME", "mongodb"); + credential.mechanism_properties = Some(doc); + } + credential.mechanism = Some(mechanism.clone()); mechanism.validate_credential(credential)?; } diff --git a/src/test/spec/auth.rs b/src/test/spec/auth.rs index e19b72b64..2689533d3 100644 --- a/src/test/spec/auth.rs +++ b/src/test/spec/auth.rs @@ -52,26 +52,17 @@ async fn run_auth_test(test_file: TestFile) { test_case.description = test_case.description.replace('$', "%"); let skipped_mechanisms = [ - "GSSAPI", "MONGODB-CR", #[cfg(not(feature = "aws-auth"))] "MONGODB-AWS", ]; - // TODO: GSSAPI (RUST-196) if skipped_mechanisms .iter() .any(|mech| test_case.description.contains(mech)) { continue; } - // This one's GSSAPI but doesn't include it in the description - if test_case - .description - .contains("must raise an error when the hostname canonicalization is invalid") - { - continue; - } // Lack of callback can't be validated from just URI parsing, as it's set in code if test_case .description From 9789819bc1aa6e6868e47c064a1c426bd44a3ab6 Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Tue, 24 Jun 2025 13:11:43 -0400 Subject: [PATCH 02/31] RUST-2235: Update auth spec tests to guard GSSAPI tests behind feature flag --- Cargo.toml | 2 +- src/test/spec/auth.rs | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ad3219b4f..835185aa1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -113,7 +113,7 @@ sha1 = "0.10.0" sha2 = "0.10.2" snap = { version = "1.0.5", optional = true } socket2 = "0.5.5" -cross-krb5 = { version = "0.4.1", optional = true } +cross-krb5 = { version = "0.4.2", optional = true, default-features = false } stringprep = "0.1.2" strsim = "0.11.1" take_mut = "0.2.2" diff --git a/src/test/spec/auth.rs b/src/test/spec/auth.rs index 2689533d3..db1962726 100644 --- a/src/test/spec/auth.rs +++ b/src/test/spec/auth.rs @@ -52,6 +52,8 @@ async fn run_auth_test(test_file: TestFile) { test_case.description = test_case.description.replace('$', "%"); let skipped_mechanisms = [ + #[cfg(not(feature = "gssapi-auth"))] + "GSSAPI", "MONGODB-CR", #[cfg(not(feature = "aws-auth"))] "MONGODB-AWS", @@ -63,6 +65,14 @@ async fn run_auth_test(test_file: TestFile) { { continue; } + #[cfg(not(feature = "gssapi-auth"))] + // This one's GSSAPI but doesn't include it in the description + if test_case + .description + .contains("must raise an error when the hostname canonicalization is invalid") + { + continue; + } // Lack of callback can't be validated from just URI parsing, as it's set in code if test_case .description From e406e165c4a332ed155039e1bcdebb8a84cf3437 Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Tue, 24 Jun 2025 17:12:45 -0400 Subject: [PATCH 03/31] RUST-2235: Clean up options and client/auth --- src/client/auth.rs | 58 ++++++++++++++++++++++++------------------- src/client/options.rs | 18 +++++++++----- 2 files changed, 45 insertions(+), 31 deletions(-) diff --git a/src/client/auth.rs b/src/client/auth.rs index 5c946c6f0..1e0754910 100644 --- a/src/client/auth.rs +++ b/src/client/auth.rs @@ -149,6 +149,25 @@ impl AuthMechanism { Ok(()) } + #[cfg(feature = "gssapi-auth")] + AuthMechanism::Gssapi => { + if credential.username.is_none() { + return Err(ErrorKind::InvalidArgument { + message: "No username provided for GSSAPI authentication".to_string(), + } + .into()); + } + + if credential.source.as_deref().unwrap_or("$external") != "$external" { + return Err(ErrorKind::InvalidArgument { + message: "only $external may be specified as an auth source for GSSAPI" + .to_string(), + } + .into()); + } + + Ok(()) + } AuthMechanism::Plain => { if credential.username.is_none() { return Err(ErrorKind::InvalidArgument { @@ -187,25 +206,6 @@ impl AuthMechanism { Ok(()) } AuthMechanism::MongoDbOidc => oidc::validate_credential(credential), - #[cfg(feature = "gssapi-auth")] - AuthMechanism::Gssapi => { - if credential.username.is_none() { - return Err(ErrorKind::InvalidArgument { - message: "No username provided for GSSAPI authentication".to_string(), - } - .into()); - } - - if credential.source.as_deref().unwrap_or("$external") != "$external" { - return Err(ErrorKind::InvalidArgument { - message: "only $external may be specified as an auth source for GSSAPI" - .to_string(), - } - .into()); - } - - Ok(()) - } _ => Ok(()), } } @@ -264,14 +264,14 @@ impl AuthMechanism { Self::MongoDbX509 => Ok(Some(ClientFirst::X509(Box::new( x509::build_speculative_client_first(credential)?, )))), + #[cfg(feature = "gssapi-auth")] + AuthMechanism::Gssapi => Ok(None), Self::Plain => Ok(None), Self::MongoDbOidc => Ok(oidc::build_speculative_client_first(credential) .await .map(|comm| ClientFirst::Oidc(Box::new(comm)))), #[cfg(feature = "aws-auth")] AuthMechanism::MongoDbAws => Ok(None), - #[cfg(feature = "gssapi-auth")] - AuthMechanism::Gssapi => Ok(None), AuthMechanism::MongoDbCr => Err(ErrorKind::Authentication { message: "MONGODB-CR is deprecated and not supported by this driver. Use SCRAM \ for password-based authentication instead" @@ -308,6 +308,10 @@ impl AuthMechanism { AuthMechanism::MongoDbX509 => { x509::authenticate_stream(stream, credential, server_api, None).await } + #[cfg(feature = "gssapi-auth")] + AuthMechanism::Gssapi => { + gssapi::authenticate_stream(stream, credential, server_api, None).await + } AuthMechanism::Plain => { plain::authenticate_stream(stream, credential, server_api).await } @@ -324,10 +328,6 @@ impl AuthMechanism { AuthMechanism::MongoDbOidc => { oidc::authenticate_stream(stream, credential, server_api, None).await } - #[cfg(feature = "gssapi-auth")] - AuthMechanism::Gssapi => { - gssapi::authenticate_stream(stream, credential, server_api, None).await - } _ => Err(ErrorKind::Authentication { message: format!("Authentication mechanism {:?} not yet implemented.", self), } @@ -355,6 +355,14 @@ impl AuthMechanism { ), } .into()), + #[cfg(feature = "gssapi-auth")] + AuthMechanism::Gssapi => Err(ErrorKind::Authentication { + message: format!( + "Reauthentication for authentication mechanism {:?} is not supported.", + self + ), + } + .into()), #[cfg(feature = "aws-auth")] AuthMechanism::MongoDbAws => Err(ErrorKind::Authentication { message: format!( diff --git a/src/client/options.rs b/src/client/options.rs index 4f54e5997..9718d2e09 100644 --- a/src/client/options.rs +++ b/src/client/options.rs @@ -1605,12 +1605,18 @@ impl ConnectionString { } #[cfg(feature = "gssapi-auth")] - if mechanism == &crate::options::AuthMechanism::Gssapi - && credential.mechanism_properties.is_none() - { - // If no auth_mechanism_properties are provided, set mongodb as the default SERVICE_NAME - let mut doc = crate::bson::Document::new(); - doc.insert("SERVICE_NAME", "mongodb"); + if mechanism == &AuthMechanism::Gssapi { + // Set mongodb as the default SERVICE_NAME if none is provided + let mut doc = if let Some(doc) = credential.mechanism_properties.take() { + doc + } else { + Document::new() + }; + + if !doc.contains_key("SERVICE_NAME") { + doc.insert("SERVICE_NAME", "mongodb"); + } + credential.mechanism_properties = Some(doc); } From 50aa9d9b7ce52c86a28e51677044a82899a85845 Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Mon, 30 Jun 2025 11:09:34 -0400 Subject: [PATCH 04/31] RUST-2235: Fix warnings --- src/client/auth.rs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/client/auth.rs b/src/client/auth.rs index 1e0754910..66ec3b5cf 100644 --- a/src/client/auth.rs +++ b/src/client/auth.rs @@ -278,10 +278,6 @@ impl AuthMechanism { .into(), } .into()), - _ => Err(ErrorKind::Authentication { - message: format!("Authentication mechanism {:?} not yet implemented.", self), - } - .into()), } } @@ -310,7 +306,7 @@ impl AuthMechanism { } #[cfg(feature = "gssapi-auth")] AuthMechanism::Gssapi => { - gssapi::authenticate_stream(stream, credential, server_api, None).await + gssapi::authenticate_stream(stream, credential, server_api).await } AuthMechanism::Plain => { plain::authenticate_stream(stream, credential, server_api).await @@ -328,10 +324,6 @@ impl AuthMechanism { AuthMechanism::MongoDbOidc => { oidc::authenticate_stream(stream, credential, server_api, None).await } - _ => Err(ErrorKind::Authentication { - message: format!("Authentication mechanism {:?} not yet implemented.", self), - } - .into()), } } @@ -374,10 +366,6 @@ impl AuthMechanism { AuthMechanism::MongoDbOidc => { oidc::reauthenticate_stream(stream, credential, server_api).await } - _ => Err(ErrorKind::Authentication { - message: format!("Authentication mechanism {:?} not yet implemented.", self), - } - .into()), } } } From 9267940721b5523c144d6aafe451818c95b60b03 Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Tue, 1 Jul 2025 14:53:29 -0400 Subject: [PATCH 05/31] RUST-2235: Clean up gssapi.rs and include debug code for further testing --- src/client/auth/gssapi.rs | 272 +++++++++++++++++++++++--------------- src/test/auth.rs | 20 +++ 2 files changed, 186 insertions(+), 106 deletions(-) diff --git a/src/client/auth/gssapi.rs b/src/client/auth/gssapi.rs index 6955ffd29..20f26800e 100644 --- a/src/client/auth/gssapi.rs +++ b/src/client/auth/gssapi.rs @@ -1,11 +1,9 @@ -#![cfg(feature = "gssapi-auth")] - use cross_krb5::{ClientCtx, InitiateFlags, Step}; use dns_lookup::getnameinfo; use std::net::SocketAddr; use crate::{ - bson::{Bson, Document}, + bson::Bson, client::{ auth::{ sasl::{SaslContinue, SaslResponse, SaslStart}, @@ -13,29 +11,141 @@ use crate::{ }, options::ServerApi, }, - cmap::{Command, Connection}, - error::{Error, ErrorKind, Result}, + cmap::Connection, + error::{Error, Result}, }; + +const SERVICE_NAME: &str = "SERVICE_NAME"; +const CANONICALIZE_HOST_NAME: &str = "CANONICALIZE_HOST_NAME"; +const SERVICE_REALM: &str = "SERVICE_REALM"; +const SERVICE_HOST: &str = "SERVICE_HOST"; +const MECH_NAME: &str = "GSSAPI"; + #[derive(Debug, Clone)] pub(crate) struct GssapiProperties { pub service_name: String, pub canonicalize_host_name: CanonicalizeHostName, pub service_realm: Option, pub service_host: Option, - pub kdc_url: Option, } -#[derive(Debug, Clone)] +#[derive(Debug, Default, Clone)] pub(crate) enum CanonicalizeHostName { + #[default] None, Forward, ForwardAndReverse, } -impl Default for CanonicalizeHostName { - fn default() -> Self { - CanonicalizeHostName::None +pub(crate) async fn authenticate_stream( + conn: &mut Connection, + credential: &Credential, + server_api: Option<&ServerApi>, +) -> Result<()> { + let properties = GssapiProperties::from_credential(credential)?; + + let conn_host = conn.address.host().to_string(); + let hostname = properties.service_host.as_ref().unwrap_or(&conn_host); + let hostname = canonicalize_hostname(hostname, &properties.canonicalize_host_name).await?; + + let user_principal = credential.username.clone(); + let mut authenticator = + GssapiAuthenticator::new(user_principal, properties.clone(), &hostname).await?; + + println!("start authenticator.init()"); + let output_token = authenticator.init().await?; + println!("finish authenticator.init() - output_token = {output_token:?}"); + + let source = credential.source.as_deref().unwrap_or("$external"); + + let command = SaslStart::new( + source.to_string(), + crate::client::auth::AuthMechanism::Gssapi, + output_token, + server_api.cloned(), + ) + .into_command(); + + println!("start conn.send_message() - command = {command:?}"); + let response_doc = conn.send_message(command).await?.body()?; + println!("finish conn.send_message() - response_doc = {response_doc:?}"); + let sasl_response = SaslResponse::parse("GSSAPI", response_doc)?; + + let mut conversation_id = Some(sasl_response.conversation_id); + let mut payload = sasl_response.payload; + + // let mut conversation_id = None; + // let mut payload = Vec::new(); + + for i in 0..10 { + println!("loop iteration {i}"); + let challenge = if payload.is_empty() { + None + } else { + Some(payload.as_slice()) + }; + println!("\tstart authenticator.step() - challenge = {challenge:?}"); + let output_token = authenticator.step(challenge).await?; + println!("\tfinish authenticator.step() - output_token = {output_token:?}"); + + let token = output_token.unwrap_or(vec![]); + let command = SaslContinue::new( + source.to_string(), + conversation_id.clone().unwrap(), + token, + server_api.cloned(), + ) + .into_command(); + + println!("\tstart conn.send_message() - command = {command:?}"); + let response_doc = conn.send_message(command).await?.body()?; + println!("\tfinish conn.send_message() - response_doc = {response_doc:?}"); + let sasl_response = SaslResponse::parse("GSSAPI", response_doc)?; + + conversation_id = Some(sasl_response.conversation_id); + payload = sasl_response.payload; + + if sasl_response.done && authenticator.is_complete() { + return Ok(()); + } + + // if let Some(token) = output_token { + // let command = if conversation_id.is_none() { + // SaslStart::new( + // source.to_string(), + // crate::client::auth::AuthMechanism::Gssapi, + // token, + // server_api.cloned(), + // ) + // .into_command() + // } else { + // SaslContinue::new( + // source.to_string(), + // conversation_id.clone().unwrap(), + // token, + // server_api.cloned(), + // ) + // .into_command() + // }; + // + // let response_doc = conn.send_message(command).await?.body()?; + // let sasl_response = SaslResponse::parse("GSSAPI", response_doc)?; + // + // conversation_id = Some(sasl_response.conversation_id); + // payload = sasl_response.payload; + // + // if sasl_response.done && authenticator.is_complete() { + // return Ok(()); + // } + // } else if authenticator.is_complete() { + // return Ok(()); + // } } + + Err(Error::authentication_error( + "GSSAPI", + "GSSAPI authentication failed after 10 attempts", + )) } impl GssapiProperties { @@ -45,51 +155,46 @@ impl GssapiProperties { canonicalize_host_name: CanonicalizeHostName::None, service_realm: None, service_host: None, - kdc_url: None, }; if let Some(mechanism_properties) = &credential.mechanism_properties { - if let Some(service_name) = mechanism_properties.get("SERVICE_NAME") { + if let Some(service_name) = mechanism_properties.get(SERVICE_NAME) { if let Bson::String(name) = service_name { properties.service_name = name.clone(); } } - if let Some(canonicalize) = mechanism_properties.get("CANONICALIZE_HOST_NAME") { + if let Some(canonicalize) = mechanism_properties.get(CANONICALIZE_HOST_NAME) { properties.canonicalize_host_name = match canonicalize { Bson::String(s) => match s.as_str() { "none" => CanonicalizeHostName::None, "forward" => CanonicalizeHostName::Forward, "forwardAndReverse" => CanonicalizeHostName::ForwardAndReverse, - _ => return Err(ErrorKind::InvalidArgument { - message: format!("Invalid CANONICALIZE_HOST_NAME value: {}. Valid values are 'none', 'forward', 'forwardAndReverse'", s), - }.into()), + _ => return Err(Error::authentication_error( + MECH_NAME, + format!("Invalid CANONICALIZE_HOST_NAME value: {}. Valid values are 'none', 'forward', 'forwardAndReverse'", s).as_str() + )), }, Bson::Boolean(true) => CanonicalizeHostName::ForwardAndReverse, Bson::Boolean(false) => CanonicalizeHostName::None, - _ => return Err(ErrorKind::InvalidArgument { - message: "CANONICALIZE_HOST_NAME must be a string or boolean".to_string(), - }.into()), + _ => return Err(Error::authentication_error( + MECH_NAME, + "CANONICALIZE_HOST_NAME must be a string or boolean", + )), }; } - if let Some(service_realm) = mechanism_properties.get("SERVICE_REALM") { + if let Some(service_realm) = mechanism_properties.get(SERVICE_REALM) { if let Bson::String(realm) = service_realm { properties.service_realm = Some(realm.clone()); } } - if let Some(service_host) = mechanism_properties.get("SERVICE_HOST") { + if let Some(service_host) = mechanism_properties.get(SERVICE_HOST) { if let Bson::String(host) = service_host { properties.service_host = Some(host.clone()); } } - - if let Some(kdc_url) = mechanism_properties.get("KDC_URL") { - if let Bson::String(url) = kdc_url { - properties.kdc_url = Some(url.clone()); - } - } } Ok(properties) @@ -98,34 +203,58 @@ impl GssapiProperties { struct GssapiAuthenticator { pending_ctx: Option, - client_ctx: Option, service_principal: String, - is_complete: bool, - properties: GssapiProperties, user_principal: Option, + is_complete: bool, } impl GssapiAuthenticator { async fn new( - credential: &Credential, + user_principal: Option, properties: GssapiProperties, hostname: &str, ) -> Result { let service_name: &str = properties.service_name.as_ref(); - let service_principal = format!("{}/{}", service_name, hostname); + let mut service_principal = format!("{}/{}", service_name, hostname); + if let Some(service_realm) = properties.service_realm.as_ref() { + service_principal = format!("{}@{}", service_principal, service_realm); + } else if let Some(user_principal) = user_principal.as_ref() { + if let Some(idx) = user_principal.find('@') { + // If no SERVICE_REALM was specified, use realm specified in the + // username. Note that `realm` starts with '@'. + let (_, realm) = user_principal.split_at(idx); + service_principal = format!("{}{}", service_principal, realm); + } + } Ok(Self { pending_ctx: None, - client_ctx: None, service_principal, + user_principal, is_complete: false, - properties, - user_principal: credential.username.clone(), }) } + async fn init(&mut self) -> Result> { + let (pending_ctx, initial_token) = ClientCtx::new( + InitiateFlags::empty(), + self.user_principal.as_deref(), // Use provided credentials + &self.service_principal, + None, // No channel bindings + ) + .map_err(|e| { + Error::authentication_error( + "GSSAPI", + &format!("Failed to initialize GSSAPI context: {}", e), + ) + })?; + + self.pending_ctx = Some(pending_ctx); + return Ok(initial_token.to_vec()); + } + async fn step(&mut self, challenge: Option<&[u8]>) -> Result>> { - if self.pending_ctx.is_none() && self.client_ctx.is_none() { + if self.pending_ctx.is_none() { let (pending_ctx, initial_token) = ClientCtx::new( InitiateFlags::empty(), self.user_principal.as_deref(), // Use provided credentials @@ -145,11 +274,11 @@ impl GssapiAuthenticator { if let Some(challenge_data) = challenge { if let Some(pending_ctx) = self.pending_ctx.take() { + println!("\t\tabout to call pending_ctx.step()"); match pending_ctx.step(challenge_data).map_err(|e| { Error::authentication_error("GSSAPI", &format!("GSSAPI step failed: {}", e)) })? { Step::Finished((ctx, token)) => { - self.client_ctx = Some(ctx); self.is_complete = true; Ok(token.map(|t| t.to_vec())) } @@ -223,72 +352,3 @@ async fn perform_reverse_dns_lookup(ip: std::net::IpAddr) -> Result { )), } } - -pub(crate) async fn authenticate_stream( - conn: &mut Connection, - credential: &Credential, - server_api: Option<&ServerApi>, - _server_first: impl Into>, -) -> Result<()> { - let properties = GssapiProperties::from_credential(credential)?; - let hostname = - canonicalize_hostname(&conn.address().host(), &properties.canonicalize_host_name).await?; - let _service_host = properties.service_host.as_ref().unwrap_or(&hostname); - - let mut authenticator = - GssapiAuthenticator::new(credential, properties.clone(), &hostname).await?; - - let source = credential.source.as_deref().unwrap_or("$external"); - let mut conversation_id = None; - let mut payload = Vec::new(); - - for _ in 0..10 { - let challenge = if payload.is_empty() { - None - } else { - Some(payload.as_slice()) - }; - let output_token = authenticator.step(challenge).await?; - - if let Some(token) = output_token { - let command = if conversation_id.is_none() { - SaslStart::new( - source.to_string(), - crate::client::auth::AuthMechanism::Gssapi, - token, - server_api.cloned(), - ) - .into_command() - } else { - SaslContinue::new( - source.to_string(), - conversation_id.clone().unwrap(), - token, - server_api.cloned(), - ) - .into_command() - }; - - let response_doc = conn.send_message(command).await?.body()?; - let sasl_response = SaslResponse::parse("GSSAPI", response_doc)?; - - conversation_id = Some(sasl_response.conversation_id); - payload = sasl_response.payload; - - if sasl_response.done && authenticator.is_complete() { - return Ok(()); - } - } else if authenticator.is_complete() { - return Ok(()); - } - } - - Err(Error::authentication_error( - "GSSAPI", - "GSSAPI authentication failed after 10 attempts", - )) -} - -pub(crate) fn build_speculative_client_first(_credential: &Credential) -> Option { - None -} diff --git a/src/test/auth.rs b/src/test/auth.rs index c6f4ca430..2e4b87b5c 100644 --- a/src/test/auth.rs +++ b/src/test/auth.rs @@ -46,3 +46,23 @@ async fn plain_auth() { } ); } + +#[tokio::test] +async fn krb5() { + let uri = "mongodb://testuser%40EXAMPLE.COM@localhost:27017/admin?authSource=%24external&authMechanism=GSSAPI"; + let client = Client::with_uri_str(uri).await.unwrap(); + + let coll = client.database("test").collection("foo"); + + let doc = coll.find_one(doc! {}).await.unwrap().unwrap(); + + #[derive(Debug, Deserialize, PartialEq)] + struct TestDocument { + r#_id: i32, + a: i32, + } + + let doc: TestDocument = crate::bson::from_document(doc).unwrap(); + + assert_eq!(doc, TestDocument { r#_id: 1, a: 1 },); +} From 43a4472c7783fdb2adc4d8f0f09133f3f72c4ffb Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Wed, 2 Jul 2025 10:24:49 -0400 Subject: [PATCH 06/31] RUST-2235: Update wrap_unwrap --- src/client/auth/gssapi.rs | 110 +++++++++++++++++++++++++------------- src/test/auth.rs | 4 +- 2 files changed, 74 insertions(+), 40 deletions(-) diff --git a/src/client/auth/gssapi.rs b/src/client/auth/gssapi.rs index 20f26800e..d2efbf01f 100644 --- a/src/client/auth/gssapi.rs +++ b/src/client/auth/gssapi.rs @@ -1,4 +1,4 @@ -use cross_krb5::{ClientCtx, InitiateFlags, Step}; +use cross_krb5::{ClientCtx, InitiateFlags, K5Ctx, PendingClientCtx, Step}; use dns_lookup::getnameinfo; use std::net::SocketAddr; @@ -79,11 +79,7 @@ pub(crate) async fn authenticate_stream( for i in 0..10 { println!("loop iteration {i}"); - let challenge = if payload.is_empty() { - None - } else { - Some(payload.as_slice()) - }; + let challenge = payload.as_slice(); println!("\tstart authenticator.step() - challenge = {challenge:?}"); let output_token = authenticator.step(challenge).await?; println!("\tfinish authenticator.step() - output_token = {output_token:?}"); @@ -105,10 +101,14 @@ pub(crate) async fn authenticate_stream( conversation_id = Some(sasl_response.conversation_id); payload = sasl_response.payload; - if sasl_response.done && authenticator.is_complete() { + if sasl_response.done { return Ok(()); } + if authenticator.is_complete() { + break; + } + // if let Some(token) = output_token { // let command = if conversation_id.is_none() { // SaslStart::new( @@ -142,6 +142,28 @@ pub(crate) async fn authenticate_stream( // } } + println!("starting do_unwrap_wrap - payload = {payload:?}"); + if let Some(output_token) = authenticator.do_unwrap_wrap(payload.as_slice())? { + println!("finished do_unwrap_wrap - output_token = {output_token:?}"); + let command = SaslContinue::new( + source.to_string(), + conversation_id.unwrap(), + output_token, + server_api.cloned(), + ) + .into_command(); + + println!("\tstart conn.send_message() - command = {command:?}"); + let response_doc = conn.send_message(command).await?.body()?; + println!("\tfinish conn.send_message() - response_doc = {response_doc:?}"); + let sasl_response = SaslResponse::parse("GSSAPI", response_doc)?; + + if sasl_response.done { + println!("got done"); + return Ok(()); + } + } + Err(Error::authentication_error( "GSSAPI", "GSSAPI authentication failed after 10 attempts", @@ -202,7 +224,8 @@ impl GssapiProperties { } struct GssapiAuthenticator { - pending_ctx: Option, + pending_ctx: Option, + established_ctx: Option, service_principal: String, user_principal: Option, is_complete: bool, @@ -218,17 +241,18 @@ impl GssapiAuthenticator { let mut service_principal = format!("{}/{}", service_name, hostname); if let Some(service_realm) = properties.service_realm.as_ref() { service_principal = format!("{}@{}", service_principal, service_realm); - } else if let Some(user_principal) = user_principal.as_ref() { - if let Some(idx) = user_principal.find('@') { - // If no SERVICE_REALM was specified, use realm specified in the - // username. Note that `realm` starts with '@'. - let (_, realm) = user_principal.split_at(idx); - service_principal = format!("{}{}", service_principal, realm); - } - } + } /*else if let Some(user_principal) = user_principal.as_ref() { + if let Some(idx) = user_principal.find('@') { + // If no SERVICE_REALM was specified, use realm specified in the + // username. Note that `realm` starts with '@'. + let (_, realm) = user_principal.split_at(idx); + service_principal = format!("{}{}", service_principal, realm); + } + }*/ Ok(Self { pending_ctx: None, + established_ctx: None, service_principal, user_principal, is_complete: false, @@ -253,33 +277,21 @@ impl GssapiAuthenticator { return Ok(initial_token.to_vec()); } - async fn step(&mut self, challenge: Option<&[u8]>) -> Result>> { - if self.pending_ctx.is_none() { - let (pending_ctx, initial_token) = ClientCtx::new( - InitiateFlags::empty(), - self.user_principal.as_deref(), // Use provided credentials - &self.service_principal, - None, // No channel bindings - ) - .map_err(|e| { - Error::authentication_error( - "GSSAPI", - &format!("Failed to initialize GSSAPI context: {}", e), - ) - })?; - - self.pending_ctx = Some(pending_ctx); - return Ok(Some(initial_token.to_vec())); - } - - if let Some(challenge_data) = challenge { + async fn step(&mut self, challenge: &[u8]) -> Result>> { + if challenge.is_empty() { + Err(Error::authentication_error( + "GSSAPI", + "Expected challenge data for GSSAPI continuation", + )) + } else { if let Some(pending_ctx) = self.pending_ctx.take() { println!("\t\tabout to call pending_ctx.step()"); - match pending_ctx.step(challenge_data).map_err(|e| { + match pending_ctx.step(challenge).map_err(|e| { Error::authentication_error("GSSAPI", &format!("GSSAPI step failed: {}", e)) })? { Step::Finished((ctx, token)) => { self.is_complete = true; + self.established_ctx = Some(ctx); Ok(token.map(|t| t.to_vec())) } Step::Continue((ctx, token)) => { @@ -293,10 +305,32 @@ impl GssapiAuthenticator { "Authentication context not initialized", )) } + } + } + + fn do_unwrap_wrap(&mut self, payload: &[u8]) -> Result>> { + if let Some(mut established_ctx) = self.established_ctx.take() { + let _ = established_ctx.unwrap(payload).map_err(|e| { + Error::authentication_error("GSSAPI", &format!("GSSAPI unwrap failed: {}", e)) + })?; + + if let Some(user_principal) = self.user_principal.take() { + let output_token = established_ctx + .wrap(true, user_principal.as_bytes()) + .map_err(|e| { + Error::authentication_error("GSSAPI", &format!("GSSAPI wrap failed: {}", e)) + })?; + Ok(Some(output_token.to_vec())) + } else { + Err(Error::authentication_error( + "GSSAPI", + "User principal not specified", + )) + } } else { Err(Error::authentication_error( "GSSAPI", - "Expected challenge data for GSSAPI continuation", + "Authentication context not established", )) } } diff --git a/src/test/auth.rs b/src/test/auth.rs index 2e4b87b5c..1729439b4 100644 --- a/src/test/auth.rs +++ b/src/test/auth.rs @@ -4,7 +4,7 @@ mod aws; use serde::Deserialize; use crate::{ - bson::doc, + bson::{doc, Document}, options::{AuthMechanism, ClientOptions, Credential, ServerAddress}, Client, }; @@ -52,7 +52,7 @@ async fn krb5() { let uri = "mongodb://testuser%40EXAMPLE.COM@localhost:27017/admin?authSource=%24external&authMechanism=GSSAPI"; let client = Client::with_uri_str(uri).await.unwrap(); - let coll = client.database("test").collection("foo"); + let coll = client.database("test").collection::("foo"); let doc = coll.find_one(doc! {}).await.unwrap().unwrap(); From 774375aa95b1216326c2206911c0118f54f9aa24 Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Mon, 7 Jul 2025 14:44:56 -0400 Subject: [PATCH 07/31] RUST-2235: Update final wrap step to use proper starting bytes --- src/client/auth/gssapi.rs | 80 +++++++++++++++------------------------ 1 file changed, 31 insertions(+), 49 deletions(-) diff --git a/src/client/auth/gssapi.rs b/src/client/auth/gssapi.rs index d2efbf01f..2a42d5bce 100644 --- a/src/client/auth/gssapi.rs +++ b/src/client/auth/gssapi.rs @@ -74,9 +74,6 @@ pub(crate) async fn authenticate_stream( let mut conversation_id = Some(sasl_response.conversation_id); let mut payload = sasl_response.payload; - // let mut conversation_id = None; - // let mut payload = Vec::new(); - for i in 0..10 { println!("loop iteration {i}"); let challenge = payload.as_slice(); @@ -108,38 +105,6 @@ pub(crate) async fn authenticate_stream( if authenticator.is_complete() { break; } - - // if let Some(token) = output_token { - // let command = if conversation_id.is_none() { - // SaslStart::new( - // source.to_string(), - // crate::client::auth::AuthMechanism::Gssapi, - // token, - // server_api.cloned(), - // ) - // .into_command() - // } else { - // SaslContinue::new( - // source.to_string(), - // conversation_id.clone().unwrap(), - // token, - // server_api.cloned(), - // ) - // .into_command() - // }; - // - // let response_doc = conn.send_message(command).await?.body()?; - // let sasl_response = SaslResponse::parse("GSSAPI", response_doc)?; - // - // conversation_id = Some(sasl_response.conversation_id); - // payload = sasl_response.payload; - // - // if sasl_response.done && authenticator.is_complete() { - // return Ok(()); - // } - // } else if authenticator.is_complete() { - // return Ok(()); - // } } println!("starting do_unwrap_wrap - payload = {payload:?}"); @@ -241,14 +206,14 @@ impl GssapiAuthenticator { let mut service_principal = format!("{}/{}", service_name, hostname); if let Some(service_realm) = properties.service_realm.as_ref() { service_principal = format!("{}@{}", service_principal, service_realm); - } /*else if let Some(user_principal) = user_principal.as_ref() { - if let Some(idx) = user_principal.find('@') { - // If no SERVICE_REALM was specified, use realm specified in the - // username. Note that `realm` starts with '@'. - let (_, realm) = user_principal.split_at(idx); - service_principal = format!("{}{}", service_principal, realm); - } - }*/ + } else if let Some(user_principal) = user_principal.as_ref() { + if let Some(idx) = user_principal.find('@') { + // If no SERVICE_REALM was specified, use realm specified in the + // username. Note that `realm` starts with '@'. + let (_, realm) = user_principal.split_at(idx); + service_principal = format!("{}{}", service_principal, realm); + } + } Ok(Self { pending_ctx: None, @@ -274,7 +239,7 @@ impl GssapiAuthenticator { })?; self.pending_ctx = Some(pending_ctx); - return Ok(initial_token.to_vec()); + Ok(initial_token.to_vec()) } async fn step(&mut self, challenge: &[u8]) -> Result>> { @@ -314,12 +279,29 @@ impl GssapiAuthenticator { Error::authentication_error("GSSAPI", &format!("GSSAPI unwrap failed: {}", e)) })?; + // // todo: instead of wrapping the user principal, try wrapping the byte array + // // [ 0x1, 0x0, 0x0, 0x0 ] + // // bytesReceivedFromServer = new byte[length]; + // // bytesReceivedFromServer[0] = 0x1; // NO_PROTECTION + // // bytesReceivedFromServer[1] = 0x0; // NO_PROTECTION + // // bytesReceivedFromServer[2] = 0x0; // NO_PROTECTION + // // bytesReceivedFromServer[3] = 0x0; // NO_PROTECTION + // + // let bytes: &[u8] = &[0x00, 0xFF, 0xFF, 0xFF]; + // let output_token = established_ctx.wrap(true, bytes).map_err(|e| { + // Error::authentication_error("GSSAPI", &format!("GSSAPI wrap failed: {}", e)) + // })?; + // Ok(Some(output_token.to_vec())) + if let Some(user_principal) = self.user_principal.take() { - let output_token = established_ctx - .wrap(true, user_principal.as_bytes()) - .map_err(|e| { - Error::authentication_error("GSSAPI", &format!("GSSAPI wrap failed: {}", e)) - })?; + let bytes: &[u8] = &[0x1, 0x0, 0x0, 0x0]; + // let bytes: &[u8] = &[0x00, 0xFF, 0xFF, 0xFF]; + let bytes = [bytes, user_principal.as_bytes()].concat(); + println!("user principal: {user_principal}"); + println!("user principal bytes: {:?}", user_principal.as_bytes()); + let output_token = established_ctx.wrap(false, bytes.as_slice()).map_err(|e| { + Error::authentication_error("GSSAPI", &format!("GSSAPI wrap failed: {}", e)) + })?; Ok(Some(output_token.to_vec())) } else { Err(Error::authentication_error( From 9e6e38d2eb67b255acb0918765107dae8d59d469 Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Mon, 7 Jul 2025 15:41:06 -0400 Subject: [PATCH 08/31] RUST-2235: Clean up gssapi auth code --- src/client/auth/gssapi.rs | 95 +++++++++++++++++---------------------- src/test/auth.rs | 20 --------- 2 files changed, 41 insertions(+), 74 deletions(-) diff --git a/src/client/auth/gssapi.rs b/src/client/auth/gssapi.rs index 2a42d5bce..620ad1ce0 100644 --- a/src/client/auth/gssapi.rs +++ b/src/client/auth/gssapi.rs @@ -52,9 +52,7 @@ pub(crate) async fn authenticate_stream( let mut authenticator = GssapiAuthenticator::new(user_principal, properties.clone(), &hostname).await?; - println!("start authenticator.init()"); let output_token = authenticator.init().await?; - println!("finish authenticator.init() - output_token = {output_token:?}"); let source = credential.source.as_deref().unwrap_or("$external"); @@ -66,21 +64,21 @@ pub(crate) async fn authenticate_stream( ) .into_command(); - println!("start conn.send_message() - command = {command:?}"); let response_doc = conn.send_message(command).await?.body()?; - println!("finish conn.send_message() - response_doc = {response_doc:?}"); let sasl_response = SaslResponse::parse("GSSAPI", response_doc)?; let mut conversation_id = Some(sasl_response.conversation_id); let mut payload = sasl_response.payload; - for i in 0..10 { - println!("loop iteration {i}"); + // Limit number of auth challenge steps (typically, only one step is needed, however + // different configurations may require more). + for _ in 0..10 { let challenge = payload.as_slice(); - println!("\tstart authenticator.step() - challenge = {challenge:?}"); let output_token = authenticator.step(challenge).await?; - println!("\tfinish authenticator.step() - output_token = {output_token:?}"); + // The step may return None, which is a valid final step. We still need to + // send a saslContinue command, so we send an empty payload if there is no + // token. let token = output_token.unwrap_or(vec![]); let command = SaslContinue::new( source.to_string(), @@ -90,49 +88,47 @@ pub(crate) async fn authenticate_stream( ) .into_command(); - println!("\tstart conn.send_message() - command = {command:?}"); let response_doc = conn.send_message(command).await?.body()?; - println!("\tfinish conn.send_message() - response_doc = {response_doc:?}"); let sasl_response = SaslResponse::parse("GSSAPI", response_doc)?; conversation_id = Some(sasl_response.conversation_id); payload = sasl_response.payload; + // Although unlikely, there are cases where authentication can be done + // at this point. if sasl_response.done { return Ok(()); } + // The authenticator is considered "complete" when the Kerberos auth + // process is done. However, this is not the end of the full auth flow. + // We no longer need to issue challenges to the authenticator, so we + // break the loop and continue with the rest of the flow. if authenticator.is_complete() { break; } } - println!("starting do_unwrap_wrap - payload = {payload:?}"); - if let Some(output_token) = authenticator.do_unwrap_wrap(payload.as_slice())? { - println!("finished do_unwrap_wrap - output_token = {output_token:?}"); - let command = SaslContinue::new( - source.to_string(), - conversation_id.unwrap(), - output_token, - server_api.cloned(), - ) - .into_command(); + let output_token = authenticator.do_unwrap_wrap(payload.as_slice())?; + let command = SaslContinue::new( + source.to_string(), + conversation_id.unwrap(), + output_token, + server_api.cloned(), + ) + .into_command(); - println!("\tstart conn.send_message() - command = {command:?}"); - let response_doc = conn.send_message(command).await?.body()?; - println!("\tfinish conn.send_message() - response_doc = {response_doc:?}"); - let sasl_response = SaslResponse::parse("GSSAPI", response_doc)?; + let response_doc = conn.send_message(command).await?.body()?; + let sasl_response = SaslResponse::parse("GSSAPI", response_doc)?; - if sasl_response.done { - println!("got done"); - return Ok(()); - } + if sasl_response.done { + Ok(()) + } else { + Err(Error::authentication_error( + "GSSAPI", + "GSSAPI authentication failed after 10 attempts", + )) } - - Err(Error::authentication_error( - "GSSAPI", - "GSSAPI authentication failed after 10 attempts", - )) } impl GssapiProperties { @@ -224,10 +220,12 @@ impl GssapiAuthenticator { }) } + // Initialize the GssapiAuthenticator by creating a PendingClientCtx and + // getting an initial token to send to the server. async fn init(&mut self) -> Result> { let (pending_ctx, initial_token) = ClientCtx::new( InitiateFlags::empty(), - self.user_principal.as_deref(), // Use provided credentials + self.user_principal.as_deref(), &self.service_principal, None, // No channel bindings ) @@ -242,6 +240,10 @@ impl GssapiAuthenticator { Ok(initial_token.to_vec()) } + // Issue the server provided token to the client context. If the ClientCtx + // is established, an optional final token that must be sent to the server + // may be returned; otherwise another token to pass to the server is + // returned and the client context remains in the pending state. async fn step(&mut self, challenge: &[u8]) -> Result>> { if challenge.is_empty() { Err(Error::authentication_error( @@ -250,7 +252,6 @@ impl GssapiAuthenticator { )) } else { if let Some(pending_ctx) = self.pending_ctx.take() { - println!("\t\tabout to call pending_ctx.step()"); match pending_ctx.step(challenge).map_err(|e| { Error::authentication_error("GSSAPI", &format!("GSSAPI step failed: {}", e)) })? { @@ -273,36 +274,22 @@ impl GssapiAuthenticator { } } - fn do_unwrap_wrap(&mut self, payload: &[u8]) -> Result>> { + // Perform the final step of Kerberos authentication by gss_unwrap-ing the + // final server challenge, then wrapping the protocol bytes + user principal. + // The resulting token must be sent to the server. + fn do_unwrap_wrap(&mut self, payload: &[u8]) -> Result> { if let Some(mut established_ctx) = self.established_ctx.take() { let _ = established_ctx.unwrap(payload).map_err(|e| { Error::authentication_error("GSSAPI", &format!("GSSAPI unwrap failed: {}", e)) })?; - // // todo: instead of wrapping the user principal, try wrapping the byte array - // // [ 0x1, 0x0, 0x0, 0x0 ] - // // bytesReceivedFromServer = new byte[length]; - // // bytesReceivedFromServer[0] = 0x1; // NO_PROTECTION - // // bytesReceivedFromServer[1] = 0x0; // NO_PROTECTION - // // bytesReceivedFromServer[2] = 0x0; // NO_PROTECTION - // // bytesReceivedFromServer[3] = 0x0; // NO_PROTECTION - // - // let bytes: &[u8] = &[0x00, 0xFF, 0xFF, 0xFF]; - // let output_token = established_ctx.wrap(true, bytes).map_err(|e| { - // Error::authentication_error("GSSAPI", &format!("GSSAPI wrap failed: {}", e)) - // })?; - // Ok(Some(output_token.to_vec())) - if let Some(user_principal) = self.user_principal.take() { let bytes: &[u8] = &[0x1, 0x0, 0x0, 0x0]; - // let bytes: &[u8] = &[0x00, 0xFF, 0xFF, 0xFF]; let bytes = [bytes, user_principal.as_bytes()].concat(); - println!("user principal: {user_principal}"); - println!("user principal bytes: {:?}", user_principal.as_bytes()); let output_token = established_ctx.wrap(false, bytes.as_slice()).map_err(|e| { Error::authentication_error("GSSAPI", &format!("GSSAPI wrap failed: {}", e)) })?; - Ok(Some(output_token.to_vec())) + Ok(output_token.to_vec()) } else { Err(Error::authentication_error( "GSSAPI", diff --git a/src/test/auth.rs b/src/test/auth.rs index 1729439b4..126da120a 100644 --- a/src/test/auth.rs +++ b/src/test/auth.rs @@ -46,23 +46,3 @@ async fn plain_auth() { } ); } - -#[tokio::test] -async fn krb5() { - let uri = "mongodb://testuser%40EXAMPLE.COM@localhost:27017/admin?authSource=%24external&authMechanism=GSSAPI"; - let client = Client::with_uri_str(uri).await.unwrap(); - - let coll = client.database("test").collection::("foo"); - - let doc = coll.find_one(doc! {}).await.unwrap().unwrap(); - - #[derive(Debug, Deserialize, PartialEq)] - struct TestDocument { - r#_id: i32, - a: i32, - } - - let doc: TestDocument = crate::bson::from_document(doc).unwrap(); - - assert_eq!(doc, TestDocument { r#_id: 1, a: 1 },); -} From 7e040aacf0f495096400fb254b4a53e598f23b48 Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Tue, 8 Jul 2025 11:21:09 -0400 Subject: [PATCH 09/31] RUST-2235: Clean up canonicalize_hostname --- src/client/auth/gssapi.rs | 74 ++++++++++++++++----------------------- src/test/auth.rs | 2 +- 2 files changed, 32 insertions(+), 44 deletions(-) diff --git a/src/client/auth/gssapi.rs b/src/client/auth/gssapi.rs index 620ad1ce0..e683920e2 100644 --- a/src/client/auth/gssapi.rs +++ b/src/client/auth/gssapi.rs @@ -1,6 +1,5 @@ use cross_krb5::{ClientCtx, InitiateFlags, K5Ctx, PendingClientCtx, Step}; -use dns_lookup::getnameinfo; -use std::net::SocketAddr; +use dns_lookup::{lookup_addr, lookup_host}; use crate::{ bson::Bson, @@ -29,7 +28,7 @@ pub(crate) struct GssapiProperties { pub service_host: Option, } -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default, Clone, PartialEq)] pub(crate) enum CanonicalizeHostName { #[default] None, @@ -46,7 +45,7 @@ pub(crate) async fn authenticate_stream( let conn_host = conn.address.host().to_string(); let hostname = properties.service_host.as_ref().unwrap_or(&conn_host); - let hostname = canonicalize_hostname(hostname, &properties.canonicalize_host_name).await?; + let hostname = canonicalize_hostname(hostname, &properties.canonicalize_host_name)?; let user_principal = credential.username.clone(); let mut authenticator = @@ -309,49 +308,38 @@ impl GssapiAuthenticator { } } -async fn canonicalize_hostname(hostname: &str, mode: &CanonicalizeHostName) -> Result { - match mode { - CanonicalizeHostName::None => Ok(hostname.to_string()), - CanonicalizeHostName::Forward => { - let (canonical_host, _address) = resolve_hostname_with_canonical(hostname).await?; - Ok(canonical_host.to_lowercase()) - } - CanonicalizeHostName::ForwardAndReverse => { - let (canonical_host, address) = resolve_hostname_with_canonical(hostname).await?; - match perform_reverse_dns_lookup(address).await { - Ok(reversed_hostname) => Ok(reversed_hostname.to_lowercase()), - Err(_) => Ok(canonical_host.to_lowercase()), - } - } +fn canonicalize_hostname(hostname: &str, mode: &CanonicalizeHostName) -> Result { + if mode == &CanonicalizeHostName::None { + return Ok(hostname.to_string()); } -} -async fn resolve_hostname_with_canonical(hostname: &str) -> Result<(String, std::net::IpAddr)> { - match tokio::net::lookup_host((hostname, 0)).await { - Ok(mut addrs) => { - if let Some(addr) = addrs.next() { - Ok((hostname.to_string(), addr.ip())) - } else { - return Err(Error::authentication_error( - "GSSAPI", - &format!("No addresses found for hostname '{}'", hostname), - )); - } - } - Err(e) => Err(Error::authentication_error( + // Forward lookup + let ip_addrs = lookup_host(hostname).map_err(|e| { + Error::authentication_error( "GSSAPI", - &format!("DNS resolution failed for hostname '{}': {}", hostname, e), - )), - } -} + &format!("Failed to look up hostname for canonicalization: {:?}", e), + ) + })?; -async fn perform_reverse_dns_lookup(ip: std::net::IpAddr) -> Result { - let sockaddr = SocketAddr::new(ip, 0); - match tokio::task::spawn_blocking(move || getnameinfo(&sockaddr, 0)).await { - Ok(Ok((hostname, _))) => Ok(hostname), - Ok(Err(_)) | Err(_) => Err(Error::authentication_error( + if let Some(ip_addr) = ip_addrs.first() { + match mode { + CanonicalizeHostName::Forward => { + // Return the original host, but lowercased to match FQDN convention + Ok(hostname.to_ascii_lowercase()) + } + CanonicalizeHostName::ForwardAndReverse => { + // Reverse-lookup for FQDN, fallback to hostname + match lookup_addr(ip_addr) { + Ok(fqdn) => Ok(fqdn.to_ascii_lowercase()), + Err(_) => Ok(hostname.to_ascii_lowercase()), + } + } + CanonicalizeHostName::None => unreachable!(), + } + } else { + Err(Error::authentication_error( "GSSAPI", - "Reverse DNS lookup failed", - )), + &format!("No addresses found for hostname: {}", hostname), + )) } } diff --git a/src/test/auth.rs b/src/test/auth.rs index 126da120a..c6f4ca430 100644 --- a/src/test/auth.rs +++ b/src/test/auth.rs @@ -4,7 +4,7 @@ mod aws; use serde::Deserialize; use crate::{ - bson::{doc, Document}, + bson::doc, options::{AuthMechanism, ClientOptions, Credential, ServerAddress}, Client, }; From 3134bb66ca24f9450f9068ed2257c9d4b097567f Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Tue, 8 Jul 2025 11:41:39 -0400 Subject: [PATCH 10/31] RUST-2235: Fix api calls after rebase --- src/client/auth/gssapi.rs | 41 +++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/client/auth/gssapi.rs b/src/client/auth/gssapi.rs index e683920e2..fd2d552cd 100644 --- a/src/client/auth/gssapi.rs +++ b/src/client/auth/gssapi.rs @@ -6,7 +6,7 @@ use crate::{ client::{ auth::{ sasl::{SaslContinue, SaslResponse, SaslStart}, - Credential, + Credential, GSSAPI_STR, }, options::ServerApi, }, @@ -61,10 +61,11 @@ pub(crate) async fn authenticate_stream( output_token, server_api.cloned(), ) - .into_command(); + .into_command()?; - let response_doc = conn.send_message(command).await?.body()?; - let sasl_response = SaslResponse::parse("GSSAPI", response_doc)?; + let response_doc = conn.send_message(command).await?; + let sasl_response = + SaslResponse::parse(GSSAPI_STR, response_doc.auth_response_body(GSSAPI_STR)?)?; let mut conversation_id = Some(sasl_response.conversation_id); let mut payload = sasl_response.payload; @@ -87,8 +88,9 @@ pub(crate) async fn authenticate_stream( ) .into_command(); - let response_doc = conn.send_message(command).await?.body()?; - let sasl_response = SaslResponse::parse("GSSAPI", response_doc)?; + let response_doc = conn.send_message(command).await?; + let sasl_response = + SaslResponse::parse(GSSAPI_STR, response_doc.auth_response_body(GSSAPI_STR)?)?; conversation_id = Some(sasl_response.conversation_id); payload = sasl_response.payload; @@ -117,14 +119,15 @@ pub(crate) async fn authenticate_stream( ) .into_command(); - let response_doc = conn.send_message(command).await?.body()?; - let sasl_response = SaslResponse::parse("GSSAPI", response_doc)?; + let response_doc = conn.send_message(command).await?; + let sasl_response = + SaslResponse::parse(GSSAPI_STR, response_doc.auth_response_body(GSSAPI_STR)?)?; if sasl_response.done { Ok(()) } else { Err(Error::authentication_error( - "GSSAPI", + GSSAPI_STR, "GSSAPI authentication failed after 10 attempts", )) } @@ -230,7 +233,7 @@ impl GssapiAuthenticator { ) .map_err(|e| { Error::authentication_error( - "GSSAPI", + GSSAPI_STR, &format!("Failed to initialize GSSAPI context: {}", e), ) })?; @@ -246,13 +249,13 @@ impl GssapiAuthenticator { async fn step(&mut self, challenge: &[u8]) -> Result>> { if challenge.is_empty() { Err(Error::authentication_error( - "GSSAPI", + GSSAPI_STR, "Expected challenge data for GSSAPI continuation", )) } else { if let Some(pending_ctx) = self.pending_ctx.take() { match pending_ctx.step(challenge).map_err(|e| { - Error::authentication_error("GSSAPI", &format!("GSSAPI step failed: {}", e)) + Error::authentication_error(GSSAPI_STR, &format!("GSSAPI step failed: {}", e)) })? { Step::Finished((ctx, token)) => { self.is_complete = true; @@ -266,7 +269,7 @@ impl GssapiAuthenticator { } } else { Err(Error::authentication_error( - "GSSAPI", + GSSAPI_STR, "Authentication context not initialized", )) } @@ -279,25 +282,25 @@ impl GssapiAuthenticator { fn do_unwrap_wrap(&mut self, payload: &[u8]) -> Result> { if let Some(mut established_ctx) = self.established_ctx.take() { let _ = established_ctx.unwrap(payload).map_err(|e| { - Error::authentication_error("GSSAPI", &format!("GSSAPI unwrap failed: {}", e)) + Error::authentication_error(GSSAPI_STR, &format!("GSSAPI unwrap failed: {}", e)) })?; if let Some(user_principal) = self.user_principal.take() { let bytes: &[u8] = &[0x1, 0x0, 0x0, 0x0]; let bytes = [bytes, user_principal.as_bytes()].concat(); let output_token = established_ctx.wrap(false, bytes.as_slice()).map_err(|e| { - Error::authentication_error("GSSAPI", &format!("GSSAPI wrap failed: {}", e)) + Error::authentication_error(GSSAPI_STR, &format!("GSSAPI wrap failed: {}", e)) })?; Ok(output_token.to_vec()) } else { Err(Error::authentication_error( - "GSSAPI", + GSSAPI_STR, "User principal not specified", )) } } else { Err(Error::authentication_error( - "GSSAPI", + GSSAPI_STR, "Authentication context not established", )) } @@ -316,7 +319,7 @@ fn canonicalize_hostname(hostname: &str, mode: &CanonicalizeHostName) -> Result< // Forward lookup let ip_addrs = lookup_host(hostname).map_err(|e| { Error::authentication_error( - "GSSAPI", + GSSAPI_STR, &format!("Failed to look up hostname for canonicalization: {:?}", e), ) })?; @@ -338,7 +341,7 @@ fn canonicalize_hostname(hostname: &str, mode: &CanonicalizeHostName) -> Result< } } else { Err(Error::authentication_error( - "GSSAPI", + GSSAPI_STR, &format!("No addresses found for hostname: {}", hostname), )) } From f323fe1ca42ea4ea0f769701fab34ee0a7370c8f Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Tue, 8 Jul 2025 11:55:04 -0400 Subject: [PATCH 11/31] RUST-2235: Update constants --- src/client/auth/gssapi.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/client/auth/gssapi.rs b/src/client/auth/gssapi.rs index fd2d552cd..9d0c3a1b3 100644 --- a/src/client/auth/gssapi.rs +++ b/src/client/auth/gssapi.rs @@ -18,7 +18,6 @@ const SERVICE_NAME: &str = "SERVICE_NAME"; const CANONICALIZE_HOST_NAME: &str = "CANONICALIZE_HOST_NAME"; const SERVICE_REALM: &str = "SERVICE_REALM"; const SERVICE_HOST: &str = "SERVICE_HOST"; -const MECH_NAME: &str = "GSSAPI"; #[derive(Debug, Clone)] pub(crate) struct GssapiProperties { @@ -156,14 +155,14 @@ impl GssapiProperties { "forward" => CanonicalizeHostName::Forward, "forwardAndReverse" => CanonicalizeHostName::ForwardAndReverse, _ => return Err(Error::authentication_error( - MECH_NAME, + GSSAPI_STR, format!("Invalid CANONICALIZE_HOST_NAME value: {}. Valid values are 'none', 'forward', 'forwardAndReverse'", s).as_str() )), }, Bson::Boolean(true) => CanonicalizeHostName::ForwardAndReverse, Bson::Boolean(false) => CanonicalizeHostName::None, _ => return Err(Error::authentication_error( - MECH_NAME, + GSSAPI_STR, "CANONICALIZE_HOST_NAME must be a string or boolean", )), }; From e175e2e47446b31d4bf52b4f699fd453661d4ea4 Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Tue, 8 Jul 2025 12:09:58 -0400 Subject: [PATCH 12/31] RUST-2235: Combine new and init --- src/client/auth/gssapi.rs | 42 +++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/src/client/auth/gssapi.rs b/src/client/auth/gssapi.rs index 9d0c3a1b3..21a147edc 100644 --- a/src/client/auth/gssapi.rs +++ b/src/client/auth/gssapi.rs @@ -47,17 +47,15 @@ pub(crate) async fn authenticate_stream( let hostname = canonicalize_hostname(hostname, &properties.canonicalize_host_name)?; let user_principal = credential.username.clone(); - let mut authenticator = - GssapiAuthenticator::new(user_principal, properties.clone(), &hostname).await?; - - let output_token = authenticator.init().await?; + let (mut authenticator, initial_token) = + GssapiAuthenticator::init(user_principal, properties.clone(), &hostname).await?; let source = credential.source.as_deref().unwrap_or("$external"); let command = SaslStart::new( source.to_string(), crate::client::auth::AuthMechanism::Gssapi, - output_token, + initial_token, server_api.cloned(), ) .into_command()?; @@ -188,17 +186,18 @@ impl GssapiProperties { struct GssapiAuthenticator { pending_ctx: Option, established_ctx: Option, - service_principal: String, user_principal: Option, is_complete: bool, } impl GssapiAuthenticator { - async fn new( + // Initialize the GssapiAuthenticator by creating a PendingClientCtx and + // getting an initial token to send to the server. + async fn init( user_principal: Option, properties: GssapiProperties, hostname: &str, - ) -> Result { + ) -> Result<(Self, Vec)> { let service_name: &str = properties.service_name.as_ref(); let mut service_principal = format!("{}/{}", service_name, hostname); if let Some(service_realm) = properties.service_realm.as_ref() { @@ -212,22 +211,10 @@ impl GssapiAuthenticator { } } - Ok(Self { - pending_ctx: None, - established_ctx: None, - service_principal, - user_principal, - is_complete: false, - }) - } - - // Initialize the GssapiAuthenticator by creating a PendingClientCtx and - // getting an initial token to send to the server. - async fn init(&mut self) -> Result> { let (pending_ctx, initial_token) = ClientCtx::new( InitiateFlags::empty(), - self.user_principal.as_deref(), - &self.service_principal, + user_principal.as_deref(), + &service_principal, None, // No channel bindings ) .map_err(|e| { @@ -237,8 +224,15 @@ impl GssapiAuthenticator { ) })?; - self.pending_ctx = Some(pending_ctx); - Ok(initial_token.to_vec()) + Ok(( + Self { + pending_ctx: Some(pending_ctx), + established_ctx: None, + user_principal, + is_complete: false, + }, + initial_token.to_vec(), + )) } // Issue the server provided token to the client context. If the ClientCtx From de0c4b7c75b6063af71783ed248335cb891a1603 Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Wed, 9 Jul 2025 11:48:59 -0400 Subject: [PATCH 13/31] SQL-2235: Properly alphabetize Cargo.toml --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 835185aa1..314662aec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,6 +83,7 @@ chrono = { version = "0.4.7", default-features = false, features = [ "clock", "std", ] } +cross-krb5 = { version = "0.4.2", optional = true, default-features = false } derive_more = "0.99.17" derive-where = "1.2.7" dns-lookup = { version = "2.0", optional = true } @@ -113,7 +114,6 @@ sha1 = "0.10.0" sha2 = "0.10.2" snap = { version = "1.0.5", optional = true } socket2 = "0.5.5" -cross-krb5 = { version = "0.4.2", optional = true, default-features = false } stringprep = "0.1.2" strsim = "0.11.1" take_mut = "0.2.2" From 3fec2d9f45b8a80fd6a0a555a27777db10546fff Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Wed, 9 Jul 2025 11:57:15 -0400 Subject: [PATCH 14/31] SQL-2235: Update compile-only task to include gssapi feature flag --- .evergreen/compile-only.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.evergreen/compile-only.sh b/.evergreen/compile-only.sh index b88796837..3742566da 100755 --- a/.evergreen/compile-only.sh +++ b/.evergreen/compile-only.sh @@ -17,7 +17,7 @@ cargo $TOOLCHAIN build # Test with all features. if [ "$RUST_VERSION" != "" ]; then - cargo $TOOLCHAIN build --features openssl-tls,sync,aws-auth,zlib-compression,zstd-compression,snappy-compression,in-use-encryption,tracing-unstable + cargo $TOOLCHAIN build --features openssl-tls,sync,aws-auth,gssapi-auth,zlib-compression,zstd-compression,snappy-compression,in-use-encryption,tracing-unstable else cargo $TOOLCHAIN build --all-features fi From 8ef60974eade7ffda242a9eead033beb6da90e23 Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Wed, 9 Jul 2025 12:03:24 -0400 Subject: [PATCH 15/31] RUST-2235: Update Cargo.lock --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 7ab280239..4bc92dc5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -257,7 +257,7 @@ dependencies = [ [[package]] name = "bson" version = "3.0.0" -source = "git+https://github.com/mongodb/bson-rust?branch=main#7703fee7e296ddf045d1dafe61359fccac41368a" +source = "git+https://github.com/mongodb/bson-rust?branch=main#194177a1593835bf897dd2408db31ce949e32e77" dependencies = [ "ahash", "base64 0.22.1", From 13a68782abd2a79d0cb878fde4ab378436de47a1 Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Wed, 9 Jul 2025 12:04:20 -0400 Subject: [PATCH 16/31] RUST-2235: Properly update Cargo.lock --- Cargo.lock | 261 +++++++++++++++++++---------------------------------- 1 file changed, 94 insertions(+), 167 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4bc92dc5c..890e346b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -35,7 +35,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.3.3", + "getrandom 0.3.2", "once_cell", "version_check", "zerocopy", @@ -111,7 +111,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -122,7 +122,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -176,7 +176,7 @@ version = "0.71.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.0", "cexpr", "clang-sys", "itertools", @@ -187,7 +187,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -198,9 +198,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "bitvec" @@ -241,9 +241,9 @@ dependencies = [ "base64 0.22.1", "bitvec", "getrandom 0.2.16", - "getrandom 0.3.3", + "getrandom 0.3.2", "hex", - "indexmap 2.10.0", + "indexmap 2.9.0", "js-sys", "once_cell", "rand 0.9.1", @@ -257,15 +257,15 @@ dependencies = [ [[package]] name = "bson" version = "3.0.0" -source = "git+https://github.com/mongodb/bson-rust?branch=main#194177a1593835bf897dd2408db31ce949e32e77" +source = "git+https://github.com/mongodb/bson-rust?branch=main#431d4483856b18d1b8885d0b46a60be7f2eb2dee" dependencies = [ "ahash", "base64 0.22.1", "bitvec", "getrandom 0.2.16", - "getrandom 0.3.3", + "getrandom 0.3.2", "hex", - "indexmap 2.10.0", + "indexmap 2.9.0", "js-sys", "once_cell", "rand 0.9.1", @@ -438,7 +438,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d4ddf7139e64dc916b11d434421031bcc5ba02e521a49a011652a0f68775188" dependencies = [ "anyhow", - "bitflags 2.9.1", + "bitflags 2.9.0", "bytes", "libgssapi", "windows", @@ -516,7 +516,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -527,7 +527,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -564,7 +564,7 @@ checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -575,7 +575,7 @@ checksum = "510c292c8cf384b1a340b816a9a6cf2599eb8f566a44949024af88418000c50b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -588,7 +588,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -625,7 +625,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -670,7 +670,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -686,7 +686,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.59.0", ] [[package]] @@ -817,7 +817,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -875,9 +875,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", "js-sys", @@ -911,7 +911,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.3.1", - "indexmap 2.10.0", + "indexmap 2.9.0", "slab", "tokio", "tokio-util", @@ -926,9 +926,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" [[package]] name = "heck" @@ -987,7 +987,7 @@ dependencies = [ "parking_lot", "rand 0.8.5", "resolv-conf", - "smallvec 1.15.1", + "smallvec 1.15.0", "thiserror 1.0.69", "tokio", "tracing", @@ -1117,7 +1117,7 @@ dependencies = [ "httparse", "itoa", "pin-project-lite", - "smallvec 1.15.1", + "smallvec 1.15.0", "tokio", "want", ] @@ -1136,7 +1136,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots 1.0.1", + "webpki-roots 1.0.0", ] [[package]] @@ -1157,9 +1157,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.15" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" +checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" dependencies = [ "base64 0.22.1", "bytes", @@ -1242,7 +1242,7 @@ dependencies = [ "icu_normalizer_data", "icu_properties", "icu_provider", - "smallvec 1.15.1", + "smallvec 1.15.0", "zerovec", ] @@ -1304,7 +1304,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ "idna_adapter", - "smallvec 1.15.1", + "smallvec 1.15.0", "utf8_iter", ] @@ -1331,12 +1331,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.10.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown 0.15.4", + "hashbrown 0.15.3", "serde", ] @@ -1356,7 +1356,7 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.0", "cfg-if", "libc", ] @@ -1410,7 +1410,7 @@ version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.2", "libc", ] @@ -1474,7 +1474,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "834339e86b2561169d45d3b01741967fee3e5716c7d0b6e33cd4e3b34c9558cd" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.0", "bytes", "lazy_static", "libgssapi-sys", @@ -1497,7 +1497,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.53.2", + "windows-targets 0.52.6", ] [[package]] @@ -1558,7 +1558,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -1572,7 +1572,7 @@ dependencies = [ "macro_magic_core_macros", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -1583,7 +1583,7 @@ checksum = "b02abfe41815b5bd98dbd4260173db2c116dda171dc0fe7838cb206333b83308" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -1594,7 +1594,7 @@ checksum = "73ea28ee64b88876bf45277ed9a5817c1817df061a74f2b988971a12570e5869" dependencies = [ "macro_magic_core", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -1755,7 +1755,7 @@ dependencies = [ "macro_magic", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -1781,7 +1781,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.0", "cfg-if", "cfg_aliases", "libc", @@ -1859,7 +1859,7 @@ version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.0", "cfg-if", "foreign-types", "libc", @@ -1876,7 +1876,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -1922,7 +1922,7 @@ dependencies = [ "cfg-if", "libc", "redox_syscall", - "smallvec 1.15.1", + "smallvec 1.15.0", "windows-targets 0.52.6", ] @@ -1978,7 +1978,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -2064,12 +2064,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.35" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" +checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55" dependencies = [ "proc-macro2", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -2117,7 +2117,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" dependencies = [ "bytes", - "getrandom 0.3.3", + "getrandom 0.3.2", "lru-slab", "rand 0.9.1", "ring", @@ -2222,7 +2222,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.2", ] [[package]] @@ -2251,7 +2251,7 @@ version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.0", ] [[package]] @@ -2271,7 +2271,7 @@ checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -2344,7 +2344,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 1.0.1", + "webpki-roots 1.0.0", ] [[package]] @@ -2404,7 +2404,7 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.0", "errno", "libc", "linux-raw-sys", @@ -2524,7 +2524,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.0", "core-foundation", "core-foundation-sys", "libc", @@ -2584,7 +2584,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -2593,7 +2593,7 @@ version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ - "indexmap 2.10.0", + "indexmap 2.9.0", "itoa", "memchr", "ryu", @@ -2632,7 +2632,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.10.0", + "indexmap 2.9.0", "schemars 0.9.0", "schemars 1.0.4", "serde", @@ -2651,7 +2651,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -2723,9 +2723,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "snap" @@ -2795,9 +2795,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.104" +version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", @@ -2821,7 +2821,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -2830,7 +2830,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.0", "core-foundation", "system-configuration-sys", ] @@ -2864,7 +2864,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ "fastrand", - "getrandom 0.3.3", + "getrandom 0.3.2", "once_cell", "rustix", "windows-sys 0.59.0", @@ -2896,7 +2896,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -2907,7 +2907,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -3012,7 +3012,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -3116,7 +3116,7 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.0", "bytes", "futures-util", "http 1.3.1", @@ -3160,7 +3160,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -3192,7 +3192,7 @@ checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "nu-ansi-term", "sharded-slab", - "smallvec 1.15.1", + "smallvec 1.15.0", "thread_local", "tracing-core", "tracing-log", @@ -3221,7 +3221,7 @@ checksum = "3c36781cc0e46a83726d9879608e4cf6c2505237e263a8eb8c24502989cfdb28" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -3286,7 +3286,7 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.2", "js-sys", "serde", "wasm-bindgen", @@ -3356,7 +3356,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", "wasm-bindgen-shared", ] @@ -3391,7 +3391,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3431,14 +3431,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.1", + "webpki-roots 1.0.0", ] [[package]] name = "webpki-roots" -version = "1.0.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502" +checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb" dependencies = [ "rustls-pki-types", ] @@ -3525,7 +3525,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -3536,7 +3536,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -3611,15 +3611,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.2", -] - [[package]] name = "windows-targets" version = "0.48.5" @@ -3644,29 +3635,13 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", + "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] -[[package]] -name = "windows-targets" -version = "0.53.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" -dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", -] - [[package]] name = "windows-threading" version = "0.1.0" @@ -3688,12 +3663,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -3706,12 +3675,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -3724,24 +3687,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -3754,12 +3705,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -3772,12 +3717,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -3790,12 +3729,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -3808,12 +3741,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - [[package]] name = "winreg" version = "0.50.0" @@ -3830,7 +3757,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.0", ] [[package]] @@ -3874,7 +3801,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", "synstructure", ] @@ -3895,7 +3822,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -3915,7 +3842,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", "synstructure", ] @@ -3955,7 +3882,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] From fafc56f783aa2e6b5ff935b2cdb871eb401b9185 Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Wed, 9 Jul 2025 13:57:53 -0400 Subject: [PATCH 17/31] RUST-2235: Update tests to run with gssapi-auth feature enabled --- .evergreen/run-tests.sh | 2 +- src/client/options/test.rs | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index b88a5cc36..535f16a7a 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -6,7 +6,7 @@ set -o pipefail source .evergreen/env.sh source .evergreen/cargo-test.sh -FEATURE_FLAGS+=("tracing-unstable" "cert-key-password") +FEATURE_FLAGS+=("tracing-unstable" "cert-key-password" "gssapi-auth") if [ "$OPENSSL" = true ]; then FEATURE_FLAGS+=("openssl-tls") diff --git a/src/client/options/test.rs b/src/client/options/test.rs index 107a46caf..7bce3795b 100644 --- a/src/client/options/test.rs +++ b/src/client/options/test.rs @@ -209,13 +209,19 @@ async fn run_tests(path: &[&str], skipped_files: &[&str]) { #[tokio::test] async fn run_uri_options_spec_tests() { - let skipped_files = vec!["single-threaded-options.json"]; + let mut skipped_files = vec!["single-threaded-options.json"]; + if cfg!(not(feature = "gssapi-auth")) { + skipped_files.push("auth-options.json"); + } run_tests(&["uri-options"], &skipped_files).await; } #[tokio::test] async fn run_connection_string_spec_tests() { let mut skipped_files = Vec::new(); + if cfg!(not(feature = "gssapi-auth")) { + skipped_files.push("valid-auth.json"); + } if cfg!(not(unix)) { skipped_files.push("valid-unix_socket-absolute.json"); skipped_files.push("valid-unix_socket-relative.json"); From 934f0f41b61c9d34cd93b89ad5acabf44ed7930e Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Wed, 9 Jul 2025 14:31:49 -0400 Subject: [PATCH 18/31] RUST-2235: Fix rustfmt failures --- src/client/auth/gssapi.rs | 28 +++++++++++++++++++--------- src/client/options.rs | 7 +++++-- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/client/auth/gssapi.rs b/src/client/auth/gssapi.rs index 21a147edc..55d84244c 100644 --- a/src/client/auth/gssapi.rs +++ b/src/client/auth/gssapi.rs @@ -6,7 +6,8 @@ use crate::{ client::{ auth::{ sasl::{SaslContinue, SaslResponse, SaslStart}, - Credential, GSSAPI_STR, + Credential, + GSSAPI_STR, }, options::ServerApi, }, @@ -152,17 +153,26 @@ impl GssapiProperties { "none" => CanonicalizeHostName::None, "forward" => CanonicalizeHostName::Forward, "forwardAndReverse" => CanonicalizeHostName::ForwardAndReverse, - _ => return Err(Error::authentication_error( - GSSAPI_STR, - format!("Invalid CANONICALIZE_HOST_NAME value: {}. Valid values are 'none', 'forward', 'forwardAndReverse'", s).as_str() - )), + _ => { + return Err(Error::authentication_error( + GSSAPI_STR, + format!( + "Invalid CANONICALIZE_HOST_NAME value: {}. Valid values are \ + 'none', 'forward', 'forwardAndReverse'", + s + ) + .as_str(), + )) + } }, Bson::Boolean(true) => CanonicalizeHostName::ForwardAndReverse, Bson::Boolean(false) => CanonicalizeHostName::None, - _ => return Err(Error::authentication_error( - GSSAPI_STR, - "CANONICALIZE_HOST_NAME must be a string or boolean", - )), + _ => { + return Err(Error::authentication_error( + GSSAPI_STR, + "CANONICALIZE_HOST_NAME must be a string or boolean", + )) + } }; } diff --git a/src/client/options.rs b/src/client/options.rs index 9718d2e09..aea136115 100644 --- a/src/client/options.rs +++ b/src/client/options.rs @@ -1587,10 +1587,13 @@ impl ConnectionString { _ => { return Err(ErrorKind::InvalidArgument { message: format!( - "Invalid CANONICALIZE_HOST_NAME value: {}. Valid values are 'none', 'forward', 'forwardAndReverse', 'true', 'false'", + "Invalid CANONICALIZE_HOST_NAME value: {}. Valid \ + values are 'none', 'forward', 'forwardAndReverse', \ + 'true', 'false'", s ), - }.into()); + } + .into()); } }; doc.insert("CANONICALIZE_HOST_NAME", val); From 5f5722c0520bc0b6509cfb4d46c83ee65ad0e0b1 Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Wed, 9 Jul 2025 14:39:25 -0400 Subject: [PATCH 19/31] RUST-2235: Address clippy failures --- src/client/auth/gssapi.rs | 54 +++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/src/client/auth/gssapi.rs b/src/client/auth/gssapi.rs index 55d84244c..d18407b44 100644 --- a/src/client/auth/gssapi.rs +++ b/src/client/auth/gssapi.rs @@ -141,10 +141,8 @@ impl GssapiProperties { }; if let Some(mechanism_properties) = &credential.mechanism_properties { - if let Some(service_name) = mechanism_properties.get(SERVICE_NAME) { - if let Bson::String(name) = service_name { - properties.service_name = name.clone(); - } + if let Some(Bson::String(name)) = mechanism_properties.get(SERVICE_NAME) { + properties.service_name = name.clone(); } if let Some(canonicalize) = mechanism_properties.get(CANONICALIZE_HOST_NAME) { @@ -176,16 +174,12 @@ impl GssapiProperties { }; } - if let Some(service_realm) = mechanism_properties.get(SERVICE_REALM) { - if let Bson::String(realm) = service_realm { - properties.service_realm = Some(realm.clone()); - } + if let Some(Bson::String(realm)) = mechanism_properties.get(SERVICE_REALM) { + properties.service_realm = Some(realm.clone()); } - if let Some(service_host) = mechanism_properties.get(SERVICE_HOST) { - if let Bson::String(host) = service_host { - properties.service_host = Some(host.clone()); - } + if let Some(Bson::String(host)) = mechanism_properties.get(SERVICE_HOST) { + properties.service_host = Some(host.clone()); } } @@ -255,27 +249,25 @@ impl GssapiAuthenticator { GSSAPI_STR, "Expected challenge data for GSSAPI continuation", )) - } else { - if let Some(pending_ctx) = self.pending_ctx.take() { - match pending_ctx.step(challenge).map_err(|e| { - Error::authentication_error(GSSAPI_STR, &format!("GSSAPI step failed: {}", e)) - })? { - Step::Finished((ctx, token)) => { - self.is_complete = true; - self.established_ctx = Some(ctx); - Ok(token.map(|t| t.to_vec())) - } - Step::Continue((ctx, token)) => { - self.pending_ctx = Some(ctx); - Ok(Some(token.to_vec())) - } + } else if let Some(pending_ctx) = self.pending_ctx.take() { + match pending_ctx.step(challenge).map_err(|e| { + Error::authentication_error(GSSAPI_STR, &format!("GSSAPI step failed: {}", e)) + })? { + Step::Finished((ctx, token)) => { + self.is_complete = true; + self.established_ctx = Some(ctx); + Ok(token.map(|t| t.to_vec())) + } + Step::Continue((ctx, token)) => { + self.pending_ctx = Some(ctx); + Ok(Some(token.to_vec())) } - } else { - Err(Error::authentication_error( - GSSAPI_STR, - "Authentication context not initialized", - )) } + } else { + Err(Error::authentication_error( + GSSAPI_STR, + "Authentication context not initialized", + )) } } From 58b928ee97f4f11a775e4328bf74183791b63e78 Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Wed, 16 Jul 2025 13:19:40 -0400 Subject: [PATCH 20/31] RUST-2235: Update format calls to place args in brackets --- src/client/auth/gssapi.rs | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/client/auth/gssapi.rs b/src/client/auth/gssapi.rs index d18407b44..4c83f00e9 100644 --- a/src/client/auth/gssapi.rs +++ b/src/client/auth/gssapi.rs @@ -6,8 +6,7 @@ use crate::{ client::{ auth::{ sasl::{SaslContinue, SaslResponse, SaslStart}, - Credential, - GSSAPI_STR, + Credential, GSSAPI_STR, }, options::ServerApi, }, @@ -155,9 +154,8 @@ impl GssapiProperties { return Err(Error::authentication_error( GSSAPI_STR, format!( - "Invalid CANONICALIZE_HOST_NAME value: {}. Valid values are \ + "Invalid CANONICALIZE_HOST_NAME value: {s}. Valid values are \ 'none', 'forward', 'forwardAndReverse'", - s ) .as_str(), )) @@ -203,15 +201,15 @@ impl GssapiAuthenticator { hostname: &str, ) -> Result<(Self, Vec)> { let service_name: &str = properties.service_name.as_ref(); - let mut service_principal = format!("{}/{}", service_name, hostname); + let mut service_principal = format!("{service_name}/{hostname}"); if let Some(service_realm) = properties.service_realm.as_ref() { - service_principal = format!("{}@{}", service_principal, service_realm); + service_principal = format!("{service_principal}@{service_realm}"); } else if let Some(user_principal) = user_principal.as_ref() { if let Some(idx) = user_principal.find('@') { // If no SERVICE_REALM was specified, use realm specified in the // username. Note that `realm` starts with '@'. let (_, realm) = user_principal.split_at(idx); - service_principal = format!("{}{}", service_principal, realm); + service_principal = format!("{service_principal}{realm}"); } } @@ -224,7 +222,7 @@ impl GssapiAuthenticator { .map_err(|e| { Error::authentication_error( GSSAPI_STR, - &format!("Failed to initialize GSSAPI context: {}", e), + &format!("Failed to initialize GSSAPI context: {e}"), ) })?; @@ -251,7 +249,7 @@ impl GssapiAuthenticator { )) } else if let Some(pending_ctx) = self.pending_ctx.take() { match pending_ctx.step(challenge).map_err(|e| { - Error::authentication_error(GSSAPI_STR, &format!("GSSAPI step failed: {}", e)) + Error::authentication_error(GSSAPI_STR, &format!("GSSAPI step failed: {e}")) })? { Step::Finished((ctx, token)) => { self.is_complete = true; @@ -277,14 +275,14 @@ impl GssapiAuthenticator { fn do_unwrap_wrap(&mut self, payload: &[u8]) -> Result> { if let Some(mut established_ctx) = self.established_ctx.take() { let _ = established_ctx.unwrap(payload).map_err(|e| { - Error::authentication_error(GSSAPI_STR, &format!("GSSAPI unwrap failed: {}", e)) + Error::authentication_error(GSSAPI_STR, &format!("GSSAPI unwrap failed: {e}")) })?; if let Some(user_principal) = self.user_principal.take() { let bytes: &[u8] = &[0x1, 0x0, 0x0, 0x0]; let bytes = [bytes, user_principal.as_bytes()].concat(); let output_token = established_ctx.wrap(false, bytes.as_slice()).map_err(|e| { - Error::authentication_error(GSSAPI_STR, &format!("GSSAPI wrap failed: {}", e)) + Error::authentication_error(GSSAPI_STR, &format!("GSSAPI wrap failed: {e}")) })?; Ok(output_token.to_vec()) } else { @@ -315,7 +313,7 @@ fn canonicalize_hostname(hostname: &str, mode: &CanonicalizeHostName) -> Result< let ip_addrs = lookup_host(hostname).map_err(|e| { Error::authentication_error( GSSAPI_STR, - &format!("Failed to look up hostname for canonicalization: {:?}", e), + &format!("Failed to look up hostname for canonicalization: {e:?}"), ) })?; @@ -337,7 +335,7 @@ fn canonicalize_hostname(hostname: &str, mode: &CanonicalizeHostName) -> Result< } else { Err(Error::authentication_error( GSSAPI_STR, - &format!("No addresses found for hostname: {}", hostname), + &format!("No addresses found for hostname: {hostname}"), )) } } From 85ec3129b9a4547593ac49681c02af027fda65a5 Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Wed, 16 Jul 2025 13:21:32 -0400 Subject: [PATCH 21/31] RUST-2235: Update OIDC authSource error message to be consistent with the other auth mechs --- src/client/auth/oidc.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/client/auth/oidc.rs b/src/client/auth/oidc.rs index f3e91eec4..0fc0da327 100644 --- a/src/client/auth/oidc.rs +++ b/src/client/auth/oidc.rs @@ -18,9 +18,7 @@ use crate::{ use super::{ sasl::{SaslContinue, SaslResponse, SaslStart}, - AuthMechanism, - Credential, - MONGODB_OIDC_STR, + AuthMechanism, Credential, MONGODB_OIDC_STR, }; pub(crate) const TOKEN_RESOURCE_PROP_STR: &str = "TOKEN_RESOURCE"; @@ -309,7 +307,9 @@ enum CallbackKind { Machine, } +use crate::error::ErrorKind; use std::fmt::Debug; + impl std::fmt::Debug for Function { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct(format!("Callback: {:?}", self.kind).as_str()) @@ -972,8 +972,7 @@ pub(super) fn validate_credential(credential: &Credential) -> Result<()> { .is_some_and(|source| source != "$external") { return Err(Error::invalid_argument(format!( - "source must be $external for {} authentication, found: {:?}", - MONGODB_OIDC_STR, credential.source + "only $external may be specified as an auth source for {MONGODB_OIDC_STR}", ))); } #[cfg(test)] From 841778bcd69daaeff616cb16be9f14bd828609a7 Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Wed, 16 Jul 2025 13:27:03 -0400 Subject: [PATCH 22/31] RUST-2235: Bump MSRV to 1.82.0 --- .evergreen/aws-lambda-test/README.md | 2 +- .evergreen/config.yml | 2 +- README.md | 4 ++-- clippy.toml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.evergreen/aws-lambda-test/README.md b/.evergreen/aws-lambda-test/README.md index 1affe1c5e..46b2eaa99 100644 --- a/.evergreen/aws-lambda-test/README.md +++ b/.evergreen/aws-lambda-test/README.md @@ -33,7 +33,7 @@ To deploy the application, you need the folllowing tools: * SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) * Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) -* [Rust](https://www.rust-lang.org/) version 1.81.0 or newer +* [Rust](https://www.rust-lang.org/) version 1.82.0 or newer * [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda) for cross-compilation To build and deploy your application for the first time, run the following in your shell: diff --git a/.evergreen/config.yml b/.evergreen/config.yml index df014fe12..72e6aebee 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -819,7 +819,7 @@ tasks: - func: "compile only" vars: # Our minimum supported Rust version. This should be updated whenever the MSRV is bumped. - RUST_VERSION: 1.81.0 + RUST_VERSION: 1.82.0 - name: check-cargo-deny commands: diff --git a/README.md b/README.md index 4ef2138cd..cc314ba61 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ For more details, including features, runnable examples, troubleshooting resourc ## Installation ### Requirements -- Rust 1.81.0+ (See the [MSRV policy](#minimum-supported-rust-version-msrv-policy) for more information) +- Rust 1.82.0+ (See the [MSRV policy](#minimum-supported-rust-version-msrv-policy) for more information) - MongoDB 4.0+ #### Supported Platforms @@ -149,7 +149,7 @@ Commits to main are run automatically on [evergreen](https://evergreen.mongodb.c ## Minimum supported Rust version (MSRV) policy -The MSRV for this crate is currently 1.81.0. Increases to the MSRV will only happen in a minor or major version release, and will be to a Rust version at least six months old. +The MSRV for this crate is currently 1.82.0. Increases to the MSRV will only happen in a minor or major version release, and will be to a Rust version at least six months old. ## License diff --git a/clippy.toml b/clippy.toml index 5e90250c4..c3aa6421b 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1 @@ -msrv = "1.81.0" +msrv = "1.82.0" From 293aa3b0c88033ec589ce9ce4450c0a6e712c181 Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Wed, 16 Jul 2025 13:48:11 -0400 Subject: [PATCH 23/31] RUST-2235: Update tests to not include gssapi-auth flag by default, and introduce GSSAPI-specific variants, tasks, and functions to evergreen config --- .evergreen/config.yml | 23 +++++++++++++++++++++++ .evergreen/run-gssapi-tests.sh | 20 ++++++++++++++++++++ .evergreen/run-tests.sh | 2 +- 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 .evergreen/run-gssapi-tests.sh diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 72e6aebee..3ef32436d 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -256,6 +256,14 @@ buildvariants: # Limit the test to only schedule every 14 days to reduce external resource usage. batchtime: 20160 + - name: gssapi-auth + display_name: "GSSAPI Authentication" + patchable: true + run_on: + - ubuntu2004-small + tasks: + - test-gssapi-auth + - name: x509-auth display_name: "x509 Authentication" patchable: false @@ -971,6 +979,10 @@ tasks: vars: AWS_ROLE_SESSION_NAME: test + - name: test-gssapi-auth + commands: + - func: "run gssapi auth test" + - name: test-atlas-connectivity commands: - func: "run atlas tests" @@ -1439,6 +1451,17 @@ functions: env: AWS_AUTH_TYPE: web-identity + "run gssapi auth test": + - command: subprocess.exec + type: test + params: + binary: bash + working_dir: ${PROJECT_DIRECTORY} + args: + - .evergreen/run-gssapi-tests.sh + include_expansions_in_env: + - PROJECT_DIRECTORY + "run x509 tests": - command: shell.exec type: test diff --git a/.evergreen/run-gssapi-tests.sh b/.evergreen/run-gssapi-tests.sh new file mode 100644 index 000000000..63478b563 --- /dev/null +++ b/.evergreen/run-gssapi-tests.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +set -o xtrace +set -o errexit # Exit the script with error if any of the commands fail + +echo "Running MONGODB-GSSAPI authentication tests" + +cd ${PROJECT_DIRECTORY} +source .evergreen/env.sh +source .evergreen/cargo-test.sh + +FEATURE_FLAGS+=("gssapi-auth") + +set +o errexit + +cargo_test spec::auth +cargo_test uri_options +cargo_test connection_string + +exit $CARGO_RESULT diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index 535f16a7a..b88a5cc36 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -6,7 +6,7 @@ set -o pipefail source .evergreen/env.sh source .evergreen/cargo-test.sh -FEATURE_FLAGS+=("tracing-unstable" "cert-key-password" "gssapi-auth") +FEATURE_FLAGS+=("tracing-unstable" "cert-key-password") if [ "$OPENSSL" = true ]; then FEATURE_FLAGS+=("openssl-tls") From b567aca20a483d6f706a5caaf3e676b1cfe881d9 Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Wed, 16 Jul 2025 14:56:59 -0400 Subject: [PATCH 24/31] RUST-2235: Update canonicalize_hostname to use hickory-resolver instead of dns-lookup --- Cargo.lock | 13 ------- Cargo.toml | 3 +- src/client/auth/gssapi.rs | 81 ++++++++++++++++++++++++++++----------- src/client/auth/oidc.rs | 1 - 4 files changed, 60 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 890e346b9..a660e384a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -628,18 +628,6 @@ dependencies = [ "syn 2.0.101", ] -[[package]] -name = "dns-lookup" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5766087c2235fec47fafa4cfecc81e494ee679d0fd4a59887ea0919bfb0e4fc" -dependencies = [ - "cfg-if", - "libc", - "socket2", - "windows-sys 0.48.0", -] - [[package]] name = "dyn-clone" version = "1.0.19" @@ -1685,7 +1673,6 @@ dependencies = [ "ctrlc", "derive-where", "derive_more", - "dns-lookup", "flate2", "function_name", "futures", diff --git a/Cargo.toml b/Cargo.toml index 314662aec..3a63280c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,7 +59,7 @@ gcp-oidc = ["dep:reqwest"] gcp-kms = ["dep:reqwest"] # Enable support for GSSAPI (Kerberos) authentication. -gssapi-auth = ["dep:cross-krb5", "dep:dns-lookup"] +gssapi-auth = ["dep:cross-krb5", "dep:hickory-resolver"] zstd-compression = ["dep:zstd"] zlib-compression = ["dep:flate2"] @@ -86,7 +86,6 @@ chrono = { version = "0.4.7", default-features = false, features = [ cross-krb5 = { version = "0.4.2", optional = true, default-features = false } derive_more = "0.99.17" derive-where = "1.2.7" -dns-lookup = { version = "2.0", optional = true } flate2 = { version = "1.0", optional = true } futures-io = "0.3.21" futures-core = "0.3.14" diff --git a/src/client/auth/gssapi.rs b/src/client/auth/gssapi.rs index 4c83f00e9..6f6edc787 100644 --- a/src/client/auth/gssapi.rs +++ b/src/client/auth/gssapi.rs @@ -1,5 +1,8 @@ use cross_krb5::{ClientCtx, InitiateFlags, K5Ctx, PendingClientCtx, Step}; -use dns_lookup::{lookup_addr, lookup_host}; +use hickory_resolver::{ + proto::rr::{RData, RecordType}, + TokioAsyncResolver, +}; use crate::{ bson::Bson, @@ -44,7 +47,7 @@ pub(crate) async fn authenticate_stream( let conn_host = conn.address.host().to_string(); let hostname = properties.service_host.as_ref().unwrap_or(&conn_host); - let hostname = canonicalize_hostname(hostname, &properties.canonicalize_host_name)?; + let hostname = canonicalize_hostname(hostname, &properties.canonicalize_host_name).await?; let user_principal = credential.username.clone(); let (mut authenticator, initial_token) = @@ -304,38 +307,72 @@ impl GssapiAuthenticator { } } -fn canonicalize_hostname(hostname: &str, mode: &CanonicalizeHostName) -> Result { +async fn canonicalize_hostname(hostname: &str, mode: &CanonicalizeHostName) -> Result { if mode == &CanonicalizeHostName::None { return Ok(hostname.to_string()); } - // Forward lookup - let ip_addrs = lookup_host(hostname).map_err(|e| { + let resolver = TokioAsyncResolver::tokio_from_system_conf().map_err(|e| { Error::authentication_error( GSSAPI_STR, - &format!("Failed to look up hostname for canonicalization: {e:?}"), + &format!("Failed to initialize hostname Resolver for canonicalization: {e:?}"), ) })?; - if let Some(ip_addr) = ip_addrs.first() { - match mode { - CanonicalizeHostName::Forward => { - // Return the original host, but lowercased to match FQDN convention - Ok(hostname.to_ascii_lowercase()) + match mode { + CanonicalizeHostName::Forward => { + let lookup_records = + resolver + .lookup(hostname, RecordType::CNAME) + .await + .map_err(|e| { + Error::authentication_error( + GSSAPI_STR, + &format!("Failed to look up hostname for canonicalization: {e:?}"), + ) + })?; + + if let Some(first_record) = lookup_records.records().first() { + if let Some(RData::CNAME(cname)) = first_record.data() { + Ok(cname.to_lowercase().to_string()) + } else { + Ok(hostname.to_string()) + } + } else { + Err(Error::authentication_error( + GSSAPI_STR, + &format!("No addresses found for hostname: {hostname}"), + )) } - CanonicalizeHostName::ForwardAndReverse => { - // Reverse-lookup for FQDN, fallback to hostname - match lookup_addr(ip_addr) { - Ok(fqdn) => Ok(fqdn.to_ascii_lowercase()), - Err(_) => Ok(hostname.to_ascii_lowercase()), + } + CanonicalizeHostName::ForwardAndReverse => { + // forward lookup + let ips = resolver.lookup_ip(hostname).await.map_err(|e| { + Error::authentication_error( + GSSAPI_STR, + &format!("Failed to look up hostname for canonicalization: {e:?}"), + ) + })?; + + if let Some(first_address) = ips.iter().next() { + // reverse lookup + match resolver.reverse_lookup(first_address).await { + Ok(reverse_lookup) => { + if let Some(name) = reverse_lookup.iter().next() { + Ok(name.to_lowercase().to_string()) + } else { + Ok(hostname.to_lowercase()) + } + } + Err(_) => Ok(hostname.to_lowercase()), } + } else { + Err(Error::authentication_error( + GSSAPI_STR, + &format!("No addresses found for hostname: {hostname}"), + )) } - CanonicalizeHostName::None => unreachable!(), } - } else { - Err(Error::authentication_error( - GSSAPI_STR, - &format!("No addresses found for hostname: {hostname}"), - )) + CanonicalizeHostName::None => unreachable!(), } } diff --git a/src/client/auth/oidc.rs b/src/client/auth/oidc.rs index 0fc0da327..3ca497c33 100644 --- a/src/client/auth/oidc.rs +++ b/src/client/auth/oidc.rs @@ -307,7 +307,6 @@ enum CallbackKind { Machine, } -use crate::error::ErrorKind; use std::fmt::Debug; impl std::fmt::Debug for Function { From 4d881d1b8cdf1f4790c1d867991b1bdbeefc49bf Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Wed, 16 Jul 2025 14:58:11 -0400 Subject: [PATCH 25/31] RUST-2235: Rustfmt fixes --- src/client/auth/gssapi.rs | 3 ++- src/client/auth/oidc.rs | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/client/auth/gssapi.rs b/src/client/auth/gssapi.rs index 6f6edc787..868991bb9 100644 --- a/src/client/auth/gssapi.rs +++ b/src/client/auth/gssapi.rs @@ -9,7 +9,8 @@ use crate::{ client::{ auth::{ sasl::{SaslContinue, SaslResponse, SaslStart}, - Credential, GSSAPI_STR, + Credential, + GSSAPI_STR, }, options::ServerApi, }, diff --git a/src/client/auth/oidc.rs b/src/client/auth/oidc.rs index 3ca497c33..22da31e70 100644 --- a/src/client/auth/oidc.rs +++ b/src/client/auth/oidc.rs @@ -18,7 +18,9 @@ use crate::{ use super::{ sasl::{SaslContinue, SaslResponse, SaslStart}, - AuthMechanism, Credential, MONGODB_OIDC_STR, + AuthMechanism, + Credential, + MONGODB_OIDC_STR, }; pub(crate) const TOKEN_RESOURCE_PROP_STR: &str = "TOKEN_RESOURCE"; From 896262364a83c8ff59836b37d2341167db8a2124 Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Wed, 16 Jul 2025 14:59:35 -0400 Subject: [PATCH 26/31] RUST-2235: Update lingering 1.81 ref to 1.82 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 3a63280c6..5956b6bab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ license = "Apache-2.0" readme = "README.md" name = "mongodb" version = "3.2.3" -rust-version = "1.81" +rust-version = "1.82" exclude = [ "etc/**", From 66f0f3b96800e087fd7a8b8ff43fe0a48244f151 Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Wed, 16 Jul 2025 15:23:05 -0400 Subject: [PATCH 27/31] RUST-2235: Fix lint failures --- src/client/action/perf.rs | 4 ++-- src/cursor/session.rs | 2 +- src/operation/aggregate/change_stream.rs | 2 +- src/serde_util.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/client/action/perf.rs b/src/client/action/perf.rs index 1d2ab780c..eda5de0a0 100644 --- a/src/client/action/perf.rs +++ b/src/client/action/perf.rs @@ -5,12 +5,12 @@ impl<'a> Action for crate::action::WarmConnectionPool<'a> { type Future = WarmConnectionPoolFuture; async fn execute(self) -> () { - if !self + if self .client .inner .options .min_pool_size - .is_some_and(|size| size > 0) + .is_some_and(|size| size == 0) { // No-op when min_pool_size is zero. return; diff --git a/src/cursor/session.rs b/src/cursor/session.rs index 33f17ca76..a2a0b3dc6 100644 --- a/src/cursor/session.rs +++ b/src/cursor/session.rs @@ -349,7 +349,7 @@ impl SessionCursor { impl SessionCursor { pub(crate) fn is_exhausted(&self) -> bool { - self.state.as_ref().map_or(true, |state| state.exhausted) + self.state.as_ref().is_none_or(|state| state.exhausted) } #[cfg(test)] diff --git a/src/operation/aggregate/change_stream.rs b/src/operation/aggregate/change_stream.rs index d405ec5ae..8b8bab53c 100644 --- a/src/operation/aggregate/change_stream.rs +++ b/src/operation/aggregate/change_stream.rs @@ -109,7 +109,7 @@ impl OperationWithDefaults for ChangeStreamAggregate { }; let description = context.connection.stream_description()?; - if self.args.options.as_ref().map_or(true, has_no_time) + if self.args.options.as_ref().is_none_or(has_no_time) && description.max_wire_version.is_some_and(|v| v >= 7) && spec.initial_buffer.is_empty() && spec.post_batch_resume_token.is_none() diff --git a/src/serde_util.rs b/src/serde_util.rs index 35d8896c6..3a6f62e52 100644 --- a/src/serde_util.rs +++ b/src/serde_util.rs @@ -164,7 +164,7 @@ where pub(crate) fn write_concern_is_empty(write_concern: &Option) -> bool { write_concern .as_ref() - .map_or(true, |write_concern| write_concern.is_empty()) + .is_none_or(|write_concern| write_concern.is_empty()) } #[cfg(test)] From 0cd9f3a1bd7fb6ad74338c5ef8dae2ce537f996f Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Thu, 17 Jul 2025 10:09:31 -0400 Subject: [PATCH 28/31] RUST-2235: Remove outdated comments --- Cargo.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 18bce98bd..ea794db3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,11 +41,9 @@ dns-resolver = ["dep:hickory-resolver", "dep:hickory-proto"] cert-key-password = ["dep:pem", "dep:pkcs8"] # Enable support for MONGODB-AWS authentication. -# This can only be used with the tokio-runtime feature flag. aws-auth = ["dep:reqwest"] # Enable support for on-demand Azure KMS credentials. -# This can only be used with the tokio-runtime feature flag. azure-kms = ["dep:reqwest"] # Enable support for azure OIDC authentication. @@ -55,7 +53,6 @@ azure-oidc = ["dep:reqwest"] gcp-oidc = ["dep:reqwest"] # Enable support for on-demand GCP KMS credentials. -# This can only be used with the tokio-runtime feature flag. gcp-kms = ["dep:reqwest"] # Enable support for GSSAPI (Kerberos) authentication. From efe0fcbb8cbbb5d5e860c264d6522ff054038778 Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Thu, 17 Jul 2025 11:39:55 -0400 Subject: [PATCH 29/31] RUST-2235: Pipe resolver_config through to canonicalize_hostname --- src/action/client_options.rs | 5 +-- src/client/auth.rs | 12 ++++++- src/client/auth/gssapi.rs | 46 +++++++++++---------------- src/client/options.rs | 8 ++--- src/client/options/parse.rs | 4 +-- src/client/options/resolver_config.rs | 6 ++-- src/cmap/establish.rs | 2 ++ src/cmap/establish/handshake.rs | 16 +++++++++- src/cmap/establish/handshake/test.rs | 4 +++ src/error.rs | 4 +-- src/runtime.rs | 4 +-- src/runtime/resolver.rs | 37 +++++++++++++++++++-- 12 files changed, 100 insertions(+), 48 deletions(-) diff --git a/src/action/client_options.rs b/src/action/client_options.rs index 1de6f4946..64be2bc5b 100644 --- a/src/action/client_options.rs +++ b/src/action/client_options.rs @@ -100,8 +100,9 @@ pub struct ParseConnectionString { #[export_tokens(parse_conn_str_setters)] impl ParseConnectionString { /// In the case that "mongodb+srv" is used, SRV and TXT record lookups will be done using the - /// provided `ResolverConfig` as part of this method. - #[cfg(feature = "dns-resolver")] + /// provided `ResolverConfig` as part of this method. In the case that "GSSAPI" auth is used, + /// hostname canonicalization will be done using the provided `ResolverConfig`. + #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] pub fn resolver_config(mut self, value: ResolverConfig) -> Self { self.resolver_config = Some(value); self diff --git a/src/client/auth.rs b/src/client/auth.rs index 2fa42b3b5..680699cf4 100644 --- a/src/client/auth.rs +++ b/src/client/auth.rs @@ -24,6 +24,8 @@ use serde::Deserialize; use typed_builder::TypedBuilder; use self::scram::ScramVersion; +#[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] +use crate::options::ResolverConfig; use crate::{ bson::Document, client::options::ServerApi, @@ -287,6 +289,9 @@ impl AuthMechanism { credential: &Credential, server_api: Option<&ServerApi>, #[cfg(feature = "aws-auth")] http_client: &crate::runtime::HttpClient, + #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] resolver_config: Option< + &ResolverConfig, + >, ) -> Result<()> { self.validate_credential(credential)?; @@ -306,7 +311,7 @@ impl AuthMechanism { } #[cfg(feature = "gssapi-auth")] AuthMechanism::Gssapi => { - gssapi::authenticate_stream(stream, credential, server_api).await + gssapi::authenticate_stream(stream, credential, server_api, resolver_config).await } AuthMechanism::Plain => { plain::authenticate_stream(stream, credential, server_api).await @@ -495,6 +500,9 @@ impl Credential { server_api: Option<&ServerApi>, first_round: Option, #[cfg(feature = "aws-auth")] http_client: &crate::runtime::HttpClient, + #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] resolver_config: Option< + &ResolverConfig, + >, ) -> Result<()> { let stream_description = conn.stream_description()?; @@ -533,6 +541,8 @@ impl Credential { server_api, #[cfg(feature = "aws-auth")] http_client, + #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] + resolver_config, ) .await } diff --git a/src/client/auth/gssapi.rs b/src/client/auth/gssapi.rs index 868991bb9..b554dd353 100644 --- a/src/client/auth/gssapi.rs +++ b/src/client/auth/gssapi.rs @@ -1,8 +1,5 @@ use cross_krb5::{ClientCtx, InitiateFlags, K5Ctx, PendingClientCtx, Step}; -use hickory_resolver::{ - proto::rr::{RData, RecordType}, - TokioAsyncResolver, -}; +use hickory_resolver::proto::rr::RData; use crate::{ bson::Bson, @@ -16,6 +13,7 @@ use crate::{ }, cmap::Connection, error::{Error, Result}, + options::ResolverConfig, }; const SERVICE_NAME: &str = "SERVICE_NAME"; @@ -43,12 +41,18 @@ pub(crate) async fn authenticate_stream( conn: &mut Connection, credential: &Credential, server_api: Option<&ServerApi>, + resolver_config: Option<&ResolverConfig>, ) -> Result<()> { let properties = GssapiProperties::from_credential(credential)?; let conn_host = conn.address.host().to_string(); let hostname = properties.service_host.as_ref().unwrap_or(&conn_host); - let hostname = canonicalize_hostname(hostname, &properties.canonicalize_host_name).await?; + let hostname = canonicalize_hostname( + hostname, + &properties.canonicalize_host_name, + resolver_config, + ) + .await?; let user_principal = credential.username.clone(); let (mut authenticator, initial_token) = @@ -308,30 +312,21 @@ impl GssapiAuthenticator { } } -async fn canonicalize_hostname(hostname: &str, mode: &CanonicalizeHostName) -> Result { +async fn canonicalize_hostname( + hostname: &str, + mode: &CanonicalizeHostName, + resolver_config: Option<&ResolverConfig>, +) -> Result { if mode == &CanonicalizeHostName::None { return Ok(hostname.to_string()); } - let resolver = TokioAsyncResolver::tokio_from_system_conf().map_err(|e| { - Error::authentication_error( - GSSAPI_STR, - &format!("Failed to initialize hostname Resolver for canonicalization: {e:?}"), - ) - })?; + let resolver = + crate::runtime::AsyncResolver::new(resolver_config.map(|c| c.inner.clone())).await?; match mode { CanonicalizeHostName::Forward => { - let lookup_records = - resolver - .lookup(hostname, RecordType::CNAME) - .await - .map_err(|e| { - Error::authentication_error( - GSSAPI_STR, - &format!("Failed to look up hostname for canonicalization: {e:?}"), - ) - })?; + let lookup_records = resolver.cname_lookup(hostname).await?; if let Some(first_record) = lookup_records.records().first() { if let Some(RData::CNAME(cname)) = first_record.data() { @@ -348,12 +343,7 @@ async fn canonicalize_hostname(hostname: &str, mode: &CanonicalizeHostName) -> R } CanonicalizeHostName::ForwardAndReverse => { // forward lookup - let ips = resolver.lookup_ip(hostname).await.map_err(|e| { - Error::authentication_error( - GSSAPI_STR, - &format!("Failed to look up hostname for canonicalization: {e:?}"), - ) - })?; + let ips = resolver.ip_lookup(hostname).await?; if let Some(first_address) = ips.iter().next() { // reverse lookup diff --git a/src/client/options.rs b/src/client/options.rs index aea136115..20b3146c6 100644 --- a/src/client/options.rs +++ b/src/client/options.rs @@ -48,7 +48,7 @@ use crate::{ }; pub use bulk_write::*; -#[cfg(feature = "dns-resolver")] +#[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] pub use resolver_config::ResolverConfig; #[cfg(not(feature = "dns-resolver"))] pub(crate) use resolver_config::ResolverConfig; @@ -318,7 +318,7 @@ impl ServerAddress { }) } - #[cfg(feature = "dns-resolver")] + #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] pub(crate) fn host(&self) -> std::borrow::Cow<'_, str> { match self { Self::Tcp { host, .. } => std::borrow::Cow::Borrowed(host.as_str()), @@ -632,7 +632,7 @@ pub struct ClientOptions { #[builder(setter(skip))] #[serde(skip)] #[derive_where(skip(Debug))] - #[cfg(feature = "dns-resolver")] + #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] pub(crate) resolver_config: Option, /// Control test behavior of the client. @@ -1323,7 +1323,7 @@ impl ClientOptions { } pub(crate) fn resolver_config(&self) -> Option<&ResolverConfig> { - #[cfg(feature = "dns-resolver")] + #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] { self.resolver_config.as_ref() } diff --git a/src/client/options/parse.rs b/src/client/options/parse.rs index 8aed36ae4..5f157ee74 100644 --- a/src/client/options/parse.rs +++ b/src/client/options/parse.rs @@ -19,7 +19,7 @@ impl Action for ParseConnectionString { .is_some(); let host_info = std::mem::take(&mut conn_str.host_info); let mut options = ClientOptions::from_connection_string(conn_str); - #[cfg(feature = "dns-resolver")] + #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] { options.resolver_config.clone_from(&self.resolver_config); } @@ -151,7 +151,7 @@ impl ClientOptions { original_srv_info: None, #[cfg(test)] original_uri: Some(conn_str.original_uri), - #[cfg(feature = "dns-resolver")] + #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] resolver_config: None, server_api: None, load_balanced: conn_str.load_balanced, diff --git a/src/client/options/resolver_config.rs b/src/client/options/resolver_config.rs index 3c7b20da6..5c8a0965a 100644 --- a/src/client/options/resolver_config.rs +++ b/src/client/options/resolver_config.rs @@ -1,4 +1,4 @@ -#[cfg(feature = "dns-resolver")] +#[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] use hickory_resolver::config::ResolverConfig as HickoryResolverConfig; /// Configuration for the upstream nameservers to use for resolution. @@ -7,11 +7,11 @@ use hickory_resolver::config::ResolverConfig as HickoryResolverConfig; /// API stability. #[derive(Clone, Debug, PartialEq)] pub struct ResolverConfig { - #[cfg(feature = "dns-resolver")] + #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] pub(crate) inner: HickoryResolverConfig, } -#[cfg(feature = "dns-resolver")] +#[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] impl ResolverConfig { /// Creates a default configuration, using 1.1.1.1, 1.0.0.1 and 2606:4700:4700::1111, /// 2606:4700:4700::1001 (thank you, Cloudflare). diff --git a/src/cmap/establish.rs b/src/cmap/establish.rs index 58873a052..7239527b0 100644 --- a/src/cmap/establish.rs +++ b/src/cmap/establish.rs @@ -62,6 +62,8 @@ impl EstablisherOptions { driver_info: opts.driver_info.clone(), server_api: opts.server_api.clone(), load_balanced: opts.load_balanced.unwrap_or(false), + #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] + resolver_config: opts.resolver_config.clone(), }, tls_options: opts.tls_options(), connect_timeout: opts.connect_timeout, diff --git a/src/cmap/establish/handshake.rs b/src/cmap/establish/handshake.rs index 605f2f6b2..35e8069fe 100644 --- a/src/cmap/establish/handshake.rs +++ b/src/cmap/establish/handshake.rs @@ -16,6 +16,8 @@ use tokio::sync::broadcast; feature = "snappy-compression" ))] use crate::options::Compressor; +#[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] +use crate::options::ResolverConfig; use crate::{ client::auth::ClientFirst, cmap::{Command, Connection, StreamDescription}, @@ -342,6 +344,9 @@ pub(crate) struct Handshaker { #[cfg(feature = "aws-auth")] http_client: crate::runtime::HttpClient, + + #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] + resolver_config: Option, } #[cfg(test)] @@ -411,6 +416,8 @@ impl Handshaker { metadata, #[cfg(feature = "aws-auth")] http_client: crate::runtime::HttpClient::default(), + #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] + resolver_config: options.resolver_config, }) } @@ -498,6 +505,8 @@ impl Handshaker { first_round, #[cfg(feature = "aws-auth")] &self.http_client, + #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] + self.resolver_config.as_ref(), ) .await? } @@ -532,9 +541,14 @@ pub(crate) struct HandshakerOptions { /// Whether or not the client is connecting to a MongoDB cluster through a load balancer. pub(crate) load_balanced: bool, + + /// Configuration of the DNS resolver used for SRV and TXT lookups, as well + /// as hostname canonicalization for GSSAPI. + #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] + pub(crate) resolver_config: Option, } -/// Updates the handshake command document with the speculative authenitication info. +/// Updates the handshake command document with the speculative authentication info. async fn set_speculative_auth_info( command: &mut RawDocumentBuf, credential: Option<&Credential>, diff --git a/src/cmap/establish/handshake/test.rs b/src/cmap/establish/handshake/test.rs index ff861c2eb..b597ef1c4 100644 --- a/src/cmap/establish/handshake/test.rs +++ b/src/cmap/establish/handshake/test.rs @@ -18,6 +18,8 @@ async fn metadata_no_options() { driver_info: None, server_api: None, load_balanced: false, + #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] + resolver_config: None, }) .unwrap(); @@ -66,6 +68,8 @@ async fn metadata_with_options() { compressors: None, server_api: None, load_balanced: false, + #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] + resolver_config: None, }; let handshaker = Handshaker::new(options).unwrap(); diff --git a/src/error.rs b/src/error.rs index f7b4641e1..3b306d090 100644 --- a/src/error.rs +++ b/src/error.rs @@ -282,7 +282,7 @@ impl Error { self.labels.insert(label); } - #[cfg(feature = "dns-resolver")] + #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] pub(crate) fn from_resolve_error(error: hickory_resolver::error::ResolveError) -> Self { ErrorKind::DnsResolve { message: error.to_string(), @@ -290,7 +290,7 @@ impl Error { .into() } - #[cfg(feature = "dns-resolver")] + #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] pub(crate) fn from_resolve_proto_error(error: hickory_proto::error::ProtoError) -> Self { ErrorKind::DnsResolve { message: error.to_string(), diff --git a/src/runtime.rs b/src/runtime.rs index e46605bb2..1fdbd82dc 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -12,7 +12,7 @@ mod join_handle; mod pem; #[cfg(any(feature = "in-use-encryption", test))] pub(crate) mod process; -#[cfg(feature = "dns-resolver")] +#[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] mod resolver; pub(crate) mod stream; mod sync_read_ext; @@ -25,7 +25,7 @@ mod worker_handle; use std::{future::Future, net::SocketAddr, time::Duration}; -#[cfg(feature = "dns-resolver")] +#[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] pub(crate) use self::resolver::AsyncResolver; pub(crate) use self::{ acknowledged_message::{AcknowledgedMessage, AcknowledgmentReceiver, AcknowledgmentSender}, diff --git a/src/runtime/resolver.rs b/src/runtime/resolver.rs index bd75a9d09..d123e2de1 100644 --- a/src/runtime/resolver.rs +++ b/src/runtime/resolver.rs @@ -1,11 +1,13 @@ +use crate::error::{Error, Result}; use hickory_resolver::{ config::ResolverConfig, error::ResolveErrorKind, - lookup::{SrvLookup, TxtLookup}, + lookup::{Lookup, ReverseLookup, SrvLookup, TxtLookup}, + lookup_ip::LookupIp, + proto::rr::RecordType, Name, }; - -use crate::error::{Error, Result}; +use std::net::IpAddr; /// An async runtime agnostic DNS resolver. pub(crate) struct AsyncResolver { @@ -25,6 +27,35 @@ impl AsyncResolver { } impl AsyncResolver { + pub async fn cname_lookup(&self, query: &str) -> Result { + let name = Name::from_str_relaxed(query).map_err(Error::from_resolve_proto_error)?; + let lookup = self + .resolver + .lookup(name, RecordType::CNAME) + .await + .map_err(Error::from_resolve_error)?; + Ok(lookup) + } + + pub async fn ip_lookup(&self, query: &str) -> Result { + let name = Name::from_str_relaxed(query).map_err(Error::from_resolve_proto_error)?; + let lookup = self + .resolver + .lookup_ip(name) + .await + .map_err(Error::from_resolve_error)?; + Ok(lookup) + } + + pub async fn reverse_lookup(&self, ip_addr: IpAddr) -> Result { + let lookup = self + .resolver + .reverse_lookup(ip_addr) + .await + .map_err(Error::from_resolve_error)?; + Ok(lookup) + } + pub async fn srv_lookup(&self, query: &str) -> Result { let name = Name::from_str_relaxed(query).map_err(Error::from_resolve_proto_error)?; let lookup = self From 9de299951b596a73abe16e029163fb3092d7b497 Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Thu, 17 Jul 2025 14:39:18 -0400 Subject: [PATCH 30/31] RUST-2235: Clean up gssapi-auth and dns-lookup feature coupling --- Cargo.toml | 2 +- src/action/client_options.rs | 2 +- src/client/auth.rs | 12 ++++-------- src/client/options.rs | 8 ++++---- src/client/options/parse.rs | 4 ++-- src/client/options/resolver_config.rs | 6 +++--- src/cmap/establish.rs | 2 +- src/cmap/establish/handshake.rs | 10 +++++----- src/cmap/establish/handshake/test.rs | 4 ++-- src/error.rs | 4 ++-- src/runtime.rs | 4 ++-- 11 files changed, 27 insertions(+), 31 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ea794db3b..263c7fc5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,7 +56,7 @@ gcp-oidc = ["dep:reqwest"] gcp-kms = ["dep:reqwest"] # Enable support for GSSAPI (Kerberos) authentication. -gssapi-auth = ["dep:cross-krb5", "dep:hickory-resolver"] +gssapi-auth = ["dep:cross-krb5", "dns-resolver"] zstd-compression = ["dep:zstd"] zlib-compression = ["dep:flate2"] diff --git a/src/action/client_options.rs b/src/action/client_options.rs index 64be2bc5b..a3234edc6 100644 --- a/src/action/client_options.rs +++ b/src/action/client_options.rs @@ -102,7 +102,7 @@ impl ParseConnectionString { /// In the case that "mongodb+srv" is used, SRV and TXT record lookups will be done using the /// provided `ResolverConfig` as part of this method. In the case that "GSSAPI" auth is used, /// hostname canonicalization will be done using the provided `ResolverConfig`. - #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] + #[cfg(feature = "dns-resolver")] pub fn resolver_config(mut self, value: ResolverConfig) -> Self { self.resolver_config = Some(value); self diff --git a/src/client/auth.rs b/src/client/auth.rs index 680699cf4..91a4c5c01 100644 --- a/src/client/auth.rs +++ b/src/client/auth.rs @@ -24,7 +24,7 @@ use serde::Deserialize; use typed_builder::TypedBuilder; use self::scram::ScramVersion; -#[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] +#[cfg(feature = "dns-resolver")] use crate::options::ResolverConfig; use crate::{ bson::Document, @@ -289,9 +289,7 @@ impl AuthMechanism { credential: &Credential, server_api: Option<&ServerApi>, #[cfg(feature = "aws-auth")] http_client: &crate::runtime::HttpClient, - #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] resolver_config: Option< - &ResolverConfig, - >, + #[cfg(feature = "gssapi-auth")] resolver_config: Option<&ResolverConfig>, ) -> Result<()> { self.validate_credential(credential)?; @@ -500,9 +498,7 @@ impl Credential { server_api: Option<&ServerApi>, first_round: Option, #[cfg(feature = "aws-auth")] http_client: &crate::runtime::HttpClient, - #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] resolver_config: Option< - &ResolverConfig, - >, + #[cfg(feature = "gssapi-auth")] resolver_config: Option<&ResolverConfig>, ) -> Result<()> { let stream_description = conn.stream_description()?; @@ -541,7 +537,7 @@ impl Credential { server_api, #[cfg(feature = "aws-auth")] http_client, - #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] + #[cfg(feature = "gssapi-auth")] resolver_config, ) .await diff --git a/src/client/options.rs b/src/client/options.rs index 20b3146c6..aea136115 100644 --- a/src/client/options.rs +++ b/src/client/options.rs @@ -48,7 +48,7 @@ use crate::{ }; pub use bulk_write::*; -#[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] +#[cfg(feature = "dns-resolver")] pub use resolver_config::ResolverConfig; #[cfg(not(feature = "dns-resolver"))] pub(crate) use resolver_config::ResolverConfig; @@ -318,7 +318,7 @@ impl ServerAddress { }) } - #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] + #[cfg(feature = "dns-resolver")] pub(crate) fn host(&self) -> std::borrow::Cow<'_, str> { match self { Self::Tcp { host, .. } => std::borrow::Cow::Borrowed(host.as_str()), @@ -632,7 +632,7 @@ pub struct ClientOptions { #[builder(setter(skip))] #[serde(skip)] #[derive_where(skip(Debug))] - #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] + #[cfg(feature = "dns-resolver")] pub(crate) resolver_config: Option, /// Control test behavior of the client. @@ -1323,7 +1323,7 @@ impl ClientOptions { } pub(crate) fn resolver_config(&self) -> Option<&ResolverConfig> { - #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] + #[cfg(feature = "dns-resolver")] { self.resolver_config.as_ref() } diff --git a/src/client/options/parse.rs b/src/client/options/parse.rs index 5f157ee74..8aed36ae4 100644 --- a/src/client/options/parse.rs +++ b/src/client/options/parse.rs @@ -19,7 +19,7 @@ impl Action for ParseConnectionString { .is_some(); let host_info = std::mem::take(&mut conn_str.host_info); let mut options = ClientOptions::from_connection_string(conn_str); - #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] + #[cfg(feature = "dns-resolver")] { options.resolver_config.clone_from(&self.resolver_config); } @@ -151,7 +151,7 @@ impl ClientOptions { original_srv_info: None, #[cfg(test)] original_uri: Some(conn_str.original_uri), - #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] + #[cfg(feature = "dns-resolver")] resolver_config: None, server_api: None, load_balanced: conn_str.load_balanced, diff --git a/src/client/options/resolver_config.rs b/src/client/options/resolver_config.rs index 5c8a0965a..3c7b20da6 100644 --- a/src/client/options/resolver_config.rs +++ b/src/client/options/resolver_config.rs @@ -1,4 +1,4 @@ -#[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] +#[cfg(feature = "dns-resolver")] use hickory_resolver::config::ResolverConfig as HickoryResolverConfig; /// Configuration for the upstream nameservers to use for resolution. @@ -7,11 +7,11 @@ use hickory_resolver::config::ResolverConfig as HickoryResolverConfig; /// API stability. #[derive(Clone, Debug, PartialEq)] pub struct ResolverConfig { - #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] + #[cfg(feature = "dns-resolver")] pub(crate) inner: HickoryResolverConfig, } -#[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] +#[cfg(feature = "dns-resolver")] impl ResolverConfig { /// Creates a default configuration, using 1.1.1.1, 1.0.0.1 and 2606:4700:4700::1111, /// 2606:4700:4700::1001 (thank you, Cloudflare). diff --git a/src/cmap/establish.rs b/src/cmap/establish.rs index 7239527b0..cfec685e6 100644 --- a/src/cmap/establish.rs +++ b/src/cmap/establish.rs @@ -62,7 +62,7 @@ impl EstablisherOptions { driver_info: opts.driver_info.clone(), server_api: opts.server_api.clone(), load_balanced: opts.load_balanced.unwrap_or(false), - #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] + #[cfg(feature = "dns-resolver")] resolver_config: opts.resolver_config.clone(), }, tls_options: opts.tls_options(), diff --git a/src/cmap/establish/handshake.rs b/src/cmap/establish/handshake.rs index 35e8069fe..c97ce9260 100644 --- a/src/cmap/establish/handshake.rs +++ b/src/cmap/establish/handshake.rs @@ -16,7 +16,7 @@ use tokio::sync::broadcast; feature = "snappy-compression" ))] use crate::options::Compressor; -#[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] +#[cfg(feature = "dns-resolver")] use crate::options::ResolverConfig; use crate::{ client::auth::ClientFirst, @@ -345,7 +345,7 @@ pub(crate) struct Handshaker { #[cfg(feature = "aws-auth")] http_client: crate::runtime::HttpClient, - #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] + #[cfg(feature = "dns-resolver")] resolver_config: Option, } @@ -416,7 +416,7 @@ impl Handshaker { metadata, #[cfg(feature = "aws-auth")] http_client: crate::runtime::HttpClient::default(), - #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] + #[cfg(afeature = "dns-resolver")] resolver_config: options.resolver_config, }) } @@ -505,7 +505,7 @@ impl Handshaker { first_round, #[cfg(feature = "aws-auth")] &self.http_client, - #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] + #[cfg(feature = "dns-resolver")] self.resolver_config.as_ref(), ) .await? @@ -544,7 +544,7 @@ pub(crate) struct HandshakerOptions { /// Configuration of the DNS resolver used for SRV and TXT lookups, as well /// as hostname canonicalization for GSSAPI. - #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] + #[cfg(feature = "dns-resolver")] pub(crate) resolver_config: Option, } diff --git a/src/cmap/establish/handshake/test.rs b/src/cmap/establish/handshake/test.rs index b597ef1c4..caa420950 100644 --- a/src/cmap/establish/handshake/test.rs +++ b/src/cmap/establish/handshake/test.rs @@ -18,7 +18,7 @@ async fn metadata_no_options() { driver_info: None, server_api: None, load_balanced: false, - #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] + #[cfg(feature = "dns-resolver")] resolver_config: None, }) .unwrap(); @@ -68,7 +68,7 @@ async fn metadata_with_options() { compressors: None, server_api: None, load_balanced: false, - #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] + #[cfg(feature = "dns-resolver")] resolver_config: None, }; diff --git a/src/error.rs b/src/error.rs index 3b306d090..f7b4641e1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -282,7 +282,7 @@ impl Error { self.labels.insert(label); } - #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] + #[cfg(feature = "dns-resolver")] pub(crate) fn from_resolve_error(error: hickory_resolver::error::ResolveError) -> Self { ErrorKind::DnsResolve { message: error.to_string(), @@ -290,7 +290,7 @@ impl Error { .into() } - #[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] + #[cfg(feature = "dns-resolver")] pub(crate) fn from_resolve_proto_error(error: hickory_proto::error::ProtoError) -> Self { ErrorKind::DnsResolve { message: error.to_string(), diff --git a/src/runtime.rs b/src/runtime.rs index 1fdbd82dc..e46605bb2 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -12,7 +12,7 @@ mod join_handle; mod pem; #[cfg(any(feature = "in-use-encryption", test))] pub(crate) mod process; -#[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] +#[cfg(feature = "dns-resolver")] mod resolver; pub(crate) mod stream; mod sync_read_ext; @@ -25,7 +25,7 @@ mod worker_handle; use std::{future::Future, net::SocketAddr, time::Duration}; -#[cfg(any(feature = "dns-resolver", feature = "gssapi-auth"))] +#[cfg(feature = "dns-resolver")] pub(crate) use self::resolver::AsyncResolver; pub(crate) use self::{ acknowledged_message::{AcknowledgedMessage, AcknowledgmentReceiver, AcknowledgmentSender}, From 23aacbb981eed081de3723ea8448ad985c017d21 Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Thu, 17 Jul 2025 14:50:40 -0400 Subject: [PATCH 31/31] RUST-2235: Fix feature flags and address clippy and rustfmt failures --- src/client/auth.rs | 2 +- src/cmap/establish.rs | 2 +- src/cmap/establish/handshake.rs | 13 ++++++------- src/cmap/establish/handshake/test.rs | 4 ++-- src/runtime/resolver.rs | 13 +++++++++++-- 5 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/client/auth.rs b/src/client/auth.rs index 91a4c5c01..9624f9c56 100644 --- a/src/client/auth.rs +++ b/src/client/auth.rs @@ -24,7 +24,7 @@ use serde::Deserialize; use typed_builder::TypedBuilder; use self::scram::ScramVersion; -#[cfg(feature = "dns-resolver")] +#[cfg(feature = "gssapi-auth")] use crate::options::ResolverConfig; use crate::{ bson::Document, diff --git a/src/cmap/establish.rs b/src/cmap/establish.rs index cfec685e6..9520ff13c 100644 --- a/src/cmap/establish.rs +++ b/src/cmap/establish.rs @@ -62,7 +62,7 @@ impl EstablisherOptions { driver_info: opts.driver_info.clone(), server_api: opts.server_api.clone(), load_balanced: opts.load_balanced.unwrap_or(false), - #[cfg(feature = "dns-resolver")] + #[cfg(feature = "gssapi-auth")] resolver_config: opts.resolver_config.clone(), }, tls_options: opts.tls_options(), diff --git a/src/cmap/establish/handshake.rs b/src/cmap/establish/handshake.rs index c97ce9260..53f118710 100644 --- a/src/cmap/establish/handshake.rs +++ b/src/cmap/establish/handshake.rs @@ -16,7 +16,7 @@ use tokio::sync::broadcast; feature = "snappy-compression" ))] use crate::options::Compressor; -#[cfg(feature = "dns-resolver")] +#[cfg(feature = "gssapi-auth")] use crate::options::ResolverConfig; use crate::{ client::auth::ClientFirst, @@ -345,7 +345,7 @@ pub(crate) struct Handshaker { #[cfg(feature = "aws-auth")] http_client: crate::runtime::HttpClient, - #[cfg(feature = "dns-resolver")] + #[cfg(feature = "gssapi-auth")] resolver_config: Option, } @@ -416,7 +416,7 @@ impl Handshaker { metadata, #[cfg(feature = "aws-auth")] http_client: crate::runtime::HttpClient::default(), - #[cfg(afeature = "dns-resolver")] + #[cfg(feature = "gssapi-auth")] resolver_config: options.resolver_config, }) } @@ -505,7 +505,7 @@ impl Handshaker { first_round, #[cfg(feature = "aws-auth")] &self.http_client, - #[cfg(feature = "dns-resolver")] + #[cfg(feature = "gssapi-auth")] self.resolver_config.as_ref(), ) .await? @@ -542,9 +542,8 @@ pub(crate) struct HandshakerOptions { /// Whether or not the client is connecting to a MongoDB cluster through a load balancer. pub(crate) load_balanced: bool, - /// Configuration of the DNS resolver used for SRV and TXT lookups, as well - /// as hostname canonicalization for GSSAPI. - #[cfg(feature = "dns-resolver")] + /// Configuration of the DNS resolver used for hostname canonicalization for GSSAPI. + #[cfg(feature = "gssapi-auth")] pub(crate) resolver_config: Option, } diff --git a/src/cmap/establish/handshake/test.rs b/src/cmap/establish/handshake/test.rs index caa420950..846f38590 100644 --- a/src/cmap/establish/handshake/test.rs +++ b/src/cmap/establish/handshake/test.rs @@ -18,7 +18,7 @@ async fn metadata_no_options() { driver_info: None, server_api: None, load_balanced: false, - #[cfg(feature = "dns-resolver")] + #[cfg(feature = "gssapi-auth")] resolver_config: None, }) .unwrap(); @@ -68,7 +68,7 @@ async fn metadata_with_options() { compressors: None, server_api: None, load_balanced: false, - #[cfg(feature = "dns-resolver")] + #[cfg(feature = "gssapi-auth")] resolver_config: None, }; diff --git a/src/runtime/resolver.rs b/src/runtime/resolver.rs index d123e2de1..94ba80123 100644 --- a/src/runtime/resolver.rs +++ b/src/runtime/resolver.rs @@ -2,11 +2,17 @@ use crate::error::{Error, Result}; use hickory_resolver::{ config::ResolverConfig, error::ResolveErrorKind, - lookup::{Lookup, ReverseLookup, SrvLookup, TxtLookup}, + lookup::{SrvLookup, TxtLookup}, + Name, +}; + +#[cfg(feature = "gssapi-auth")] +use hickory_resolver::{ + lookup::{Lookup, ReverseLookup}, lookup_ip::LookupIp, proto::rr::RecordType, - Name, }; +#[cfg(feature = "gssapi-auth")] use std::net::IpAddr; /// An async runtime agnostic DNS resolver. @@ -27,6 +33,7 @@ impl AsyncResolver { } impl AsyncResolver { + #[cfg(feature = "gssapi-auth")] pub async fn cname_lookup(&self, query: &str) -> Result { let name = Name::from_str_relaxed(query).map_err(Error::from_resolve_proto_error)?; let lookup = self @@ -37,6 +44,7 @@ impl AsyncResolver { Ok(lookup) } + #[cfg(feature = "gssapi-auth")] pub async fn ip_lookup(&self, query: &str) -> Result { let name = Name::from_str_relaxed(query).map_err(Error::from_resolve_proto_error)?; let lookup = self @@ -47,6 +55,7 @@ impl AsyncResolver { Ok(lookup) } + #[cfg(feature = "gssapi-auth")] pub async fn reverse_lookup(&self, ip_addr: IpAddr) -> Result { let lookup = self .resolver