From a77a7aa2a4e6a633856cbada9d1cfe18d72c262b Mon Sep 17 00:00:00 2001 From: Firstyear Date: Thu, 15 Jun 2023 13:24:53 +1000 Subject: [PATCH] 20230614 unix account security - move account name deny to unixd (#1733) --- Cargo.lock | 390 +++++++++++++++------ Cargo.toml | 1 + book/src/integrations/pam_and_nsswitch.md | 7 + examples/unixd | 2 + server/lib/src/value.rs | 13 +- unix_integration/Cargo.toml | 5 +- unix_integration/src/cache.rs | 103 ++++-- unix_integration/src/daemon.rs | 80 ++++- unix_integration/src/lib.rs | 2 + unix_integration/src/ssh_authorizedkeys.rs | 2 +- unix_integration/src/unix_config.rs | 12 +- unix_integration/src/unix_passwd.rs | 110 ++++++ unix_integration/tests/cache_layer_test.rs | 233 +++++++++--- 13 files changed, 775 insertions(+), 185 deletions(-) create mode 100644 unix_integration/src/unix_passwd.rs diff --git a/Cargo.lock b/Cargo.lock index 483243011..8e830954e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,7 +68,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.10", "once_cell", "version_check", ] @@ -86,13 +86,19 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4f263788a35611fba42eb41ff811c5d0360c58b97402570312a350736e2542e" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -151,7 +157,7 @@ dependencies = [ "num-traits", "rusticata-macros", "thiserror", - "time 0.3.21", + "time 0.3.22", ] [[package]] @@ -566,9 +572,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6776fc96284a0bb647b615056fc496d1fe1644a7ab01829818a6d91cae888b84" +checksum = "6dbe3c979c178231552ecba20214a8272df4e09f232a87aef4320cf06539aded" [[package]] name = "blake3" @@ -936,7 +942,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ "percent-encoding", - "time 0.3.21", + "time 0.3.22", "version_check", ] @@ -952,7 +958,7 @@ dependencies = [ "publicsuffix", "serde", "serde_json", - "time 0.3.21", + "time 0.3.22", "url", ] @@ -1080,14 +1086,14 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.14" +version = "0.9.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" dependencies = [ "autocfg", "cfg-if 1.0.0", "crossbeam-utils", - "memoffset 0.8.0", + "memoffset 0.9.0", "scopeguard", ] @@ -1103,9 +1109,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if 1.0.0", ] @@ -1161,16 +1167,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "ctor" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" -dependencies = [ - "quote", - "syn 1.0.109", -] - [[package]] name = "ctr" version = "0.6.0" @@ -1504,11 +1500,20 @@ checksum = "3364d69f691f3903b1a71605fa04f40a7c2d259f0f0512347e36d19a63debf1f" dependencies = [ "base64 0.21.2", "byteorder", - "getrandom 0.2.9", + "getrandom 0.2.10", "openssl", "zeroize", ] +[[package]] +name = "file-id" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13be71e6ca82e91bc0cb862bebaac0b2d1924a5a1d970c822b2f98b63fda8c3" +dependencies = [ + "winapi-util", +] + [[package]] name = "filetime" version = "0.2.21" @@ -1571,6 +1576,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "futures" version = "0.3.28" @@ -1718,9 +1732,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -1973,12 +1987,22 @@ dependencies = [ ] [[package]] -name = "hashlink" -version = "0.8.2" +name = "hashbrown" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0761a1b9491c4f2e3d66aa0f62d0fba0af9a0e2852e4d48ea506632a4b56e6aa" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" dependencies = [ - "hashbrown 0.13.2", + "ahash 0.8.3", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312f66718a2d7789ffef4f4b7b213138ed9f1eb3aa1d0d82fc99f88fb3ffd26f" +dependencies = [ + "hashbrown 0.14.0", ] [[package]] @@ -2154,9 +2178,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.56" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -2262,6 +2286,26 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac" +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "instant" version = "0.1.12" @@ -2314,9 +2358,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] @@ -2377,7 +2421,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "time 0.3.21", + "time 0.3.22", "tokio", "toml", "tracing", @@ -2421,7 +2465,7 @@ dependencies = [ "scim_proto", "serde", "serde_json", - "time 0.3.21", + "time 0.3.22", "tracing", "url", "urlencoding", @@ -2447,7 +2491,7 @@ dependencies = [ "serde", "serde_json", "shellexpand", - "time 0.3.21", + "time 0.3.22", "tokio", "tracing", "tracing-subscriber", @@ -2464,6 +2508,7 @@ dependencies = [ "bytes", "clap", "clap_complete", + "csv", "futures", "kanidm_client", "kanidm_lib_crypto", @@ -2473,6 +2518,7 @@ dependencies = [ "libc", "libsqlite3-sys", "lru 0.8.1", + "notify-debouncer-full", "profiles", "r2d2", "r2d2_sqlite", @@ -2515,7 +2561,7 @@ dependencies = [ "tide", "tide-compress", "tide-openssl", - "time 0.3.21", + "time 0.3.22", "tokio", "tokio-openssl", "tokio-util", @@ -2567,7 +2613,7 @@ dependencies = [ "smolset", "sshkeys", "tide", - "time 0.3.21", + "time 0.3.22", "tokio", "tokio-util", "toml", @@ -2609,7 +2655,7 @@ dependencies = [ "serde_json", "sketching", "testkit-macros", - "time 0.3.21", + "time 0.3.22", "tokio", "tracing", "url", @@ -2629,7 +2675,7 @@ dependencies = [ "serde", "serde-wasm-bindgen", "serde_json", - "time 0.3.21", + "time 0.3.22", "url", "uuid", "wasm-bindgen", @@ -2640,6 +2686,26 @@ dependencies = [ "yew-router", ] +[[package]] +name = "kqueue" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8fc60ba15bf51257aa9807a48a61013db043fcf3a78cb0d916e8e396dcad98" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "kv-log-macro" version = "1.0.7" @@ -2672,9 +2738,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "lber" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5d85f5e00e12cb50c70c3b1c1f0daff6546eb4c608b44d0a990e38a539e0446" +checksum = "2df7f9fd9f64cf8f59e1a4a0753fe7d575a5b38d3d7ac5758dcee9357d83ef0a" dependencies = [ "bytes", "nom", @@ -2822,9 +2888,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", @@ -2832,11 +2898,10 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" dependencies = [ - "cfg-if 1.0.0", "serde", "value-bag", ] @@ -2900,9 +2965,9 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg", ] @@ -2930,14 +2995,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -2977,6 +3042,37 @@ dependencies = [ "serde", ] +[[package]] +name = "notify" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d9ba6c734de18ca27c8cef5cd7058aa4ac9f63596131e4c7e41e579319032a2" +dependencies = [ + "bitflags 1.3.2", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "mio", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "notify-debouncer-full" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4812c1eb49be776fb8df4961623bdc01ec9dfdc1abe8211ceb09150a2e64219" +dependencies = [ + "crossbeam-channel", + "file-id", + "notify", + "parking_lot", + "walkdir", +] + [[package]] name = "nss_kanidm" version = "1.1.0-beta.13-dev" @@ -3092,13 +3188,13 @@ dependencies = [ [[package]] name = "oauth2" -version = "4.4.0" +version = "4.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50df55a3cc0374df91ef8da8741542d9e0b9e6581481ed1cffe84f64d2f5fc3d" +checksum = "09a6e2a2b13a56ebeabba9142f911745be6456163fd6c3d361274ebcd891a80c" dependencies = [ "base64 0.13.1", "chrono", - "getrandom 0.2.9", + "getrandom 0.2.10", "http", "rand 0.8.5", "serde", @@ -3120,9 +3216,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "oorandom" @@ -3216,9 +3312,9 @@ dependencies = [ [[package]] name = "os_str_bytes" -version = "6.5.0" +version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" +checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" [[package]] name = "overload" @@ -3253,15 +3349,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.2.16", + "redox_syscall 0.3.5", "smallvec", - "windows-sys 0.45.0", + "windows-targets 0.48.0", ] [[package]] @@ -3660,7 +3756,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.10", ] [[package]] @@ -3718,7 +3814,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.10", "redox_syscall 0.2.16", "thiserror", ] @@ -3891,9 +3987,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.19" +version = "0.37.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" dependencies = [ "bitflags 1.3.2", "errno", @@ -3952,7 +4048,7 @@ dependencies = [ "peg", "serde", "serde_json", - "time 0.3.21", + "time 0.3.22", "tracing", "tracing-subscriber", "url", @@ -4000,7 +4096,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b5ff8f655381fc80f470384da0a461b19b7bf18d685639ccf6df0abd3d2363d" dependencies = [ - "bitflags 2.3.1", + "bitflags 2.3.2", "libc", "once_cell", "reference-counted-singleton", @@ -4010,9 +4106,9 @@ dependencies = [ [[package]] name = "selinux-sys" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f37ccfd0557caec11f117e6d6103aaa217d1aad80cfb6ee3d0e1af5b568a1d9" +checksum = "f2b8dbf5dd0b21d466538786194081b2c4d61878e1427f7e52f99d5845432483" dependencies = [ "bindgen", "cc", @@ -4412,11 +4508,70 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "sval" -version = "1.0.0-alpha.5" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45f6ee7c7b87caf59549e9fe45d6a69c75c8019e79e212a835c5da0e92f0ba08" +checksum = "e2faba619276044eec7cd160d87b15d9191fb9b9f7198440343d2144f760cf08" + +[[package]] +name = "sval_buffer" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a353d3cca10721384077c9643c3fafdd6ed2600e57933b8e45c0b580d97b25af" +dependencies = [ + "sval", + "sval_ref", +] + +[[package]] +name = "sval_dynamic" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee5fc7349e9f6cb2ab950046818f66ad3f2d7209ccc5dced93da19292a30273a" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_fmt" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "098fb51d5d6007bd2c3f0a23b79aa953d7c46bf943086ce51424c3187c40f9b1" +dependencies = [ + "itoa", + "ryu", + "sval", +] + +[[package]] +name = "sval_json" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f01126a2783d767496f18f13af26ab2587881f6343368bb26dc62956a723d1c7" +dependencies = [ + "itoa", + "ryu", + "sval", +] + +[[package]] +name = "sval_ref" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5854d9eaa7bd31840a850322591c59c5b547eb29c9a6ecee1989d6ef963312ce" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_serde" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cdd25fc04c5e882787d62112591aa93efb5bdc2000b43164d29f08582bb85f7" dependencies = [ "serde", + "sval", + "sval_buffer", + "sval_fmt", ] [[package]] @@ -4455,15 +4610,16 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.5.0" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" dependencies = [ + "autocfg", "cfg-if 1.0.0", "fastrand", "redox_syscall 0.3.5", "rustix", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -4620,9 +4776,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.21" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" +checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" dependencies = [ "itoa", "libc", @@ -4972,11 +5128,11 @@ dependencies = [ [[package]] name = "uuid" -version = "1.3.3" +version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" +checksum = "0fa2982af2eec27de306107c027578ff7f423d65f7250e40ce0fea8f45248b81" dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.10", "serde", ] @@ -4988,16 +5144,38 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "value-bag" -version = "1.0.0-alpha.9" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" +checksum = "a4d330786735ea358f3bc09eea4caa098569c1c93f342d9aca0514915022fe7e" +dependencies = [ + "value-bag-serde1", + "value-bag-sval2", +] + +[[package]] +name = "value-bag-serde1" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4735c95b4cca1447b448e2e2e87e98d7e7498f4da27e355cf7af02204521001d" dependencies = [ - "ctor", "erased-serde", "serde", "serde_fmt", +] + +[[package]] +name = "value-bag-sval2" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859cb4f0ce7da6a118b559ba74b0e63bf569bea867c20ba457a6b1c886a04e97" +dependencies = [ "sval", - "version_check", + "sval_buffer", + "sval_dynamic", + "sval_fmt", + "sval_json", + "sval_ref", + "sval_serde", ] [[package]] @@ -5058,9 +5236,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if 1.0.0", "serde", @@ -5070,9 +5248,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", @@ -5085,9 +5263,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.36" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -5097,9 +5275,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5107,9 +5285,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", @@ -5120,15 +5298,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "wasm-bindgen-test" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e636f3a428ff62b3742ebc3c70e254dfe12b8c2b469d688ea59cdd4abcf502" +checksum = "6e6e302a7ea94f83a6d09e78e7dc7d9ca7b186bc2829c24a22d0753efd680671" dependencies = [ "console_error_panic_hook", "js-sys", @@ -5140,9 +5318,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f18c1fad2f7c4958e7bcce014fa212f59a65d5e3721d0f77e6c0b27ede936ba3" +checksum = "ecb993dd8c836930ed130e020e77d9b2e65dd0fbab1b67c790b0f5d80b11a575" dependencies = [ "proc-macro2", "quote", @@ -5150,9 +5328,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", @@ -5531,7 +5709,7 @@ dependencies = [ "oid-registry", "rusticata-macros", "thiserror", - "time 0.3.21", + "time 0.3.22", ] [[package]] @@ -5636,5 +5814,5 @@ dependencies = [ "lazy_static", "quick-error", "regex", - "time 0.3.21", + "time 0.3.22", ] diff --git a/Cargo.toml b/Cargo.toml index fa9d12e1c..8905be110 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,6 +99,7 @@ libnss = "^0.4.0" libsqlite3-sys = "^0.25.0" lru = "^0.8.0" mathru = "^0.13.0" +notify-debouncer-full = { version = "0.1" } num_enum = "^0.5.11" oauth2_ext = { version = "^4.1.0", package = "oauth2", default-features = false } openssl-sys = "^0.9" diff --git a/book/src/integrations/pam_and_nsswitch.md b/book/src/integrations/pam_and_nsswitch.md index 4aee89760..e1fb60ee8 100644 --- a/book/src/integrations/pam_and_nsswitch.md +++ b/book/src/integrations/pam_and_nsswitch.md @@ -56,6 +56,7 @@ use_etc_skel = false uid_attr_map = "spn" gid_attr_map = "spn" selinux = true +allow_local_account_override = ["account_name"] ``` `pam_allowed_login_groups` defines a set of POSIX groups where membership of any of these groups @@ -96,6 +97,12 @@ setting as no bearing on systems without SELinux, as these features will automat if SELinux is not detected when the daemon starts. Note that `kanidm_unixd_tasks` must also be built with the SELinux feature flag for this functionality. Defaults to true. +`allow_local_account_override` allows kanidm to "override" the content of a user or group that is +defined locally. By default kanidm will detect when a user/group conflict with their entries from +`/etc/passwd` or `/etc/group` and will ignore the kanidm entry. However if you want kanidm to +override users or groups from the local system, you must list them in this field. Note that this can +have many unexpected consequences, so it is not recommended to enable this. + You can then check the communication status of the daemon: ```bash diff --git a/examples/unixd b/examples/unixd index 31cd66e2d..c80b82a11 100644 --- a/examples/unixd +++ b/examples/unixd @@ -8,3 +8,5 @@ # use_etc_skel = false # uid_attr_map = "spn" # gid_attr_map = "spn" +# allow_local_account_override = ["admin"] + diff --git a/server/lib/src/value.rs b/server/lib/src/value.rs index 355e0e4ba..f4bdfb74a 100644 --- a/server/lib/src/value.rs +++ b/server/lib/src/value.rs @@ -42,17 +42,10 @@ lazy_static! { }; pub static ref DISALLOWED_NAMES: HashSet<&'static str> = { - let mut m = HashSet::with_capacity(16); + // Most of these were removed in favour of the unixd daemon filtering out + // local users instead. + let mut m = HashSet::with_capacity(2); m.insert("root"); - m.insert("nobody"); - m.insert("nogroup"); - m.insert("wheel"); - m.insert("sshd"); - m.insert("shadow"); - m.insert("systemd"); - m.insert("mail"); - m.insert("man"); - m.insert("administrator"); m.insert("dn=token"); m }; diff --git a/unix_integration/Cargo.toml b/unix_integration/Cargo.toml index 3a5e7e283..17b6fe9d8 100644 --- a/unix_integration/Cargo.toml +++ b/unix_integration/Cargo.toml @@ -43,6 +43,7 @@ path = "src/lib.rs" [dependencies] bytes.workspace = true clap = { workspace = true, features = ["derive", "env"] } +csv.workspace = true futures.workspace = true libc.workspace = true libsqlite3-sys.workspace = true @@ -51,7 +52,7 @@ kanidm_client.workspace = true kanidm_proto.workspace = true kanidm_lib_crypto.workspace = true kanidm_lib_file_permissions.workspace = true - +notify-debouncer-full.workspace = true r2d2.workspace = true r2d2_sqlite.workspace = true rpassword.workspace = true @@ -62,7 +63,7 @@ serde_json.workspace = true sketching.workspace = true toml.workspace = true -tokio = { workspace = true, features = ["rt", "macros", "sync", "time", "net", "io-util"] } +tokio = { workspace = true, features = ["rt", "fs", "macros", "sync", "time", "net", "io-util"] } tokio-util = { workspace = true, features = ["codec"] } tracing.workspace = true reqwest = { workspace = true, default-features = false } diff --git a/unix_integration/src/cache.rs b/unix_integration/src/cache.rs index d2d442ab8..e51868bf6 100644 --- a/unix_integration/src/cache.rs +++ b/unix_integration/src/cache.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeSet; +use std::collections::{BTreeSet, HashSet}; use std::num::NonZeroUsize; use std::ops::{Add, Sub}; use std::path::Path; @@ -15,6 +15,8 @@ use crate::db::Db; use crate::unix_config::{HomeAttr, UidAttr}; use crate::unix_proto::{HomeDirectoryInfo, NssGroup, NssUser}; +// use crate::unix_passwd::{EtcUser, EtcGroup}; + const NXCACHE_SIZE: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(2048) }; #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -43,6 +45,8 @@ pub struct CacheLayer { home_alias: Option, uid_attr_map: UidAttr, gid_attr_map: UidAttr, + allow_id_overrides: HashSet, + nxset: Mutex>, nxcache: Mutex>, } @@ -72,6 +76,7 @@ impl CacheLayer { home_alias: Option, uid_attr_map: UidAttr, gid_attr_map: UidAttr, + allow_id_overrides: Vec, ) -> Result { let db = Db::new(path)?; @@ -83,7 +88,7 @@ impl CacheLayer { } if pam_allow_groups.is_empty() { - eprintln!("Will not be able to authenticate users, pam_allow_groups config is not configured."); + eprintln!("Will not be able to authorise user logins, pam_allow_groups config is not configured."); } // We assume we are offline at start up, and we mark the next "online check" as @@ -100,6 +105,11 @@ impl CacheLayer { home_alias, uid_attr_map, gid_attr_map, + allow_id_overrides: allow_id_overrides + .into_iter() + .map(|name| Id::Name(name)) + .collect(), + nxset: Mutex::new(HashSet::new()), nxcache: Mutex::new(LruCache::new(NXCACHE_SIZE)), }) } @@ -154,9 +164,31 @@ impl CacheLayer { nxcache_txn.put(id.clone(), ex_time); } - pub async fn check_nxcache(&self, id: &Id) -> bool { - let nxcache_txn = self.nxcache.lock().await; - nxcache_txn.contains(id) + pub async fn check_nxcache(&self, id: &Id) -> Option { + let mut nxcache_txn = self.nxcache.lock().await; + nxcache_txn.get(id).copied() + } + + pub async fn reload_nxset(&self, iter: impl Iterator) { + let mut nxset_txn = self.nxset.lock().await; + nxset_txn.clear(); + for (name, gid) in iter { + let name = Id::Name(name); + let gid = Id::Gid(gid); + + // Skip anything that the admin opted in to + if !(self.allow_id_overrides.contains(&gid) || self.allow_id_overrides.contains(&name)) + { + debug!("Adding {:?}:{:?} to resolver exclusion set", name, gid); + nxset_txn.insert(name); + nxset_txn.insert(gid); + } + } + } + + pub async fn check_nxset(&self, name: &str, idnumber: u32) -> bool { + let nxset_txn = self.nxset.lock().await; + nxset_txn.contains(&Id::Gid(idnumber)) || nxset_txn.contains(&Id::Name(name.to_string())) } async fn get_cached_usertoken( @@ -187,11 +219,10 @@ impl CacheLayer { } None => { // it wasn't in the DB - lets see if it's in the nxcache. - let mut nxcache_txn = self.nxcache.lock().await; - match nxcache_txn.get(account_id) { + match self.check_nxcache(account_id).await { Some(ex_time) => { let now = SystemTime::now(); - if &now >= ex_time { + if now >= ex_time { // It's in the LRU, but we are past the expiry so // lets attempt a refresh. Ok((true, None)) @@ -239,11 +270,10 @@ impl CacheLayer { } None => { // it wasn't in the DB - lets see if it's in the nxcache. - let mut nxcache_txn = self.nxcache.lock().await; - match nxcache_txn.get(grp_id) { + match self.check_nxcache(grp_id).await { Some(ex_time) => { let now = SystemTime::now(); - if &now >= ex_time { + if now >= ex_time { // It's in the LRU, but we are past the expiry so // lets attempt a refresh. Ok((true, None)) @@ -271,7 +301,7 @@ impl CacheLayer { .map_err(|e| { error!("time conversion error - ex_time less than epoch? {:?}", e); })?; - // WIP #392 + // Check if requested `shell` exists on the system, else use `default_shell` let requested_shell_exists: bool = token .shell @@ -295,11 +325,20 @@ impl CacheLayer { token.shell = Some(self.default_shell.clone()) } + // Filter out groups that are in the nxset + { + let nxset_txn = self.nxset.lock().await; + token.groups.retain(|g| { + !(nxset_txn.contains(&Id::Gid(g.gidnumber)) + || nxset_txn.contains(&Id::Name(g.name.clone()))) + }); + } + let dbtxn = self.db.write().await; - // We need to add the groups first token .groups .iter() + // We need to add the groups first .try_for_each(|g| dbtxn.update_group(g, offset.as_secs())) .and_then(|_| // So that when we add the account it can make the relationships. @@ -360,9 +399,15 @@ impl CacheLayer { .await { Ok(mut n_tok) => { - // We have the token! - self.set_cache_usertoken(&mut n_tok).await?; - Ok(Some(n_tok)) + if self.check_nxset(&n_tok.name, n_tok.gidnumber).await { + // Refuse to release the token, it's in the denied set. + self.delete_cache_usertoken(&n_tok.uuid).await?; + Ok(None) + } else { + // We have the token! + self.set_cache_usertoken(&mut n_tok).await?; + Ok(Some(n_tok)) + } } Err(e) => { match e { @@ -440,9 +485,15 @@ impl CacheLayer { .await { Ok(n_tok) => { - // We have the token! - self.set_cache_grouptoken(&n_tok).await?; - Ok(Some(n_tok)) + if self.check_nxset(&n_tok.name, n_tok.gidnumber).await { + // Refuse to release the token, it's in the denied set. + self.delete_cache_grouptoken(&n_tok.uuid).await?; + Ok(None) + } else { + // We have the token! + self.set_cache_grouptoken(&n_tok).await?; + Ok(Some(n_tok)) + } } Err(e) => { match e { @@ -763,10 +814,16 @@ impl CacheLayer { .await { Ok(Some(mut n_tok)) => { - debug!("online password check success."); - self.set_cache_usertoken(&mut n_tok).await?; - self.set_cache_userpassword(&n_tok.uuid, cred).await?; - Ok(Some(true)) + if self.check_nxset(&n_tok.name, n_tok.gidnumber).await { + // Refuse to release the token, it's in the denied set. + self.delete_cache_usertoken(&n_tok.uuid).await?; + Ok(None) + } else { + debug!("online password check success."); + self.set_cache_usertoken(&mut n_tok).await?; + self.set_cache_userpassword(&n_tok.uuid, cred).await?; + Ok(Some(true)) + } } Ok(None) => { error!("incorrect password"); diff --git a/unix_integration/src/daemon.rs b/unix_integration/src/daemon.rs index 0a832bd02..5c93d9b5c 100644 --- a/unix_integration/src/daemon.rs +++ b/unix_integration/src/daemon.rs @@ -28,12 +28,15 @@ use kanidm_proto::constants::DEFAULT_CLIENT_CONFIG_PATH; use kanidm_unix_common::cache::CacheLayer; use kanidm_unix_common::constants::DEFAULT_CONFIG_PATH; use kanidm_unix_common::unix_config::KanidmUnixdConfig; +use kanidm_unix_common::unix_passwd::{parse_etc_group, parse_etc_passwd}; use kanidm_unix_common::unix_proto::{ClientRequest, ClientResponse, TaskRequest, TaskResponse}; use libc::umask; use sketching::tracing_forest::traits::*; use sketching::tracing_forest::util::*; use sketching::tracing_forest::{self}; +use tokio::fs::File; +use tokio::io::AsyncReadExt; // for read_to_end() use tokio::net::{UnixListener, UnixStream}; use tokio::sync::broadcast; use tokio::sync::mpsc::{channel, Receiver, Sender}; @@ -42,6 +45,8 @@ use tokio::time; use tokio_util::codec::{Decoder, Encoder, Framed}; use users::{get_current_gid, get_current_uid, get_effective_gid, get_effective_uid}; +use notify_debouncer_full::{new_debouncer, notify::RecursiveMode, notify::Watcher}; + //=== the codec type AsyncTaskRequest = (TaskRequest, oneshot::Sender<()>); @@ -365,6 +370,29 @@ async fn handle_client( Ok(()) } +async fn process_etc_passwd_group(cachelayer: &CacheLayer) -> Result<(), Box> { + let mut file = File::open("/etc/passwd").await?; + let mut contents = vec![]; + file.read_to_end(&mut contents).await?; + + let users = parse_etc_passwd(contents.as_slice()).map_err(|()| "Invalid passwd content")?; + + let mut file = File::open("/etc/group").await?; + let mut contents = vec![]; + file.read_to_end(&mut contents).await?; + + let groups = parse_etc_group(contents.as_slice()).map_err(|()| "Invalid group content")?; + + let id_iter = users + .iter() + .map(|user| (user.name.clone(), user.uid)) + .chain(groups.iter().map(|group| (group.name.clone(), group.gid))); + + cachelayer.reload_nxset(id_iter).await; + + Ok(()) +} + #[tokio::main(flavor = "current_thread")] async fn main() -> ExitCode { let cuid = get_current_uid(); @@ -544,7 +572,6 @@ async fn main() -> ExitCode { rm_if_exist(cfg.task_sock_path.as_str()); - // Check the db path will be okay. if !cfg.db_path.is_empty() { let db_path = PathBuf::from(cfg.db_path.as_str()); @@ -633,7 +660,6 @@ async fn main() -> ExitCode { } }; - let cl_inner = match CacheLayer::new( cfg.db_path.as_str(), // The sqlite db path cfg.cache_timeout, @@ -645,6 +671,7 @@ async fn main() -> ExitCode { cfg.home_alias, cfg.uid_attr_map, cfg.gid_attr_map, + cfg.allow_local_account_override.clone(), ) .await { @@ -669,6 +696,12 @@ async fn main() -> ExitCode { // Undo umask changes. let _ = unsafe { umask(before) }; + // Pre-process /etc/passwd and /etc/group for nxset + if let Err(_) = process_etc_passwd_group(&cachelayer).await { + error!("Failed to process system id providers"); + return ExitCode::FAILURE + } + // Setup the tasks socket first. let (task_channel_tx, mut task_channel_rx) = channel(16); let task_channel_tx = Arc::new(task_channel_tx); @@ -724,6 +757,48 @@ async fn main() -> ExitCode { // TODO: Setup a task that handles pre-fetching here. + let (inotify_tx, mut inotify_rx) = channel(4); + + let _watcher = + match new_debouncer(Duration::from_secs(2), None, move |_event| { + let _ = inotify_tx.try_send(true); + }) + .and_then(|mut debouncer| { + debouncer.watcher().watch(Path::new("/etc/passwd"), RecursiveMode::NonRecursive) + .map(|()| debouncer) + }) + .and_then(|mut debouncer| debouncer.watcher().watch(Path::new("/etc/group"), RecursiveMode::NonRecursive) + .map(|()| debouncer) + ) + + { + Ok(watcher) => { + watcher + } + Err(e) => { + error!("Failed to setup inotify {:?}", e); + return ExitCode::FAILURE + } + }; + + let mut c_broadcast_rx = broadcast_tx.subscribe(); + + let inotify_cachelayer = cachelayer.clone(); + let task_c = tokio::spawn(async move { + loop { + tokio::select! { + _ = c_broadcast_rx.recv() => { + break; + } + _ = inotify_rx.recv() => { + if let Err(_) = process_etc_passwd_group(&inotify_cachelayer).await { + error!("Failed to process system id providers"); + } + } + } + } + }); + // Set the umask while we open the path for most clients. let before = unsafe { umask(0) }; let listener = match UnixListener::bind(cfg.sock_path.as_str()) { @@ -812,6 +887,7 @@ async fn main() -> ExitCode { let _ = task_a.await; let _ = task_b.await; + let _ = task_c.await; ExitCode::SUCCESS }) diff --git a/unix_integration/src/lib.rs b/unix_integration/src/lib.rs index bbefec968..cc91975a4 100644 --- a/unix_integration/src/lib.rs +++ b/unix_integration/src/lib.rs @@ -32,4 +32,6 @@ pub mod selinux_util; #[cfg(target_family = "unix")] pub mod unix_config; #[cfg(target_family = "unix")] +pub mod unix_passwd; +#[cfg(target_family = "unix")] pub mod unix_proto; diff --git a/unix_integration/src/ssh_authorizedkeys.rs b/unix_integration/src/ssh_authorizedkeys.rs index 4d2e1989c..cb5f01ceb 100644 --- a/unix_integration/src/ssh_authorizedkeys.rs +++ b/unix_integration/src/ssh_authorizedkeys.rs @@ -25,7 +25,7 @@ use kanidm_unix_common::unix_proto::{ClientRequest, ClientResponse}; include!("./opt/ssh_authorizedkeys.rs"); -#[tokio::main] +#[tokio::main(flavor = "current_thread")] async fn main() -> ExitCode { let opt = SshAuthorizedOpt::parse(); if opt.debug { diff --git a/unix_integration/src/unix_config.rs b/unix_integration/src/unix_config.rs index 3fff33366..8eba92bd8 100644 --- a/unix_integration/src/unix_config.rs +++ b/unix_integration/src/unix_config.rs @@ -31,6 +31,8 @@ struct ConfigInt { uid_attr_map: Option, gid_attr_map: Option, selinux: Option, + #[serde(default)] + allow_local_account_override: Vec, } #[derive(Debug, Copy, Clone)] @@ -90,6 +92,7 @@ pub struct KanidmUnixdConfig { pub uid_attr_map: UidAttr, pub gid_attr_map: UidAttr, pub selinux: bool, + pub allow_local_account_override: Vec, } impl Default for KanidmUnixdConfig { @@ -122,7 +125,12 @@ impl Display for KanidmUnixdConfig { writeln!(f, "uid_attr_map: {}", self.uid_attr_map)?; writeln!(f, "gid_attr_map: {}", self.gid_attr_map)?; - writeln!(f, "selinux: {}", self.selinux) + writeln!(f, "selinux: {}", self.selinux)?; + writeln!( + f, + "allow_local_account_override: {:#?}", + self.allow_local_account_override + ) } } @@ -148,6 +156,7 @@ impl KanidmUnixdConfig { uid_attr_map: DEFAULT_UID_ATTR_MAP, gid_attr_map: DEFAULT_GID_ATTR_MAP, selinux: DEFAULT_SELINUX, + allow_local_account_override: Vec::default(), } } @@ -259,6 +268,7 @@ impl KanidmUnixdConfig { true => selinux_util::supported(), _ => false, }, + allow_local_account_override: config.allow_local_account_override, }) } } diff --git a/unix_integration/src/unix_passwd.rs b/unix_integration/src/unix_passwd.rs new file mode 100644 index 000000000..098f938bf --- /dev/null +++ b/unix_integration/src/unix_passwd.rs @@ -0,0 +1,110 @@ +use serde::{ + de::{self, Visitor}, + Deserialize, Deserializer, Serialize, +}; + +use std::fmt; + +#[derive(Serialize, Deserialize, Debug)] +pub struct EtcUser { + pub name: String, + pub password: String, + pub uid: u32, + pub gid: u32, + pub gecos: String, + pub homedir: String, + pub shell: String, +} + +pub fn parse_etc_passwd(bytes: &[u8]) -> Result, ()> { + use csv::ReaderBuilder; + let mut rdr = ReaderBuilder::new() + .has_headers(false) + .delimiter(b':') + .from_reader(bytes); + rdr.deserialize() + .map(|result| result.map_err(|_e| ())) + .collect::, ()>>() +} + +fn members<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + struct InnerCsv; + + impl<'de> Visitor<'de> for InnerCsv { + type Value = Vec; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("string") + } + + fn visit_str(self, value: &str) -> Result, E> + where + E: de::Error, + { + Ok(value.split(',').map(|s| s.to_string()).collect()) + } + } + + deserializer.deserialize_str(InnerCsv) +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct EtcGroup { + pub name: String, + pub password: String, + pub gid: u32, + #[serde(deserialize_with = "members")] + pub members: Vec, +} + +pub fn parse_etc_group(bytes: &[u8]) -> Result, ()> { + use csv::ReaderBuilder; + let mut rdr = ReaderBuilder::new() + .has_headers(false) + .delimiter(b':') + .from_reader(bytes); + rdr.deserialize() + .map(|result| result.map_err(|_e| ())) + .collect::, ()>>() +} + +#[cfg(test)] +mod tests { + use super::*; + + const EXAMPLE_PASSWD: &str = r#"root:x:0:0:root:/root:/bin/bash +systemd-timesync:x:498:498:systemd Time Synchronization:/:/usr/sbin/nologin +messagebus:x:484:484:User for D-Bus:/run/dbus:/usr/sbin/nologin +tftp:x:483:483:TFTP Account:/srv/tftpboot:/usr/sbin/nologin +nobody:x:65534:65534:nobody:/var/lib/nobody:/bin/bash +"#; + + const EXAMPLE_GROUP: &str = r#"root:x:0: +shadow:x:15: +trusted:x:42: +users:x:100: +systemd-journal:x:499: +systemd-timesync:x:498: +kmem:x:497: +lock:x:496: +tty:x:5: +wheel:x:481:admin,testuser +"#; + + #[test] + fn test_parse_passwd() { + for record in parse_etc_passwd(EXAMPLE_PASSWD.as_bytes()).unwrap() { + println!("{:?}", record); + } + } + + #[test] + fn test_parse_group() { + for record in parse_etc_group(EXAMPLE_GROUP.as_bytes()).unwrap() { + println!("{:?}", record); + } + } +} diff --git a/unix_integration/tests/cache_layer_test.rs b/unix_integration/tests/cache_layer_test.rs index cff5e8ba8..236f5edf2 100644 --- a/unix_integration/tests/cache_layer_test.rs +++ b/unix_integration/tests/cache_layer_test.rs @@ -110,6 +110,7 @@ async fn setup_test(fix_fn: Fixture) -> (CacheLayer, KanidmClient) { DEFAULT_HOME_ALIAS, DEFAULT_UID_ATTR_MAP, DEFAULT_GID_ATTR_MAP, + vec!["masked_group".to_string()], ) .await .expect("Failed to build cache layer."); @@ -171,6 +172,13 @@ async fn test_fixture(rsclient: KanidmClient) { .idm_group_unix_extend("allowed_group", Some(20002)) .await .unwrap(); + + // Setup a group that is masked by nxset, but allowed in overrides + rsclient.idm_group_create("masked_group").await.unwrap(); + rsclient + .idm_group_unix_extend("masked_group", Some(20003)) + .await + .unwrap(); } #[tokio::test] @@ -620,70 +628,215 @@ async fn test_cache_nxcache() { assert!(cachelayer.test_connection().await); // Is it in the nxcache? - assert!( - !cachelayer - .check_nxcache(&Id::Name("root".to_string())) - .await - ); - assert!(!cachelayer.check_nxcache(&Id::Gid(0)).await); - assert!( - !cachelayer - .check_nxcache(&Id::Name("root_group".to_string())) - .await - ); - assert!(!cachelayer.check_nxcache(&Id::Gid(1)).await); + assert!(cachelayer + .check_nxcache(&Id::Name("oracle".to_string())) + .await + .is_none()); + assert!(cachelayer.check_nxcache(&Id::Gid(2000)).await.is_none()); + assert!(cachelayer + .check_nxcache(&Id::Name("oracle_group".to_string())) + .await + .is_none()); + assert!(cachelayer.check_nxcache(&Id::Gid(3000)).await.is_none()); // Look for the acc id + nss id let ut = cachelayer - .get_nssaccount_name("root") + .get_nssaccount_name("oracle") .await .expect("Failed to get from cache"); assert!(ut.is_none()); let ut = cachelayer - .get_nssaccount_gid(0) + .get_nssaccount_gid(2000) .await .expect("Failed to get from cache"); assert!(ut.is_none()); let gt = cachelayer - .get_nssgroup_name("root_group") + .get_nssgroup_name("oracle_group") .await .expect("Failed to get from cache"); assert!(gt.is_none()); let gt = cachelayer - .get_nssgroup_gid(1) + .get_nssgroup_gid(3000) .await .expect("Failed to get from cache"); assert!(gt.is_none()); // Should all now be nxed - assert!( - cachelayer - .check_nxcache(&Id::Name("root".to_string())) - .await - ); - assert!(cachelayer.check_nxcache(&Id::Gid(0)).await); - assert!( - cachelayer - .check_nxcache(&Id::Name("root_group".to_string())) - .await - ); - assert!(cachelayer.check_nxcache(&Id::Gid(1)).await); + assert!(cachelayer + .check_nxcache(&Id::Name("oracle".to_string())) + .await + .is_some()); + assert!(cachelayer.check_nxcache(&Id::Gid(2000)).await.is_some()); + assert!(cachelayer + .check_nxcache(&Id::Name("oracle_group".to_string())) + .await + .is_some()); + assert!(cachelayer.check_nxcache(&Id::Gid(3000)).await.is_some()); // invalidate cache assert!(cachelayer.invalidate().await.is_ok()); // Both should NOT be in nxcache now. - assert!( - !cachelayer - .check_nxcache(&Id::Name("root".to_string())) - .await - ); - assert!(!cachelayer.check_nxcache(&Id::Gid(0)).await); - assert!( - !cachelayer - .check_nxcache(&Id::Name("root_group".to_string())) - .await - ); - assert!(!cachelayer.check_nxcache(&Id::Gid(1)).await); + assert!(cachelayer + .check_nxcache(&Id::Name("oracle".to_string())) + .await + .is_none()); + assert!(cachelayer.check_nxcache(&Id::Gid(2000)).await.is_none()); + assert!(cachelayer + .check_nxcache(&Id::Name("oracle_group".to_string())) + .await + .is_none()); + assert!(cachelayer.check_nxcache(&Id::Gid(3000)).await.is_none()); +} + +#[tokio::test] +async fn test_cache_nxset_account() { + let (cachelayer, _adminclient) = setup_test(fixture(test_fixture)).await; + + // Important! This is what sets up that testaccount1 won't be resolved + // because it's in the "local" user set. + cachelayer + .reload_nxset(vec![("testaccount1".to_string(), 20000)].into_iter()) + .await; + + // Force offline. Show we have no account + cachelayer.mark_offline().await; + + let ut = cachelayer + .get_nssaccount_name("testaccount1") + .await + .expect("Failed to get from cache"); + assert!(ut.is_none()); + + // go online + cachelayer.attempt_online().await; + assert!(cachelayer.test_connection().await); + + // get the account + let ut = cachelayer + .get_nssaccount_name("testaccount1") + .await + .expect("Failed to get from cache"); + assert!(ut.is_none()); + + // go offline + cachelayer.mark_offline().await; + + // still not present, was not cached. + let ut = cachelayer + .get_nssaccount_name("testaccount1") + .await + .expect("Failed to get from cache"); + assert!(ut.is_none()); + + // Finally, check it's not in all accounts. + let us = cachelayer + .get_nssaccounts() + .await + .expect("failed to list all accounts"); + assert!(us.is_empty()); +} + +#[tokio::test] +async fn test_cache_nxset_group() { + let (cachelayer, _adminclient) = setup_test(fixture(test_fixture)).await; + + // Important! This is what sets up that testgroup1 won't be resolved + // because it's in the "local" group set. + cachelayer + .reload_nxset(vec![("testgroup1".to_string(), 20001)].into_iter()) + .await; + + // Force offline. Show we have no groups. + cachelayer.mark_offline().await; + let gt = cachelayer + .get_nssgroup_name("testgroup1") + .await + .expect("Failed to get from cache"); + assert!(gt.is_none()); + + // go online. Get the group + cachelayer.attempt_online().await; + assert!(cachelayer.test_connection().await); + let gt = cachelayer + .get_nssgroup_name("testgroup1") + .await + .expect("Failed to get from cache"); + assert!(gt.is_none()); + + // go offline. still works + cachelayer.mark_offline().await; + let gt = cachelayer + .get_nssgroup_name("testgroup1") + .await + .expect("Failed to get from cache"); + assert!(gt.is_none()); + + // clear cache, go online + assert!(cachelayer.invalidate().await.is_ok()); + cachelayer.attempt_online().await; + assert!(cachelayer.test_connection().await); + + // get an account with the group + // DO NOT get the group yet. + let ut = cachelayer + .get_nssaccount_name("testaccount1") + .await + .expect("Failed to get from cache"); + assert!(ut.is_some()); + + // go offline. + cachelayer.mark_offline().await; + + // show we have the group despite no direct calls + let gt = cachelayer + .get_nssgroup_name("testgroup1") + .await + .expect("Failed to get from cache"); + assert!(gt.is_none()); + + // Finally, check we only have the upg in the list + let gs = cachelayer + .get_nssgroups() + .await + .expect("failed to list all groups"); + assert!(gs.len() == 1); + assert!(gs[0].name == "testaccount1@idm.example.com"); +} + +#[tokio::test] +async fn test_cache_nxset_allow_overrides() { + let (cachelayer, _adminclient) = setup_test(fixture(test_fixture)).await; + + // Important! masked_group is set as an allowed override group even though + // it's been "inserted" to the nxset. This means it will still resolve! + cachelayer + .reload_nxset(vec![("masked_group".to_string(), 20003)].into_iter()) + .await; + + // Force offline. Show we have no groups. + cachelayer.mark_offline().await; + let gt = cachelayer + .get_nssgroup_name("masked_group") + .await + .expect("Failed to get from cache"); + assert!(gt.is_none()); + + // go online. Get the group + cachelayer.attempt_online().await; + assert!(cachelayer.test_connection().await); + let gt = cachelayer + .get_nssgroup_name("masked_group") + .await + .expect("Failed to get from cache"); + assert!(gt.is_some()); + + // go offline. still works + cachelayer.mark_offline().await; + let gt = cachelayer + .get_nssgroup_name("masked_group") + .await + .expect("Failed to get from cache"); + assert!(gt.is_some()); }