From de87f07da02498e1e796381830139d6a3186a0fa Mon Sep 17 00:00:00 2001 From: CommanderKeynes Date: Fri, 24 May 2024 20:47:58 -0500 Subject: [PATCH] Implement trust authentication --- .circleci/pgcat.toml | 3 + Cargo.lock | 356 ++++++++++++++++++++++++++++++---- Cargo.toml | 3 + pgcat.minimal.toml | 10 +- pgcat.toml | 5 + src/auth_passthrough.rs | 3 + src/client.rs | 419 +++++++++++++++++++++++++++++----------- src/config.rs | 12 ++ src/messages.rs | 14 ++ 9 files changed, 670 insertions(+), 155 deletions(-) diff --git a/.circleci/pgcat.toml b/.circleci/pgcat.toml index 8b87aa03..30899107 100644 --- a/.circleci/pgcat.toml +++ b/.circleci/pgcat.toml @@ -97,6 +97,7 @@ sharding_function = "pg_bigint_hash" [pools.sharded_db.users.0] username = "sharding_user" password = "sharding_user" +auth_type = "md5" # Maximum number of server connections that can be established for this user # The maximum number of connection from a single Pgcat process to any database in the cluster # is the sum of pool_size across all users. @@ -106,6 +107,7 @@ statement_timeout = 0 [pools.sharded_db.users.1] username = "other_user" password = "other_user" +auth_type = "md5" pool_size = 21 statement_timeout = 30000 @@ -145,6 +147,7 @@ sharding_function = "pg_bigint_hash" [pools.simple_db.users.0] username = "simple_user" password = "simple_user" +auth_type = "md5" pool_size = 5 statement_timeout = 30000 diff --git a/Cargo.lock b/Cargo.lock index 6699ce61..ea729f2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,7 +94,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -104,7 +104,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -205,9 +205,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.3" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "block-buffer" @@ -310,6 +310,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -378,23 +388,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ - "cc", "libc", + "windows-sys 0.52.0", ] [[package]] @@ -409,12 +408,33 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.0" @@ -737,7 +757,7 @@ checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ "socket2 0.5.3", "widestring", - "windows-sys", + "windows-sys 0.48.0", "winreg", ] @@ -755,7 +775,7 @@ checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -808,11 +828,45 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lber" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2df7f9fd9f64cf8f59e1a4a0753fe7d575a5b38d3d7ac5758dcee9357d83ef0a" +dependencies = [ + "bytes", + "nom", +] + +[[package]] +name = "ldap3" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dceb52eac140c8679326f619b963577ccf5a4ab025fe5e6db424d2fd3e4f400" +dependencies = [ + "async-trait", + "bytes", + "futures", + "futures-util", + "lazy_static", + "lber", + "log", + "native-tls", + "nom", + "percent-encoding", + "thiserror", + "tokio", + "tokio-native-tls", + "tokio-stream", + "tokio-util", + "url", +] + [[package]] name = "libc" -version = "0.2.147" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "linked-hash-map" @@ -822,9 +876,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.3" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" @@ -905,6 +959,12 @@ dependencies = [ "autocfg", ] +[[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.7.1" @@ -922,7 +982,25 @@ checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", ] [[package]] @@ -939,6 +1017,16 @@ dependencies = [ "static_assertions", ] +[[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" @@ -983,6 +1071,50 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "openssl" +version = "0.10.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +dependencies = [ + "bitflags 2.5.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.26", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "overload" version = "0.1.1" @@ -1009,7 +1141,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.48.1", ] [[package]] @@ -1037,6 +1169,7 @@ dependencies = [ "hyper", "itertools", "jemallocator", + "ldap3", "log", "lru", "md-5", @@ -1143,6 +1276,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + [[package]] name = "postgres-protocol" version = "0.6.5" @@ -1307,15 +1446,15 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.4" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -1365,6 +1504,15 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -1381,6 +1529,29 @@ dependencies = [ "untrusted", ] +[[package]] +name = "security-framework" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.171" @@ -1499,7 +1670,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1579,6 +1750,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "thiserror" version = "1.0.43" @@ -1652,7 +1835,7 @@ dependencies = [ "signal-hook-registry", "socket2 0.4.9", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1666,6 +1849,16 @@ dependencies = [ "syn 2.0.26", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.24.1" @@ -1936,6 +2129,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -2070,7 +2269,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets", + "windows-targets 0.48.1", ] [[package]] @@ -2079,7 +2278,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.1", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.5", ] [[package]] @@ -2088,13 +2296,29 @@ version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -2103,42 +2327,90 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + [[package]] name = "windows_x86_64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + [[package]] name = "winnow" version = "0.5.0" @@ -2155,5 +2427,5 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ "cfg-if", - "windows-sys", + "windows-sys 0.48.0", ] diff --git a/Cargo.toml b/Cargo.toml index f408ba4e..57a58441 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,3 +56,6 @@ lru = "0.12.0" [target.'cfg(not(target_env = "msvc"))'.dependencies] jemallocator = "0.5.0" + +[dependencies.ldap3] +version = "0.11.3" diff --git a/pgcat.minimal.toml b/pgcat.minimal.toml index 4b17a454..db26f5e2 100644 --- a/pgcat.minimal.toml +++ b/pgcat.minimal.toml @@ -5,12 +5,18 @@ host = "0.0.0.0" port = 6433 -admin_username = "pgcat" +admin_username = "jdoe" admin_password = "pgcat" +admin_auth_type = "ldap" +admin_auth_ldapurl = "ldap://127.0.0.1:3893" +admin_auth_ldapsuffix = "@example.com" [pools.pgml.users.0] -username = "postgres" +username = "jdoe" password = "postgres" +auth_type = "ldap" +auth_ldapurl = "ldap://127.0.0.1:3893" +auth_ldapsuffix = "@example.com" pool_size = 10 min_pool_size = 1 pool_mode = "transaction" diff --git a/pgcat.toml b/pgcat.toml index 9e19c13b..4023c763 100644 --- a/pgcat.toml +++ b/pgcat.toml @@ -76,6 +76,7 @@ verify_server_certificate = false admin_username = "admin_user" # Password to access the virtual administrative database admin_password = "admin_pass" +admin_auth_type = "md5" # Default plugins that are configured on all pools. [plugins] @@ -274,6 +275,8 @@ result = [ # if `server_username` is not set. username = "sharding_user" +auth_type = "md5" + # PostgreSQL password used to authenticate the user and connect to the server # if `server_password` is not set. password = "sharding_user" @@ -299,6 +302,7 @@ statement_timeout = 0 [pools.sharded_db.users.1] username = "other_user" password = "other_user" +auth_type = "md5" pool_size = 21 statement_timeout = 15000 connect_timeout = 1000 @@ -337,6 +341,7 @@ sharding_function = "pg_bigint_hash" [pools.simple_db.users.0] username = "simple_user" password = "simple_user" +auth_type = "md5" pool_size = 5 min_pool_size = 3 server_lifetime = 60000 diff --git a/src/auth_passthrough.rs b/src/auth_passthrough.rs index 159847ed..f58f3e8b 100644 --- a/src/auth_passthrough.rs +++ b/src/auth_passthrough.rs @@ -71,6 +71,9 @@ impl AuthPassthrough { pub async fn fetch_hash(&self, address: &crate::config::Address) -> Result { let auth_user = crate::config::User { username: self.user.clone(), + auth_type: "".to_string(), + auth_ldapsuffix: None, + auth_ldapurl: None, password: Some(self.password.clone()), server_username: None, server_password: None, diff --git a/src/client.rs b/src/client.rs index 23392b73..8e5c707a 100644 --- a/src/client.rs +++ b/src/client.rs @@ -2,6 +2,7 @@ use crate::errors::{ClientIdentifier, Error}; use crate::pool::BanReason; /// Handle clients by pretending to be a PostgreSQL server. use bytes::{Buf, BufMut, BytesMut}; +use ldap3::{LdapConnAsync, LdapConnSettings}; use log::{debug, error, info, trace, warn}; use once_cell::sync::Lazy; use std::collections::{HashMap, VecDeque}; @@ -413,6 +414,40 @@ pub async fn startup_tls( } } +// Pass in username and password to authenticate against LDAP +async fn authenticate_ldap( + username: &str, + password: &str, + ldapurl: &str, + ldapsuffix: &str, +) -> bool { + // Connection to the LDAP Server + let ldap_conn_settings = LdapConnSettings::new(); + let (conn, mut ldap) = LdapConnAsync::with_settings(ldap_conn_settings, ldapurl) + .await + .unwrap(); + ldap3::drive!(conn); + + // Takes the username provided and converts it into an email for validation + // This is required because LDAP uses either the Distinguished name or Email in order to bind. Username alone will not work :/ + let email = format!("{}{}", username, ldapsuffix); + + // Attempts a simple bind using the passed in values of username and Password + println!("{:?}", password); + let result = ldap + .simple_bind(email.as_str(), &password) + .await + .unwrap() + .success(); + ldap.unbind().await.unwrap(); + + // If the authentication is successful return true, else return false. + match result { + Ok(_) => true, + Err(_) => false, + } +} + impl Client where S: tokio::io::AsyncRead + std::marker::Unpin, @@ -463,8 +498,8 @@ where .count() == 1; - // Kick any client that's not admin while we're in admin-only mode. if !admin && admin_only { + // Kick any client that's not admin while we're in admin-only mode. debug!( "Rejecting non-admin connection to {} when in admin only mode", pool_name @@ -481,72 +516,131 @@ where let process_id: i32 = rand::random(); let secret_key: i32 = rand::random(); - // Perform MD5 authentication. - // TODO: Add SASL support. - let salt = md5_challenge(&mut write).await?; + let mut prepared_statements_enabled = false; - let code = match read.read_u8().await { - Ok(p) => p, - Err(_) => { - return Err(Error::ClientSocketError( - "password code".into(), - client_identifier, - )) - } - }; + // Authenticate admin user. + let (transaction_mode, mut server_parameters) = if admin { + let config = get_config(); + // TODO: Add SASL support. + // Perform MD5 authentication. + if let "md5" = config.general.admin_auth_type.as_str() { + let salt = md5_challenge(&mut write).await?; + + let code = match read.read_u8().await { + Ok(p) => p, + Err(_) => { + return Err(Error::ClientSocketError( + "password code".into(), + client_identifier, + )) + } + }; - // PasswordMessage - if code as char != 'p' { - return Err(Error::ProtocolSyncError(format!( - "Expected p, got {}", - code as char - ))); - } + // PasswordMessage + if code as char != 'p' { + return Err(Error::ProtocolSyncError(format!( + "Expected p, got {}", + code as char + ))); + } - let len = match read.read_i32().await { - Ok(len) => len, - Err(_) => { - return Err(Error::ClientSocketError( - "password message length".into(), - client_identifier, - )) - } - }; + let len = match read.read_i32().await { + Ok(len) => len, + Err(_) => { + return Err(Error::ClientSocketError( + "password message length".into(), + client_identifier, + )) + } + }; - let mut password_response = vec![0u8; (len - 4) as usize]; + let mut password_response = vec![0u8; (len - 4) as usize]; - match read.read_exact(&mut password_response).await { - Ok(_) => (), - Err(_) => { - return Err(Error::ClientSocketError( - "password message".into(), - client_identifier, - )) - } - }; + match read.read_exact(&mut password_response).await { + Ok(_) => (), + Err(_) => { + return Err(Error::ClientSocketError( + "password message".into(), + client_identifier, + )) + } + }; - let mut prepared_statements_enabled = false; + // Compare server and client hashes. + let password_hash = md5_hash_password( + &config.general.admin_username, + &config.general.admin_password, + &salt, + ); - // Authenticate admin user. - let (transaction_mode, mut server_parameters) = if admin { - let config = get_config(); + if password_hash != password_response { + let error = + Error::ClientGeneralError("Invalid password".into(), client_identifier); - // Compare server and client hashes. - let password_hash = md5_hash_password( - &config.general.admin_username, - &config.general.admin_password, - &salt, - ); + warn!("{}", error); + wrong_password(&mut write, username).await?; - if password_hash != password_response { - let error = Error::ClientGeneralError("Invalid password".into(), client_identifier); + return Err(error); + } + } else if let "ldap" = config.general.admin_auth_type.as_str() { + clear_text_challenge(&mut write).await?; + let code = match read.read_u8().await { + Ok(p) => p, + Err(_) => { + return Err(Error::ClientSocketError( + "password code".into(), + client_identifier, + )) + } + }; - warn!("{}", error); - wrong_password(&mut write, username).await?; + // PasswordMessage + if code as char != 'p' { + return Err(Error::ProtocolSyncError(format!( + "Expected p, got {}", + code as char + ))); + } - return Err(error); - } + let len = match read.read_i32().await { + Ok(len) => len, + Err(_) => { + return Err(Error::ClientSocketError( + "password message length".into(), + client_identifier, + )) + } + }; + + let mut password_response = vec![0u8; (len - 4) as usize]; + + match read.read_exact(&mut password_response).await { + Ok(_) => (), + Err(_) => { + return Err(Error::ClientSocketError( + "password message".into(), + client_identifier, + )) + } + }; + let str_password = String::from_utf8(password_response).unwrap(); + let str_password = str_password.trim_matches(char::from(0)); + let unsuccessful_auth = !authenticate_ldap( + &config.general.admin_username, + &str_password, + &config.general.admin_auth_ldapurl.unwrap(), + &config.general.admin_auth_ldapsuffix.unwrap(), + ) + .await; + if unsuccessful_auth { + wrong_password(&mut write, username).await?; + return Err(Error::ClientGeneralError( + "Invalid password".into(), + client_identifier, + )); + } + } (false, generate_server_parameters_for_admin()) } // Authenticate normal user. @@ -573,92 +667,195 @@ where // Obtain the hash to compare, we give preference to that written in cleartext in config // if there is nothing set in cleartext and auth passthrough (auth_query) is configured, we use the hash obtained // when the pool was created. If there is no hash there, we try to fetch it one more time. - let password_hash = if let Some(password) = &pool.settings.user.password { - Some(md5_hash_password(username, password, &salt)) - } else { - if !get_config().is_auth_query_configured() { - wrong_password(&mut write, username).await?; - return Err(Error::ClientAuthImpossible(username.into())); + if let "md5" = pool.settings.user.auth_type.as_str() { + // Perform MD5 authentication. + // TODO: Add SASL support. + let salt = md5_challenge(&mut write).await?; + + let code = match read.read_u8().await { + Ok(p) => p, + Err(_) => { + return Err(Error::ClientSocketError( + "password code".into(), + client_identifier, + )) + } + }; + + // PasswordMessage + if code as char != 'p' { + return Err(Error::ProtocolSyncError(format!( + "Expected p, got {}", + code as char + ))); } - let mut hash = (*pool.auth_hash.read()).clone(); + let len = match read.read_i32().await { + Ok(len) => len, + Err(_) => { + return Err(Error::ClientSocketError( + "password message length".into(), + client_identifier, + )) + } + }; - if hash.is_none() { - warn!( - "Query auth configured \ - but no hash password found \ - for pool {}. Will try to refetch it.", - pool_name - ); + let mut password_response = vec![0u8; (len - 4) as usize]; - match refetch_auth_hash(&pool).await { - Ok(fetched_hash) => { - warn!("Password for {}, obtained. Updating.", client_identifier); + match read.read_exact(&mut password_response).await { + Ok(_) => (), + Err(_) => { + return Err(Error::ClientSocketError( + "password message".into(), + client_identifier, + )) + } + }; - { - let mut pool_auth_hash = pool.auth_hash.write(); - *pool_auth_hash = Some(fetched_hash.clone()); + let password_hash = if let Some(password) = &pool.settings.user.password { + Some(md5_hash_password(username, password, &salt)) + } else { + if !get_config().is_auth_query_configured() { + wrong_password(&mut write, username).await?; + return Err(Error::ClientAuthImpossible(username.into())); + } + + let mut hash = (*pool.auth_hash.read()).clone(); + + if hash.is_none() { + warn!( + "Query auth configured \ + but no hash password found \ + for pool {}. Will try to refetch it.", + pool_name + ); + + match refetch_auth_hash(&pool).await { + Ok(fetched_hash) => { + warn!("Password for {}, obtained. Updating.", client_identifier); + + { + let mut pool_auth_hash = pool.auth_hash.write(); + *pool_auth_hash = Some(fetched_hash.clone()); + } + + hash = Some(fetched_hash); } - hash = Some(fetched_hash); + Err(err) => { + wrong_password(&mut write, username).await?; + + return Err(Error::ClientAuthPassthroughError( + err.to_string(), + client_identifier, + )); + } } + }; + + Some(md5_hash_second_pass(&hash.unwrap(), &salt)) + }; + + // Once we have the resulting hash, we compare with what the client gave us. + // If they do not match and auth query is set up, we try to refetch the hash one more time + // to see if the password has changed since the pool was created. + // + // @TODO: we could end up fetching again the same password twice (see above). + if password_hash.unwrap() != password_response { + warn!( + "Invalid password {}, will try to refetch it.", + client_identifier + ); + let fetched_hash = match refetch_auth_hash(&pool).await { + Ok(fetched_hash) => fetched_hash, Err(err) => { wrong_password(&mut write, username).await?; - return Err(Error::ClientAuthPassthroughError( - err.to_string(), - client_identifier, - )); + return Err(err); } - } - }; + }; - Some(md5_hash_second_pass(&hash.unwrap(), &salt)) - }; + let new_password_hash = md5_hash_second_pass(&fetched_hash, &salt); - // Once we have the resulting hash, we compare with what the client gave us. - // If they do not match and auth query is set up, we try to refetch the hash one more time - // to see if the password has changed since the pool was created. - // - // @TODO: we could end up fetching again the same password twice (see above). - if password_hash.unwrap() != password_response { - warn!( - "Invalid password {}, will try to refetch it.", - client_identifier - ); + // Ok password changed in server an auth is possible. + if new_password_hash == password_response { + warn!( + "Password for {}, changed in server. Updating.", + client_identifier + ); - let fetched_hash = match refetch_auth_hash(&pool).await { - Ok(fetched_hash) => fetched_hash, - Err(err) => { + { + let mut pool_auth_hash = pool.auth_hash.write(); + *pool_auth_hash = Some(fetched_hash); + } + } else { wrong_password(&mut write, username).await?; - - return Err(err); + return Err(Error::ClientGeneralError( + "Invalid password".into(), + client_identifier, + )); + } + } + } else if let "ldap" = pool.settings.user.auth_type.as_str() { + clear_text_challenge(&mut write).await?; + let code = match read.read_u8().await { + Ok(p) => p, + Err(_) => { + return Err(Error::ClientSocketError( + "password code".into(), + client_identifier, + )) } }; - let new_password_hash = md5_hash_second_pass(&fetched_hash, &salt); + // PasswordMessage + if code as char != 'p' { + return Err(Error::ProtocolSyncError(format!( + "Expected p, got {}", + code as char + ))); + } - // Ok password changed in server an auth is possible. - if new_password_hash == password_response { - warn!( - "Password for {}, changed in server. Updating.", - client_identifier - ); + let len = match read.read_i32().await { + Ok(len) => len, + Err(_) => { + return Err(Error::ClientSocketError( + "password message length".into(), + client_identifier, + )) + } + }; - { - let mut pool_auth_hash = pool.auth_hash.write(); - *pool_auth_hash = Some(fetched_hash); + let mut password_response = vec![0u8; (len - 4) as usize]; + + match read.read_exact(&mut password_response).await { + Ok(_) => (), + Err(_) => { + return Err(Error::ClientSocketError( + "password message".into(), + client_identifier, + )) } - } else { + }; + let str_password = String::from_utf8(password_response).unwrap(); + let str_password = str_password.trim_matches(char::from(0)); + let unsuccessful_auth = !authenticate_ldap( + &pool.settings.user.username.as_str(), + &str_password, + &pool.settings.user.auth_ldapurl.clone().unwrap(), + &pool.settings.user.auth_ldapsuffix.clone().unwrap(), + ) + .await; + if unsuccessful_auth { wrong_password(&mut write, username).await?; + return Err(Error::ClientGeneralError( "Invalid password".into(), client_identifier, )); } } - let transaction_mode = pool.settings.pool_mode == PoolMode::Transaction; prepared_statements_enabled = transaction_mode && pool.prepared_statement_cache.is_some(); diff --git a/src/config.rs b/src/config.rs index ef7952f2..2bc8cd8a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -208,6 +208,9 @@ impl Address { pub struct User { pub username: String, pub password: Option, + pub auth_type: String, + pub auth_ldapsuffix: Option, + pub auth_ldapurl: Option, pub server_username: Option, pub server_password: Option, pub pool_size: u32, @@ -225,6 +228,9 @@ impl Default for User { User { username: String::from("postgres"), password: None, + auth_type: "md5".to_string(), + auth_ldapsuffix: None, + auth_ldapurl: None, server_username: None, server_password: None, pool_size: 15, @@ -333,6 +339,9 @@ pub struct General { pub admin_username: String, pub admin_password: String, + pub admin_auth_type: String, + pub admin_auth_ldapurl: Option, + pub admin_auth_ldapsuffix: Option, #[serde(default = "General::default_validate_config")] pub validate_config: bool, @@ -456,6 +465,9 @@ impl Default for General { verify_server_certificate: false, admin_username: String::from("admin"), admin_password: String::from("admin"), + admin_auth_type: String::from("md5"), + admin_auth_ldapurl: None, + admin_auth_ldapsuffix: None, validate_config: true, auth_query: None, auth_query_user: None, diff --git a/src/messages.rs b/src/messages.rs index 4390d9f9..38a678df 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -64,6 +64,20 @@ where write_all(stream, auth_ok).await } +/// Tell the client to use clearr text auth +pub async fn clear_text_challenge(stream: &mut S) -> Result<(), Error> +where + S: tokio::io::AsyncWrite + std::marker::Unpin, +{ + let mut auth_clear_text = BytesMut::with_capacity(9); + + auth_clear_text.put_u8(b'R'); + auth_clear_text.put_i32(8); + auth_clear_text.put_i32(3); + + write_all(stream, auth_clear_text).await +} + /// Generate md5 password challenge. pub async fn md5_challenge(stream: &mut S) -> Result<[u8; 4], Error> where