diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c98a521..8e47ea81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,13 @@ - Improved CRD docs ([#431]). - Helm: support labels in values.yaml ([#448]). +- Add support for OpenID Connect ([#423]). ### Fixed - BREAKING: Fixed various issues in the CRD structure. `clusterConfig.credentialsSecret` is now mandatory ([#429]). +[#423]: https://github.com/stackabletech/superset-operator/pull/423 [#429]: https://github.com/stackabletech/superset-operator/pull/429 [#431]: https://github.com/stackabletech/superset-operator/pull/431 [#448]: https://github.com/stackabletech/superset-operator/pull/448 diff --git a/Cargo.lock b/Cargo.lock index 29bd6efe..0088e18b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,21 +19,22 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" dependencies = [ "cfg-if", "getrandom", "once_cell", "version_check", + "zerocopy", ] [[package]] name = "aho-corasick" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] @@ -61,9 +62,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.1" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6cd65a4b849ace0b7f6daeebcc1a1d111282227ca745458c61dbf670e52a597" +checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" dependencies = [ "anstyle", "anstyle-parse", @@ -81,47 +82,47 @@ checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.0" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0238ca56c96dfa37bdf7c373c8886dd591322500aceeeccdb2216fe06dc2f796" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" [[package]] name = "async-trait" -version = "0.1.73" +version = "0.1.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.46", ] [[package]] @@ -158,9 +159,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.21.4" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] name = "bit-set" @@ -185,9 +186,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "block-buffer" @@ -217,9 +218,9 @@ checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" @@ -265,14 +266,14 @@ dependencies = [ "iana-time-zone", "num-traits", "serde", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] name = "clap" -version = "4.4.6" +version = "4.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" +checksum = "dcfab8ba68f3668e89f6ff60f5b205cea56aa7b769451a59f34b8682f51c056d" dependencies = [ "clap_builder", "clap_derive", @@ -280,9 +281,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.6" +version = "4.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" +checksum = "fb7fb5e4e979aec3be7791562fcba452f94ad85e954da024396433e0e25a79e9" dependencies = [ "anstream", "anstyle", @@ -292,21 +293,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.2" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.46", ] [[package]] name = "clap_lex" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "colorchoice" @@ -316,18 +317,18 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "const_format" -version = "0.2.31" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c990efc7a285731f9a4378d81aff2f0e85a2c8781a05ef0f8baa8dac54d0ff48" +checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" dependencies = [ "const_format_proc_macros", ] [[package]] name = "const_format_proc_macros" -version = "0.2.31" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e026b6ce194a874cb9cf32cd5772d1ef9767cc8fcb5765948d74f37a9d8b2bf6" +checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" dependencies = [ "proc-macro2", "quote", @@ -336,9 +337,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -346,24 +347,24 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" dependencies = [ "libc", ] [[package]] name = "crossbeam-channel" -version = "0.5.8" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +checksum = "82a9b73a36529d9c47029b9fb3a6f0ea3cc916a261195352ba19e770fc1748b2" dependencies = [ "cfg-if", "crossbeam-utils", @@ -371,9 +372,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c" dependencies = [ "cfg-if", ] @@ -409,7 +410,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.39", + "syn 2.0.46", ] [[package]] @@ -420,7 +421,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.39", + "syn 2.0.46", ] [[package]] @@ -431,7 +432,7 @@ checksum = "4e018fccbeeb50ff26562ece792ed06659b9c2dae79ece77c4456bb10d9bf79b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.46", ] [[package]] @@ -477,9 +478,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d2f3407d9a573d666de4b5bdf10569d73ca9478087346697dcbae6244bfbcd" +checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" [[package]] name = "either" @@ -529,9 +530,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] @@ -544,9 +545,9 @@ checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" [[package]] name = "futures" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -559,9 +560,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -569,15 +570,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", @@ -586,38 +587,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.46", ] [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures 0.1.31", "futures-channel", @@ -644,9 +645,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "libc", @@ -655,9 +656,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "git2" @@ -680,9 +681,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "hashbrown" -version = "0.14.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ "ahash", "allocator-api2", @@ -702,18 +703,18 @@ checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "home" -version = "0.5.5" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "http" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" dependencies = [ "bytes", "fnv", @@ -722,9 +723,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", "http", @@ -751,9 +752,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.27" +version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", @@ -765,7 +766,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "socket2", "tokio", "tower-service", "tracing", @@ -774,9 +775,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http", @@ -802,16 +803,16 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.57" +version = "0.1.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows-core", ] [[package]] @@ -831,9 +832,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -872,9 +873,9 @@ checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "java-properties" @@ -889,27 +890,27 @@ dependencies = [ [[package]] name = "jobserver" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" dependencies = [ "wasm-bindgen", ] [[package]] name = "json-patch" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f7765dccf8c39c3a470fc694efe322969d791e713ca46bc7b5c506886157572" +checksum = "55ff1e1486799e3f64129f8ccad108b38290df9cd7015cd31bed17239f0789d6" dependencies = [ "serde", "serde_json", @@ -918,14 +919,16 @@ dependencies = [ ] [[package]] -name = "jsonpath_lib" -version = "0.3.0" +name = "jsonpath-rust" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaa63191d68230cccb81c5aa23abd53ed64d83337cacbb25a7b8c7979523774f" +checksum = "06cc127b7c3d270be504572364f9569761a180b981919dd0d87693a7f5fb7829" dependencies = [ - "log", - "serde", + "pest", + "pest_derive", + "regex", "serde_json", + "thiserror", ] [[package]] @@ -945,9 +948,9 @@ dependencies = [ [[package]] name = "kube" -version = "0.87.1" +version = "0.87.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34392aea935145070dcd5b39a6dea689ac6534d7d117461316c3d157b1d0fc3" +checksum = "3499c8d60c763246c7a213f51caac1e9033f46026904cb89bc8951ae8601f26e" dependencies = [ "k8s-openapi", "kube-client", @@ -958,22 +961,22 @@ dependencies = [ [[package]] name = "kube-client" -version = "0.87.1" +version = "0.87.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7266548b9269d9fa19022620d706697e64f312fb2ba31b93e6986453fcc82c92" +checksum = "033450dfa0762130565890dadf2f8835faedf749376ca13345bcd8ecd6b5f29f" dependencies = [ "base64", "bytes", "chrono", "either", - "futures 0.3.28", + "futures 0.3.30", "home", "http", "http-body", "hyper", "hyper-rustls", "hyper-timeout", - "jsonpath_lib", + "jsonpath-rust", "k8s-openapi", "kube-core", "pem", @@ -994,9 +997,9 @@ dependencies = [ [[package]] name = "kube-core" -version = "0.87.1" +version = "0.87.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8321c315b96b59f59ef6b33f604b84b905ab8f9ff114a4f909d934c520227b1" +checksum = "b5bba93d054786eba7994d03ce522f368ef7d48c88a1826faa28478d85fb63ae" dependencies = [ "chrono", "form_urlencoded", @@ -1012,28 +1015,28 @@ dependencies = [ [[package]] name = "kube-derive" -version = "0.87.1" +version = "0.87.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54591e1f37fc329d412c0fdaced010cc1305b546a39f283fc51700f8fb49421" +checksum = "91e98dd5e5767c7b894c1f0e41fd628b145f808e981feb8b08ed66455d47f1a4" dependencies = [ "darling", "proc-macro2", "quote", "serde_json", - "syn 2.0.39", + "syn 2.0.46", ] [[package]] name = "kube-runtime" -version = "0.87.1" +version = "0.87.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e511e2c1a368d9d4bf6e70db58197e535d818df355b5a2007a8aeb17a370a8ba" +checksum = "2d8893eb18fbf6bb6c80ef6ee7dd11ec32b1dc3c034c988ac1b3a84d46a230ae" dependencies = [ "ahash", "async-trait", "backoff", "derivative", - "futures 0.3.28", + "futures 0.3.30", "hashbrown", "json-patch", "k8s-openapi", @@ -1057,9 +1060,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.148" +version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "libgit2-sys" @@ -1087,9 +1090,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -1112,9 +1115,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.6.3" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "mime" @@ -1133,13 +1136,13 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "wasi", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1154,9 +1157,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] @@ -1173,18 +1176,18 @@ dependencies = [ [[package]] name = "object" -version = "0.32.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl-probe" @@ -1257,9 +1260,9 @@ dependencies = [ [[package]] name = "ordered-float" -version = "2.10.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" dependencies = [ "num-traits", ] @@ -1291,22 +1294,22 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] name = "pem" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3163d2912b7c3b52d651a055f2c7eec9ba5cd22d26ef75b8dd3a59980b185923" +checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310" dependencies = [ "base64", "serde", @@ -1314,9 +1317,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" @@ -1349,7 +1352,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.46", ] [[package]] @@ -1380,7 +1383,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.46", ] [[package]] @@ -1397,9 +1400,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" [[package]] name = "ppv-lite86" @@ -1409,9 +1412,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.67" +version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +checksum = "2de98502f212cfcea8d0bb305bd0f49d7ebdd75b64ba0a68f937d888f4e0d6db" dependencies = [ "unicode-ident", ] @@ -1434,9 +1437,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -1473,23 +1476,23 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.3.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "regex" -version = "1.9.5" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.3.8", - "regex-syntax 0.7.5", + "regex-automata 0.4.3", + "regex-syntax 0.8.2", ] [[package]] @@ -1503,13 +1506,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.8" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.5", + "regex-syntax 0.8.2", ] [[package]] @@ -1520,23 +1523,22 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "ring" -version = "0.16.20" +version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" dependencies = [ "cc", + "getrandom", "libc", - "once_cell", "spin", "untrusted", - "web-sys", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -1547,9 +1549,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustls" -version = "0.21.7" +version = "0.21.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" dependencies = [ "log", "ring", @@ -1571,18 +1573,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ "base64", ] [[package]] name = "rustls-webpki" -version = "0.101.6" +version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ "ring", "untrusted", @@ -1596,24 +1598,24 @@ checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "schemars" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f7b0ce13155372a76ee2e1c5ffba1fe61ede73fbea5630d61eee6fac4929c0c" +checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" dependencies = [ "dyn-clone", "schemars_derive", @@ -1623,9 +1625,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c" +checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" dependencies = [ "proc-macro2", "quote", @@ -1641,9 +1643,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sct" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ "ring", "untrusted", @@ -1684,18 +1686,18 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.188" +version = "1.0.194" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "0b114498256798c94a0689e1a15fec6005dee8ac1f41de56404b67afc2a4b773" dependencies = [ "serde_derive", ] @@ -1706,19 +1708,19 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" dependencies = [ - "ordered-float 2.10.0", + "ordered-float 2.10.1", "serde", ] [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.194" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "a3385e45322e8f9931410f01b3031ec534c3947d0e94c18049af4d9f9907d4e0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.46", ] [[package]] @@ -1734,11 +1736,10 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "6fbd975230bada99c8bb618e0c365c2eefa219158d5c6c29610fd09ff1833257" dependencies = [ - "indexmap", "itoa", "ryu", "serde", @@ -1746,18 +1747,18 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" dependencies = [ "serde", ] [[package]] name = "serde_yaml" -version = "0.9.25" +version = "0.9.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574" +checksum = "b1bf28c79a99f70ee1f1d83d10c875d2e70618417fda01ad1785e027579d9d38" dependencies = [ "indexmap", "itoa", @@ -1779,9 +1780,9 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b21f559e07218024e7e9f90f96f601825397de0e25420135f7f952453fed0b" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] @@ -1806,9 +1807,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.1" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "snafu" @@ -1855,34 +1856,24 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "socket2" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "spin" -version = "0.5.2" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "stackable-operator" -version = "0.61.0" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=0.61.0#ddc57addbc741e3977b1589e553164505a59f639" +version = "0.64.0" +source = "git+https://github.com/stackabletech/operator-rs.git?tag=0.64.0#d988822e7af3d363d52fb35643f0c6ed933f340a" dependencies = [ "chrono", "clap", @@ -1891,7 +1882,7 @@ dependencies = [ "derivative", "dockerfile-parser", "either", - "futures 0.3.28", + "futures 0.3.30", "json-patch", "k8s-openapi", "kube", @@ -1920,19 +1911,20 @@ dependencies = [ [[package]] name = "stackable-operator-derive" -version = "0.61.0" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=0.61.0#ddc57addbc741e3977b1589e553164505a59f639" +version = "0.64.0" +source = "git+https://github.com/stackabletech/operator-rs.git?tag=0.64.0#d988822e7af3d363d52fb35643f0c6ed933f340a" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.46", ] [[package]] name = "stackable-superset-crd" version = "0.0.0-dev" dependencies = [ + "indoc", "product-config", "serde", "serde_json", @@ -1940,6 +1932,7 @@ dependencies = [ "snafu 0.7.5", "stackable-operator", "strum", + "tokio", "tracing", ] @@ -1951,7 +1944,7 @@ dependencies = [ "built", "clap", "fnv", - "futures 0.3.28", + "futures 0.3.30", "indoc", "product-config", "serde", @@ -1980,15 +1973,15 @@ dependencies = [ [[package]] name = "strum_macros" -version = "0.25.2" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" dependencies = [ "heck", "proc-macro2", "quote", "rustversion", - "syn 2.0.39", + "syn 2.0.46", ] [[package]] @@ -2004,9 +1997,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "89456b690ff72fddcecf231caedbe615c59480c93358a93dfae7fc29e3ebbf0e" dependencies = [ "proc-macro2", "quote", @@ -2015,22 +2008,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.49" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.49" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.46", ] [[package]] @@ -2061,7 +2054,7 @@ dependencies = [ "byteorder", "integer-encoding", "log", - "ordered-float 2.10.0", + "ordered-float 2.10.1", "threadpool", ] @@ -2082,9 +2075,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.32.0" +version = "1.35.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" dependencies = [ "backtrace", "bytes", @@ -2094,9 +2087,9 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.4", + "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2111,13 +2104,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.46", ] [[package]] @@ -2143,9 +2136,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", @@ -2170,9 +2163,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" dependencies = [ "serde", ] @@ -2214,7 +2207,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" dependencies = [ "base64", - "bitflags 2.4.0", + "bitflags 2.4.1", "bytes", "futures-core", "futures-util", @@ -2242,11 +2235,10 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "log", "pin-project-lite", "tracing-attributes", @@ -2255,36 +2247,25 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.46", ] [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", "valuable", ] -[[package]] -name = "tracing-log" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" -dependencies = [ - "lazy_static", - "log", - "tracing-core", -] - [[package]] name = "tracing-log" version = "0.2.0" @@ -2309,16 +2290,16 @@ dependencies = [ "smallvec", "tracing", "tracing-core", - "tracing-log 0.2.0", + "tracing-log", "tracing-subscriber", "web-time", ] [[package]] name = "tracing-subscriber" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers", "nu-ansi-term", @@ -2329,7 +2310,7 @@ dependencies = [ "thread_local", "tracing", "tracing-core", - "tracing-log 0.1.3", + "tracing-log", ] [[package]] @@ -2343,9 +2324,9 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" @@ -2361,9 +2342,9 @@ checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" [[package]] name = "unicode-ident" @@ -2388,21 +2369,21 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "unsafe-libyaml" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" +checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" [[package]] name = "untrusted" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", @@ -2456,9 +2437,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2466,24 +2447,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.46", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2491,32 +2472,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.46", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" - -[[package]] -name = "web-sys" -version = "0.3.64" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" -dependencies = [ - "js-sys", - "wasm-bindgen", -] +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" [[package]] name = "web-time" @@ -2551,12 +2522,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows" -version = "0.48.0" +name = "windows-core" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets", + "windows-targets 0.52.0", ] [[package]] @@ -2565,7 +2536,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", ] [[package]] @@ -2574,13 +2554,28 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] @@ -2589,47 +2584,89 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "winnow" -version = "0.5.15" +version = "0.5.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +checksum = "97a4882e6b134d6c28953a387571f1acdd3496830d5e36c5e3a1075580ea641c" dependencies = [ "memchr", ] @@ -2640,8 +2677,28 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.46", +] + [[package]] name = "zeroize" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/Cargo.toml b/Cargo.toml index a89c51cf..a1255b0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_yaml = "0.9" snafu = "0.7" -stackable-operator = { git = "https://github.com/stackabletech/operator-rs.git", tag = "0.61.0" } +stackable-operator = { git = "https://github.com/stackabletech/operator-rs.git", tag = "0.64.0" } strum = { version = "0.25", features = ["derive"] } tokio = { version = "1.29", features = ["full"] } tracing = "0.1" diff --git a/deploy/helm/superset-operator/crds/crds.yaml b/deploy/helm/superset-operator/crds/crds.yaml index 2a6a87f8..3a19be6d 100644 --- a/deploy/helm/superset-operator/crds/crds.yaml +++ b/deploy/helm/superset-operator/crds/crds.yaml @@ -30,13 +30,31 @@ spec: properties: authentication: default: [] - description: The Superset [authentication](https://docs.stackable.tech/home/nightly/superset/usage-guide/security) settings. Currently the underlying Flask App Builder only supports one authentication mechanism at a time. This means the operator will error out if multiple references to an AuthenticationClass are provided. + description: List of AuthenticationClasses used to authenticate users. items: properties: authenticationClass: - description: Name of the [AuthenticationClass](https://docs.stackable.tech/home/nightly/concepts/authentication.html#authenticationclass) used to authenticate the users. At the moment only LDAP is supported. If not specified the default authentication (AUTH_DB) will be used. - nullable: true + description: A name/key which references an authentication class. To get the concrete [`AuthenticationClass`], we must resolve it. This resolution can be achieved by using [`ClientAuthenticationDetails::resolve_class`]. type: string + oidc: + description: |- + This field contains authentication provider specific configuration. + + Use [`ClientAuthenticationDetails::oidc_or_error`] to get the value or report an error to the user. + nullable: true + properties: + clientCredentialsSecret: + description: A reference to the OIDC client credentials secret. The secret contains the client id and secret. + type: string + extraScopes: + default: [] + description: An optional list of extra scopes which get merged with the scopes defined in the [`AuthenticationClass`]. + items: + type: string + type: array + required: + - clientCredentialsSecret + type: object syncRolesAt: default: Registration description: If we should replace ALL the user's roles each login, or only on registration. Gets mapped to `AUTH_ROLES_SYNC_AT_LOGIN` @@ -52,13 +70,15 @@ spec: default: Public description: This role will be given in addition to any AUTH_ROLES_MAPPING. Gets mapped to `AUTH_USER_REGISTRATION_ROLE` type: string + required: + - authenticationClass type: object type: array clusterOperation: default: - stopped: false reconciliationPaused: false - description: '[Cluster operations](https://docs.stackable.tech/home/nightly/concepts/operations/cluster_operations) properties, allow stopping the product instance as well as pausing reconciliation.' + stopped: false + description: Cluster operations like pause reconciliation or cluster stop. properties: reconciliationPaused: default: false @@ -160,10 +180,10 @@ spec: properties: affinity: default: - podAffinity: null - podAntiAffinity: null nodeAffinity: null nodeSelector: null + podAffinity: null + podAntiAffinity: null description: These configuration settings control [Pod placement](https://docs.stackable.tech/home/nightly/concepts/operations/pod_placement). properties: nodeAffinity: @@ -636,8 +656,8 @@ spec: type: string logging: default: - enableVectorAgent: null containers: {} + enableVectorAgent: null description: Logging configuration, learn more in the [logging concept documentation](https://docs.stackable.tech/home/nightly/concepts/logging). properties: containers: @@ -720,19 +740,19 @@ spec: type: object resources: default: + cpu: + max: null + min: null memory: limit: null runtimeLimits: {} - cpu: - min: null - max: null storage: {} description: CPU and memory limits for Superset pods properties: cpu: default: - min: null max: null + min: null properties: max: description: The maximum amount of CPU cores that can be requested by Pods. Equivalent to the `limit` for Pod resource configuration. Cores are specified either as a decimal point number or as milli units. For example:`1.5` will be 1.5 cores, also written as `1500m`. @@ -3623,10 +3643,10 @@ spec: properties: affinity: default: - podAffinity: null - podAntiAffinity: null nodeAffinity: null nodeSelector: null + podAffinity: null + podAntiAffinity: null description: These configuration settings control [Pod placement](https://docs.stackable.tech/home/nightly/concepts/operations/pod_placement). properties: nodeAffinity: @@ -4099,8 +4119,8 @@ spec: type: string logging: default: - enableVectorAgent: null containers: {} + enableVectorAgent: null description: Logging configuration, learn more in the [logging concept documentation](https://docs.stackable.tech/home/nightly/concepts/logging). properties: containers: @@ -4183,19 +4203,19 @@ spec: type: object resources: default: + cpu: + max: null + min: null memory: limit: null runtimeLimits: {} - cpu: - min: null - max: null storage: {} description: CPU and memory limits for Superset pods properties: cpu: default: - min: null max: null + min: null properties: max: description: The maximum amount of CPU cores that can be requested by Pods. Equivalent to the `limit` for Pod resource configuration. Cores are specified either as a decimal point number or as milli units. For example:`1.5` will be 1.5 cores, also written as `1500m`. diff --git a/docs/modules/superset/pages/usage-guide/security.adoc b/docs/modules/superset/pages/usage-guide/security.adoc index 9de5a623..b26a30fb 100644 --- a/docs/modules/superset/pages/usage-guide/security.adoc +++ b/docs/modules/superset/pages/usage-guide/security.adoc @@ -1,14 +1,25 @@ = Security == [[authentication]]Authentication -Every user has to be authenticated before using Superset: there are several ways in which this can be set up. -=== Webinterface -The default setting is to manually set up users via the Webinterface. +Every user has to be authenticated before using Superset: +there are several ways in which this can be set up. + +[IMPORTANT] +.Multiple authentication methods +==== +Only one authentication method is supported at a time, and in case of LDAP, only one authentication class is allowed. +This means, it is not possible to configure both LDAP and OIDC authentication methods at the same time, but *it is* possible to configure multiple OIDC classes *or* one LDAP authentication class. +==== + +=== Superset database + +The default setting is to manually set up users via the web interface where they are stored in the database attached to Superset. === LDAP -Superset supports xref:home:concepts:authentication.adoc[authentication] of users against an LDAP server. This requires setting up an AuthenticationClass for the LDAP server. +Superset supports xref:home:concepts:authentication.adoc[authentication] of users against a single LDAP server. +This requires setting up an AuthenticationClass for the LDAP server. The AuthenticationClass is then referenced in the SupersetCluster resource as follows: [source,yaml] @@ -29,83 +40,66 @@ spec: <1> The reference to an AuthenticationClass called `ldap` <2> The default role to which all users are assigned -Users that log in with LDAP are assigned to a default https://superset.apache.org/docs/security/#roles[Role] which is specified with the `userRegistrationRole` property. +Users that log in with LDAP are assigned to a default https://superset.apache.org/docs/security/#roles[Role^{external-link-icon}^] which is specified with the `userRegistrationRole` property. -You can follow the xref:home:tutorials:authentication_with_openldap.adoc[] tutorial to learn how to set up an AuthenticationClass for an LDAP server, as well as consulting the {crd-docs}/authentication.stackable.tech/authenticationclass/v1alpha1/[AuthenticationClass reference {external-link-icon}^]. +You can follow the xref:home:tutorials:authentication_with_openldap.adoc[] tutorial to learn how to set up an AuthenticationClass for an LDAP server, as well as consulting the xref:home:concepts:authentication.adoc#_ldap[AuthenticationClass concepts page]. + +=== [[oidc]]OpenID Connect -=== [[oauth]]OAuth +An OpenID Connect provider can be used for authentication. +Unfortunately, there is no generic support for OpenID Connect built into Superset. +This means that only specific OpenID Connect providers can be configured. -Strictly speaking, OAuth is an authorization protocol but can be used for authentication if the -security implications are acceptable. In the Superset cluster CRD, authentication via OAuth is not -directly supported but can be configured by overriding properties in `superset_config.py`. The -following example uses https://www.keycloak.org/[Keycloak 21.1] as OAuth provider: +IMPORTANT: Superset deployments on the Stackable Data Platform only support https://www.keycloak.org/[Keycloak^{external-link-icon}^]. [source,yaml] ---- apiVersion: superset.stackable.tech/v1alpha1 kind: SupersetCluster metadata: - name: superset-with-oauth + name: superset-with-oidc spec: image: productVersion: 3.0.1 - [...] - nodes: - configOverrides: - superset_config.py: - AUTH_TYPE: AUTH_OAUTH # <1> - AUTH_USER_REGISTRATION: 'true' # <2> - AUTH_USER_REGISTRATION_ROLE: 'Gamma' # <3> - OAUTH_PROVIDERS: |- - [ - { 'name': 'keycloak', # <4> - 'icon': 'fa-key', # <5> - 'token_key': 'access_token', # <6> - 'remote_app': { - 'client_id': 'KEYCLOAK_CLIENT_ID', - 'client_secret': 'KEYCLOAK_CLIENT_SECRET', - 'api_base_url': 'https://KEYCLOAK_DOMAIN/realms/KEYCLOAK_REALM/protocol/openid-connect', # <7> - 'client_kwargs': { - 'scope': 'email profile openid' # <8> - }, - 'access_token_url': 'https://KEYCLOAK_DOMAIN/realms/KEYCLOAK_REALM/protocol/openid-connect/token', # <9> - 'authorize_url': 'https://KEYCLOAK_DOMAIN/realms/KEYCLOAK_REALM/protocol/openid-connect/auth', # <10> - 'request_token_url': None, - }, - } - ] + clusterConfig: + authentication: + - authenticationClass: keycloak # <1> + oidc: + clientCredentialsSecret: superset-keycloak-client # <2> + userRegistrationRole: Gamma # <3> +---- + +<1> The reference to an AuthenticationClass called `keycloak` +<2> The reference to the Secret containing the Superset client credentials +<3> The default role to which all users are assigned + +Users that log in with OpenID Connect are assigned to a default https://superset.apache.org/docs/security/#roles[Role^{external-link-icon}^] which is specified with the `userRegistrationRole` property. + +The Secret containing the Superset client credentials: + +[source,yaml] +---- +apiVersion: v1 +kind: Secret +metadata: + name: superset-keycloak-client +stringData: + clientId: superset # <1> + clientSecret: superset_client_secret # <2> ---- -<1> The authentication type must be set to `AUTH_OAUTH`. -<2> Authenticated users are added to the Superset database if they do not exist yet. The user - information is fetched from the `/userinfo` endpoint of the OAuth provider which is only - available if the `openid` scope is requested. The admin user is already present in the Superset - database as defined in the credentials secret, but the authentication is performed with the - password stored in Keycloak. The property `clusterConfig.authenticationConfig.userRegistration` - cannot be used here because it is only taken into account when an authentication class is set. -<3> This role will be given in addition to any roles defined in `AUTH_ROLE_MAPPING`. The property - `clusterConfig.authenticationConfig.userRegistrationRole` cannot be used here because it is only - taken into account when an authentication class is set. -<4> The name of the OAuth provider; Superset has built-in logic for `keycloak` and some other - providers. -<5> The Font Awesome icon on the sign-in button -<6> The token key name the provider uses -<7> The base URL used for well-known endpoints like `/userinfo`. It must be reachable from the - Kubernetes cluster/Superset pod. -<8> The scopes `email` and `profile` return claims which contain the user's name and email address - respectively. The `openid` scope is required for the `/userinfo` endpoint. -<9> The access token URL must be reachable from the Kubernetes client/Superset pod. -<10> The authorize URL must be reachable from the user's browser. - -A minimum client configuration in Keycloak looks like this: +<1> The client ID of Superset as defined in Keycloak +<1> The client secret as defined in Keycloak + +A minimum client configuration in Keycloak for this example looks like this: [source,json] ---- { - "clientId": "KEYCLOAK_CLIENT_ID", + "clientId": "superset", "enabled": true, "clientAuthenticatorType": "client-secret", # <1> - "secret": "KEYCLOAK_CLIENT_SECRET", + "secret": "superset_client_secret", "redirectUris": [ "*" ], @@ -121,34 +115,20 @@ A minimum client configuration in Keycloak looks like this: <2> Enables the OAuth2 "Authorization Code Flow". <3> Enables OpenID Connect and OAuth2 support. -Superset configuration examples for other providers can be found at -https://flask-appbuilder.readthedocs.io/en/latest/security.html#authentication-oauth[]. - -=== [[oidc]]OpenID Connect - -OpenID Connect (OIDC) is an authentication protocol based on the OAuth 2.0 framework. Unfortunately, -it is not supported by Superset out of the box. An adapted `SupersetSecurityManager` and the -https://github.com/puiterwijk/flask-oidc[`flask-oidc`] library would be required which are both not -included in the official Stackable product image. But as OpenID Connect is just an authentication -layer on top of the OAuth 2.0 authorization framework, the configuration described in the -xref:oauth[OAuth section] usually works for OpenID Connect providers too. - -=== OpenID - -OpenID Authentication 2.0 is an authentication protocol. It is deprecated in favor of -xref:oidc[OpenID Connect]. Superset provides the authentication type `AUTH_OID` for it but also -requires the https://github.com/pallets-eco/flask-openid[Flask-OpenID] library which is not included -in the official Stackable product image. +Further information for specifying an AuthenticationClass for an OIDC provider can be found at the xref:home:concepts:authentication.adoc#_oidc[concepts page]. == [[authorization]]Authorization + Superset has a concept called `Roles` which allows you to grant user permissions based on roles. -Have a look at the https://superset.apache.org/docs/security[Superset documentation on Security]. +Have a look at the https://superset.apache.org/docs/security[Superset documentation on Security^{external-link-icon}^]. + +=== Superset database -=== Webinterface -You can view all the available roles in the Webinterface of Superset and can also assign users to these roles. +You can view all the available roles in the web interface of Superset and can also assign users to these roles. === LDAP -Superset supports assigning https://superset.apache.org/docs/security/#roles[Roles] to users based on their LDAP group membership, though this is not yet supported by the Stackable operator. + +Superset supports assigning https://superset.apache.org/docs/security/#roles[Roles^{external-link-icon}^] to users based on their LDAP group membership, though this is not yet supported by the Stackable operator. All the users logging in via LDAP get assigned to the same role which you can configure via the attribute `authentication[*].userRegistrationRole` on the `SupersetCluster` object: [source,yaml] @@ -165,3 +145,28 @@ spec: ---- <1> All users are assigned to the `Admin` role + +=== OpenID Connect + +The mechanism for assigning roles to users described in the LDAP section also applies to OpenID Connect. +Superset supports assigning https://superset.apache.org/docs/security/#roles[Roles^{external-link-icon}^] to users based on their OpenID Connect scopes, though this is not yet supported by the Stackable operator. +All the users logging in via OpenID Connect get assigned to the same role which you can configure via the attribute `authentication[*].userRegistrationRole` on the `SupersetCluster` object: + +[source,yaml] +---- +apiVersion: superset.stackable.tech/v1alpha1 +kind: SupersetCluster +metadata: + name: superset-with-oidc +spec: + image: + productVersion: 3.0.1 + clusterConfig: + authentication: + - authenticationClass: keycloak + oidc: + clientCredentialsSecret: superset-keycloak-client + userRegistrationRole: Gamma # <1> +---- + +<1> All users are assigned to the `Gamma` role diff --git a/rust/crd/Cargo.toml b/rust/crd/Cargo.toml index c0cb9007..1b7ddd9d 100644 --- a/rust/crd/Cargo.toml +++ b/rust/crd/Cargo.toml @@ -18,4 +18,6 @@ snafu.workspace = true tracing.workspace = true [dev-dependencies] +indoc.workspace = true serde_yaml.workspace = true +tokio.workspace = true diff --git a/rust/crd/src/affinity.rs b/rust/crd/src/affinity.rs index 1a2382c4..6c0ff1c4 100644 --- a/rust/crd/src/affinity.rs +++ b/rust/crd/src/affinity.rs @@ -45,7 +45,7 @@ mod tests { image: productVersion: 3.0.1 clusterConfig: - credentialsSecret: superset-credentials + credentialsSecret: superset-db-credentials nodes: roleGroups: default: diff --git a/rust/crd/src/authentication.rs b/rust/crd/src/authentication.rs index fec2c227..6b1e3660 100644 --- a/rust/crd/src/authentication.rs +++ b/rust/crd/src/authentication.rs @@ -1,85 +1,113 @@ +use std::{collections::BTreeSet, future::Future, mem}; + use serde::{Deserialize, Serialize}; -use snafu::{ResultExt, Snafu}; -use stackable_operator::commons::authentication::AuthenticationClassProvider; +use snafu::{ensure, ResultExt, Snafu}; use stackable_operator::{ client::Client, - commons::authentication::AuthenticationClass, - kube::runtime::reflector::ObjectRef, + commons::authentication::{ + ldap, + oidc::{self, IdentityProviderHint}, + AuthenticationClass, AuthenticationClassProvider, ClientAuthenticationDetails, + }, + error::OperatorResult, schemars::{self, JsonSchema}, }; +use tracing::info; + +// The assumed OIDC provider if no hint is given in the AuthClass +pub const DEFAULT_OIDC_PROVIDER: IdentityProviderHint = IdentityProviderHint::Keycloak; -const SUPPORTED_AUTHENTICATION_CLASS_PROVIDERS: [&str; 1] = ["LDAP"]; +const SUPPORTED_AUTHENTICATION_CLASS_PROVIDERS: &[&str] = &["LDAP", "OIDC"]; +const SUPPORTED_OIDC_PROVIDERS: &[IdentityProviderHint] = &[IdentityProviderHint::Keycloak]; #[derive(Snafu, Debug)] pub enum Error { - #[snafu(display("Failed to retrieve AuthenticationClass {authentication_class}"))] - AuthenticationClassRetrieval { + #[snafu(display( + "The AuthenticationClass {auth_class_name:?} is referenced several times which is not allowed." + ))] + DuplicateAuthenticationClassReferencesNotAllowed { auth_class_name: String }, + + #[snafu(display("Failed to retrieve AuthenticationClass"))] + AuthenticationClassRetrievalFailed { source: stackable_operator::error::Error, - authentication_class: ObjectRef, }, - // TODO: Adapt message if multiple authentication classes are supported simultaneously - #[snafu(display("Only one authentication class is currently supported at a time"))] - MultipleAuthenticationClassesProvided, + + #[snafu(display("Only one authentication type at a time is supported by Superset, see https://github.com/dpgaspar/Flask-AppBuilder/issues/1924."))] + MultipleAuthenticationTypesNotSupported, + + #[snafu(display("Only one LDAP provider at a time is supported by Superset."))] + MultipleLdapProvidersNotSupported, + #[snafu(display( - "Failed to use authentication provider [{provider}] for authentication class [{authentication_class}] - supported providers: {SUPPORTED_AUTHENTICATION_CLASS_PROVIDERS:?}", + "The userRegistration settings must not differ between the authentication entries.", + ))] + DifferentUserRegistrationSettingsNotAllowed, + + #[snafu(display( + "The userRegistrationRole settings must not differ between the authentication entries.", + ))] + DifferentUserRegistrationRoleSettingsNotAllowed, + + #[snafu(display( + "The syncRolesAt settings must not differ between the authentication entries.", + ))] + DifferentSyncRolesAtSettingsNotAllowed, + + #[snafu(display( + "Failed to use authentication provider {provider:?} for authentication class {auth_class_name:?} - supported providers: {SUPPORTED_AUTHENTICATION_CLASS_PROVIDERS:?}", ))] AuthenticationProviderNotSupported { - authentication_class: ObjectRef, + auth_class_name: String, provider: String, }, -} -type Result = std::result::Result; + #[snafu(display("Invalid OIDC configuration"))] + OidcConfigurationInvalid { + source: stackable_operator::error::Error, + }, -/// Resolved counter part for `SuperSetAuthenticationConfig`. -pub struct SuperSetAuthenticationConfigResolved { - pub authentication_class: Option, - pub user_registration: bool, - pub user_registration_role: String, - pub sync_roles_at: FlaskRolesSyncMoment, -} + #[snafu(display("The OIDC provider {oidc_provider:?} is not yet supported (AuthenticationClass {auth_class_name:?})."))] + OidcProviderNotSupported { + auth_class_name: String, + oidc_provider: String, + }, -#[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct SupersetAuthentication { - /// The Superset [authentication](DOCS_BASE_URL_PLACEHOLDER/superset/usage-guide/security) settings. - /// Currently the underlying Flask App Builder only supports one authentication mechanism - /// at a time. This means the operator will error out if multiple references to an - /// AuthenticationClass are provided. - #[serde(default)] - authentication: Vec, -} + #[snafu(display( + "TLS verification cannot be disabled in Superset (AuthenticationClass {auth_class_name:?})." + ))] + TlsVerificationCannotBeDisabled { auth_class_name: String }, -impl SupersetAuthentication { - pub fn authentication_class_names(&self) -> Vec<&str> { - let mut auth_classes = vec![]; - for config in &self.authentication { - if let Some(auth_config) = &config.authentication_class { - auth_classes.push(auth_config.as_str()); - } - } - auth_classes - } + #[snafu(display( + "{configured:?} is not a supported principalClaim in Superset for the Keycloak OIDC provider. Please use {supported:?} in the AuthenticationClass {auth_class_name:?}" + ))] + OidcPrincipalClaimNotSupported { + configured: String, + supported: String, + auth_class_name: String, + }, } +type Result = std::result::Result; + #[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct SuperSetAuthenticationConfig { - /// Name of the [AuthenticationClass](DOCS_BASE_URL_PLACEHOLDER/concepts/authentication.html#authenticationclass) used to authenticate the users. - /// At the moment only LDAP is supported. - /// If not specified the default authentication (AUTH_DB) will be used. - pub authentication_class: Option, +pub struct SupersetClientAuthenticationDetails { + #[serde(flatten)] + pub common: ClientAuthenticationDetails<()>, + /// Allow users who are not already in the FAB DB. /// Gets mapped to `AUTH_USER_REGISTRATION` #[serde(default = "default_user_registration")] pub user_registration: bool, + /// This role will be given in addition to any AUTH_ROLES_MAPPING. /// Gets mapped to `AUTH_USER_REGISTRATION_ROLE` #[serde(default = "default_user_registration_role")] pub user_registration_role: String, + /// If we should replace ALL the user's roles each login, or only on registration. /// Gets mapped to `AUTH_ROLES_SYNC_AT_LOGIN` - #[serde(default = "default_sync_roles_at")] + #[serde(default)] pub sync_roles_at: FlaskRolesSyncMoment, } @@ -91,64 +119,838 @@ pub fn default_user_registration_role() -> String { "Public".to_string() } -/// Matches Flask's default mode of syncing at registration -pub fn default_sync_roles_at() -> FlaskRolesSyncMoment { - FlaskRolesSyncMoment::Registration -} - -#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] pub enum FlaskRolesSyncMoment { + #[default] Registration, Login, } -impl SupersetAuthentication { - /// Retrieve all provided `AuthenticationClass` references. - pub async fn resolve( - &self, +/// Resolved and validated counter part for `SupersetClientAuthenticationDetails`. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct SupersetClientAuthenticationDetailsResolved { + pub authentication_classes_resolved: Vec, + pub user_registration: bool, + pub user_registration_role: String, + pub sync_roles_at: FlaskRolesSyncMoment, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum SupersetAuthenticationClassResolved { + Ldap { + provider: ldap::AuthenticationProvider, + }, + Oidc { + provider: oidc::AuthenticationProvider, + oidc: oidc::ClientAuthenticationOptions<()>, + }, +} + +impl SupersetClientAuthenticationDetailsResolved { + pub async fn from( + auth_details: &[SupersetClientAuthenticationDetails], client: &Client, - ) -> Result> { - let mut resolved = vec![]; + ) -> Result { + let resolve_auth_class = |auth_details: ClientAuthenticationDetails| async move { + auth_details.resolve_class(client).await + }; + SupersetClientAuthenticationDetailsResolved::resolve(auth_details, resolve_auth_class).await + } + + pub async fn resolve( + auth_details: &[SupersetClientAuthenticationDetails], + resolve_auth_class: impl Fn(ClientAuthenticationDetails) -> R, + ) -> Result + where + R: Future>, + { + let mut resolved_auth_classes = Vec::new(); + let mut user_registration = None; + let mut user_registration_role = None; + let mut sync_roles_at = None; + + let mut auth_class_names = BTreeSet::new(); + + for entry in auth_details { + let auth_class_name = entry.common.authentication_class_name(); + + let is_new_auth_class = auth_class_names.insert(auth_class_name); + ensure!( + is_new_auth_class, + DuplicateAuthenticationClassReferencesNotAllowedSnafu { auth_class_name } + ); + + let auth_class = resolve_auth_class(entry.common.clone()) + .await + .context(AuthenticationClassRetrievalFailedSnafu)?; + + match &auth_class.spec.provider { + AuthenticationClassProvider::Ldap(provider) => { + let resolved_auth_class = SupersetAuthenticationClassResolved::Ldap { + provider: provider.to_owned(), + }; - // TODO: adapt if multiple authentication classes are supported by superset. - // This is currently not possible due to the Flask App Builder not supporting it. - if self.authentication.len() > 1 { - return Err(Error::MultipleAuthenticationClassesProvided); + if let Some(other) = resolved_auth_classes.first() { + ensure!( + mem::discriminant(other) == mem::discriminant(&resolved_auth_class), + MultipleAuthenticationTypesNotSupportedSnafu + ); + } + + ensure!( + resolved_auth_classes.is_empty(), + MultipleLdapProvidersNotSupportedSnafu + ); + + resolved_auth_classes.push(resolved_auth_class); + } + AuthenticationClassProvider::Oidc(provider) => { + let resolved_auth_class = + SupersetClientAuthenticationDetailsResolved::from_oidc( + auth_class_name, + provider, + entry, + )?; + + if let Some(other) = resolved_auth_classes.first() { + ensure!( + mem::discriminant(other) == mem::discriminant(&resolved_auth_class), + MultipleAuthenticationTypesNotSupportedSnafu + ); + } + + resolved_auth_classes.push(resolved_auth_class); + } + _ => { + return Err(Error::AuthenticationProviderNotSupported { + auth_class_name: auth_class_name.to_owned(), + provider: auth_class.spec.provider.to_string(), + }); + } + } + + match user_registration { + Some(user_registration) => { + ensure!( + user_registration == entry.user_registration, + DifferentUserRegistrationSettingsNotAllowedSnafu + ); + } + None => user_registration = Some(entry.user_registration), + } + + match &user_registration_role { + Some(user_registration_role) => { + ensure!( + user_registration_role == &entry.user_registration_role, + DifferentUserRegistrationRoleSettingsNotAllowedSnafu + ); + } + None => user_registration_role = Some(entry.user_registration_role.to_owned()), + } + + match &sync_roles_at { + Some(sync_roles_at) => { + ensure!( + sync_roles_at == &entry.sync_roles_at, + DifferentSyncRolesAtSettingsNotAllowedSnafu + ); + } + None => sync_roles_at = Some(entry.sync_roles_at.to_owned()), + } } - for config in &self.authentication { - let auth_class = if let Some(auth_class) = &config.authentication_class { - let resolved = AuthenticationClass::resolve(client, auth_class) - .await - .context(AuthenticationClassRetrievalSnafu { - authentication_class: ObjectRef::::new(auth_class), - })?; - - // Checking for supported AuthenticationClass here is a little out of place, but is does not - // make sense to iterate further after finding an unsupported AuthenticationClass. - Some(match resolved.spec.provider { - AuthenticationClassProvider::Ldap(_) => resolved, - AuthenticationClassProvider::Oidc(_) - | AuthenticationClassProvider::Tls(_) - | AuthenticationClassProvider::Static(_) => { - return Err(Error::AuthenticationProviderNotSupported { - authentication_class: ObjectRef::from_obj(&resolved), - provider: resolved.spec.provider.to_string(), - }) + Ok(SupersetClientAuthenticationDetailsResolved { + authentication_classes_resolved: resolved_auth_classes, + user_registration: user_registration.unwrap_or_else(default_user_registration), + user_registration_role: user_registration_role + .unwrap_or_else(default_user_registration_role), + sync_roles_at: sync_roles_at.unwrap_or_else(FlaskRolesSyncMoment::default), + }) + } + + fn from_oidc( + auth_class_name: &str, + provider: &oidc::AuthenticationProvider, + auth_details: &SupersetClientAuthenticationDetails, + ) -> Result { + let oidc_provider = match &provider.provider_hint { + None => { + info!("No OIDC provider hint given in AuthClass {auth_class_name}, assuming {default_oidc_provider_name}", + default_oidc_provider_name = serde_json::to_string(&DEFAULT_OIDC_PROVIDER).unwrap()); + DEFAULT_OIDC_PROVIDER + } + Some(oidc_provider) => oidc_provider.to_owned(), + }; + + ensure!( + SUPPORTED_OIDC_PROVIDERS.contains(&oidc_provider), + OidcProviderNotSupportedSnafu { + auth_class_name, + oidc_provider: serde_json::to_string(&oidc_provider).unwrap(), + } + ); + + match oidc_provider { + IdentityProviderHint::Keycloak => { + ensure!( + &provider.principal_claim == "preferred_username", + OidcPrincipalClaimNotSupportedSnafu { + configured: provider.principal_claim.clone(), + supported: "preferred_username".to_owned(), + auth_class_name, } + ); + } + } + + ensure!( + !provider.tls.uses_tls() || provider.tls.uses_tls_verification(), + TlsVerificationCannotBeDisabledSnafu { auth_class_name } + ); + + Ok(SupersetAuthenticationClassResolved::Oidc { + provider: provider.to_owned(), + oidc: auth_details + .common + .oidc_or_error(auth_class_name) + .context(OidcConfigurationInvalidSnafu)? + .clone(), + }) + } +} + +#[cfg(test)] +mod tests { + use std::pin::Pin; + + use indoc::indoc; + use stackable_operator::{ + commons::authentication::{ + oidc, + tls::{CaCert, Tls, TlsClientDetails, TlsServerVerification, TlsVerification}, + }, + kube, + }; + + use super::*; + + #[tokio::test] + async fn resolve_without_authentication_details() { + let auth_details_resolved = test_resolve_and_expect_success("[]", "").await; + + assert_eq!( + SupersetClientAuthenticationDetailsResolved { + authentication_classes_resolved: Vec::default(), + user_registration: default_user_registration(), + user_registration_role: default_user_registration_role(), + sync_roles_at: FlaskRolesSyncMoment::default() + }, + auth_details_resolved + ); + } + + #[tokio::test] + async fn resolve_ldap_with_all_authentication_details() { + // Avoid using defaults here + let auth_details_resolved = test_resolve_and_expect_success( + indoc! {" + - authenticationClass: ldap + userRegistration: false + userRegistrationRole: Gamma + syncRolesAt: Login + "}, + indoc! {" + --- + apiVersion: authentication.stackable.tech/v1alpha1 + kind: AuthenticationClass + metadata: + name: ldap + spec: + provider: + ldap: + hostname: my.ldap.server + "}, + ) + .await; + + assert_eq!( + SupersetClientAuthenticationDetailsResolved { + authentication_classes_resolved: vec![SupersetAuthenticationClassResolved::Ldap { + provider: serde_yaml::from_str("hostname: my.ldap.server").unwrap() + }], + user_registration: false, + user_registration_role: "Gamma".into(), + sync_roles_at: FlaskRolesSyncMoment::Login + }, + auth_details_resolved + ); + } + + #[tokio::test] + async fn resolve_oidc_with_all_authentication_details() { + // Avoid using defaults here + let auth_details_resolved = test_resolve_and_expect_success( + indoc! {" + - authenticationClass: oidc1 + oidc: + clientCredentialsSecret: superset-oidc-client1 + extraScopes: + - groups + userRegistration: false + userRegistrationRole: Gamma + syncRolesAt: Login + - authenticationClass: oidc2 + oidc: + clientCredentialsSecret: superset-oidc-client2 + userRegistration: false + userRegistrationRole: Gamma + syncRolesAt: Login + "}, + indoc! {" + --- + apiVersion: authentication.stackable.tech/v1alpha1 + kind: AuthenticationClass + metadata: + name: oidc1 + spec: + provider: + oidc: + hostname: first.oidc.server + port: 443 + rootPath: /realms/main + principalClaim: preferred_username + scopes: + - openid + - email + - profile + providerHint: Keycloak + tls: + verification: + server: + caCert: + secretClass: tls + --- + apiVersion: authentication.stackable.tech/v1alpha1 + kind: AuthenticationClass + metadata: + name: oidc2 + spec: + provider: + oidc: + hostname: second.oidc.server + rootPath: /realms/test + principalClaim: preferred_username + scopes: + - openid + - email + - profile + "}, + ) + .await; + + assert_eq!( + SupersetClientAuthenticationDetailsResolved { + authentication_classes_resolved: vec![ + SupersetAuthenticationClassResolved::Oidc { + provider: oidc::AuthenticationProvider::new( + "first.oidc.server".into(), + Some(443), + "/realms/main".into(), + TlsClientDetails { + tls: Some(Tls { + verification: TlsVerification::Server(TlsServerVerification { + ca_cert: CaCert::SecretClass("tls".into()) + }) + }) + }, + "preferred_username".into(), + vec!["openid".into(), "email".into(), "profile".into()], + Some(IdentityProviderHint::Keycloak) + ), + oidc: oidc::ClientAuthenticationOptions { + client_credentials_secret_ref: "superset-oidc-client1".into(), + extra_scopes: vec!["groups".into()], + product_specific_fields: () + } + }, + SupersetAuthenticationClassResolved::Oidc { + provider: oidc::AuthenticationProvider::new( + "second.oidc.server".into(), + None, + "/realms/test".into(), + TlsClientDetails { tls: None }, + "preferred_username".into(), + vec!["openid".into(), "email".into(), "profile".into()], + None + ), + oidc: oidc::ClientAuthenticationOptions { + client_credentials_secret_ref: "superset-oidc-client2".into(), + extra_scopes: Vec::new(), + product_specific_fields: () + } + } + ], + user_registration: false, + user_registration_role: "Gamma".into(), + sync_roles_at: FlaskRolesSyncMoment::Login + }, + auth_details_resolved + ); + } + + #[tokio::test] + async fn reject_duplicate_authentication_class_references() { + let error_message = test_resolve_and_expect_error( + indoc! {" + - authenticationClass: oidc + oidc: + clientCredentialsSecret: superset-oidc-client1 + - authenticationClass: oidc + oidc: + clientCredentialsSecret: superset-oidc-client2 + "}, + indoc! {" + --- + apiVersion: authentication.stackable.tech/v1alpha1 + kind: AuthenticationClass + metadata: + name: oidc + spec: + provider: + oidc: + hostname: my.oidc.server + principalClaim: preferred_username + scopes: [] + "}, + ) + .await; + + assert_eq!( + r#"The AuthenticationClass "oidc" is referenced several times which is not allowed."#, + error_message + ); + } + + #[tokio::test] + async fn reject_different_authentication_types() { + let error_message = test_resolve_and_expect_error( + indoc! {" + - authenticationClass: oidc + oidc: + clientCredentialsSecret: superset-oidc-client + - authenticationClass: ldap + "}, + indoc! {" + --- + apiVersion: authentication.stackable.tech/v1alpha1 + kind: AuthenticationClass + metadata: + name: oidc + spec: + provider: + oidc: + hostname: my.oidc.server + principalClaim: preferred_username + scopes: [] + --- + apiVersion: authentication.stackable.tech/v1alpha1 + kind: AuthenticationClass + metadata: + name: ldap + spec: + provider: + ldap: + hostname: my.ldap.server + "}, + ) + .await; + + assert_eq!( + "Only one authentication type at a time is supported by Superset, see https://github.com/dpgaspar/Flask-AppBuilder/issues/1924.", + error_message + ); + } + + #[tokio::test] + async fn reject_multiple_ldap_providers() { + let error_message = test_resolve_and_expect_error( + indoc! {" + - authenticationClass: ldap1 + - authenticationClass: ldap2 + "}, + indoc! {" + --- + apiVersion: authentication.stackable.tech/v1alpha1 + kind: AuthenticationClass + metadata: + name: ldap1 + spec: + provider: + ldap: + hostname: first.ldap.server + --- + apiVersion: authentication.stackable.tech/v1alpha1 + kind: AuthenticationClass + metadata: + name: ldap2 + spec: + provider: + ldap: + hostname: second.ldap.server + "}, + ) + .await; + + assert_eq!( + "Only one LDAP provider at a time is supported by Superset.", + error_message + ); + } + + #[tokio::test] + async fn reject_different_user_registration_settings() { + let error_message = test_resolve_and_expect_error( + indoc! {" + - authenticationClass: oidc1 + oidc: + clientCredentialsSecret: superset-oidc-client1 + - authenticationClass: oidc2 + oidc: + clientCredentialsSecret: superset-oidc-client2 + userRegistration: false + "}, + indoc! {" + --- + apiVersion: authentication.stackable.tech/v1alpha1 + kind: AuthenticationClass + metadata: + name: oidc1 + spec: + provider: + oidc: + hostname: first.oidc.server + principalClaim: preferred_username + scopes: [] + --- + apiVersion: authentication.stackable.tech/v1alpha1 + kind: AuthenticationClass + metadata: + name: oidc2 + spec: + provider: + oidc: + hostname: second.oidc.server + principalClaim: preferred_username + scopes: [] + "}, + ) + .await; + + assert_eq!( + "The userRegistration settings must not differ between the authentication entries.", + error_message + ); + } + + #[tokio::test] + async fn reject_different_user_registration_role_settings() { + let error_message = test_resolve_and_expect_error( + indoc! {" + - authenticationClass: oidc1 + oidc: + clientCredentialsSecret: superset-oidc-client1 + - authenticationClass: oidc2 + oidc: + clientCredentialsSecret: superset-oidc-client2 + userRegistrationRole: Gamma + "}, + indoc! {" + --- + apiVersion: authentication.stackable.tech/v1alpha1 + kind: AuthenticationClass + metadata: + name: oidc1 + spec: + provider: + oidc: + hostname: first.oidc.server + principalClaim: preferred_username + scopes: [] + --- + apiVersion: authentication.stackable.tech/v1alpha1 + kind: AuthenticationClass + metadata: + name: oidc2 + spec: + provider: + oidc: + hostname: second.oidc.server + principalClaim: preferred_username + scopes: [] + "}, + ) + .await; + + assert_eq!( + "The userRegistrationRole settings must not differ between the authentication entries.", + error_message + ); + } + + #[tokio::test] + async fn reject_different_sync_roles_at_settings() { + let error_message = test_resolve_and_expect_error( + indoc! {" + - authenticationClass: oidc1 + oidc: + clientCredentialsSecret: superset-oidc-client1 + - authenticationClass: oidc2 + oidc: + clientCredentialsSecret: superset-oidc-client2 + syncRolesAt: Login + "}, + indoc! {" + --- + apiVersion: authentication.stackable.tech/v1alpha1 + kind: AuthenticationClass + metadata: + name: oidc1 + spec: + provider: + oidc: + hostname: first.oidc.server + principalClaim: preferred_username + scopes: [] + --- + apiVersion: authentication.stackable.tech/v1alpha1 + kind: AuthenticationClass + metadata: + name: oidc2 + spec: + provider: + oidc: + hostname: second.oidc.server + principalClaim: preferred_username + scopes: [] + "}, + ) + .await; + + assert_eq!( + "The syncRolesAt settings must not differ between the authentication entries.", + error_message + ); + } + + #[tokio::test] + async fn reject_if_oidc_details_are_missing() { + let error_message = test_resolve_and_expect_error( + indoc! {" + - authenticationClass: oidc + "}, + indoc! {" + --- + apiVersion: authentication.stackable.tech/v1alpha1 + kind: AuthenticationClass + metadata: + name: oidc + spec: + provider: + oidc: + hostname: my.oidc.server + principalClaim: preferred_username + scopes: [] + "}, + ) + .await; + + assert_eq!( + indoc! { r#" + Invalid OIDC configuration + + Caused by this error: + 1: OIDC authentication details not specified. The AuthenticationClass "oidc" uses an OIDC provider, you need to specify OIDC authentication details (such as client credentials) as well"# + }, + error_message + ); + } + + #[tokio::test] + async fn reject_wrong_principal_claim() { + let error_message = test_resolve_and_expect_error( + indoc! {" + - authenticationClass: oidc + oidc: + clientCredentialsSecret: superset-oidc-client + "}, + indoc! {" + --- + apiVersion: authentication.stackable.tech/v1alpha1 + kind: AuthenticationClass + metadata: + name: oidc + spec: + provider: + oidc: + hostname: my.oidc.server + principalClaim: sub + scopes: [] + "}, + ) + .await; + + assert_eq!( + r#""sub" is not a supported principalClaim in Superset for the Keycloak OIDC provider. Please use "preferred_username" in the AuthenticationClass "oidc""#, + error_message + ); + } + + #[tokio::test] + async fn reject_disabled_tls_verification() { + let error_message = test_resolve_and_expect_error( + indoc! {" + - authenticationClass: oidc + oidc: + clientCredentialsSecret: superset-oidc-client + "}, + indoc! {" + --- + apiVersion: authentication.stackable.tech/v1alpha1 + kind: AuthenticationClass + metadata: + name: oidc + spec: + provider: + oidc: + hostname: my.oidc.server + principalClaim: preferred_username + scopes: [] + tls: + verification: + none: {} + "}, + ) + .await; + + assert_eq!( + r#"TLS verification cannot be disabled in Superset (AuthenticationClass "oidc")."#, + error_message + ); + } + + /// Call `SupersetClientAuthenticationDetailsResolved::resolve` with + /// the given lists of `SupersetClientAuthenticationDetails` and + /// `AuthenticationClass`es and return the + /// `SupersetClientAuthenticationDetailsResolved`. + /// + /// The parameters are meant to be valid and resolvable. Just fail + /// if there is an error. + async fn test_resolve_and_expect_success( + auth_details_yaml: &str, + auth_classes_yaml: &str, + ) -> SupersetClientAuthenticationDetailsResolved { + test_resolve(auth_details_yaml, auth_classes_yaml) + .await + .expect("The SupersetClientAuthenticationDetails should be resolvable.") + } + + /// Call `SupersetClientAuthenticationDetailsResolved::resolve` with + /// the given lists of `SupersetClientAuthenticationDetails` and + /// `AuthenticationClass`es and return the error message. + /// + /// The parameters are meant to be invalid or not resolvable. Just + /// fail if there is no error. + async fn test_resolve_and_expect_error( + auth_details_yaml: &str, + auth_classes_yaml: &str, + ) -> String { + let error = test_resolve(auth_details_yaml, auth_classes_yaml) + .await + .expect_err( + "The SupersetClientAuthenticationDetails are invalid and should not be resolvable.", + ); + snafu::Report::from_error(error) + .to_string() + .trim_end() + .to_owned() + } + + /// Call `SupersetClientAuthenticationDetailsResolved::resolve` with + /// the given lists of `SupersetClientAuthenticationDetails` and + /// `AuthenticationClass`es and return the result. + async fn test_resolve( + auth_details_yaml: &str, + auth_classes_yaml: &str, + ) -> Result { + let auth_details = deserialize_superset_client_authentication_details(auth_details_yaml); + + let auth_classes = deserialize_auth_classes(auth_classes_yaml); + + let resolve_auth_class = create_auth_class_resolver(auth_classes); + + SupersetClientAuthenticationDetailsResolved::resolve(&auth_details, resolve_auth_class) + .await + } + + /// Deserialize the given list of + /// `SupersetClientAuthenticationDetails`. + /// + /// Fail if the given string cannot be deserialized. + fn deserialize_superset_client_authentication_details( + input: &str, + ) -> Vec { + serde_yaml::from_str(input) + .expect("The definition of the authentication configuration should be valid.") + } + + /// Deserialize the given `AuthenticationClass` YAML documents. + /// + /// Fail if the given string cannot be deserialized. + fn deserialize_auth_classes(input: &str) -> Vec { + if input.is_empty() { + Vec::new() + } else { + let deserializer = serde_yaml::Deserializer::from_str(input); + deserializer + .map(|d| { + serde_yaml::with::singleton_map_recursive::deserialize(d) + .expect("The definition of the AuthenticationClass should be valid.") }) - } else { - None - }; - - resolved.push(SuperSetAuthenticationConfigResolved { - authentication_class: auth_class, - user_registration: config.user_registration, - user_registration_role: config.user_registration_role.clone(), - sync_roles_at: config.sync_roles_at.clone(), - }) + .collect() } + } - Ok(resolved) + /// Returns a function which resolves `AuthenticationClass` names to + /// the given list of `AuthenticationClass`es. + /// + /// Use this function in the tests to replace + /// `stackable_operator::commons::authentication::ClientAuthenticationDetails` + /// which requires a Kubernetes client. + fn create_auth_class_resolver( + auth_classes: Vec, + ) -> impl Fn( + ClientAuthenticationDetails, + ) -> Pin>>> { + move |auth_details: ClientAuthenticationDetails| { + let auth_classes = auth_classes.clone(); + Box::pin(async move { + auth_classes + .iter() + .find(|auth_class| { + auth_class.metadata.name.as_ref() + == Some(auth_details.authentication_class_name()) + }) + .cloned() + .ok_or_else(|| stackable_operator::error::Error::KubeError { + source: kube::Error::Api(kube::error::ErrorResponse { + code: 404, + message: "AuthenticationClass not found".into(), + reason: "NotFound".into(), + status: "Failure".into(), + }), + }) + }) + } } } diff --git a/rust/crd/src/lib.rs b/rust/crd/src/lib.rs index 63628cb3..3cd92835 100644 --- a/rust/crd/src/lib.rs +++ b/rust/crd/src/lib.rs @@ -1,5 +1,6 @@ use std::collections::BTreeMap; +use authentication::SupersetClientAuthenticationDetails; use product_config::flask_app_config_writer::{FlaskAppConfigOptions, PythonType}; use serde::{Deserialize, Serialize}; use snafu::{OptionExt, ResultExt, Snafu}; @@ -26,7 +27,7 @@ use stackable_operator::{ }; use strum::{Display, EnumIter, EnumString, IntoEnumIterator}; -use crate::{affinity::get_affinity, authentication::SupersetAuthentication}; +use crate::affinity::get_affinity; pub mod affinity; pub mod authentication; @@ -62,6 +63,7 @@ pub enum SupersetConfigOptions { StatsLogger, RowLimit, MapboxApiKey, + OauthProviders, SupersetWebserverTimeout, LoggingConfigurator, AuthType, @@ -108,6 +110,7 @@ impl FlaskAppConfigOptions for SupersetConfigOptions { SupersetConfigOptions::StatsLogger => PythonType::Expression, SupersetConfigOptions::RowLimit => PythonType::IntLiteral, SupersetConfigOptions::MapboxApiKey => PythonType::Expression, + SupersetConfigOptions::OauthProviders => PythonType::Expression, SupersetConfigOptions::SupersetWebserverTimeout => PythonType::IntLiteral, SupersetConfigOptions::LoggingConfigurator => PythonType::Expression, SupersetConfigOptions::AuthType => PythonType::Expression, @@ -168,8 +171,9 @@ pub struct SupersetClusterSpec { #[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct SupersetClusterConfig { - #[serde(default, flatten)] - pub authentication: SupersetAuthentication, + /// List of AuthenticationClasses used to authenticate users. + #[serde(default)] + pub authentication: Vec, /// The name of the Secret object containing the admin user credentials and database connection details. /// Read the @@ -177,7 +181,7 @@ pub struct SupersetClusterConfig { /// to find out more. pub credentials_secret: String, - // no doc - docs in the struct. + /// Cluster operations like pause reconciliation or cluster stop. #[serde(default)] pub cluster_operation: ClusterOperation, diff --git a/rust/operator-binary/src/commands.rs b/rust/operator-binary/src/commands.rs new file mode 100644 index 00000000..0b1cb7cb --- /dev/null +++ b/rust/operator-binary/src/commands.rs @@ -0,0 +1,4 @@ +/// Adds a CA file from `cert_file` to the root certificates of Python Certifi +pub fn add_cert_to_python_certifi_command(cert_file: &str) -> String { + format!("cat {cert_file} >> \"$(python -c 'import certifi; print(certifi.where())')\"") +} diff --git a/rust/operator-binary/src/config.rs b/rust/operator-binary/src/config.rs index 0fd30414..21c2b562 100644 --- a/rust/operator-binary/src/config.rs +++ b/rust/operator-binary/src/config.rs @@ -1,7 +1,13 @@ +use indoc::formatdoc; use snafu::{ResultExt, Snafu}; -use stackable_operator::commons::authentication::{ldap, AuthenticationClassProvider}; -use stackable_superset_crd::authentication::SuperSetAuthenticationConfigResolved; -use stackable_superset_crd::{authentication::FlaskRolesSyncMoment, SupersetConfigOptions}; +use stackable_operator::commons::authentication::{ldap, oidc}; +use stackable_superset_crd::{ + authentication::{ + FlaskRolesSyncMoment, SupersetAuthenticationClassResolved, + SupersetClientAuthenticationDetailsResolved, DEFAULT_OIDC_PROVIDER, + }, + SupersetConfigOptions, +}; use std::collections::BTreeMap; #[derive(Snafu, Debug)] @@ -21,7 +27,7 @@ pub const PYTHON_IMPORTS: &[&str] = &[ pub fn add_superset_config( config: &mut BTreeMap, - authentication_config: &Vec, + authentication_config: &SupersetClientAuthenticationDetailsResolved, ) -> Result<(), Error> { config.insert( SupersetConfigOptions::SecretKey.to_string(), @@ -51,32 +57,57 @@ pub fn add_superset_config( fn append_authentication_config( config: &mut BTreeMap, - authentication_config: &Vec, + auth_config: &SupersetClientAuthenticationDetailsResolved, ) -> Result<(), Error> { - // TODO: we make sure in crd/src/authentication.rs that currently there is only one - // AuthenticationClass provided. If the FlaskAppBuilder ever supports this we have - // to adapt the config here accordingly - for auth_config in authentication_config { - if let Some(auth_class) = &auth_config.authentication_class { - if let AuthenticationClassProvider::Ldap(ldap) = &auth_class.spec.provider { - append_ldap_config(config, ldap)?; + // SupersetClientAuthenticationDetailsResolved ensures that there + // are either only LDAP or OIDC providers configured. It is not + // necessary to check this here again. + + let ldap_providers = auth_config + .authentication_classes_resolved + .iter() + .filter_map(|auth_class| { + if let SupersetAuthenticationClassResolved::Ldap { provider } = auth_class { + Some(provider) + } else { + None } - } + }) + .collect::>(); - config.insert( - SupersetConfigOptions::AuthUserRegistration.to_string(), - auth_config.user_registration.to_string(), - ); - config.insert( - SupersetConfigOptions::AuthUserRegistrationRole.to_string(), - auth_config.user_registration_role.to_string(), - ); - config.insert( - SupersetConfigOptions::AuthRolesSyncAtLogin.to_string(), - (auth_config.sync_roles_at == FlaskRolesSyncMoment::Login).to_string(), - ); + let oidc_providers = auth_config + .authentication_classes_resolved + .iter() + .filter_map(|auth_class| { + if let SupersetAuthenticationClassResolved::Oidc { provider, oidc } = auth_class { + Some((provider, oidc)) + } else { + None + } + }) + .collect::>(); + + if let Some(ldap_provider) = ldap_providers.first() { + append_ldap_config(config, ldap_provider)?; + } + + if !oidc_providers.is_empty() { + append_oidc_config(config, &oidc_providers); } + config.insert( + SupersetConfigOptions::AuthUserRegistration.to_string(), + auth_config.user_registration.to_string(), + ); + config.insert( + SupersetConfigOptions::AuthUserRegistrationRole.to_string(), + auth_config.user_registration_role.to_string(), + ); + config.insert( + SupersetConfigOptions::AuthRolesSyncAtLogin.to_string(), + (auth_config.sync_roles_at == FlaskRolesSyncMoment::Login).to_string(), + ); + Ok(()) } @@ -153,3 +184,68 @@ fn append_ldap_config( Ok(()) } + +fn append_oidc_config( + config: &mut BTreeMap, + providers: &[( + &oidc::AuthenticationProvider, + &oidc::ClientAuthenticationOptions<()>, + )], +) { + config.insert( + SupersetConfigOptions::AuthType.to_string(), + "AUTH_OAUTH".into(), + ); + + let mut oauth_providers_config = Vec::new(); + + for (oidc, client_options) in providers { + let (env_client_id, env_client_secret) = + oidc::AuthenticationProvider::client_credentials_env_names( + &client_options.client_credentials_secret_ref, + ); + let mut scopes = oidc.scopes.clone(); + scopes.extend_from_slice(&client_options.extra_scopes); + + let oidc_provider = oidc + .provider_hint + .as_ref() + .unwrap_or(&DEFAULT_OIDC_PROVIDER); + + let oauth_providers_config_entry = match oidc_provider { + oidc::IdentityProviderHint::Keycloak => { + formatdoc!( + " + {{ 'name': 'keycloak', + 'icon': 'fa-key', + 'token_key': 'access_token', + 'remote_app': {{ + 'client_id': os.environ.get('{env_client_id}'), + 'client_secret': os.environ.get('{env_client_secret}'), + 'client_kwargs': {{ + 'scope': '{scopes}' + }}, + 'api_base_url': '{url}/protocol/', + 'server_metadata_url': '{url}/.well-known/openid-configuration', + }}, + }}", + url = oidc.endpoint_url().unwrap(), + scopes = scopes.join(" "), + ) + } + }; + + oauth_providers_config.push(oauth_providers_config_entry); + } + + config.insert( + SupersetConfigOptions::OauthProviders.to_string(), + formatdoc!( + "[ + {joined_oauth_providers_config} + ] + ", + joined_oauth_providers_config = oauth_providers_config.join(",\n") + ), + ); +} diff --git a/rust/operator-binary/src/main.rs b/rust/operator-binary/src/main.rs index 874b2fe0..0a663908 100644 --- a/rust/operator-binary/src/main.rs +++ b/rust/operator-binary/src/main.rs @@ -1,14 +1,4 @@ -mod config; -mod controller_commons; -mod druid_connection_controller; -mod operations; -mod product_logging; -mod rbac; -mod superset_controller; -mod util; - -use crate::druid_connection_controller::DRUID_CONNECTION_CONTROLLER_NAME; -use crate::superset_controller::SUPERSET_CONTROLLER_NAME; +use std::sync::Arc; use clap::{crate_description, crate_version, Parser}; use futures::StreamExt; @@ -27,11 +17,20 @@ use stackable_operator::{ logging::controller::report_controller_reconciled, CustomResourceExt, }; -use stackable_superset_crd::{ - authentication::SupersetAuthentication, druidconnection::DruidConnection, SupersetCluster, - APP_NAME, -}; -use std::sync::Arc; +use stackable_superset_crd::{druidconnection::DruidConnection, SupersetCluster, APP_NAME}; + +use crate::druid_connection_controller::DRUID_CONNECTION_CONTROLLER_NAME; +use crate::superset_controller::SUPERSET_CONTROLLER_NAME; + +mod commands; +mod config; +mod controller_commons; +mod druid_connection_controller; +mod operations; +mod product_logging; +mod rbac; +mod superset_controller; +mod util; mod built_info { include!(concat!(env!("OUT_DIR"), "/built.rs")); @@ -107,10 +106,7 @@ async fn main() -> anyhow::Result<()> { .state() .into_iter() .filter(move |superset: &Arc| { - references_authentication_class( - &superset.spec.cluster_config.authentication, - &authentication_class, - ) + references_authentication_class(superset, &authentication_class) }) .map(|superset| ObjectRef::from_obj(&*superset)) }, @@ -210,15 +206,14 @@ async fn main() -> anyhow::Result<()> { } fn references_authentication_class( - authentication_config: &SupersetAuthentication, + superset: &SupersetCluster, authentication_class: &AuthenticationClass, ) -> bool { - assert!(authentication_class.metadata.name.is_some()); - - authentication_config - .authentication_class_names() - .into_iter() - .filter(|c| *c == authentication_class.name_any()) - .count() - > 0 + let authentication_class_name = authentication_class.name_any(); + superset + .spec + .cluster_config + .authentication + .iter() + .any(|c| c.common.authentication_class_name() == &authentication_class_name) } diff --git a/rust/operator-binary/src/superset_controller.rs b/rust/operator-binary/src/superset_controller.rs index 7171cdf0..f50485b0 100644 --- a/rust/operator-binary/src/superset_controller.rs +++ b/rust/operator-binary/src/superset_controller.rs @@ -1,7 +1,7 @@ //! Ensures that `Pod`s are configured and running for each [`SupersetCluster`] use std::{ borrow::Cow, - collections::{BTreeMap, HashMap}, + collections::{BTreeMap, BTreeSet, HashMap}, sync::Arc, }; @@ -19,13 +19,15 @@ use stackable_operator::{ }, cluster_resources::{ClusterResourceApplyStrategy, ClusterResources}, commons::{ - authentication::AuthenticationClassProvider, product_image_selection::ResolvedProductImage, + authentication::oidc, product_image_selection::ResolvedProductImage, rbac::build_rbac_resources, }, k8s_openapi::{ api::{ apps::v1::{StatefulSet, StatefulSetSpec}, - core::v1::{ConfigMap, HTTPGetAction, Probe, Service, ServicePort, ServiceSpec}, + core::v1::{ + ConfigMap, EnvVar, HTTPGetAction, Probe, Service, ServicePort, ServiceSpec, + }, }, apimachinery::pkg::{apis::meta::v1::LabelSelector, util::intstr::IntOrString}, DeepMerge, @@ -47,8 +49,9 @@ use stackable_operator::{ time::Duration, utils::COMMON_BASH_TRAP_FUNCTIONS, }; +use stackable_superset_crd::authentication::SupersetAuthenticationClassResolved; use stackable_superset_crd::{ - authentication::SuperSetAuthenticationConfigResolved, Container, SupersetCluster, + authentication::SupersetClientAuthenticationDetailsResolved, Container, SupersetCluster, SupersetClusterStatus, SupersetConfig, SupersetConfigOptions, SupersetRole, APP_NAME, PYTHONPATH, STACKABLE_CONFIG_DIR, STACKABLE_LOG_CONFIG_DIR, STACKABLE_LOG_DIR, SUPERSET_CONFIG_FILENAME, @@ -56,6 +59,7 @@ use stackable_superset_crd::{ use strum::{EnumDiscriminants, IntoStaticStr}; use crate::{ + commands::add_cert_to_python_certifi_command, config::{self, PYTHON_IMPORTS}, controller_commons::{self, CONFIG_VOLUME_NAME, LOG_CONFIG_VOLUME_NAME, LOG_VOLUME_NAME}, operations::{graceful_shutdown::add_graceful_shutdown_config, pdb::add_pdbs}, @@ -237,6 +241,11 @@ pub enum Error { AddLdapVolumesAndVolumeMounts { source: stackable_operator::commons::authentication::ldap::Error, }, + + #[snafu(display("failed to add TLS Volumes and VolumeMounts"))] + AddTlsVolumesAndVolumeMounts { + source: stackable_operator::commons::authentication::tls::TlsClientDetailsError, + }, } type Result = std::result::Result; @@ -272,13 +281,12 @@ pub async fn reconcile_superset(superset: Arc, ctx: Arc) - .await .context(ResolveVectorAggregatorAddressSnafu)?; - let authentication_config = superset - .spec - .cluster_config - .authentication - .resolve(client) - .await - .context(InvalidAuthenticationConfigSnafu)?; + let auth_config = SupersetClientAuthenticationDetailsResolved::from( + &superset.spec.cluster_config.authentication, + client, + ) + .await + .context(InvalidAuthenticationConfigSnafu)?; let validated_config = validate_all_roles_and_groups_config( &resolved_product_image.product_version, @@ -357,7 +365,7 @@ pub async fn reconcile_superset(superset: Arc, ctx: Arc) - &resolved_product_image, &rolegroup, rolegroup_config, - &authentication_config, + &auth_config, &config.logging, vector_aggregator_address.as_deref(), )?; @@ -367,7 +375,7 @@ pub async fn reconcile_superset(superset: Arc, ctx: Arc) - &superset_role, &rolegroup, rolegroup_config, - &authentication_config, + &auth_config, &rbac_sa.name_any(), &config, )?; @@ -484,7 +492,7 @@ fn build_rolegroup_config_map( resolved_product_image: &ResolvedProductImage, rolegroup: &RoleGroupRef, rolegroup_config: &HashMap>, - authentication_config: &Vec, + authentication_config: &SupersetClientAuthenticationDetailsResolved, logging: &Logging, vector_aggregator_address: Option<&str>, ) -> Result { @@ -632,7 +640,7 @@ fn build_server_rolegroup_statefulset( superset_role: &SupersetRole, rolegroup_ref: &RoleGroupRef, node_config: &HashMap>, - authentication_config: &Vec, + authentication_config: &SupersetClientAuthenticationDetailsResolved, sa_name: &str, merged_config: &SupersetConfig, ) -> Result { @@ -717,6 +725,8 @@ fn build_server_rolegroup_statefulset( .add_env_var_from_secret("ADMIN_LASTNAME", secret, "adminUser.lastname") .add_env_var_from_secret("ADMIN_EMAIL", secret, "adminUser.email") .add_env_var_from_secret("ADMIN_PASSWORD", secret, "adminUser.password") + .add_env_var("SSL_CERT_DIR", "/stackable/certs/") + .add_env_vars(authentication_env_vars(authentication_config)) .command(vec![ "/bin/bash".to_string(), "-x".to_string(), @@ -727,6 +737,7 @@ fn build_server_rolegroup_statefulset( mkdir --parents {PYTHONPATH} && \ cp {STACKABLE_CONFIG_DIR}/* {PYTHONPATH} && \ cp {STACKABLE_LOG_CONFIG_DIR}/{LOG_CONFIG_FILE} {PYTHONPATH} && \ + {auth_commands} superset db upgrade && \ superset fab create-admin \ --username \"$ADMIN_USERNAME\" \ @@ -748,6 +759,7 @@ fn build_server_rolegroup_statefulset( 'superset.app:create_app()' & wait_for_termination $! {create_vector_shutdown_file_command}", + auth_commands = authentication_start_commands(authentication_config), remove_vector_shutdown_file_command = remove_vector_shutdown_file_command(STACKABLE_LOG_DIR), create_vector_shutdown_file_command = @@ -871,30 +883,100 @@ fn build_server_rolegroup_statefulset( } fn add_authentication_volumes_and_volume_mounts( - authentication_config: &Vec, + auth_config: &SupersetClientAuthenticationDetailsResolved, cb: &mut ContainerBuilder, pb: &mut PodBuilder, ) -> Result<()> { - // TODO: Currently there can be only one AuthenticationClass due to FlaskAppBuilder restrictions. - // Needs adaptation once FAB and superset support multiple auth methods. - // The checks for max one AuthenticationClass and the provider are done in crd/src/authentication.rs - for config in authentication_config { - if let Some(auth_class) = &config.authentication_class { - match &auth_class.spec.provider { - AuthenticationClassProvider::Ldap(ldap) => { - ldap.add_volumes_and_mounts(pb, vec![cb]) - .context(AddLdapVolumesAndVolumeMountsSnafu)?; - } - AuthenticationClassProvider::Oidc(_) - | AuthenticationClassProvider::Tls(_) - | AuthenticationClassProvider::Static(_) => {} + // Different authentication entries can reference the same secret + // class or TLS certificate. It must be ensured that the volumes + // and volume mounts are only added once in such a case. + + let mut ldap_authentication_providers = BTreeSet::new(); + let mut tls_client_credentials = BTreeSet::new(); + + for auth_class_resolved in &auth_config.authentication_classes_resolved { + match auth_class_resolved { + SupersetAuthenticationClassResolved::Ldap { provider } => { + ldap_authentication_providers.insert(provider); + } + SupersetAuthenticationClassResolved::Oidc { provider, .. } => { + tls_client_credentials.insert(&provider.tls); } } } + for provider in ldap_authentication_providers { + provider + .add_volumes_and_mounts(pb, vec![cb]) + .context(AddLdapVolumesAndVolumeMountsSnafu)?; + } + + for tls in tls_client_credentials { + tls.add_volumes_and_mounts(pb, vec![cb]) + .context(AddTlsVolumesAndVolumeMountsSnafu)?; + } + Ok(()) } +fn authentication_env_vars( + auth_config: &SupersetClientAuthenticationDetailsResolved, +) -> Vec { + // Different OIDC authentication entries can reference the same + // client secret. It must be ensured that the env variables are only + // added once in such a case. + + let mut oidc_client_credentials_secrets = BTreeSet::new(); + + for auth_class_resolved in &auth_config.authentication_classes_resolved { + match auth_class_resolved { + SupersetAuthenticationClassResolved::Ldap { .. } => {} + SupersetAuthenticationClassResolved::Oidc { oidc, .. } => { + oidc_client_credentials_secrets + .insert(oidc.client_credentials_secret_ref.to_owned()); + } + } + } + + oidc_client_credentials_secrets + .iter() + .cloned() + .flat_map(oidc::AuthenticationProvider::client_credentials_env_var_mounts) + .collect() +} + +fn authentication_start_commands( + auth_config: &SupersetClientAuthenticationDetailsResolved, +) -> String { + let mut commands = Vec::new(); + + let mut tls_client_credentials = BTreeSet::new(); + + for auth_class_resolved in &auth_config.authentication_classes_resolved { + match auth_class_resolved { + SupersetAuthenticationClassResolved::Oidc { provider, .. } => { + tls_client_credentials.insert(&provider.tls); + + // WebPKI will be handled implicitly + } + SupersetAuthenticationClassResolved::Ldap { .. } => {} + } + } + + for tls in tls_client_credentials { + commands.push(tls.tls_ca_cert_mount_path().map(|tls_ca_cert_mount_path| { + add_cert_to_python_certifi_command(&tls_ca_cert_mount_path) + })); + } + + commands + .iter() + .flatten() + .cloned() + .collect::>() + .join("\n") +} + pub fn error_policy(_obj: Arc, _error: &Error, _ctx: Arc) -> Action { Action::requeue(*Duration::from_secs(5)) } diff --git a/tests/templates/kuttl/oidc/10-assert.yaml b/tests/templates/kuttl/oidc/10-assert.yaml new file mode 100644 index 00000000..e9c60b15 --- /dev/null +++ b/tests/templates/kuttl/oidc/10-assert.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +metadata: + name: test-superset-postgresql +timeout: 480 +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: superset-postgresql +status: + readyReplicas: 1 + replicas: 1 diff --git a/tests/templates/kuttl/oidc/10-install-postgresql.yaml b/tests/templates/kuttl/oidc/10-install-postgresql.yaml new file mode 100644 index 00000000..50c5ad67 --- /dev/null +++ b/tests/templates/kuttl/oidc/10-install-postgresql.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: >- + helm install superset-postgresql + --namespace $NAMESPACE + --version 12.5.6 + -f helm-bitnami-postgresql-values.yaml + --repo https://charts.bitnami.com/bitnami postgresql + --wait + timeout: 600 diff --git a/tests/templates/kuttl/oidc/20-assert.yaml.j2 b/tests/templates/kuttl/oidc/20-assert.yaml.j2 new file mode 100644 index 00000000..50b1d4c3 --- /dev/null +++ b/tests/templates/kuttl/oidc/20-assert.yaml.j2 @@ -0,0 +1,10 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +{% if lookup('env', 'VECTOR_AGGREGATOR') %} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: vector-aggregator-discovery +{% endif %} diff --git a/tests/templates/kuttl/oidc/20-install-vector-aggregator-discovery-configmap.yaml.j2 b/tests/templates/kuttl/oidc/20-install-vector-aggregator-discovery-configmap.yaml.j2 new file mode 100644 index 00000000..2d6a0df5 --- /dev/null +++ b/tests/templates/kuttl/oidc/20-install-vector-aggregator-discovery-configmap.yaml.j2 @@ -0,0 +1,9 @@ +{% if lookup('env', 'VECTOR_AGGREGATOR') %} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: vector-aggregator-discovery +data: + ADDRESS: {{ lookup('env', 'VECTOR_AGGREGATOR') }} +{% endif %} diff --git a/tests/templates/kuttl/oidc/30-assert.yaml b/tests/templates/kuttl/oidc/30-assert.yaml new file mode 100644 index 00000000..93965698 --- /dev/null +++ b/tests/templates/kuttl/oidc/30-assert.yaml @@ -0,0 +1,22 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +metadata: + name: test-keycloak +timeout: 480 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: keycloak1 +status: + readyReplicas: 1 + replicas: 1 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: keycloak2 +status: + readyReplicas: 1 + replicas: 1 diff --git a/tests/templates/kuttl/oidc/30-install-keycloak.yaml b/tests/templates/kuttl/oidc/30-install-keycloak.yaml new file mode 100644 index 00000000..33104195 --- /dev/null +++ b/tests/templates/kuttl/oidc/30-install-keycloak.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: | + INSTANCE_NAME=keycloak1 \ + REALM=test1 \ + USERNAME=jane.doe \ + FIRST_NAME=Jane \ + LAST_NAME=Doe \ + EMAIL=jane.doe@stackable.tech \ + PASSWORD=T8mn72D9 \ + CLIENT_ID=superset1 \ + CLIENT_SECRET=R1bxHUD569vHeQdw \ + envsubst < install-keycloak.yaml | kubectl apply -n $NAMESPACE -f - + + INSTANCE_NAME=keycloak2 \ + REALM=test2 \ + USERNAME=richard.roe \ + FIRST_NAME=Richard \ + LAST_NAME=Roe \ + EMAIL=richard.roe@stackable.tech \ + PASSWORD=NvfpU518 \ + CLIENT_ID=superset2 \ + CLIENT_SECRET=scWzh0D4v0GN8NrN \ + envsubst < install-keycloak.yaml | kubectl apply -n $NAMESPACE -f - diff --git a/tests/templates/kuttl/oidc/40-assert.yaml b/tests/templates/kuttl/oidc/40-assert.yaml new file mode 100644 index 00000000..3eb18400 --- /dev/null +++ b/tests/templates/kuttl/oidc/40-assert.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +metadata: + name: install-superset +timeout: 300 +commands: + - script: kubectl -n $NAMESPACE wait --for=condition=available=true supersetclusters.superset.stackable.tech/superset --timeout 301s +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: superset-node-default +status: + readyReplicas: 1 + replicas: 1 diff --git a/tests/templates/kuttl/oidc/40-install-superset.yaml b/tests/templates/kuttl/oidc/40-install-superset.yaml new file mode 100644 index 00000000..0cba3c7b --- /dev/null +++ b/tests/templates/kuttl/oidc/40-install-superset.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +timeout: 300 +commands: + - script: > + envsubst '$NAMESPACE' < install-superset.yaml | + kubectl apply -n $NAMESPACE -f - diff --git a/tests/templates/kuttl/oidc/50-assert.yaml b/tests/templates/kuttl/oidc/50-assert.yaml new file mode 100644 index 00000000..58987778 --- /dev/null +++ b/tests/templates/kuttl/oidc/50-assert.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +metadata: + name: install-test-container +timeout: 300 +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: python +status: + readyReplicas: 1 + replicas: 1 diff --git a/tests/templates/kuttl/oidc/50-install-test-container.yaml b/tests/templates/kuttl/oidc/50-install-test-container.yaml new file mode 100644 index 00000000..0d04647d --- /dev/null +++ b/tests/templates/kuttl/oidc/50-install-test-container.yaml @@ -0,0 +1,50 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +metadata: + name: install-test-container +timeout: 300 +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: python + labels: + app: python +spec: + replicas: 1 + selector: + matchLabels: + app: python + template: + metadata: + labels: + app: python + spec: + securityContext: + fsGroup: 1000 + containers: + - name: python + image: docker.stackable.tech/stackable/testing-tools:0.2.0-stackable0.0.0-dev + stdin: true + tty: true + resources: + requests: + memory: "128Mi" + cpu: "512m" + limits: + memory: "128Mi" + cpu: "1" + volumeMounts: + - name: tls + mountPath: /stackable/tls + env: + - name: REQUESTS_CA_BUNDLE + value: /stackable/tls/ca.crt + volumes: + - name: tls + csi: + driver: secrets.stackable.tech + volumeAttributes: + secrets.stackable.tech/class: tls + secrets.stackable.tech/scope: pod diff --git a/tests/templates/kuttl/oidc/60-assert.yaml b/tests/templates/kuttl/oidc/60-assert.yaml new file mode 100644 index 00000000..fee66ceb --- /dev/null +++ b/tests/templates/kuttl/oidc/60-assert.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +metadata: + name: login +timeout: 300 +commands: + - script: kubectl exec -n $NAMESPACE python-0 -- python /stackable/login.py diff --git a/tests/templates/kuttl/oidc/60-login.yaml b/tests/templates/kuttl/oidc/60-login.yaml new file mode 100644 index 00000000..0745bc4b --- /dev/null +++ b/tests/templates/kuttl/oidc/60-login.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +metadata: + name: login +commands: + - script: > + envsubst '$NAMESPACE' < login.py | + kubectl exec -n $NAMESPACE -i python-0 -- tee /stackable/login.py > /dev/null diff --git a/tests/templates/kuttl/oidc/helm-bitnami-postgresql-values.yaml.j2 b/tests/templates/kuttl/oidc/helm-bitnami-postgresql-values.yaml.j2 new file mode 100644 index 00000000..7991d27e --- /dev/null +++ b/tests/templates/kuttl/oidc/helm-bitnami-postgresql-values.yaml.j2 @@ -0,0 +1,31 @@ +--- +volumePermissions: + enabled: false + securityContext: + runAsUser: auto + +primary: + podSecurityContext: +{% if test_scenario['values']['openshift'] == 'true' %} + enabled: false +{% else %} + enabled: true +{% endif %} + containerSecurityContext: + enabled: false + resources: + requests: + memory: "128Mi" + cpu: "512m" + limits: + memory: "128Mi" + cpu: "1" + +shmVolume: + chmod: + enabled: false + +auth: + username: superset + password: superset + database: superset diff --git a/tests/templates/kuttl/oidc/install-keycloak.yaml b/tests/templates/kuttl/oidc/install-keycloak.yaml new file mode 100644 index 00000000..e9127b97 --- /dev/null +++ b/tests/templates/kuttl/oidc/install-keycloak.yaml @@ -0,0 +1,140 @@ +# The environment variables must be replaced. +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: $INSTANCE_NAME-realms +data: + test-realm.json: | + { + "realm": "$REALM", + "enabled": true, + "users": [ + { + "enabled": true, + "username": "$USERNAME", + "firstName" : "$FIRST_NAME", + "lastName" : "$LAST_NAME", + "email" : "$EMAIL", + "credentials": [ + { + "type": "password", + "value": "$PASSWORD" + } + ], + "realmRoles": [ + "user" + ] + } + ], + "roles": { + "realm": [ + { + "name": "user", + "description": "User privileges" + } + ] + }, + "clients": [ + { + "clientId": "$CLIENT_ID", + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "$CLIENT_SECRET", + "redirectUris": [ + "*" + ], + "webOrigins": [ + "*" + ], + "standardFlowEnabled": true, + "protocol": "openid-connect" + } + ] + } +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: $INSTANCE_NAME + labels: + app: $INSTANCE_NAME +spec: + replicas: 1 + selector: + matchLabels: + app: $INSTANCE_NAME + template: + metadata: + labels: + app: $INSTANCE_NAME + spec: + containers: + - name: keycloak + image: quay.io/keycloak/keycloak:23.0.4 + args: + - start-dev + - --import-realm + - --https-certificate-file=/tls/tls.crt + - --https-certificate-key-file=/tls/tls.key + env: + - name: KEYCLOAK_ADMIN + value: admin + - name: KEYCLOAK_ADMIN_PASSWORD + value: admin + ports: + - name: https + containerPort: 8443 + volumeMounts: + - name: realms + mountPath: /opt/keycloak/data/import + - name: tls + mountPath: /tls + readinessProbe: + httpGet: + scheme: HTTPS + path: /realms/$REALM + port: 8443 + volumes: + - name: realms + configMap: + name: $INSTANCE_NAME-realms + - name: tls + csi: + driver: secrets.stackable.tech + volumeAttributes: + secrets.stackable.tech/class: tls + secrets.stackable.tech/scope: service=$INSTANCE_NAME +--- +apiVersion: v1 +kind: Service +metadata: + name: $INSTANCE_NAME +spec: + selector: + app: $INSTANCE_NAME + ports: + - protocol: TCP + port: 8443 +--- +apiVersion: authentication.stackable.tech/v1alpha1 +kind: AuthenticationClass +metadata: + name: $INSTANCE_NAME-$NAMESPACE +spec: + provider: + oidc: + hostname: $INSTANCE_NAME.$NAMESPACE.svc.cluster.local + port: 8443 + rootPath: /realms/$REALM + scopes: + - email + - openid + - profile + principalClaim: preferred_username + providerHint: Keycloak + tls: + verification: + server: + caCert: + secretClass: tls diff --git a/tests/templates/kuttl/oidc/install-superset.yaml.j2 b/tests/templates/kuttl/oidc/install-superset.yaml.j2 new file mode 100644 index 00000000..23e10379 --- /dev/null +++ b/tests/templates/kuttl/oidc/install-superset.yaml.j2 @@ -0,0 +1,59 @@ +# $NAMESPACE will be replaced with the namespace of the test case. +--- +apiVersion: v1 +kind: Secret +metadata: + name: superset-credentials +type: Opaque +stringData: + adminUser.username: admin + adminUser.firstname: Superset + adminUser.lastname: Admin + adminUser.email: admin@superset.com + adminUser.password: admin + connections.secretKey: aQC11KVUJ3yTVcy2 + connections.sqlalchemyDatabaseUri: postgresql://superset:superset@superset-postgresql/superset +--- +apiVersion: v1 +kind: Secret +metadata: + name: superset-keycloak1-client +stringData: + clientId: superset1 + clientSecret: R1bxHUD569vHeQdw +--- +apiVersion: v1 +kind: Secret +metadata: + name: superset-keycloak2-client +stringData: + clientId: superset2 + clientSecret: scWzh0D4v0GN8NrN +--- +apiVersion: superset.stackable.tech/v1alpha1 +kind: SupersetCluster +metadata: + name: superset +spec: + image: + productVersion: "{{ test_scenario['values']['superset'] }}" + pullPolicy: IfNotPresent + clusterConfig: + authentication: + - authenticationClass: keycloak1-$NAMESPACE + oidc: + clientCredentialsSecret: superset-keycloak1-client + - authenticationClass: keycloak2-$NAMESPACE + oidc: + clientCredentialsSecret: superset-keycloak2-client + credentialsSecret: superset-credentials +{% if lookup('env', 'VECTOR_AGGREGATOR') %} + vectorAggregatorConfigMapName: vector-aggregator-discovery +{% endif %} + nodes: + config: + logging: + enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} + roleGroups: + default: + replicas: 1 diff --git a/tests/templates/kuttl/oidc/login.py b/tests/templates/kuttl/oidc/login.py new file mode 100644 index 00000000..91e28eb8 --- /dev/null +++ b/tests/templates/kuttl/oidc/login.py @@ -0,0 +1,104 @@ +# $NAMESPACE will be replaced with the namespace of the test case. + +import json +import logging +import requests +import sys +from bs4 import BeautifulSoup + +logging.basicConfig( + level='DEBUG', + format="%(asctime)s %(levelname)s: %(message)s", + stream=sys.stdout) + +session = requests.Session() + +# Click on "Sign In with keycloak" in Superset +login_page = session.get("http://superset-external:8088/login/keycloak?next=") + +assert login_page.ok, "Redirection from Superset to Keycloak failed" +assert login_page.url.startswith("https://keycloak1.$NAMESPACE.svc.cluster.local:8443/realms/test1/protocol/openid-connect/auth?response_type=code&client_id=superset1"), \ + "Redirection to the Keycloak login page expected" + +# Enter username and password into the Keycloak login page and click on "Sign In" +login_page_html = BeautifulSoup(login_page.text, 'html.parser') +authenticate_url = login_page_html.form['action'] +welcome_page = session.post(authenticate_url, data={ + 'username': "jane.doe", + 'password': "T8mn72D9" +}) + +assert welcome_page.ok, "Login failed" +assert welcome_page.url == "http://superset-external:8088/superset/welcome/", \ + "Redirection to the Superset welcome page expected" + +# Open the user information page in Superset +userinfo_page = session.get("http://superset-external:8088/users/userinfo/") + +assert userinfo_page.ok, "Retrieving user information failed" +assert userinfo_page.url == "http://superset-external:8088/superset/welcome/", \ + "Redirection to the Superset welcome page expected" + +# Expect the user data provided by Keycloak in Superset +userinfo_page_html = BeautifulSoup(userinfo_page.text, 'html.parser') +raw_data = userinfo_page_html.find(id='app')['data-bootstrap'] +data = json.loads(raw_data) +user_data = data['user'] + +assert user_data['firstName'] == "Jane", \ + "The first name of the user in Superset should match the one provided by Keycloak" +assert user_data['lastName'] == "Doe", \ + "The last name of the user in Superset should match the one provided by Keycloak" +assert user_data['email'] == "jane.doe@stackable.tech", \ + "The email of the user in Superset should match the one provided by Keycloak" + +# TODO Use different OIDC providers (currently only Keycloak is +# supported) +# +# It would be beneficial if the second OAuth provider keycloak2 could +# also be tested. This would ensure that the Superset configuration is +# correct. The problem is that the Flask-AppBuilder (and hence Superset) +# do not support multiple OAuth providers with the same name. But +# keycloak1 and keycloak2 use the same name, namely "keycloak": +# +# OAUTH_PROVIDERS = [ +# { 'name': 'keycloak', +# 'icon': 'fa-key', +# 'token_key': 'access_token', +# 'remote_app': { +# 'client_id': os.environ.get('OIDC_728D9B504A6E9A10_CLIENT_ID'), +# 'client_secret': os.environ.get('OIDC_728D9B504A6E9A10_CLIENT_SECRET'), +# 'client_kwargs': { +# 'scope': 'email openid profile' +# }, +# 'api_base_url': 'https://keycloak1.kuttl.svc.cluster.local:8443/realms/test1/protocol/', +# 'server_metadata_url': 'https://keycloak1.kuttl.svc.cluster.local:8443/realms/test1/.well-known/openid-configuration', +# }, +# }, +# { 'name': 'keycloak', +# 'icon': 'fa-key', +# 'token_key': 'access_token', +# 'remote_app': { +# 'client_id': os.environ.get('OIDC_607BA683B09BC0B8_CLIENT_ID'), +# 'client_secret': os.environ.get('OIDC_607BA683B09BC0B8_CLIENT_SECRET'), +# 'client_kwargs': { +# 'scope': 'email openid profile' +# }, +# 'api_base_url': 'https://keycloak2.kuttl.svc.cluster.local:8443/realms/test2/protocol/', +# 'server_metadata_url': 'https://keycloak2.kuttl.svc.cluster.local:8443/realms/test2/.well-known/openid-configuration', +# }, +# } +# ] +# +# This name is set in the operator and cannot be changed. The reason is +# that the name is also used in Flask-AppBuilder to determine how the +# user information must be interpreted. +# +# Superset actually shows two "Sign In with keycloak" buttons in this +# test but the second one cannot be clicked. +# +# It is nevertheless useful to have two Keycloak instances in this test +# because it ensures that several authentication entries can be +# specified, no volumes or volume mounts are added twice, and that the +# configuration is correct to the extent that Superset does not complain +# about it. diff --git a/tests/test-definition.yaml b/tests/test-definition.yaml index b9673b43..5774e923 100644 --- a/tests/test-definition.yaml +++ b/tests/test-definition.yaml @@ -38,6 +38,10 @@ tests: - superset - ldap-authentication - openshift + - name: oidc + dimensions: + - superset + - openshift - name: resources dimensions: - superset-latest