diff --git a/Cargo.lock b/Cargo.lock index 6e26cd3b..ffaf4cda 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,9 +16,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.20" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" dependencies = [ "memchr", ] @@ -43,9 +43,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e579a7752471abc2a8268df8b20005e3eadd975f585398f17efcfd8d4927371" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" dependencies = [ "anstyle", "anstyle-parse", @@ -82,9 +82,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcd8291a340dd8ac70e18878bc4501dd7b4ff970cfa21c207d36ece51ea88fd" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" dependencies = [ "anstyle", "windows-sys 0.48.0", @@ -92,9 +92,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.70" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] name = "async-stream" @@ -115,7 +115,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -126,7 +126,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -148,13 +148,13 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "axum" -version = "0.6.16" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "113713495a32dd0ab52baf5c10044725aa3aec00b31beda84218e469029b72a3" +checksum = "f8175979259124331c1d7bf6586ee7e0da434155e4b2d48ec2c8386281d8df39" dependencies = [ "async-trait", "axum-core", - "bitflags", + "bitflags 1.3.2", "bytes", "futures-util", "http", @@ -214,7 +214,7 @@ version = "0.59.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cexpr", "clang-sys", "clap 2.34.0", @@ -231,6 +231,28 @@ dependencies = [ "which", ] +[[package]] +name = "bindgen" +version = "0.64.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" +dependencies = [ + "bitflags 1.3.2", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 1.0.109", + "which", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -252,6 +274,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f6b0c9ebae276e207a3e4e989ed9f3be8b7ce8728b80629c98c21d27742e6ba" + [[package]] name = "built" version = "0.5.2" @@ -265,9 +293,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.12.0" +version = "3.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b" [[package]] name = "byteorder" @@ -349,7 +377,7 @@ checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "ansi_term", "atty", - "bitflags", + "bitflags 1.3.2", "strsim 0.8.0", "textwrap", "unicode-width", @@ -358,9 +386,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.2.3" +version = "4.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f9152d70e42172fdb87de2efd7327160beee37886027cf86f30a233d5b30b4" +checksum = "34d21f9bf1b425d2968943631ec91202fe5e837264063503708b83013f8fc938" dependencies = [ "clap_builder", "clap_derive", @@ -369,13 +397,13 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.2.3" +version = "4.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e067b220911598876eb55d52725ddcc201ffe3f0904018195973bc5b012ea2ca" +checksum = "914c8c79fb560f238ef6429439a30023c862f7a28e688c58f7203f12b29970bd" dependencies = [ "anstream", "anstyle", - "bitflags", + "bitflags 1.3.2", "clap_lex", "once_cell", "strsim 0.10.0", @@ -390,7 +418,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -399,16 +427,6 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" -[[package]] -name = "codespan-reporting" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width", -] - [[package]] name = "colorchoice" version = "1.0.0" @@ -435,12 +453,35 @@ dependencies = [ "unicode-xid", ] +[[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +[[package]] +name = "cross-krb5" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1beb2bb62982b53ef0f7b8d29d946855342eccbbee8388c4937a9adad09457c" +dependencies = [ + "anyhow", + "bitflags 1.3.2", + "bytes", + "libgssapi", + "windows 0.34.0", +] + [[package]] name = "crossbeam-channel" version = "0.5.8" @@ -460,50 +501,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "cxx" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" -dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", -] - -[[package]] -name = "cxx-build" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" -dependencies = [ - "cc", - "codespan-reporting", - "once_cell", - "proc-macro2", - "quote", - "scratch", - "syn 2.0.15", -] - -[[package]] -name = "cxxbridge-flags" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.15", -] - [[package]] name = "darling" version = "0.14.4" @@ -811,7 +808,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -861,7 +858,7 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2994bee4a3a6a51eb90c218523be382fd7ea09b16380b9312e9dbe955ff7c7d1" dependencies = [ - "bitflags", + "bitflags 1.3.2", "libc", "libgit2-sys", "log", @@ -1039,17 +1036,16 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows 0.48.0", ] [[package]] name = "iana-time-zone-haiku" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "cxx", - "cxx-build", + "cc", ] [[package]] @@ -1153,9 +1149,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.61" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" dependencies = [ "wasm-bindgen", ] @@ -1202,13 +1198,14 @@ name = "krb5" version = "0.0.0-dev" dependencies = [ "krb5-sys", + "snafu", ] [[package]] name = "krb5-sys" version = "0.0.0-dev" dependencies = [ - "bindgen", + "bindgen 0.59.2", "pkg-config", ] @@ -1327,11 +1324,46 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "lber" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5d85f5e00e12cb50c70c3b1c1f0daff6546eb4c608b44d0a990e38a539e0446" +dependencies = [ + "bytes", + "nom", +] + +[[package]] +name = "ldap3" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5cfbd3c59ca16d6671b002b8b3dd013cd825d9c77a1664a3135194d3270511e" +dependencies = [ + "async-trait", + "bytes", + "cross-krb5", + "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.141" +version = "0.2.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" [[package]] name = "libgit2-sys" @@ -1345,6 +1377,27 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libgssapi" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dcfb7f77cbefc242a46ea667491c4f1129712f563cd368623d3f1b261a90e5f" +dependencies = [ + "bitflags 2.3.0", + "bytes", + "lazy_static", + "libgssapi-sys", +] + +[[package]] +name = "libgssapi-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efdcdd31923aa6280d41ff2636fd93a18cc60fe25983b24887d1a8d24478cbfb" +dependencies = [ + "bindgen 0.64.0", +] + [[package]] name = "libloading" version = "0.7.4" @@ -1357,9 +1410,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" +checksum = "56ee889ecc9568871456d42f603d6a0ce59ff328d291063a45cbdf0036baf6db" dependencies = [ "cc", "libc", @@ -1367,15 +1420,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "link-cplusplus" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" -dependencies = [ - "cc", -] - [[package]] name = "linked-hash-map" version = "0.5.6" @@ -1393,9 +1437,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.3.2" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f508063cc7bb32987c71511216bd5a32be15bccb6a80b52df8b9d7f01fc3aa2" +checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" [[package]] name = "lock_api" @@ -1467,6 +1511,24 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +[[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]] name = "nom" version = "7.1.3" @@ -1524,11 +1586,11 @@ checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "openssl" -version = "0.10.50" +version = "0.10.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e30d8bc91859781f0a943411186324d580f2bbeb71b452fe91ae344806af3f1" +checksum = "01b8574602df80f7b85fdfc5392fa884a4e3b3f4f35402c070ab34c3d3f78d56" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if", "foreign-types", "libc", @@ -1545,14 +1607,20 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", ] +[[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.85" +version = "0.9.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d3d193fb1488ad46ffe3aaabc912cc931d02ee8518fe2959aea8ef52718b0c0" +checksum = "8e17f59264b2809d77ae94f0e1ebabc434773f370d6ca667bd223ea10e06cc7e" dependencies = [ "cc", "libc", @@ -1714,22 +1782,22 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.12" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.12" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.16", ] [[package]] @@ -1746,9 +1814,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "ppv-lite86" @@ -1768,9 +1836,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "c4ec6d5fe0b140acb27c9a0444118cf55bfbb4e0b259739429abb4521dd67c16" dependencies = [ "unicode-ident", ] @@ -1847,9 +1915,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" dependencies = [ "proc-macro2", ] @@ -1890,7 +1958,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -1899,7 +1967,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -1915,13 +1983,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.3" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.7.1", ] [[package]] @@ -1930,7 +1998,7 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ - "regex-syntax", + "regex-syntax 0.6.29", ] [[package]] @@ -1939,6 +2007,12 @@ version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +[[package]] +name = "regex-syntax" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -1947,11 +2021,11 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.37.12" +version = "0.37.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "722529a737f5a942fdbac3a46cee213053196737c5eaa3386d52e85b786f2659" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno", "io-lifetimes", "libc", @@ -1971,6 +2045,15 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys 0.42.0", +] + [[package]] name = "schemars" version = "0.8.12" @@ -2001,12 +2084,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" -[[package]] -name = "scratch" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" - [[package]] name = "secrecy" version = "0.8.0" @@ -2017,6 +2094,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "security-framework" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2855b3715770894e67cbfa3df957790aa0c9edc3bf06efa1a84d77fa0839d1" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.17" @@ -2028,9 +2128,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.160" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" +checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" dependencies = [ "serde_derive", ] @@ -2047,13 +2147,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.160" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" +checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -2190,10 +2290,16 @@ dependencies = [ name = "stackable-krb5-provision-keytab" version = "0.0.0-dev" dependencies = [ + "byteorder", + "futures", "krb5", + "ldap3", + "native-tls", + "rand", "serde", "serde_json", "snafu", + "stackable-operator", "tokio", "tracing", "tracing-subscriber", @@ -2205,7 +2311,7 @@ version = "0.27.1" source = "git+https://github.com/stackabletech/operator-rs.git?tag=0.27.1#c470ea5de96c0f4081e77fd7c8ce197ecebbd406" dependencies = [ "chrono", - "clap 4.2.3", + "clap 4.2.7", "const_format", "derivative", "either", @@ -2251,7 +2357,7 @@ dependencies = [ "anyhow", "async-trait", "built", - "clap 4.2.3", + "clap 4.2.7", "futures", "h2", "libc", @@ -2324,9 +2430,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.15" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" dependencies = [ "proc-macro2", "quote", @@ -2345,7 +2451,7 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c1af10c09a6d1f65753e52772a4621e00da8b1d772d0f24595b60ccd36d6b51" dependencies = [ - "bitflags", + "bitflags 1.3.2", "libc", "smart-default", "thiserror", @@ -2400,7 +2506,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -2437,9 +2543,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.20" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" dependencies = [ "serde", "time-core", @@ -2448,15 +2554,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" dependencies = [ "time-core", ] @@ -2478,9 +2584,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.27.0" +version = "1.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" +checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105" dependencies = [ "autocfg", "bytes", @@ -2492,7 +2598,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -2507,13 +2613,23 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", +] + +[[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]] @@ -2530,9 +2646,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" dependencies = [ "futures-core", "pin-project-lite", @@ -2541,9 +2657,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" dependencies = [ "bytes", "futures-core", @@ -2649,7 +2765,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" dependencies = [ "base64", - "bitflags", + "bitflags 1.3.2", "bytes", "futures-core", "futures-util", @@ -2689,20 +2805,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.16", ] [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", "valuable", @@ -2745,9 +2861,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" dependencies = [ "matchers", "nu-ansi-term", @@ -2834,9 +2950,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.3.1" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b55a3fef2a1e3b3a00ce878640918820d3c51081576ac657d23af9fc7928fdb" +checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" dependencies = [ "getrandom", ] @@ -2883,9 +2999,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2893,24 +3009,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.16", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2918,22 +3034,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.16", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" [[package]] name = "which" @@ -2977,6 +3093,19 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45296b64204227616fdbf2614cefa4c236b98ee64dfaaaa435207ed99fe7829f" +dependencies = [ + "windows_aarch64_msvc 0.34.0", + "windows_i686_gnu 0.34.0", + "windows_i686_msvc 0.34.0", + "windows_x86_64_gnu 0.34.0", + "windows_x86_64_msvc 0.34.0", +] + [[package]] name = "windows" version = "0.48.0" @@ -2986,6 +3115,21 @@ dependencies = [ "windows-targets 0.48.0", ] +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -3046,6 +3190,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -3058,6 +3208,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +[[package]] +name = "windows_i686_gnu" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -3070,6 +3226,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +[[package]] +name = "windows_i686_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -3082,6 +3244,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +[[package]] +name = "windows_x86_64_gnu" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -3106,6 +3274,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +[[package]] +name = "windows_x86_64_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -3120,9 +3294,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "xml-rs" -version = "0.8.4" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" +checksum = "1690519550bfa95525229b9ca2350c63043a4857b3b0013811b2ccf4a2420b01" [[package]] name = "yaml-rust" diff --git a/default.nix b/default.nix index 1133d27f..cb139c6b 100644 --- a/default.nix +++ b/default.nix @@ -19,6 +19,12 @@ LIBCLANG_PATH = "${pkgs.libclang.lib}/lib"; BINDGEN_EXTRA_CLANG_ARGS = "-I${pkgs.glibc.dev}/include -I${pkgs.clang.cc.lib}/lib/clang/${pkgs.lib.getVersion pkgs.clang.cc}/include"; }; + libgssapi-sys = attrs: { + nativeBuildInputs = [ pkgs.pkg-config ]; + buildInputs = [ (pkgs.enableDebugging pkgs.krb5) ]; + LIBCLANG_PATH = "${pkgs.libclang.lib}/lib"; + BINDGEN_EXTRA_CLANG_ARGS = "-I${pkgs.glibc.dev}/include -I${pkgs.clang.cc.lib}/lib/clang/${pkgs.lib.getVersion pkgs.clang.cc}/include"; + }; }; } , meta ? pkgs.lib.importJSON ./nix/meta.json diff --git a/deploy/helm/secret-operator/crds/crds.yaml b/deploy/helm/secret-operator/crds/crds.yaml index b479a5ec..39609091 100644 --- a/deploy/helm/secret-operator/crds/crds.yaml +++ b/deploy/helm/secret-operator/crds/crds.yaml @@ -79,7 +79,44 @@ spec: oneOf: - required: - mit + - required: + - activeDirectory properties: + activeDirectory: + properties: + ldapServer: + type: string + ldapTlsCaSecret: + description: SecretReference represents a Secret Reference. It has enough information to retrieve secret in any namespace + properties: + name: + description: name is unique within a namespace to reference a secret resource. + type: string + namespace: + description: namespace defines the space within which the secret name must be unique. + type: string + type: object + passwordCacheSecret: + description: SecretReference represents a Secret Reference. It has enough information to retrieve secret in any namespace + properties: + name: + description: name is unique within a namespace to reference a secret resource. + type: string + namespace: + description: namespace defines the space within which the secret name must be unique. + type: string + type: object + schemaDistinguishedName: + type: string + userDistinguishedName: + type: string + required: + - ldapServer + - ldapTlsCaSecret + - passwordCacheSecret + - schemaDistinguishedName + - userDistinguishedName + type: object mit: properties: kadminServer: diff --git a/docs/modules/secret-operator/pages/secretclass.adoc b/docs/modules/secret-operator/pages/secretclass.adoc index a84108df..e650c9dc 100644 --- a/docs/modules/secret-operator/pages/secretclass.adoc +++ b/docs/modules/secret-operator/pages/secretclass.adoc @@ -63,7 +63,7 @@ spec: Creates a Kerberos keytab file for a selected realm. The Kerberos KDC and administrator credentials must be provided by the administrator. -IMPORTANT: Only MIT Kerberos (krb5) is supported. Heimdal and Active Directory are not supported. +IMPORTANT: Only MIT Kerberos (krb5) and Active Directory are currently supported. Heimdal is not supported. Principals will be created dynamically if they do not already exist. @@ -81,6 +81,19 @@ spec: admin: mit: kadminServer: krb5-kdc + # or... + activeDirectory: + # ldapServer must match the AD Domain Controller's FQDN or GSSAPI authn will fail + # You may need to set AD as your fallback DNS resolver in your Kube DNS Corefile + ldapServer: addc.example.com + ldapTlsCaSecret: + namespace: default + name: secret-operator-ad-ca + passwordCacheSecret: + namespace: default + name: secret-operator-ad-passwords + userDistinguishedName: CN=Users,DC=sble,DC=test + schemaDistinguishedName: CN=Schema,CN=Configuration,DC=sble,DC=test adminKeytabSecret: namespace: default name: secret-provisioner-keytab @@ -90,7 +103,14 @@ spec: `kerberosKeytab`:: Declares that the `kerberosKeytab` backend is used. `kerberosKeytab.realmName`:: The name of the Kerberos realm. This should be provided by the Kerberos administrator. `kerberosKeytab.kdc`:: The hostname of the Kerberos Key Distribution Center (KDC). This should be provided by the Kerberos administrator. +`kerberosKeytab.admin.mit`:: Credentials should be provisioned in a MIT Kerberos Admin Server. `kerberosKeytab.admin.mit.kadminServer`:: The hostname of the Kerberos Admin Server. This should be provided by the Kerberos administrator. +`kerberosKeytab.admin.activeDirectory`:: Credentials should be provisioned in a Microsoft Active Directory domain. +`kerberosKeytab.admin.activeDirectory.ldapServer`:: An AD LDAP server, such as the AD Domain Controller. This _must_ match the server's FQDN, or GSSAPI authentication will fail. +`kerberosKeytab.admin.activeDirectory.ldapTlsCaSecret`:: Reference (`name` and `namespace`) to a K8s `Secret` object containing the TLS CA (in `ca.crt`) that the LDAP server's certificate should be authenticated against. +`kerberosKeytab.admin.activeDirectory.passwordCacheSecret`:: Reference (`name` and `namespace`) to a K8s `Secret` object where workload passwords will be stored. This _must not_ be accessible to end users. +`kerberosKeytab.admin.activeDirectory.userDistinguishedName`:: The root Distinguished Name (DN) where service accounts should be provisioned, typically `CN=Users,{domain_dn}`. +`kerberosKeytab.admin.activeDirectory.schemaDistinguishedName`:: The root Distinguished Name (DN) for AD-managed schemas, typically `CN=Schema,CN=Configuration,{domain_dn}`. `kerberosKeytab.adminKeytabSecret`:: Reference (`name` and `namespace`) to a K8s `Secret` object where a keytab with administrative privileges is stored in the key `keytab`. `kerberosKeytab.adminPrincipal`:: The name of the Kerberos principal to be used by the Secret Operator. This should be provided by the Kerberos administrator. The credentials for this principal must be stored in the keytab (`adminKeytabSecret`). diff --git a/rust/krb5-provision-keytab/Cargo.toml b/rust/krb5-provision-keytab/Cargo.toml index 300594f7..0b257140 100644 --- a/rust/krb5-provision-keytab/Cargo.toml +++ b/rust/krb5-provision-keytab/Cargo.toml @@ -15,6 +15,12 @@ krb5 = { path = "../krb5" } serde = { version = "1.0.152", features = ["derive"] } serde_json = "1.0.93" snafu = "0.7.4" -tokio = { version = "1.25.0", features = ["io-util", "process"] } +tokio = { version = "1.25.0", features = ["io-util", "process", "rt-multi-thread", "macros"] } tracing = "0.1.37" tracing-subscriber = "0.3.16" +ldap3 = { version = "0.11.1", default-features = false, features = ["gssapi", "tls"] } +byteorder = "1.4.3" +stackable-operator = { git = "https://github.com/stackabletech/operator-rs.git", tag = "0.27.1" } +rand = "0.8.5" +native-tls = "0.2.11" +futures = "0.3.28" diff --git a/rust/krb5-provision-keytab/src/active_directory.rs b/rust/krb5-provision-keytab/src/active_directory.rs new file mode 100644 index 00000000..b57f8c24 --- /dev/null +++ b/rust/krb5-provision-keytab/src/active_directory.rs @@ -0,0 +1,301 @@ +use std::ffi::{CString, NulError}; + +use byteorder::{LittleEndian, WriteBytesExt}; +use krb5::{Keyblock, Keytab, KrbContext, Principal, PrincipalUnparseOptions}; +use ldap3::{Ldap, LdapConnAsync, LdapConnSettings}; +use rand::{seq::SliceRandom, thread_rng, CryptoRng}; +use snafu::{OptionExt, ResultExt, Snafu}; +use stackable_operator::k8s_openapi::api::core::v1::{Secret, SecretReference}; + +use crate::{ + credential_cache::{self, CredentialCache}, + secret_ref::{FullSecretRef, IncompleteSecretRef}, +}; + +#[derive(Debug, Snafu)] +pub enum Error { + #[snafu(display("LDAP TLS CA reference is invalid"))] + LdapTlsCaReferenceInvalid { source: IncompleteSecretRef }, + #[snafu(display("failed to retrieve LDAP TLS CA {ca_ref}"))] + GetLdapTlsCa { + source: stackable_operator::error::Error, + ca_ref: FullSecretRef, + }, + #[snafu(display("LDAP TLS CA secret is missing required key {key}"))] + LdapTlsCaKeyMissing { key: String }, + #[snafu(display("failed to parse LDAP TLS CA"))] + ParseLdapTlsCa { source: native_tls::Error }, + #[snafu(display("password cache reference is invalid"))] + PasswordCacheReferenceInvalid { source: IncompleteSecretRef }, + #[snafu(display("password cache error"))] + PasswordCache { source: credential_cache::Error }, + #[snafu(display("failed to configure LDAP TLS"))] + ConfigureLdapTls { source: native_tls::Error }, + #[snafu(display("failed to connect to LDAP server"))] + ConnectLdap { source: ldap3::LdapError }, + #[snafu(display("failed to authenticate to LDAP server"))] + LdapAuthn { source: ldap3::LdapError }, + #[snafu(display("failed to init Kubernetes client"))] + KubeInit { + source: stackable_operator::error::Error, + }, + + #[snafu(display("failed to unparse Kerberos principal"))] + UnparsePrincipal { source: krb5::Error }, + #[snafu(display("failed to get password cache {password_cache_ref}"))] + GetPasswordCache { + source: stackable_operator::error::Error, + password_cache_ref: FullSecretRef, + }, + #[snafu(display("failed to update password cache {password_cache_ref}"))] + UpdatePasswordCache { + source: stackable_operator::error::Error, + password_cache_ref: FullSecretRef, + }, + #[snafu(display("failed to create LDAP user"))] + CreateLdapUser { source: ldap3::LdapError }, + #[snafu(display( + "LDAP user already exists, either delete it manually or add it to the password cache ({password_cache_ref})" + ))] + CreateLdapUserConflict { + source: ldap3::LdapError, + password_cache_ref: FullSecretRef, + }, + #[snafu(display("failed to decode generated password"))] + DecodePassword { source: NulError }, + #[snafu(display("failed to add key to keytab"))] + AddToKeytab { source: krb5::Error }, +} +pub type Result = std::result::Result; + +// Result codes are defined by https://www.rfc-editor.org/rfc/rfc4511#appendix-A.1 +const LDAP_RESULT_CODE_ENTRY_ALREADY_EXISTS: u32 = 68; + +pub struct AdAdmin<'a> { + ldap: Ldap, + krb: &'a KrbContext, + password_cache: CredentialCache, + user_distinguished_name: String, + schema_distinguished_name: String, +} + +impl<'a> AdAdmin<'a> { + pub async fn connect( + ldap_server: &str, + krb: &'a KrbContext, + ldap_tls_ca_secret: SecretReference, + password_cache_secret: SecretReference, + user_distinguished_name: String, + schema_distinguished_name: String, + ) -> Result> { + let kube = stackable_operator::client::create_client(None) + .await + .context(KubeInitSnafu)?; + let ldap_tls = native_tls::TlsConnector::builder() + .disable_built_in_roots(true) + .add_root_certificate(get_ldap_ca_certificate(&kube, ldap_tls_ca_secret).await?) + .build() + .context(ConfigureLdapTlsSnafu)?; + let (ldap_conn, mut ldap) = LdapConnAsync::with_settings( + LdapConnSettings::new().set_connector(ldap_tls), + &format!("ldaps://{ldap_server}"), + ) + .await + .context(ConnectLdapSnafu)?; + ldap3::drive!(ldap_conn); + ldap.sasl_gssapi_bind(ldap_server) + .await + .context(LdapAuthnSnafu)?; + let password_cache = CredentialCache::new( + "AD passwords", + kube, + password_cache_secret + .try_into() + .context(PasswordCacheReferenceInvalidSnafu)?, + ) + .await + .context(PasswordCacheSnafu)?; + Ok(Self { + ldap, + krb, + password_cache, + user_distinguished_name, + schema_distinguished_name, + }) + } + + #[tracing::instrument(skip(self, principal, kt), fields(principal = %principal))] + pub async fn create_and_add_principal_to_keytab( + &mut self, + principal: &Principal<'_>, + kt: &mut Keytab<'_>, + ) -> Result<()> { + let princ_name = principal + .unparse(PrincipalUnparseOptions::default()) + .context(UnparsePrincipalSnafu)?; + let password_cache_key = princ_name.replace(['/', '@'], "__"); + let password = self + .password_cache + // CONCURRENCY: ldap.add() will only succeed once per principal, so + // we are by definition the unique writer of this key. + .get_or_insert(&password_cache_key, |ctx| async { + let password = generate_ad_password(40); + create_ad_user( + &mut self.ldap, + principal, + &password, + &self.user_distinguished_name, + &self.schema_distinguished_name, + ctx.cache_ref, + ) + .await?; + Ok(password.into_bytes()) + }) + .await + // FIXME: What about cases where ldap.add() succeeds but not the cache write? + .context(PasswordCacheSnafu)??; + let password_c = CString::new(password).context(DecodePasswordSnafu)?; + principal + .default_salt() + .and_then(|salt| { + Keyblock::from_password( + self.krb, + krb5::enctype::AES256_CTS_HMAC_SHA1_96, + &password_c, + &salt, + ) + }) + .and_then(|key| kt.add(principal, 0, &key.as_ref())) + .context(AddToKeytabSnafu)?; + Ok(()) + } +} + +async fn get_ldap_ca_certificate( + kube: &stackable_operator::client::Client, + ca_secret_ref: SecretReference, +) -> Result { + let ca_secret_ref: FullSecretRef = ca_secret_ref + .try_into() + .context(LdapTlsCaReferenceInvalidSnafu)?; + let ca_secret = kube + .get::(&ca_secret_ref.name, &ca_secret_ref.namespace) + .await + .context(GetLdapTlsCaSnafu { + ca_ref: ca_secret_ref, + })?; + let ca_key = "ca.crt"; + let ca_cert_pem = ca_secret + .data + .and_then(|mut d| d.remove(ca_key)) + .map(|ca| ca.0) + .context(LdapTlsCaKeyMissingSnafu { key: ca_key })?; + native_tls::Certificate::from_pem(&ca_cert_pem).context(ParseLdapTlsCaSnafu) +} + +fn generate_ad_password(len: usize) -> String { + let mut rng = thread_rng(); + // Assert that `rng` is crypto-safe + let _: &dyn CryptoRng = &rng; + // Allow all ASCII alphanumeric characters as well as punctuation + // Exclude double quotes (") since they are used by the AD password update protocol... + let dict: Vec = (1..=127) + .filter_map(char::from_u32) + .filter(|c| *c != '"' && (c.is_ascii_alphanumeric() || c.is_ascii_punctuation())) + .collect(); + let pw = (0..len) + .map(|_| *dict.choose(&mut rng).expect("dictionary must be non-empty")) + .collect::(); + assert_eq!(pw.len(), len); + pw +} + +fn encode_password_for_ad_update(password: &str) -> Vec { + let mut pwd_utf16le = Vec::new(); + format!("\"{password}\"").encode_utf16().for_each(|word| { + WriteBytesExt::write_u16::(&mut pwd_utf16le, word) + .expect("writing into a string is infallible") + }); + pwd_utf16le +} + +#[tracing::instrument(skip(ldap, principal, password), fields(%principal))] +async fn create_ad_user( + ldap: &mut Ldap, + principal: &Principal<'_>, + password: &str, + user_dn_base: &str, + schema_dn_base: &str, + password_cache_ref: FullSecretRef, +) -> Result<()> { + // Flags are a subset of https://learn.microsoft.com/en-us/troubleshoot/windows-server/identity/useraccountcontrol-manipulate-account-properties + const AD_UAC_NORMAL_ACCOUNT: u32 = 0x0200; + const AD_UAC_DONT_EXPIRE_PASSWORD: u32 = 0x1_0000; + + // Flags are a subset of https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/6cfc7b50-11ed-4b4d-846d-6f08f0812919 + const AD_ENCTYPE_AES128_HMAC_SHA1: u32 = 0x08; + const AD_ENCTYPE_AES256_HMAC_SHA1: u32 = 0x10; + + tracing::info!("creating principal"); + let princ_name = principal + .unparse(PrincipalUnparseOptions::default()) + .context(UnparsePrincipalSnafu)?; + let princ_name_realmless = principal + .unparse(PrincipalUnparseOptions { + realm: krb5::PrincipalRealmDisplayMode::Never, + ..Default::default() + }) + .context(UnparsePrincipalSnafu)?; + let principal_cn = ldap3::dn_escape(&princ_name); + // FIXME: AD restricts RDNs to 64 characters + let principal_cn = principal_cn.get(..64).unwrap_or(&*principal_cn); + let create_user_result = ldap + .add( + &format!("CN={principal_cn},{user_dn_base}"), + vec![ + ("cn".as_bytes(), [principal_cn.as_bytes()].into()), + ("objectClass".as_bytes(), ["user".as_bytes()].into()), + ("instanceType".as_bytes(), ["4".as_bytes()].into()), + ( + "objectCategory".as_bytes(), + [format!("CN=Container,{schema_dn_base}").as_bytes()].into(), + ), + ( + "unicodePwd".as_bytes(), + [&*encode_password_for_ad_update(password)].into(), + ), + ( + "userAccountControl".as_bytes(), + [(AD_UAC_NORMAL_ACCOUNT | AD_UAC_DONT_EXPIRE_PASSWORD) + .to_string() + .as_bytes()] + .into(), + ), + ( + "userPrincipalName".as_bytes(), + [princ_name.as_bytes()].into(), + ), + ( + "servicePrincipalName".as_bytes(), + [princ_name_realmless.as_bytes()].into(), + ), + ( + // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/6cfc7b50-11ed-4b4d-846d-6f08f0812919 + "msDS-SupportedEncryptionTypes".as_bytes(), + [(AD_ENCTYPE_AES128_HMAC_SHA1 | AD_ENCTYPE_AES256_HMAC_SHA1) + .to_string() + .as_bytes()] + .into(), + ), + ], + ) + .await + .context(CreateLdapUserSnafu)?; + match create_user_result.rc { + LDAP_RESULT_CODE_ENTRY_ALREADY_EXISTS => create_user_result + .success() + .context(CreateLdapUserConflictSnafu { password_cache_ref })?, + _ => create_user_result.success().context(CreateLdapUserSnafu)?, + }; + Ok(()) +} diff --git a/rust/krb5-provision-keytab/src/credential_cache.rs b/rust/krb5-provision-keytab/src/credential_cache.rs new file mode 100644 index 00000000..3f182cee --- /dev/null +++ b/rust/krb5-provision-keytab/src/credential_cache.rs @@ -0,0 +1,129 @@ +use futures::{TryFuture, TryFutureExt}; +use snafu::{OptionExt, ResultExt, Snafu}; +use stackable_operator::k8s_openapi::{api::core::v1::Secret, ByteString}; + +use crate::secret_ref::FullSecretRef; + +#[derive(Debug, Snafu)] +pub enum Error { + #[snafu(display("failed to load initial cache from {cache_ref}"))] + GetInitialCache { + source: stackable_operator::error::Error, + cache_ref: FullSecretRef, + }, + #[snafu(display("failed to save credential {key} to {cache_ref}"))] + SaveToCache { + source: stackable_operator::error::Error, + key: String, + cache_ref: FullSecretRef, + }, + #[snafu(display("newly saved credential {key} was not found in {cache_ref}"))] + SavedKeyNotFound { + key: String, + cache_ref: FullSecretRef, + }, +} +type Result = std::result::Result; + +pub struct CredentialCache { + name: &'static str, + kube: stackable_operator::client::Client, + cache_ref: FullSecretRef, + current_state: Secret, +} +impl CredentialCache { + #[tracing::instrument(skip(kube))] + pub async fn new( + name: &'static str, + kube: stackable_operator::client::Client, + cache_ref: FullSecretRef, + ) -> Result { + Ok(Self { + name, + current_state: kube + .get::(&cache_ref.name, &cache_ref.namespace) + .await + .context(GetInitialCacheSnafu { + cache_ref: &cache_ref, + })?, + cache_ref, + kube, + }) + } + + fn get_if_present(&self, key: &str) -> Option<&[u8]> { + Some(&self.current_state.data.as_ref()?.get(key)?.0) + } + + /// Gets the credential named `key` from the cache, or calls `mk_value` if it cannot be found. + /// + /// # Concurrency + /// There is no locking imposed by `CredentialCache`, in the face of a race condition + /// `mk_value` must either fail or be idempotent (returning exactly the same value for all concurrent calls + /// for the same key). + /// + /// # Errors + /// There is no negative caching, the result of a failed call to `mk_value` will not be saved. + #[tracing::instrument(skip(self, mk_value), fields(name = self.name, cache_ref = %self.cache_ref))] + pub async fn get_or_insert Fut, Fut: TryFuture>>( + &mut self, + key: &str, + mk_value: F, + ) -> Result> + where + Fut::Error: std::error::Error + 'static, + { + // This should be an if let Some(...) but for some reason Rust considers that borrow to conflict with + // us modifying self.current_state in the other branch + if self.get_if_present(key).is_some() { + tracing::info!("credential found in cache, reusing..."); + Ok(Ok(self + .get_if_present(key) + .expect("key was just confirmed to exist in cache"))) + } else { + tracing::info!("credential not found in cache, generating..."); + match mk_value(Ctx { + cache_ref: self.cache_ref.clone(), + }) + .into_future() + .await + { + Ok(value) => { + tracing::info!("generated credential successfully, saving..."); + self.current_state = self + .kube + .merge_patch( + &self.current_state, + &Secret { + data: Some([(key.to_string(), ByteString(value))].into()), + ..Secret::default() + }, + ) + .await + .context(SaveToCacheSnafu { + key, + cache_ref: &self.cache_ref, + })?; + Ok(Ok(self.get_if_present(key).context( + SavedKeyNotFoundSnafu { + key, + cache_ref: &self.cache_ref, + }, + )?)) + } + Err(err) => { + tracing::warn!( + error = &err as &dyn std::error::Error, + "failed to generate credential, discarding..." + ); + Ok(Err(err)) + } + } + } + } +} + +/// Information that may be useful for generating error messages in get_or_insert handlers +pub struct Ctx { + pub cache_ref: FullSecretRef, +} diff --git a/rust/krb5-provision-keytab/src/lib.rs b/rust/krb5-provision-keytab/src/lib.rs index 9698aca9..d9e9ddfc 100644 --- a/rust/krb5-provision-keytab/src/lib.rs +++ b/rust/krb5-provision-keytab/src/lib.rs @@ -7,6 +7,7 @@ use std::{ use serde::{Deserialize, Serialize}; use snafu::{ResultExt, Snafu}; +use stackable_operator::k8s_openapi::api::core::v1::SecretReference; use tokio::{io::AsyncWriteExt, process::Command}; #[derive(Serialize, Deserialize)] @@ -15,11 +16,23 @@ pub struct Request { pub admin_principal_name: String, pub pod_keytab_path: PathBuf, pub principals: Vec, + pub admin_backend: AdminBackend, } #[derive(Serialize, Deserialize)] pub struct PrincipalRequest { pub name: String, } +#[derive(Serialize, Deserialize)] +pub enum AdminBackend { + Mit, + ActiveDirectory { + ldap_server: String, + ldap_tls_ca_secret: SecretReference, + password_cache_secret: SecretReference, + user_distinguished_name: String, + schema_distinguished_name: String, + }, +} #[derive(Serialize, Deserialize)] pub struct Response {} @@ -48,6 +61,10 @@ pub async fn provision_keytab(krb5_config_path: &Path, req: &Request) -> Result< let mut child = Command::new("stackable-krb5-provision-keytab") .kill_on_drop(true) .env("KRB5_CONFIG", krb5_config_path) + // ldap3 uses the default client keytab to authenticate to the LDAP server + .env("KRB5_CLIENT_KTNAME", &req.admin_keytab_path) + // avoid leaking credentials between secret volumes/secretclasses + .env("KRB5CCNAME", "MEMORY") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn() diff --git a/rust/krb5-provision-keytab/src/main.rs b/rust/krb5-provision-keytab/src/main.rs index a382779a..a7dbe823 100644 --- a/rust/krb5-provision-keytab/src/main.rs +++ b/rust/krb5-provision-keytab/src/main.rs @@ -4,20 +4,26 @@ use std::{ io::{stdin, BufReader}, }; -use krb5::{ - kadm5::{self, KVNO_ALL}, - Keyblock, Keytab, -}; +use krb5::{kadm5, Keyblock, Keytab}; use snafu::{ResultExt, Snafu}; -use stackable_krb5_provision_keytab::{Request, Response}; +use stackable_krb5_provision_keytab::{AdminBackend, Request, Response}; use tracing::info; +mod active_directory; +mod credential_cache; +mod mit; +mod secret_ref; + #[derive(Debug, Snafu)] enum Error { #[snafu(display("failed to deserialize request"))] DeserializeRequest { source: serde_json::Error }, #[snafu(display("failed to init krb5 context"))] KrbInit { source: krb5::Error }, + #[snafu(display("failed to init MIT admin client"))] + MitAdminInit { source: mit::Error }, + #[snafu(display("failed to init Active Directory admin client"))] + ActiveDirectoryInit { source: active_directory::Error }, #[snafu(display("failed to init kadmin server handle"))] KadminInit { source: kadm5::Error }, #[snafu(display("failed to decode admin principal name"))] @@ -35,29 +41,33 @@ enum Error { source: krb5::Error, principal: String, }, - #[snafu(display("failed to create principal {principal}"))] - CreatePrincipal { - source: kadm5::Error, + #[snafu(display("failed to prepare principal {principal} (backend: MIT)"))] + PreparePrincipalMit { + source: mit::Error, principal: String, }, - #[snafu(display("failed to get keys for principal {principal}"))] - GetPrincipalKeys { - source: kadm5::Error, + #[snafu(display("failed to prepare principal {principal} (backend: Active Directory)"))] + PreparePrincipalActiveDirectory { + source: active_directory::Error, principal: String, }, - #[snafu(display("failed to create dummy key"))] - CreateDummyKey { source: krb5::Error }, - #[snafu(display("failed to add key for principal {principal} to keytab"))] - AddToKeytab { - source: krb5::Error, + #[snafu(display("failed to create principal {principal}"))] + CreatePrincipal { + source: kadm5::Error, principal: String, }, + #[snafu(display("failed to add dummy key keytab"))] + AddDummyToKeytab { source: krb5::Error }, +} + +enum AdminConnection<'a> { + Mit(mit::MitAdmin<'a>), + ActiveDirectory(active_directory::AdAdmin<'a>), } -fn run() -> Result { +async fn run() -> Result { let req = serde_json::from_reader::<_, Request>(BufReader::new(stdin().lock())) .context(DeserializeRequestSnafu)?; - let config_params = krb5::kadm5::ConfigParams::default(); info!("initing context"); let krb = krb5::KrbContext::new().context(KrbInitSnafu)?; let admin_principal_name = @@ -65,16 +75,31 @@ fn run() -> Result { let admin_keytab_path = CString::new(&*req.admin_keytab_path.as_os_str().to_string_lossy()) .context(DecodeAdminKeytabPathSnafu)?; info!("initing kadmin"); - let kadmin = krb5::kadm5::ServerHandle::new( - &krb, - &admin_principal_name, - None, - &krb5::kadm5::Credential::ServiceKey { - keytab: admin_keytab_path, - }, - &config_params, - ) - .context(KadminInitSnafu)?; + + let mut admin = match req.admin_backend { + AdminBackend::Mit => AdminConnection::Mit( + mit::MitAdmin::connect(&krb, &admin_principal_name, &admin_keytab_path) + .context(MitAdminInitSnafu)?, + ), + AdminBackend::ActiveDirectory { + ldap_server, + ldap_tls_ca_secret, + password_cache_secret, + user_distinguished_name, + schema_distinguished_name, + } => AdminConnection::ActiveDirectory( + active_directory::AdAdmin::connect( + &ldap_server, + &krb, + ldap_tls_ca_secret, + password_cache_secret, + user_distinguished_name, + schema_distinguished_name, + ) + .await + .context(ActiveDirectoryInitSnafu)?, + ), + }; let mut kt = Keytab::resolve( &krb, &CString::new(&*req.pod_keytab_path.as_os_str().to_string_lossy()) @@ -96,12 +121,10 @@ fn run() -> Result { 0, // keyblock len must be >0, or kt.add() will always fail &Keyblock::new(&krb, 0, 1) - .context(CreateDummyKeySnafu)? + .context(AddDummyToKeytabSnafu)? .as_ref(), ) - .context(AddToKeytabSnafu { - principal: &dummy_principal, - })?; + .context(AddDummyToKeytabSnafu)?; for princ_req in req.principals { let princ = krb @@ -109,20 +132,16 @@ fn run() -> Result { &CString::new(princ_req.name.as_str()).context(DecodePodPrincipalNameSnafu)?, ) .context(ParsePrincipalSnafu { - principal: princ_req.name, + principal: &princ_req.name, })?; - match kadmin.create_principal(&princ) { - Err(kadm5::Error { code, .. }) if code.0 == kadm5::error_code::DUP => { - info!("principal {princ} already exists, reusing") - } - res => res.context(CreatePrincipalSnafu { principal: &princ })?, - } - let keys = kadmin - .get_principal_keys(&princ, KVNO_ALL) - .context(GetPrincipalKeysSnafu { principal: &princ })?; - for key in keys.keys() { - kt.add(&princ, key.kvno, &key.keyblock) - .context(AddToKeytabSnafu { principal: &princ })?; + match &mut admin { + AdminConnection::Mit(mit) => mit + .create_and_add_principal_to_keytab(&princ, &mut kt) + .context(PreparePrincipalMitSnafu { principal: &princ })?, + AdminConnection::ActiveDirectory(ad) => ad + .create_and_add_principal_to_keytab(&princ, &mut kt) + .await + .context(PreparePrincipalActiveDirectorySnafu { principal: &princ })?, } } Ok(Response {}) @@ -152,11 +171,12 @@ impl Display for Report { } } -fn main() { +#[tokio::main] +async fn main() { tracing_subscriber::fmt() .with_writer(std::io::stderr) .init(); - let res = run().map_err(|err| Report::from(err).to_string()); + let res = run().await.map_err(|err| Report::from(err).to_string()); println!("{}", serde_json::to_string_pretty(&res).unwrap()); std::process::exit(res.is_ok().into()); } diff --git a/rust/krb5-provision-keytab/src/mit.rs b/rust/krb5-provision-keytab/src/mit.rs new file mode 100644 index 00000000..96e7cce1 --- /dev/null +++ b/rust/krb5-provision-keytab/src/mit.rs @@ -0,0 +1,65 @@ +use std::ffi::CStr; + +use krb5::{kadm5, Keytab, Principal}; +use snafu::{ResultExt, Snafu}; + +#[derive(Debug, Snafu)] +pub enum Error { + #[snafu(display("failed to initialize kadm5 server handle"))] + KadminInit { source: kadm5::Error }, + #[snafu(display("failed to create principal"))] + CreatePrincipal { source: kadm5::Error }, + #[snafu(display("failed to principal's keys"))] + GetPrincipalKeys { source: kadm5::Error }, + #[snafu(display("failed to add key to keytab"))] + AddToKeytab { source: krb5::Error }, +} +pub type Result = std::result::Result; + +pub struct MitAdmin<'a> { + kadmin: kadm5::ServerHandle<'a>, +} +impl<'a> MitAdmin<'a> { + pub fn connect( + krb: &'a krb5::KrbContext, + admin_principal_name: &CStr, + admin_keytab_path: &CStr, + ) -> Result { + Ok(Self { + kadmin: kadm5::ServerHandle::new( + krb, + admin_principal_name, + None, + &krb5::kadm5::Credential::ServiceKey { + keytab: admin_keytab_path.to_owned(), + }, + &kadm5::ConfigParams::default(), + ) + .context(KadminInitSnafu)?, + }) + } + + #[tracing::instrument(skip(self, principal, kt), fields(principal = %principal))] + pub fn create_and_add_principal_to_keytab( + &self, + principal: &Principal, + kt: &mut Keytab, + ) -> Result<()> { + tracing::info!("creating principal"); + match self.kadmin.create_principal(principal) { + Err(kadm5::Error { code, .. }) if code.0 == kadm5::error_code::DUP => { + tracing::info!("principal already exists, reusing") + } + res => res.context(CreatePrincipalSnafu)?, + } + let keys = self + .kadmin + .get_principal_keys(principal, kadm5::KVNO_ALL) + .context(GetPrincipalKeysSnafu)?; + for key in keys.keys() { + kt.add(principal, key.kvno, &key.keyblock) + .context(AddToKeytabSnafu)?; + } + Ok(()) + } +} diff --git a/rust/krb5-provision-keytab/src/secret_ref.rs b/rust/krb5-provision-keytab/src/secret_ref.rs new file mode 100644 index 00000000..3dcc52f8 --- /dev/null +++ b/rust/krb5-provision-keytab/src/secret_ref.rs @@ -0,0 +1,44 @@ +use std::fmt::Display; + +use snafu::{OptionExt, Snafu}; +use stackable_operator::{ + k8s_openapi::api::core::v1::{Secret, SecretReference}, + kube::runtime::reflector::ObjectRef, +}; + +#[derive(Debug, Snafu)] +#[snafu(display("secret ref is missing {field}"))] +pub struct IncompleteSecretRef { + field: String, +} +#[derive(Debug, Clone)] +pub struct FullSecretRef { + pub name: String, + pub namespace: String, +} +impl TryFrom for FullSecretRef { + type Error = IncompleteSecretRef; + + fn try_from(secret_ref: SecretReference) -> Result { + Ok(Self { + name: secret_ref + .name + .context(IncompleteSecretRefSnafu { field: "name" })?, + namespace: secret_ref + .namespace + .context(IncompleteSecretRefSnafu { field: "namespace" })?, + }) + } +} +impl From<&FullSecretRef> for FullSecretRef { + fn from(pcr: &FullSecretRef) -> Self { + pcr.clone() + } +} +impl Display for FullSecretRef { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + ObjectRef::::new(&self.name) + .within(&self.namespace) + .fmt(f) + } +} diff --git a/rust/krb5-sys/build.rs b/rust/krb5-sys/build.rs index eff68c3e..f52a6ffb 100644 --- a/rust/krb5-sys/build.rs +++ b/rust/krb5-sys/build.rs @@ -19,6 +19,7 @@ fn main() { .allowlist_function("^profile_.*") .allowlist_var("KRB5_.*") .allowlist_var("KADM5_.*") + .allowlist_var("ENCTYPE_.*") .new_type_alias("krb5_error_code") .new_type_alias("kadm5_ret_t") .must_use_type("krb5_error_code") diff --git a/rust/krb5/Cargo.toml b/rust/krb5/Cargo.toml index 399f4c89..8ffa531f 100644 --- a/rust/krb5/Cargo.toml +++ b/rust/krb5/Cargo.toml @@ -12,3 +12,4 @@ publish = false [dependencies] krb5-sys = { path = "../krb5-sys" } +snafu = "0.7.4" diff --git a/rust/krb5/src/kadm5.rs b/rust/krb5/src/kadm5.rs index cd1ac34b..85679519 100644 --- a/rust/krb5/src/kadm5.rs +++ b/rust/krb5/src/kadm5.rs @@ -219,6 +219,6 @@ impl Drop for KeyDataVec<'_> { Error::from_ret(unsafe { krb5_sys::kadm5_free_kadm5_key_data(self.ctx.raw, self.key_count, self.raw) }) - .unwrap() + .expect("failed to destroy keydata vector") } } diff --git a/rust/krb5/src/lib.rs b/rust/krb5/src/lib.rs index 01ded581..767dcef6 100644 --- a/rust/krb5/src/lib.rs +++ b/rust/krb5/src/lib.rs @@ -2,17 +2,33 @@ //! //! The primary entry point is [`KrbContext`]. -use std::{ffi::CStr, fmt::Display}; +use std::{ + ffi::{c_int, CStr}, + fmt::{Debug, Display}, + ops::Deref, +}; use krb5_sys::krb5_kt_resolve; use profile::Profile; +use snafu::{ResultExt, Snafu}; pub mod kadm5; pub mod profile; +/// An error generated by libkrb5, or from interacting with it +#[derive(Debug, Snafu)] +pub enum Error { + #[snafu(display("{reason}"))] + Krb5 { reason: Krb5Error }, + #[snafu(display("{string_name} is too long"))] + StringTooLong { + source: std::num::TryFromIntError, + string_name: &'static str, + }, +} /// An error generated by libkrb5 #[derive(Debug)] -pub struct Error { +pub struct Krb5Error { message: String, pub code: krb5_sys::krb5_error_code, } @@ -36,12 +52,14 @@ impl Error { unsafe { krb5_sys::krb5_free_error_message(raw_ctx, c_msg) } rust_msg }; - Err(Self { message, code }) + Krb5Snafu { + reason: Krb5Error { message, code }, + } + .fail() } } } -impl std::error::Error for Error {} -impl Display for Error { +impl Display for Krb5Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result where { f.write_str(&self.message) } @@ -95,6 +113,21 @@ impl KrbContext { raw: principal, }) } + + /// Get the default realm configured for this context. + pub fn default_realm(&self) -> Result { + let mut realm: *mut i8 = std::ptr::null_mut(); + unsafe { + Error::from_call_result( + Some(self), + krb5_sys::krb5_get_default_realm(self.raw, &mut realm), + )?; + Ok(DefaultRealm { + ctx: self, + raw: realm, + }) + } + } } impl Drop for KrbContext { fn drop(&mut self) { @@ -104,6 +137,26 @@ impl Drop for KrbContext { } } +/// The default realm name for a [`KrbContext`]. +/// +/// Created by [`KrbContext::default_realm`]. +pub struct DefaultRealm<'a> { + ctx: &'a KrbContext, + raw: *const i8, +} +impl Deref for DefaultRealm<'_> { + type Target = CStr; + + fn deref(&self) -> &Self::Target { + unsafe { CStr::from_ptr(self.raw) } + } +} +impl Drop for DefaultRealm<'_> { + fn drop(&mut self) { + unsafe { krb5_sys::krb5_free_default_realm(self.ctx.raw, self.raw.cast_mut()) } + } +} + /// A parsed Kerberos principal name. /// /// Created by [`KrbContext::parse_principal_name`]. @@ -111,6 +164,46 @@ pub struct Principal<'a> { ctx: &'a KrbContext, raw: krb5_sys::krb5_principal, } +impl<'a> Principal<'a> { + /// The default salt when deriving keys for this principal. + pub fn default_salt(&self) -> Result, Error> { + unsafe { + let mut salt = std::mem::zeroed::(); + Error::from_call_result( + Some(self.ctx), + krb5_sys::krb5_principal2salt(self.ctx.raw, self.raw, &mut salt), + )?; + Ok(KrbData { + ctx: self.ctx, + raw: salt, + }) + } + } + + /// Converts the parsed principal back into a string representation. + /// + /// The [`Display`] instance is equivalent to `self.unparse(PrincipalUnparseOptions::default())`. + pub fn unparse(&self, options: PrincipalUnparseOptions) -> Result { + let mut raw_name = std::ptr::null_mut(); + unsafe { + Error::from_call_result( + Some(self.ctx), + krb5_sys::krb5_unparse_name_flags( + self.ctx.raw, + self.raw, + options.to_flags(), + &mut raw_name, + ), + )?; + }; + // We need to take ownership before freeing it + let name: String = unsafe { CStr::from_ptr(raw_name) } + .to_string_lossy() + .into_owned(); + unsafe { krb5_sys::krb5_free_unparsed_name(self.ctx.raw, raw_name) } + Ok(name) + } +} impl Drop for Principal<'_> { fn drop(&mut self) { unsafe { @@ -120,24 +213,8 @@ impl Drop for Principal<'_> { } impl Display for Principal<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut raw_name = std::ptr::null_mut(); - let unparse_result = unsafe { - Error::from_call_result( - Some(self.ctx), - krb5_sys::krb5_unparse_name(self.ctx.raw, self.raw, &mut raw_name), - ) - }; - match unparse_result { - Ok(()) => { - let write_result = { - let name = unsafe { CStr::from_ptr(raw_name) }.to_string_lossy(); - f.write_str(&name) - }; - unsafe { krb5_sys::krb5_free_unparsed_name(self.ctx.raw, raw_name) } - write_result - } - Err(_) => f.write_str("(invalid)"), - } + let name = self.unparse(PrincipalUnparseOptions::default()); + f.write_str(name.as_deref().unwrap_or("(invalid)")) } } impl From<&Principal<'_>> for String { @@ -146,6 +223,41 @@ impl From<&Principal<'_>> for String { } } +/// Optional settings for [`Principal::unparse`]. +#[derive(Default, Clone, Copy)] +pub struct PrincipalUnparseOptions { + /// Controls whether the realm is included. + pub realm: PrincipalRealmDisplayMode, + /// Special characters are not quoted in display mode, even if this would generate a principal string that cannot be parsed. + pub for_display: bool, +} + +/// See [`PrincipalUnparseOptions::realm`]. +#[derive(Default, Clone, Copy)] +pub enum PrincipalRealmDisplayMode { + /// The realm is always included. + #[default] + Always, + /// The realm is only included if it is not the default realm. + IfForeign, + /// The realm is never included. This may create ambiguity in multi-realm configurations. + Never, +} +impl PrincipalUnparseOptions { + fn to_flags(self) -> c_int { + let realm = match self.realm { + PrincipalRealmDisplayMode::Always => 0, + PrincipalRealmDisplayMode::IfForeign => krb5_sys::KRB5_PRINCIPAL_UNPARSE_SHORT as c_int, + PrincipalRealmDisplayMode::Never => krb5_sys::KRB5_PRINCIPAL_UNPARSE_NO_REALM as c_int, + }; + let for_display = match self.for_display { + true => krb5_sys::KRB5_PRINCIPAL_UNPARSE_DISPLAY as c_int, + false => 0, + }; + realm | for_display + } +} + /// A reference to a Kerberos keyblock. // SAFETY: 'a must not outlive the object that owns the `KeyblockRef` pub struct KeyblockRef<'a> { @@ -161,6 +273,7 @@ pub struct Keyblock<'a> { raw: *mut krb5_sys::krb5_keyblock, } impl<'a> Keyblock<'a> { + /// Create a new zero-initialized keyblock of a given size. pub fn new( ctx: &'a KrbContext, enctype: krb5_sys::krb5_enctype, @@ -174,25 +287,63 @@ impl<'a> Keyblock<'a> { )?; let mut kb = Self { ctx, raw: keyblock }; // krb5_init_keyblock does not guarantee that the keyblock is zeroed, so let's clear it ourselves to avoid leaks - kb.contents_mut().fill(0); + kb.contents_mut()?.fill(0); Ok(kb) } } + /// Derive a key from a given password. + /// + /// Some well-known `enctype` values are available in [`enctype`]. + /// + /// `salt` may be generated using [`Principal::default_salt`]. + pub fn from_password( + ctx: &'a KrbContext, + enctype: krb5_sys::krb5_enctype, + password: &CStr, + salt: &KrbData, + ) -> Result { + let kb = Self::new( + ctx, enctype, + // not that we have a reason to use a preinitialized keyblock, + // but `krb5_c_string_to_key` doesn't free or reuse an existing + // (non-null) keyblock's contents + 0, + )?; + let password_data = krb5_sys::krb5_data { + magic: krb5_sys::krb5_error_code(0), + length: password + .to_bytes() + .len() + .try_into() + .context(StringTooLongSnafu { + string_name: "password", + })?, + data: password.as_ptr().cast::().cast_mut(), + }; + unsafe { + Error::from_call_result( + Some(ctx), + krb5_sys::krb5_c_string_to_key(ctx.raw, enctype, &password_data, &salt.raw, kb.raw), + )?; + } + Ok(kb) + } + // SAFETY: we own raw, so it is valid for as long as the reference to &śelf - pub fn contents_mut(&mut self) -> &mut [u8] { + pub fn contents_mut(&mut self) -> Result<&mut [u8], Error> { unsafe { let raw = *self.raw; if raw.length > 0 { - std::slice::from_raw_parts_mut( + Ok(std::slice::from_raw_parts_mut( raw.contents, - raw.length - .try_into() - .expect("keyblock length must fit within usize"), - ) + raw.length.try_into().context(StringTooLongSnafu { + string_name: "keyblock", + })?, + )) } else { // contents are not allocated for length=0, but slice requires that the ptr is non-null and "valid" - &mut [] + Ok(&mut []) } } } @@ -215,6 +366,12 @@ impl<'a> Drop for Keyblock<'a> { } } +/// Well-known encryption types. This is not exhaustive. +pub mod enctype { + pub const AES256_CTS_HMAC_SHA1_96: krb5_sys::krb5_enctype = + krb5_sys::ENCTYPE_AES256_CTS_HMAC_SHA1_96 as i32; +} + /// A Kerberos keytab. pub struct Keytab<'a> { ctx: &'a KrbContext, @@ -268,3 +425,26 @@ impl Drop for Keytab<'_> { } } } + +/// Opaque Kerberos data +pub struct KrbData<'a> { + ctx: &'a KrbContext, + raw: krb5_sys::krb5_data, +} +impl Debug for KrbData<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let slice = unsafe { + std::slice::from_raw_parts( + self.raw.data.cast::(), + self.raw.length.try_into().unwrap(), + ) + }; + let s = std::str::from_utf8(slice).unwrap(); + Debug::fmt(s, f) + } +} +impl Drop for KrbData<'_> { + fn drop(&mut self) { + unsafe { krb5_sys::krb5_free_data_contents(self.ctx.raw, &mut self.raw) } + } +} diff --git a/rust/operator-binary/src/backend/dynamic.rs b/rust/operator-binary/src/backend/dynamic.rs index 078dc82f..bc0c7c0a 100644 --- a/rust/operator-binary/src/backend/dynamic.rs +++ b/rust/operator-binary/src/backend/dynamic.rs @@ -118,9 +118,7 @@ pub async fn from_class( KerberosProfile { realm_name, kdc, - kadmin_server: match admin { - crd::KerberosKeytabBackendAdmin::Mit { kadmin_server } => kadmin_server, - }, + admin, }, &admin_keytab_secret, admin_principal, diff --git a/rust/operator-binary/src/backend/kerberos_keytab.rs b/rust/operator-binary/src/backend/kerberos_keytab.rs index 7389e54f..a7531a22 100644 --- a/rust/operator-binary/src/backend/kerberos_keytab.rs +++ b/rust/operator-binary/src/backend/kerberos_keytab.rs @@ -10,7 +10,9 @@ use tokio::{ io::{AsyncReadExt, AsyncWriteExt}, }; -use crate::crd::{Hostname, InvalidKerberosPrincipal, KerberosPrincipal}; +use crate::crd::{ + Hostname, InvalidKerberosPrincipal, KerberosKeytabBackendAdmin, KerberosPrincipal, +}; use super::{pod_info::Address, SecretBackend, SecretBackendError, SecretContents}; @@ -59,7 +61,7 @@ impl SecretBackendError for Error { pub struct KerberosProfile { pub realm_name: Hostname, pub kdc: Hostname, - pub kadmin_server: Hostname, + pub admin: KerberosKeytabBackendAdmin, } pub struct KerberosKeytab { @@ -123,12 +125,19 @@ impl SecretBackend for KerberosKeytab { KerberosProfile { realm_name, kdc, - kadmin_server, + admin, }, admin_keytab, admin_principal, } = self; + let admin_server_clause = match admin { + KerberosKeytabBackendAdmin::Mit { kadmin_server } => { + format!(" admin_server = {kadmin_server}") + } + KerberosKeytabBackendAdmin::ActiveDirectory { .. } => String::new(), + }; + let tmp = tempdir().context(TempSetupSnafu)?; let profile = format!( r#" @@ -141,7 +150,7 @@ udp_preference_limit = 1 [realms] {realm_name} = {{ kdc = {kdc} - admin_server = {kadmin_server} +{admin_server_clause} }} [domain_realm] @@ -196,6 +205,24 @@ cluster.local = {realm_name} name: princ.to_string(), }) .collect(), + admin_backend: match admin { + KerberosKeytabBackendAdmin::Mit { .. } => { + stackable_krb5_provision_keytab::AdminBackend::Mit + } + KerberosKeytabBackendAdmin::ActiveDirectory { + ldap_server, + ldap_tls_ca_secret, + password_cache_secret, + user_distinguished_name, + schema_distinguished_name, + } => stackable_krb5_provision_keytab::AdminBackend::ActiveDirectory { + ldap_server: ldap_server.to_string(), + ldap_tls_ca_secret: ldap_tls_ca_secret.clone(), + password_cache_secret: password_cache_secret.clone(), + user_distinguished_name: user_distinguished_name.clone(), + schema_distinguished_name: schema_distinguished_name.clone(), + }, + }, }, ) .await diff --git a/rust/operator-binary/src/crd.rs b/rust/operator-binary/src/crd.rs index e87c5cd7..9a6778cf 100644 --- a/rust/operator-binary/src/crd.rs +++ b/rust/operator-binary/src/crd.rs @@ -1,4 +1,4 @@ -use std::{ffi::OsStr, fmt::Display}; +use std::{fmt::Display, ops::Deref}; use serde::{Deserialize, Serialize}; use snafu::Snafu; @@ -26,6 +26,7 @@ pub struct SecretClassSpec { #[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] #[serde(rename_all = "camelCase")] +#[allow(clippy::large_enum_variant)] pub enum SecretClassBackend { K8sSearch(K8sSearchBackend), AutoTls(AutoTlsBackend), @@ -75,6 +76,14 @@ pub struct KerberosKeytabBackend { pub enum KerberosKeytabBackendAdmin { #[serde(rename_all = "camelCase")] Mit { kadmin_server: Hostname }, + #[serde(rename_all = "camelCase")] + ActiveDirectory { + ldap_server: Hostname, + ldap_tls_ca_secret: SecretReference, + password_cache_secret: SecretReference, + user_distinguished_name: String, + schema_distinguished_name: String, + }, } #[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] @@ -111,6 +120,13 @@ impl Display for Hostname { f.write_str(&self.0) } } +impl Deref for Hostname { + type Target = str; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} #[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] #[serde(try_from = "String", into = "String")] @@ -150,8 +166,10 @@ impl Display for KerberosPrincipal { f.write_str(&self.0) } } -impl AsRef for KerberosPrincipal { - fn as_ref(&self) -> &OsStr { - self.0.as_ref() +impl Deref for KerberosPrincipal { + type Target = str; + + fn deref(&self) -> &Self::Target { + &self.0 } } diff --git a/tests/templates/kuttl/kerberos-ad/01-install-secretclass.yaml b/tests/templates/kuttl/kerberos-ad/01-install-secretclass.yaml new file mode 100644 index 00000000..c6f6ef27 --- /dev/null +++ b/tests/templates/kuttl/kerberos-ad/01-install-secretclass.yaml @@ -0,0 +1,49 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: yq eval '.metadata.name = "kerberos-" + strenv(NAMESPACE) | .spec.backend.kerberosKeytab |= (.adminKeytabSecret.namespace = strenv(NAMESPACE) | .admin.activeDirectory.passwordCacheSecret.namespace = .adminKeytabSecret.namespace | .admin.activeDirectory.ldapTlsCaSecret.namespace = .adminKeytabSecret.namespace)' secretclass.yaml | kubectl apply -f- +--- +apiVersion: v1 +kind: Secret +metadata: + name: secret-operator-keytab +data: + # Create by running "ktpass /princ stackable-secret-operator@SBLE.TEST /out secret-op.kt +rndPass /ptype KRB5_NT_PRINCIPAL /mapuser stackable-secret-operator@SBLE.TEST /crypto AES256-SHA1" + keytab: BQIAAABVAAEACVNCTEUuVEVTVAAZc3RhY2thYmxlLXNlY3JldC1vcGVyYXRvcgAAAAEAAAAAAwASACBnoTvrav0OVO4kjI9CDZkVRFht9nFV3l/GGuRzC3klrg== +--- +apiVersion: v1 +kind: Secret +metadata: + name: secret-operator-ad-passwords +# Will be populated by the operator +data: {} +--- +apiVersion: v1 +kind: Secret +metadata: + name: secret-operator-ad-ca +stringData: + # PEM-encoded certificate of the AD root CA + ca.crt: | + -----BEGIN CERTIFICATE----- + MIIDazCCAlOgAwIBAgIQO7eOmc5HFIBOjSAkCI0SODANBgkqhkiG9w0BAQsFADBI + MRQwEgYKCZImiZPyLGQBGRYEdGVzdDEUMBIGCgmSJomT8ixkARkWBHNibGUxGjAY + BgNVBAMTEXNibGUtU0JMRS1BRERTLUNBMB4XDTIzMDMyNzEyMzgwN1oXDTI4MDMy + NzEyNDgwN1owSDEUMBIGCgmSJomT8ixkARkWBHRlc3QxFDASBgoJkiaJk/IsZAEZ + FgRzYmxlMRowGAYDVQQDExFzYmxlLVNCTEUtQUREUy1DQTCCASIwDQYJKoZIhvcN + AQEBBQADggEPADCCAQoCggEBAM1sskWrUPrVIQ0Ulwq2XLhcSthHbnCSCeqrlT+z + GPSeMd5QbL9hzo0iP1a1NBxNCbkG1xQ6otDYEGH7I7soV2YjafPJ34qalsejXeQb + HPB56ZQ9ue0QKq5I8STAkewYNdE9NLD9O4wc0r0gU3WqDXQumwMvDSGgMoJ5oCJ8 + pZaJyF8HP6v1FRK0h9BHf+pau0ZC9a/2yhPGX/y4tuka4SFE/4RSc5K2xDdCLTEf + EfHovT4zDIx6ErDmVTgLJ0e/UXWoO1v+WJz3gBcrvbwZrKnBs7CUqza26RCApgtd + tlCX0zplT3LjmFENTZO+nN1KOoCCtE3/xOAqgZsLtof4NAUCAwEAAaNRME8wCwYD + VR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFC7kiVMA8eKGHp8/ + Mozb9c1JYunUMBAGCSsGAQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBCwUAA4IBAQCQ + 6oL/8jA6ie39dAyJLMIv8U3+pDokAUCkJplc6COf537kchLrF24evFvZi8+aA3/s + PFntxXJsahcUXi8hBbZLHj+ZdmN2fjq0CE/0sRiHS2C/LRuskLTcVISELLxoiynn + SOR/zeC6mUgFdGhnV1w84cxoeZV8YD3cdrlmFcD0b2kjm3i2t8ifapJENLFllzRW + spnQeRVimyvwH1s4U8qZ/OcR4c3P37kczEuQ165tpjFVfmw7a/OCMFa+olP4bP18 + AojYiwU57w90WTveuE76qjK8Q9BGj9C1vjk6xPXM4aS6ga5kwQVmiAYlPmogooyz + EToGeyp1QmS66b5Se18l + -----END CERTIFICATE----- diff --git a/tests/templates/kuttl/kerberos-ad/02-assert.yaml b/tests/templates/kuttl/kerberos-ad/02-assert.yaml new file mode 100644 index 00000000..7055988e --- /dev/null +++ b/tests/templates/kuttl/kerberos-ad/02-assert.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 300 +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: krb5-client +status: + succeeded: 5 diff --git a/tests/templates/kuttl/kerberos-ad/02-kinit-client.yaml b/tests/templates/kuttl/kerberos-ad/02-kinit-client.yaml new file mode 100644 index 00000000..44f70f57 --- /dev/null +++ b/tests/templates/kuttl/kerberos-ad/02-kinit-client.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: yq eval '.spec.template.spec.volumes |= map (.ephemeral.volumeClaimTemplate.metadata.annotations["secrets.stackable.tech/class"] = "kerberos-" + strenv(NAMESPACE))' kinit-client.yaml | kubectl -n $NAMESPACE apply -f- +--- +apiVersion: v1 +kind: Service +metadata: + name: krb5-client +spec: + ports: + - name: bogus + port: 9999 diff --git a/tests/templates/kuttl/kerberos-ad/kinit-client.yaml b/tests/templates/kuttl/kerberos-ad/kinit-client.yaml new file mode 100644 index 00000000..a6fc9e45 --- /dev/null +++ b/tests/templates/kuttl/kerberos-ad/kinit-client.yaml @@ -0,0 +1,65 @@ +# must be applied by a command step, since the secretclass name depends on the (random) test namespace +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: krb5-client +spec: + completions: 5 + parallelism: 5 + template: + spec: + containers: + - name: client + image: docker.stackable.tech/stackable/krb5:1.18.2-stackable23.4.0-rc1 + command: + - bash + args: + - -c + - | + set -euo pipefail + echo listing keytab contents + klist -k /stackable/krb/keytab -teKC + echo kiniting node + kinit -kt /stackable/krb/keytab -p HTTP/$NODE_NAME + echo kiniting service + kinit -kt /stackable/krb/keytab -p HTTP/krb5-client.$NAMESPACE.svc.cluster.local + echo kiniting pod + kinit -kt /stackable/krb/keytab -p HTTP/$POD_NAME.krb5-client.$NAMESPACE.svc.cluster.local + env: + - name: KRB5_CONFIG + value: /stackable/krb/krb5.conf + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + volumeMounts: + - mountPath: /stackable/krb + name: kerberos + volumes: + - name: kerberos + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + # overridden by yq + secrets.stackable.tech/class: kerberos-$NAMESPACE + secrets.stackable.tech/scope: node,pod + spec: + storageClassName: secrets.stackable.tech + accessModes: + - ReadWriteOnce + resources: + requests: + storage: "1" + restartPolicy: Never + terminationGracePeriodSeconds: 0 + subdomain: krb5-client diff --git a/tests/templates/kuttl/kerberos-ad/secretclass.yaml b/tests/templates/kuttl/kerberos-ad/secretclass.yaml new file mode 100644 index 00000000..fd45ceaa --- /dev/null +++ b/tests/templates/kuttl/kerberos-ad/secretclass.yaml @@ -0,0 +1,31 @@ +# must be applied by a command step, since the reference namespace depends on the (random) test namespace +--- +apiVersion: secrets.stackable.tech/v1alpha1 +kind: SecretClass +metadata: + # overridden by yq + name: kerberos-$NAMESPACE +spec: + backend: + kerberosKeytab: + realmName: SBLE.TEST + kdc: sble-adds.sble.test + admin: + activeDirectory: + # ldapServer must match the AD Domain Controller's FQDN or GSSAPI authn will fail + # You may need to set AD as your fallback DNS resolver in your Kube DNS Corefile + ldapServer: sble-adds.sble.test + ldapTlsCaSecret: + # namespace: default + name: secret-operator-ad-ca + passwordCacheSecret: + # namespace: default + name: secret-operator-ad-passwords + # Subfolder must be created manually, of type "msDS-ShadowPrincipalContainer" + userDistinguishedName: CN=Stackable,CN=Users,DC=sble,DC=test + schemaDistinguishedName: CN=Schema,CN=Configuration,DC=sble,DC=test + adminKeytabSecret: + # Created by AD administrator + # namespace: default + name: secret-operator-keytab + adminPrincipal: stackable-secret-operator diff --git a/tests/templates/kuttl/kerberos/01-install-kdc.yaml b/tests/templates/kuttl/kerberos/01-install-kdc.yaml index 76ddf35f..23e97e6e 100644 --- a/tests/templates/kuttl/kerberos/01-install-kdc.yaml +++ b/tests/templates/kuttl/kerberos/01-install-kdc.yaml @@ -2,7 +2,7 @@ apiVersion: kuttl.dev/v1beta1 kind: TestStep commands: - - script: yq eval '.spec.backend.kerberosKeytab |= (.adminKeytabSecret.namespace = strenv(NAMESPACE) | .kdc = "krb5-kdc." + strenv(NAMESPACE) + ".svc.cluster.local" | .admin.mit.kadminServer = .kdc)' secretclass.yaml | kubectl apply -f- + - script: yq eval '.metadata.name = "kerberos-" + strenv(NAMESPACE) | .spec.backend.kerberosKeytab |= (.adminKeytabSecret.namespace = strenv(NAMESPACE) | .kdc = "krb5-kdc." + strenv(NAMESPACE) + ".svc.cluster.local" | .admin.mit.kadminServer = .kdc)' secretclass.yaml | kubectl apply -f- --- apiVersion: apps/v1 kind: StatefulSet diff --git a/tests/templates/kuttl/kerberos/02-kinit-client.yaml b/tests/templates/kuttl/kerberos/02-kinit-client.yaml index 9a17e722..44f70f57 100644 --- a/tests/templates/kuttl/kerberos/02-kinit-client.yaml +++ b/tests/templates/kuttl/kerberos/02-kinit-client.yaml @@ -1,64 +1,8 @@ --- -apiVersion: batch/v1 -kind: Job -metadata: - name: krb5-client -spec: - template: - spec: - containers: - - name: client - image: docker.stackable.tech/stackable/krb5:1.18.2-stackable23.4.0-rc1 - command: - - bash - args: - - -c - - | - set -euo pipefail - echo listing keytab contents - klist -k /stackable/krb/keytab -teKC - echo kiniting node - kinit -kt /stackable/krb/keytab -p HTTP/$NODE_NAME - echo kiniting service - kinit -kt /stackable/krb/keytab -p HTTP/krb5-client.$NAMESPACE.svc.cluster.local - echo kiniting pod - kinit -kt /stackable/krb/keytab -p HTTP/$POD_NAME.krb5-client.$NAMESPACE.svc.cluster.local - env: - - name: KRB5_CONFIG - value: /stackable/krb/krb5.conf - - name: NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName - volumeMounts: - - mountPath: /stackable/krb - name: kerberos - volumes: - - name: kerberos - ephemeral: - volumeClaimTemplate: - metadata: - annotations: - secrets.stackable.tech/class: kuttl-secret-operator-kerberos-kerberos - secrets.stackable.tech/scope: node,pod - spec: - storageClassName: secrets.stackable.tech - accessModes: - - ReadWriteOnce - resources: - requests: - storage: "1" - restartPolicy: Never - terminationGracePeriodSeconds: 0 - subdomain: krb5-client +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: yq eval '.spec.template.spec.volumes |= map (.ephemeral.volumeClaimTemplate.metadata.annotations["secrets.stackable.tech/class"] = "kerberos-" + strenv(NAMESPACE))' kinit-client.yaml | kubectl -n $NAMESPACE apply -f- --- apiVersion: v1 kind: Service diff --git a/tests/templates/kuttl/kerberos/03-cleanup.yaml b/tests/templates/kuttl/kerberos/03-cleanup.yaml deleted file mode 100644 index 6ca97f26..00000000 --- a/tests/templates/kuttl/kerberos/03-cleanup.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# secretclasses are not namespaced, so we need to clean them up ourselves ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -delete: - - apiVersion: secrets.stackable.tech/v1alpha1 - kind: SecretClass - name: kuttl-secret-operator-kerberos-kerberos diff --git a/tests/templates/kuttl/kerberos/kinit-client.yaml b/tests/templates/kuttl/kerberos/kinit-client.yaml new file mode 100644 index 00000000..760569e1 --- /dev/null +++ b/tests/templates/kuttl/kerberos/kinit-client.yaml @@ -0,0 +1,63 @@ +# must be applied by a command step, since the reference namespace depends on the (random) test namespace +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: krb5-client +spec: + template: + spec: + containers: + - name: client + image: docker.stackable.tech/stackable/krb5:1.18.2-stackable23.4.0-rc1 + command: + - bash + args: + - -c + - | + set -euo pipefail + echo listing keytab contents + klist -k /stackable/krb/keytab -teKC + echo kiniting node + kinit -kt /stackable/krb/keytab -p HTTP/$NODE_NAME + echo kiniting service + kinit -kt /stackable/krb/keytab -p HTTP/krb5-client.$NAMESPACE.svc.cluster.local + echo kiniting pod + kinit -kt /stackable/krb/keytab -p HTTP/$POD_NAME.krb5-client.$NAMESPACE.svc.cluster.local + env: + - name: KRB5_CONFIG + value: /stackable/krb/krb5.conf + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + volumeMounts: + - mountPath: /stackable/krb + name: kerberos + volumes: + - name: kerberos + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + # overridden by yq + secrets.stackable.tech/class: kerberos-$NAMESPACE + secrets.stackable.tech/scope: node,pod + spec: + storageClassName: secrets.stackable.tech + accessModes: + - ReadWriteOnce + resources: + requests: + storage: "1" + restartPolicy: Never + terminationGracePeriodSeconds: 0 + subdomain: krb5-client diff --git a/tests/templates/kuttl/kerberos/secretclass.yaml b/tests/templates/kuttl/kerberos/secretclass.yaml index 7e7a7f32..a73d6023 100644 --- a/tests/templates/kuttl/kerberos/secretclass.yaml +++ b/tests/templates/kuttl/kerberos/secretclass.yaml @@ -3,7 +3,8 @@ apiVersion: secrets.stackable.tech/v1alpha1 kind: SecretClass metadata: - name: kuttl-secret-operator-kerberos-kerberos + # overridden by yq + name: kerberos-$NAMESPACE spec: backend: kerberosKeytab: diff --git a/tests/test-definition.yaml b/tests/test-definition.yaml index 78a2d3ee..a4173d46 100644 --- a/tests/test-definition.yaml +++ b/tests/test-definition.yaml @@ -3,3 +3,6 @@ dimensions: [] tests: - name: kerberos dimensions: [] + # # Requires manual connection to an AD cluster + # - name: kerberos-ad + # dimensions: []