From 7f7e277c7b62a75a8e2b2d8530e6ae9c782bc745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Mon, 12 Aug 2024 20:01:38 +0200 Subject: [PATCH 01/18] Initial prototype of an AD UIF backend --- Cargo.lock | 54 +++++++++++ rust/crd/src/user_info_fetcher.rs | 10 ++ rust/operator-binary/src/controller.rs | 5 +- rust/user-info-fetcher/Cargo.toml | 4 + .../src/backend/active_directory.rs | 97 +++++++++++++++++++ rust/user-info-fetcher/src/backend/mod.rs | 1 + rust/user-info-fetcher/src/main.rs | 15 +++ 7 files changed, 184 insertions(+), 2 deletions(-) create mode 100644 rust/user-info-fetcher/src/backend/active_directory.rs diff --git a/Cargo.lock b/Cargo.lock index dff1ded2..b0e7b265 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1402,6 +1402,40 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lber" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2df7f9fd9f64cf8f59e1a4a0753fe7d575a5b38d3d7ac5758dcee9357d83ef0a" +dependencies = [ + "bytes", + "nom", +] + +[[package]] +name = "ldap3" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "166199a8207874a275144c8a94ff6eed5fcbf5c52303e4d9b4d53a0c7ac76554" +dependencies = [ + "async-trait", + "bytes", + "futures", + "futures-util", + "lazy_static", + "lber", + "log", + "native-tls", + "nom", + "percent-encoding", + "thiserror", + "tokio", + "tokio-native-tls", + "tokio-stream", + "tokio-util", + "url", +] + [[package]] name = "libc" version = "0.2.155" @@ -1481,6 +1515,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.4" @@ -1542,6 +1582,16 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2503,10 +2553,13 @@ name = "stackable-opa-user-info-fetcher" version = "0.0.0-dev" dependencies = [ "axum", + "base64 0.22.1", "clap", "futures", "hyper", + "ldap3", "moka", + "native-tls", "pin-project", "reqwest", "semver", @@ -2518,6 +2571,7 @@ dependencies = [ "tokio", "tracing", "url", + "uuid", ] [[package]] diff --git a/rust/crd/src/user_info_fetcher.rs b/rust/crd/src/user_info_fetcher.rs index 3f3552e6..8abbaf7f 100644 --- a/rust/crd/src/user_info_fetcher.rs +++ b/rust/crd/src/user_info_fetcher.rs @@ -30,6 +30,9 @@ pub enum Backend { /// Backend that fetches user information from the Gaia-X /// Cross Federation Services Components (XFSC) Authentication & Authorization Service. ExperimentalXfscAas(AasBackend), + + #[serde(rename = "experimentalActiveDirectory")] + ActiveDirectory(ActiveDirectoryBackend), } impl Default for Backend { @@ -88,6 +91,13 @@ fn aas_default_port() -> u16 { 5000 } +#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ActiveDirectoryBackend { + /// Hostname of the identity provider, e.g. `my.aas.corp`. + pub ldap_server: String, +} + #[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize, Derivative)] #[derivative(Default)] #[serde(rename_all = "camelCase")] diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index a305d13c..d4574097 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -596,7 +596,7 @@ fn build_rolegroup_service( let metadata = ObjectMetaBuilder::new() .name_and_namespace(opa) - .name(&rolegroup.object_name()) + .name(rolegroup.object_name()) .ownerreference_from_resource(opa, None, Some(true)) .context(ObjectMissingMetadataForOwnerRefSnafu)? .with_recommended_labels(build_recommended_labels( @@ -914,6 +914,7 @@ fn build_server_rolegroup_daemonset( match &user_info.backend { user_info_fetcher::Backend::None {} => {} user_info_fetcher::Backend::ExperimentalXfscAas(_) => {} + user_info_fetcher::Backend::ActiveDirectory(_) => {} user_info_fetcher::Backend::Keycloak(keycloak) => { pb.add_volume( VolumeBuilder::new(USER_INFO_FETCHER_CREDENTIALS_VOLUME_NAME) @@ -960,7 +961,7 @@ fn build_server_rolegroup_daemonset( let metadata = ObjectMetaBuilder::new() .name_and_namespace(opa) - .name(&rolegroup_ref.object_name()) + .name(rolegroup_ref.object_name()) .ownerreference_from_resource(opa, None, Some(true)) .context(ObjectMissingMetadataForOwnerRefSnafu)? .with_recommended_labels(build_recommended_labels( diff --git a/rust/user-info-fetcher/Cargo.toml b/rust/user-info-fetcher/Cargo.toml index c27ddd95..52f52701 100644 --- a/rust/user-info-fetcher/Cargo.toml +++ b/rust/user-info-fetcher/Cargo.toml @@ -26,3 +26,7 @@ stackable-operator.workspace = true tokio.workspace = true tracing.workspace = true url.workspace = true +ldap3 = "0.11.5" +native-tls = "0.2.12" +uuid = "1.10.0" +base64 = "0.22.1" diff --git a/rust/user-info-fetcher/src/backend/active_directory.rs b/rust/user-info-fetcher/src/backend/active_directory.rs new file mode 100644 index 00000000..4116b63e --- /dev/null +++ b/rust/user-info-fetcher/src/backend/active_directory.rs @@ -0,0 +1,97 @@ +use std::{collections::HashMap, str::FromStr}; + +use base64::Engine as _; +use hyper::StatusCode; +use ldap3::{ldap_escape, LdapConnAsync, LdapConnSettings, Scope, SearchEntry, SearchResult}; +use snafu::Snafu; +use uuid::Uuid; + +use crate::{http_error, UserInfo, UserInfoRequest}; + +#[derive(Snafu, Debug)] +pub enum Error {} + +impl http_error::Error for Error { + fn status_code(&self) -> StatusCode { + match *self {} + } +} + +const LDAP_FIELD_USER_ID: &str = "objectGUID"; +const LDAP_FIELD_USER_NAME: &str = "userPrincipalName"; + +pub(crate) async fn get_user_info( + req: &UserInfoRequest, + ldap_server: &str, +) -> Result { + let (ldap_conn, mut ldap) = + LdapConnAsync::with_settings(LdapConnSettings::new().set_no_tls_verify(true), ldap_server) + .await + .unwrap(); + ldap3::drive!(ldap_conn); + ldap.simple_bind("asdf@sble.test", "Qwer1234") + .await + .unwrap() + .success() + .unwrap(); + let filter = match req { + UserInfoRequest::UserInfoRequestById(id) => { + format!( + "{LDAP_FIELD_USER_ID}={}", + ldap_escape_bytes(&Uuid::from_str(&id.id).unwrap().to_bytes_le()) + ) + } + UserInfoRequest::UserInfoRequestByName(username) => { + format!("{LDAP_FIELD_USER_NAME}={}", ldap_escape(&username.username)) + } + }; + let user = ldap + .search( + "DC=sble,DC=test", + Scope::Subtree, + &format!("(&(objectClass=user)({filter}))"), + ["*"], + ) + .await + .unwrap() + .success() + .unwrap() + .0 + .into_iter() + .next() + .unwrap(); + let user = SearchEntry::construct(user); + let id = user + .bin_attrs + .get(LDAP_FIELD_USER_ID) + .and_then(|values| values.first()) + .map(|uuid| + // AD stores UUIDs as little-endian bytestrings + // Technically, byte order doesn't matter to us as long as it matches the filter, but + // we should try to be consistent with how MS tools display the UUIDs + Uuid::from_slice_le(uuid)) + .transpose() + .unwrap(); + let username = user + .attrs + .get(LDAP_FIELD_USER_NAME) + .and_then(|values| values.first()) + .cloned(); + Ok(UserInfo { + id: id.map(|id| id.to_string()), + username, + groups: Vec::new(), + custom_attributes: HashMap::new(), + }) +} + +/// Escapes raw byte sequences for use in LDAP filter strings +fn ldap_escape_bytes(bytes: &[u8]) -> String { + use std::fmt::Write; + let mut out = String::new(); + for byte in bytes { + // 02 -> zero-pad to length 2 + write!(out, "\\{byte:02X}").unwrap(); + } + out +} diff --git a/rust/user-info-fetcher/src/backend/mod.rs b/rust/user-info-fetcher/src/backend/mod.rs index f33bf78d..4540c6bf 100644 --- a/rust/user-info-fetcher/src/backend/mod.rs +++ b/rust/user-info-fetcher/src/backend/mod.rs @@ -1,2 +1,3 @@ +pub mod active_directory; pub mod keycloak; pub mod xfsc_aas; diff --git a/rust/user-info-fetcher/src/main.rs b/rust/user-info-fetcher/src/main.rs index ff1765fc..a2b83494 100644 --- a/rust/user-info-fetcher/src/main.rs +++ b/rust/user-info-fetcher/src/main.rs @@ -122,6 +122,10 @@ async fn main() -> Result<(), StartupError> { client_id: "".to_string(), client_secret: "".to_string(), }, + crd::Backend::ActiveDirectory(_) => Credentials { + client_id: "".to_string(), + client_secret: "".to_string(), + }, }); let mut client_builder = ClientBuilder::new(); @@ -216,6 +220,11 @@ enum GetUserInfoError { "failed to get user information from the XFSC Authentication & Authorization Service" ))] ExperimentalXfscAas { source: backend::xfsc_aas::Error }, + + #[snafu(display("failed to get user information from Active Directory"))] + ActiveDirectory { + source: backend::active_directory::Error, + }, } impl http_error::Error for GetUserInfoError { @@ -229,6 +238,7 @@ impl http_error::Error for GetUserInfoError { match self { Self::Keycloak { source } => source.status_code(), Self::ExperimentalXfscAas { source } => source.status_code(), + Self::ActiveDirectory { source } => source.status_code(), } } } @@ -277,6 +287,11 @@ async fn get_user_info( .await .context(get_user_info_error::ExperimentalXfscAasSnafu) } + crd::Backend::ActiveDirectory(ad) => { + backend::active_directory::get_user_info(&req, &ad.ldap_server) + .await + .context(get_user_info_error::ActiveDirectorySnafu) + } } }) .await?, From faa1a09ac5bf05f09b648da3fd00ae141a099c64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Tue, 13 Aug 2024 17:09:55 +0200 Subject: [PATCH 02/18] Handle errors --- .../src/backend/active_directory.rs | 69 ++++++++++++++----- rust/user-info-fetcher/src/main.rs | 25 +++++++ 2 files changed, 75 insertions(+), 19 deletions(-) diff --git a/rust/user-info-fetcher/src/backend/active_directory.rs b/rust/user-info-fetcher/src/backend/active_directory.rs index 4116b63e..c886430c 100644 --- a/rust/user-info-fetcher/src/backend/active_directory.rs +++ b/rust/user-info-fetcher/src/backend/active_directory.rs @@ -1,19 +1,47 @@ use std::{collections::HashMap, str::FromStr}; -use base64::Engine as _; use hyper::StatusCode; -use ldap3::{ldap_escape, LdapConnAsync, LdapConnSettings, Scope, SearchEntry, SearchResult}; -use snafu::Snafu; +use ldap3::{ldap_escape, LdapConnAsync, LdapConnSettings, LdapError, Scope, SearchEntry}; +use snafu::{OptionExt, ResultExt, Snafu}; use uuid::Uuid; -use crate::{http_error, UserInfo, UserInfoRequest}; +use crate::{http_error, ErrorRenderUserInfoRequest, UserInfo, UserInfoRequest}; #[derive(Snafu, Debug)] -pub enum Error {} +pub enum Error { + #[snafu(display("failed to connect to LDAP"))] + ConnectLdap { source: LdapError }, + + #[snafu(display("failed to send LDAP request"))] + RequestLdap { source: LdapError }, + + #[snafu(display("failed to bind LDAP credentials"))] + BindLdap { source: LdapError }, + + #[snafu(display("failed to search LDAP for users"))] + FindUserLdap { source: LdapError }, + + #[snafu(display("invalid user ID sent by client"))] + ParseIdByClient { source: uuid::Error }, + + #[snafu(display("invalid user ID sent by LDAP"))] + ParseIdByLdap { source: uuid::Error }, + + #[snafu(display("unable to find user {request}"))] + UserNotFound { request: ErrorRenderUserInfoRequest }, +} impl http_error::Error for Error { fn status_code(&self) -> StatusCode { - match *self {} + match *self { + Error::ConnectLdap { .. } => StatusCode::SERVICE_UNAVAILABLE, + Error::RequestLdap { .. } => StatusCode::SERVICE_UNAVAILABLE, + Error::BindLdap { .. } => StatusCode::SERVICE_UNAVAILABLE, + Error::FindUserLdap { .. } => StatusCode::SERVICE_UNAVAILABLE, + Error::ParseIdByClient { .. } => StatusCode::BAD_REQUEST, + Error::ParseIdByLdap { .. } => StatusCode::INTERNAL_SERVER_ERROR, + Error::UserNotFound { .. } => StatusCode::NOT_FOUND, + } } } @@ -21,24 +49,28 @@ const LDAP_FIELD_USER_ID: &str = "objectGUID"; const LDAP_FIELD_USER_NAME: &str = "userPrincipalName"; pub(crate) async fn get_user_info( - req: &UserInfoRequest, + request: &UserInfoRequest, ldap_server: &str, ) -> Result { let (ldap_conn, mut ldap) = LdapConnAsync::with_settings(LdapConnSettings::new().set_no_tls_verify(true), ldap_server) .await - .unwrap(); + .context(ConnectLdapSnafu)?; ldap3::drive!(ldap_conn); ldap.simple_bind("asdf@sble.test", "Qwer1234") .await - .unwrap() + .context(RequestLdapSnafu)? .success() - .unwrap(); - let filter = match req { + .context(BindLdapSnafu)?; + let filter = match request { UserInfoRequest::UserInfoRequestById(id) => { format!( "{LDAP_FIELD_USER_ID}={}", - ldap_escape_bytes(&Uuid::from_str(&id.id).unwrap().to_bytes_le()) + ldap_escape_bytes( + &Uuid::from_str(&id.id) + .context(ParseIdByClientSnafu)? + .to_bytes_le() + ) ) } UserInfoRequest::UserInfoRequestByName(username) => { @@ -53,13 +85,13 @@ pub(crate) async fn get_user_info( ["*"], ) .await - .unwrap() + .context(RequestLdapSnafu)? .success() - .unwrap() + .context(FindUserLdapSnafu)? .0 .into_iter() .next() - .unwrap(); + .context(UserNotFoundSnafu { request })?; let user = SearchEntry::construct(user); let id = user .bin_attrs @@ -69,9 +101,8 @@ pub(crate) async fn get_user_info( // AD stores UUIDs as little-endian bytestrings // Technically, byte order doesn't matter to us as long as it matches the filter, but // we should try to be consistent with how MS tools display the UUIDs - Uuid::from_slice_le(uuid)) - .transpose() - .unwrap(); + Uuid::from_slice_le(uuid).context(ParseIdByLdapSnafu)) + .transpose()?; let username = user .attrs .get(LDAP_FIELD_USER_NAME) @@ -91,7 +122,7 @@ fn ldap_escape_bytes(bytes: &[u8]) -> String { let mut out = String::new(); for byte in bytes { // 02 -> zero-pad to length 2 - write!(out, "\\{byte:02X}").unwrap(); + write!(out, "\\{byte:02X}").expect("writing to string buffer failed"); } out } diff --git a/rust/user-info-fetcher/src/main.rs b/rust/user-info-fetcher/src/main.rs index a2b83494..4414fb6e 100644 --- a/rust/user-info-fetcher/src/main.rs +++ b/rust/user-info-fetcher/src/main.rs @@ -1,5 +1,6 @@ use std::{ collections::HashMap, + fmt::Display, path::{Path, PathBuf}, sync::Arc, }; @@ -199,6 +200,30 @@ struct UserInfoRequestByName { username: String, } +/// Renders [`UserInfoRequest`] for use in error messages. +/// +/// An independent type rather than an impl on [`UserInfoRequest`], since it is +/// not suitable for use in other contexts. +#[derive(Debug, Clone)] +struct ErrorRenderUserInfoRequest(UserInfoRequest); +impl Display for ErrorRenderUserInfoRequest { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.0 { + UserInfoRequest::UserInfoRequestById(UserInfoRequestById { id }) => { + write!(f, "with id {id:?}") + } + UserInfoRequest::UserInfoRequestByName(UserInfoRequestByName { username }) => { + write!(f, "with username {username:?}") + } + } + } +} +impl From<&UserInfoRequest> for ErrorRenderUserInfoRequest { + fn from(value: &UserInfoRequest) -> Self { + Self(value.clone()) + } +} + #[derive(Serialize, Clone, Debug, Default)] #[serde(rename_all = "camelCase")] struct UserInfo { From f8706a14ab6c06e685ee4fa28b6f990f86cb6452 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Wed, 14 Aug 2024 00:44:27 +0200 Subject: [PATCH 03/18] Query user groups --- Cargo.lock | 1 + rust/user-info-fetcher/Cargo.toml | 1 + .../src/backend/active_directory.rs | 158 ++++++++++++++++-- 3 files changed, 149 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b0e7b265..ddb26153 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2554,6 +2554,7 @@ version = "0.0.0-dev" dependencies = [ "axum", "base64 0.22.1", + "byteorder", "clap", "futures", "hyper", diff --git a/rust/user-info-fetcher/Cargo.toml b/rust/user-info-fetcher/Cargo.toml index 52f52701..df3ec35b 100644 --- a/rust/user-info-fetcher/Cargo.toml +++ b/rust/user-info-fetcher/Cargo.toml @@ -30,3 +30,4 @@ ldap3 = "0.11.5" native-tls = "0.2.12" uuid = "1.10.0" base64 = "0.22.1" +byteorder = "1.5.0" diff --git a/rust/user-info-fetcher/src/backend/active_directory.rs b/rust/user-info-fetcher/src/backend/active_directory.rs index c886430c..2896697e 100644 --- a/rust/user-info-fetcher/src/backend/active_directory.rs +++ b/rust/user-info-fetcher/src/backend/active_directory.rs @@ -1,5 +1,11 @@ -use std::{collections::HashMap, str::FromStr}; +use std::{ + collections::HashMap, + fmt::Display, + io::{Cursor, Read}, + str::FromStr, +}; +use byteorder::{BigEndian, LittleEndian, ReadBytesExt}; use hyper::StatusCode; use ldap3::{ldap_escape, LdapConnAsync, LdapConnSettings, LdapError, Scope, SearchEntry}; use snafu::{OptionExt, ResultExt, Snafu}; @@ -45,13 +51,21 @@ impl http_error::Error for Error { } } -const LDAP_FIELD_USER_ID: &str = "objectGUID"; +// Matching rules defined at https://learn.microsoft.com/en-us/windows/win32/adsi/search-filter-syntax#operators +/// Makes DN filters apply recursively to group membership +const LDAP_MATCHING_RULE_IN_CHAIN: &str = ":1.2.840.113556.1.4.1941:"; + +const LDAP_FIELD_OBJECT_ID: &str = "objectGUID"; +const LDAP_FIELD_OBJECT_SECURITY_ID: &str = "objectSid"; +const LDAP_FIELD_OBJECT_DISTINGUISHED_NAME: &str = "dn"; const LDAP_FIELD_USER_NAME: &str = "userPrincipalName"; +const LDAP_FIELD_USER_PRIMARY_GROUP_RID: &str = "primaryGroupID"; +const LDAP_FIELD_GROUP_MEMBER: &str = "member"; pub(crate) async fn get_user_info( request: &UserInfoRequest, ldap_server: &str, -) -> Result { +) -> Result where { let (ldap_conn, mut ldap) = LdapConnAsync::with_settings(LdapConnSettings::new().set_no_tls_verify(true), ldap_server) .await @@ -62,10 +76,10 @@ pub(crate) async fn get_user_info( .context(RequestLdapSnafu)? .success() .context(BindLdapSnafu)?; - let filter = match request { + let user_filter = match request { UserInfoRequest::UserInfoRequestById(id) => { format!( - "{LDAP_FIELD_USER_ID}={}", + "{LDAP_FIELD_OBJECT_ID}={}", ldap_escape_bytes( &Uuid::from_str(&id.id) .context(ParseIdByClientSnafu)? @@ -77,12 +91,18 @@ pub(crate) async fn get_user_info( format!("{LDAP_FIELD_USER_NAME}={}", ldap_escape(&username.username)) } }; + let base_dn = "DC=sble,DC=test"; let user = ldap .search( - "DC=sble,DC=test", + base_dn, Scope::Subtree, - &format!("(&(objectClass=user)({filter}))"), - ["*"], + &format!("(&(objectClass=user)({user_filter}))"), + [ + LDAP_FIELD_OBJECT_SECURITY_ID, + LDAP_FIELD_OBJECT_ID, + LDAP_FIELD_USER_NAME, + LDAP_FIELD_USER_PRIMARY_GROUP_RID, + ], ) .await .context(RequestLdapSnafu)? @@ -93,9 +113,19 @@ pub(crate) async fn get_user_info( .next() .context(UserNotFoundSnafu { request })?; let user = SearchEntry::construct(user); + + // Basic user facts + let user_sid = SecurityId::from_bytes( + user.bin_attrs + .get(LDAP_FIELD_OBJECT_SECURITY_ID) + .into_iter() + .flatten() + .next() + .unwrap(), + ); let id = user .bin_attrs - .get(LDAP_FIELD_USER_ID) + .get(LDAP_FIELD_OBJECT_ID) .and_then(|values| values.first()) .map(|uuid| // AD stores UUIDs as little-endian bytestrings @@ -108,15 +138,64 @@ pub(crate) async fn get_user_info( .get(LDAP_FIELD_USER_NAME) .and_then(|values| values.first()) .cloned(); + + // User group memberships are tricky, because users have exactly one *primary* and any number of *secondary* groups. + // Additionally groups can be members of other groups. + // Secondary groups are easy to read, either from reading the user's "memberOf" field, or by matching the user against + // the groups' "member" field. Here we use the latter method, which lets us make it recursive using the + // LDAP_MATCHING_RULE_IN_CHAIN rule. + let secondary_groups_filter = + format!("({LDAP_FIELD_GROUP_MEMBER}{LDAP_MATCHING_RULE_IN_CHAIN}=)"); + + // The user's *primary* group is trickier.. It is only available as a "RID" (relative ID), + // which is a sibling relative to the user's SID. + let primary_group_relative_id = user + .attrs + .get(LDAP_FIELD_USER_PRIMARY_GROUP_RID) + .into_iter() + .flatten() + .next() + .unwrap() + .parse::() + .unwrap(); + let mut primary_group_sid = user_sid.clone(); + *primary_group_sid.subauthorities.last_mut().unwrap() = primary_group_relative_id; + let primary_group_filter = format!("({LDAP_FIELD_OBJECT_SECURITY_ID}={primary_group_sid})"); + + // We can't trivially make the primary group query recursive... but since we know the primary group's SID, + // we can add a separate recursive filter for all of its parents. + let primary_group_parents_filter = format!( + "({LDAP_FIELD_GROUP_MEMBER}{LDAP_MATCHING_RULE_IN_CHAIN}=)" + ); + + // Let's put it all together, and make it go... + let groups_filter = + format!("(|{primary_group_filter}{primary_group_parents_filter}{secondary_groups_filter})"); + let groups = ldap + .search( + base_dn, + Scope::Subtree, + &format!("(&(objectClass=group){groups_filter})"), + [LDAP_FIELD_OBJECT_DISTINGUISHED_NAME], + ) + .await + .unwrap() + .success() + .unwrap() + .0 + .into_iter() + .map(|group| SearchEntry::construct(group).dn) + .collect::>(); + Ok(UserInfo { id: id.map(|id| id.to_string()), username, - groups: Vec::new(), + groups, custom_attributes: HashMap::new(), }) } -/// Escapes raw byte sequences for use in LDAP filter strings +/// Escapes raw byte sequences for use in LDAP filter strings. fn ldap_escape_bytes(bytes: &[u8]) -> String { use std::fmt::Write; let mut out = String::new(); @@ -126,3 +205,60 @@ fn ldap_escape_bytes(bytes: &[u8]) -> String { } out } + +/// An ActiveDirectory SID (Security ID) identifier for a user or group. +#[derive(Debug, Clone)] +struct SecurityId { + revision: u8, + identifier_authority: u64, + subauthorities: Vec, +} + +impl SecurityId { + /// Parses a SID from the binary SID--Packet representation. + fn from_bytes(bytes: &[u8]) -> Self { + let mut cursor = Cursor::new(bytes); + + // Format documented in https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/f992ad60-0fe4-4b87-9fed-beb478836861 + let revision = cursor.read_u8().unwrap(); + assert_eq!(revision, 1); + let subauthority_count = cursor.read_u8().unwrap(); + // From experimentation, yes this is a mix of big- and little endian values. Just roll with it... + let identifier_authority = cursor.read_u48::().unwrap(); + let subauthorities = (0..subauthority_count) + .map(|_| cursor.read_u32::()) + .collect::, _>>() + .unwrap(); + assert!(cursor.bytes().next().is_none()); + + Self { + revision, + identifier_authority, + subauthorities, + } + } +} + +impl Display for SecurityId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { + revision, + identifier_authority, + subauthorities, + } = self; + // Format documented in https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/c92a27b1-c772-4fa7-a432-15df5f1b66a1 + write!(f, "S-{revision}-")?; + + // Yes, this is technically part of the spec.. + if *identifier_authority < 1 << 32 { + write!(f, "{identifier_authority}")?; + } else { + write!(f, "{identifier_authority:X}")?; + } + + for subauthority in subauthorities { + write!(f, "-{subauthority}")?; + } + Ok(()) + } +} From 45ca96c270ffb2676a741c19eed10ae6302de3a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Wed, 14 Aug 2024 01:00:36 +0200 Subject: [PATCH 04/18] Allow mapping custom attributes --- rust/crd/src/user_info_fetcher.rs | 6 +++ .../src/backend/active_directory.rs | 37 +++++++++++++++---- rust/user-info-fetcher/src/main.rs | 12 +++--- 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/rust/crd/src/user_info_fetcher.rs b/rust/crd/src/user_info_fetcher.rs index 8abbaf7f..adbaefd7 100644 --- a/rust/crd/src/user_info_fetcher.rs +++ b/rust/crd/src/user_info_fetcher.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeMap; + use derivative::Derivative; use serde::{Deserialize, Serialize}; use stackable_operator::{ @@ -96,6 +98,10 @@ fn aas_default_port() -> u16 { pub struct ActiveDirectoryBackend { /// Hostname of the identity provider, e.g. `my.aas.corp`. pub ldap_server: String, + + /// Custom attributes, and their LDAP attribute names. + #[serde(default)] + pub custom_attribute_mappings: BTreeMap, } #[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize, Derivative)] diff --git a/rust/user-info-fetcher/src/backend/active_directory.rs b/rust/user-info-fetcher/src/backend/active_directory.rs index 2896697e..299bb3d8 100644 --- a/rust/user-info-fetcher/src/backend/active_directory.rs +++ b/rust/user-info-fetcher/src/backend/active_directory.rs @@ -1,5 +1,5 @@ use std::{ - collections::HashMap, + collections::{BTreeMap, HashMap}, fmt::Display, io::{Cursor, Read}, str::FromStr, @@ -65,6 +65,7 @@ const LDAP_FIELD_GROUP_MEMBER: &str = "member"; pub(crate) async fn get_user_info( request: &UserInfoRequest, ldap_server: &str, + custom_attribute_mappings: &BTreeMap, ) -> Result where { let (ldap_conn, mut ldap) = LdapConnAsync::with_settings(LdapConnSettings::new().set_no_tls_verify(true), ldap_server) @@ -92,17 +93,21 @@ pub(crate) async fn get_user_info( } }; let base_dn = "DC=sble,DC=test"; + let requested_user_attrs = [ + LDAP_FIELD_OBJECT_SECURITY_ID, + LDAP_FIELD_OBJECT_ID, + LDAP_FIELD_USER_NAME, + LDAP_FIELD_USER_PRIMARY_GROUP_RID, + ] + .into_iter() + .chain(custom_attribute_mappings.values().map(String::as_str)) + .collect::>(); let user = ldap .search( base_dn, Scope::Subtree, &format!("(&(objectClass=user)({user_filter}))"), - [ - LDAP_FIELD_OBJECT_SECURITY_ID, - LDAP_FIELD_OBJECT_ID, - LDAP_FIELD_USER_NAME, - LDAP_FIELD_USER_PRIMARY_GROUP_RID, - ], + requested_user_attrs, ) .await .context(RequestLdapSnafu)? @@ -138,6 +143,22 @@ pub(crate) async fn get_user_info( .get(LDAP_FIELD_USER_NAME) .and_then(|values| values.first()) .cloned(); + let custom_attributes = custom_attribute_mappings + .iter() + .filter_map(|(uif_key, ldap_key)| { + Some(( + uif_key.clone(), + serde_json::Value::Array( + user.attrs + .get(ldap_key)? + .iter() + .cloned() + .map(serde_json::Value::String) + .collect(), + ), + )) + }) + .collect::>(); // User group memberships are tricky, because users have exactly one *primary* and any number of *secondary* groups. // Additionally groups can be members of other groups. @@ -191,7 +212,7 @@ pub(crate) async fn get_user_info( id: id.map(|id| id.to_string()), username, groups, - custom_attributes: HashMap::new(), + custom_attributes, }) } diff --git a/rust/user-info-fetcher/src/main.rs b/rust/user-info-fetcher/src/main.rs index 4414fb6e..d2ebb97a 100644 --- a/rust/user-info-fetcher/src/main.rs +++ b/rust/user-info-fetcher/src/main.rs @@ -312,11 +312,13 @@ async fn get_user_info( .await .context(get_user_info_error::ExperimentalXfscAasSnafu) } - crd::Backend::ActiveDirectory(ad) => { - backend::active_directory::get_user_info(&req, &ad.ldap_server) - .await - .context(get_user_info_error::ActiveDirectorySnafu) - } + crd::Backend::ActiveDirectory(ad) => backend::active_directory::get_user_info( + &req, + &ad.ldap_server, + &ad.custom_attribute_mappings, + ) + .await + .context(get_user_info_error::ActiveDirectorySnafu), } }) .await?, From 6cc445ed035f8c3e12c4e766db433179fcffd09c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Wed, 14 Aug 2024 01:12:24 +0200 Subject: [PATCH 05/18] Special-case mapping special LDAP attributes --- .../src/backend/active_directory.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/rust/user-info-fetcher/src/backend/active_directory.rs b/rust/user-info-fetcher/src/backend/active_directory.rs index 299bb3d8..ab9c3036 100644 --- a/rust/user-info-fetcher/src/backend/active_directory.rs +++ b/rust/user-info-fetcher/src/backend/active_directory.rs @@ -148,14 +148,27 @@ pub(crate) async fn get_user_info( .filter_map(|(uif_key, ldap_key)| { Some(( uif_key.clone(), - serde_json::Value::Array( - user.attrs + serde_json::Value::Array(match ldap_key.as_str() { + // Some fields require special handling + LDAP_FIELD_OBJECT_DISTINGUISHED_NAME => { + vec![serde_json::Value::String(user.dn.clone())] + } + LDAP_FIELD_OBJECT_ID => { + vec![serde_json::Value::String(id?.to_string())] + } + LDAP_FIELD_OBJECT_SECURITY_ID => { + vec![serde_json::Value::String(user_sid.to_string())] + } + + // Otherwise, try to read the string value(s) + _ => user + .attrs .get(ldap_key)? .iter() .cloned() .map(serde_json::Value::String) .collect(), - ), + }), )) }) .collect::>(); From 5c6db37a819f1a4913e05503d22000f019f7ae53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Wed, 14 Aug 2024 01:18:11 +0200 Subject: [PATCH 06/18] Factor out group fetching --- .../src/backend/active_directory.rs | 55 +++++++++++-------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/rust/user-info-fetcher/src/backend/active_directory.rs b/rust/user-info-fetcher/src/backend/active_directory.rs index ab9c3036..dc49ec08 100644 --- a/rust/user-info-fetcher/src/backend/active_directory.rs +++ b/rust/user-info-fetcher/src/backend/active_directory.rs @@ -7,7 +7,7 @@ use std::{ use byteorder::{BigEndian, LittleEndian, ReadBytesExt}; use hyper::StatusCode; -use ldap3::{ldap_escape, LdapConnAsync, LdapConnSettings, LdapError, Scope, SearchEntry}; +use ldap3::{ldap_escape, Ldap, LdapConnAsync, LdapConnSettings, LdapError, Scope, SearchEntry}; use snafu::{OptionExt, ResultExt, Snafu}; use uuid::Uuid; @@ -119,7 +119,6 @@ pub(crate) async fn get_user_info( .context(UserNotFoundSnafu { request })?; let user = SearchEntry::construct(user); - // Basic user facts let user_sid = SecurityId::from_bytes( user.bin_attrs .get(LDAP_FIELD_OBJECT_SECURITY_ID) @@ -172,7 +171,23 @@ pub(crate) async fn get_user_info( )) }) .collect::>(); + let groups = user_group_distinguished_names(&mut ldap, base_dn, &user, &user_sid).await; + Ok(UserInfo { + id: id.map(|id| id.to_string()), + username, + groups, + custom_attributes, + }) +} + +/// Gets the distinguished names of all of `user`'s groups, both primary and secondary. +async fn user_group_distinguished_names( + ldap: &mut Ldap, + base_dn: &str, + user: &SearchEntry, + user_sid: &SecurityId, +) -> Vec { // User group memberships are tricky, because users have exactly one *primary* and any number of *secondary* groups. // Additionally groups can be members of other groups. // Secondary groups are easy to read, either from reading the user's "memberOf" field, or by matching the user against @@ -205,28 +220,20 @@ pub(crate) async fn get_user_info( // Let's put it all together, and make it go... let groups_filter = format!("(|{primary_group_filter}{primary_group_parents_filter}{secondary_groups_filter})"); - let groups = ldap - .search( - base_dn, - Scope::Subtree, - &format!("(&(objectClass=group){groups_filter})"), - [LDAP_FIELD_OBJECT_DISTINGUISHED_NAME], - ) - .await - .unwrap() - .success() - .unwrap() - .0 - .into_iter() - .map(|group| SearchEntry::construct(group).dn) - .collect::>(); - - Ok(UserInfo { - id: id.map(|id| id.to_string()), - username, - groups, - custom_attributes, - }) + ldap.search( + base_dn, + Scope::Subtree, + &format!("(&(objectClass=group){groups_filter})"), + [LDAP_FIELD_OBJECT_DISTINGUISHED_NAME], + ) + .await + .unwrap() + .success() + .unwrap() + .0 + .into_iter() + .map(|group| SearchEntry::construct(group).dn) + .collect::>() } /// Escapes raw byte sequences for use in LDAP filter strings. From d6f9ec1e4f10cf2f479c0b04b905a84b8ea75699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Wed, 14 Aug 2024 01:49:23 +0200 Subject: [PATCH 07/18] Error handling and cleanup --- .../src/backend/active_directory.rs | 161 +++++++++++++----- 1 file changed, 115 insertions(+), 46 deletions(-) diff --git a/rust/user-info-fetcher/src/backend/active_directory.rs b/rust/user-info-fetcher/src/backend/active_directory.rs index dc49ec08..0d68aff3 100644 --- a/rust/user-info-fetcher/src/backend/active_directory.rs +++ b/rust/user-info-fetcher/src/backend/active_directory.rs @@ -2,6 +2,7 @@ use std::{ collections::{BTreeMap, HashMap}, fmt::Display, io::{Cursor, Read}, + num::ParseIntError, str::FromStr, }; @@ -27,6 +28,9 @@ pub enum Error { #[snafu(display("failed to search LDAP for users"))] FindUserLdap { source: LdapError }, + #[snafu(display("failed to search LDAP for groups of user"))] + FindUserGroupsLdap { source: LdapError }, + #[snafu(display("invalid user ID sent by client"))] ParseIdByClient { source: uuid::Error }, @@ -35,6 +39,21 @@ pub enum Error { #[snafu(display("unable to find user {request}"))] UserNotFound { request: ErrorRenderUserInfoRequest }, + + #[snafu(display("unable to parse user {user_dn:?}'s primary group's RID"))] + InvalidPrimaryGroupRelativeId { + source: ParseIntError, + user_dn: String, + }, + + #[snafu(display("user {user_dn:?}'s SID has no subauthorities"))] + UserSidHasNoSubauthorities { user_dn: String }, + + #[snafu(display("failed to parse user {user_dn:?}'s SID"))] + ParseUserSid { + source: ParseSecurityIdError, + user_dn: String, + }, } impl http_error::Error for Error { @@ -44,9 +63,13 @@ impl http_error::Error for Error { Error::RequestLdap { .. } => StatusCode::SERVICE_UNAVAILABLE, Error::BindLdap { .. } => StatusCode::SERVICE_UNAVAILABLE, Error::FindUserLdap { .. } => StatusCode::SERVICE_UNAVAILABLE, + Error::FindUserGroupsLdap { .. } => StatusCode::SERVICE_UNAVAILABLE, Error::ParseIdByClient { .. } => StatusCode::BAD_REQUEST, Error::ParseIdByLdap { .. } => StatusCode::INTERNAL_SERVER_ERROR, Error::UserNotFound { .. } => StatusCode::NOT_FOUND, + Error::InvalidPrimaryGroupRelativeId { .. } => StatusCode::INTERNAL_SERVER_ERROR, + Error::UserSidHasNoSubauthorities { .. } => StatusCode::INTERNAL_SERVER_ERROR, + Error::ParseUserSid { .. } => StatusCode::INTERNAL_SERVER_ERROR, } } } @@ -118,15 +141,24 @@ pub(crate) async fn get_user_info( .next() .context(UserNotFoundSnafu { request })?; let user = SearchEntry::construct(user); + user_attributes(&mut ldap, base_dn, &user, custom_attribute_mappings).await +} - let user_sid = SecurityId::from_bytes( - user.bin_attrs - .get(LDAP_FIELD_OBJECT_SECURITY_ID) - .into_iter() - .flatten() - .next() - .unwrap(), - ); +#[tracing::instrument(skip(ldap, base_dn, user, custom_attribute_mappings), fields(user.dn))] +async fn user_attributes( + ldap: &mut Ldap, + base_dn: &str, + user: &SearchEntry, + custom_attribute_mappings: &BTreeMap, +) -> Result { + let user_sid = user + .bin_attrs + .get(LDAP_FIELD_OBJECT_SECURITY_ID) + .into_iter() + .flatten() + .next() + .map(|sid| SecurityId::from_bytes(sid).context(ParseUserSidSnafu { user_dn: &user.dn })) + .transpose()?; let id = user .bin_attrs .get(LDAP_FIELD_OBJECT_ID) @@ -156,7 +188,7 @@ pub(crate) async fn get_user_info( vec![serde_json::Value::String(id?.to_string())] } LDAP_FIELD_OBJECT_SECURITY_ID => { - vec![serde_json::Value::String(user_sid.to_string())] + vec![serde_json::Value::String(user_sid.as_ref()?.to_string())] } // Otherwise, try to read the string value(s) @@ -171,7 +203,12 @@ pub(crate) async fn get_user_info( )) }) .collect::>(); - let groups = user_group_distinguished_names(&mut ldap, base_dn, &user, &user_sid).await; + let groups = if let Some(user_sid) = &user_sid { + user_group_distinguished_names(ldap, base_dn, user, user_sid).await? + } else { + tracing::debug!(user.dn, "user has no SID, cannot fetch groups..."); + Vec::new() + }; Ok(UserInfo { id: id.map(|id| id.to_string()), @@ -182,12 +219,13 @@ pub(crate) async fn get_user_info( } /// Gets the distinguished names of all of `user`'s groups, both primary and secondary. +#[tracing::instrument(skip(ldap, base_dn, user, user_sid))] async fn user_group_distinguished_names( ldap: &mut Ldap, base_dn: &str, user: &SearchEntry, user_sid: &SecurityId, -) -> Vec { +) -> Result, Error> { // User group memberships are tricky, because users have exactly one *primary* and any number of *secondary* groups. // Additionally groups can be members of other groups. // Secondary groups are easy to read, either from reading the user's "memberOf" field, or by matching the user against @@ -198,17 +236,27 @@ async fn user_group_distinguished_names( // The user's *primary* group is trickier.. It is only available as a "RID" (relative ID), // which is a sibling relative to the user's SID. - let primary_group_relative_id = user + let Some(primary_group_relative_id) = user .attrs .get(LDAP_FIELD_USER_PRIMARY_GROUP_RID) .into_iter() .flatten() .next() - .unwrap() - .parse::() - .unwrap(); + .map(|rid| { + rid.parse::() + .context(InvalidPrimaryGroupRelativeIdSnafu { user_dn: &user.dn }) + }) + .transpose()? + else { + tracing::debug!("user has no primary group"); + return Ok(Vec::new()); + }; let mut primary_group_sid = user_sid.clone(); - *primary_group_sid.subauthorities.last_mut().unwrap() = primary_group_relative_id; + *primary_group_sid + .subauthorities + .last_mut() + .context(UserSidHasNoSubauthoritiesSnafu { user_dn: &user.dn })? = + primary_group_relative_id; let primary_group_filter = format!("({LDAP_FIELD_OBJECT_SECURITY_ID}={primary_group_sid})"); // We can't trivially make the primary group query recursive... but since we know the primary group's SID, @@ -220,20 +268,21 @@ async fn user_group_distinguished_names( // Let's put it all together, and make it go... let groups_filter = format!("(|{primary_group_filter}{primary_group_parents_filter}{secondary_groups_filter})"); - ldap.search( - base_dn, - Scope::Subtree, - &format!("(&(objectClass=group){groups_filter})"), - [LDAP_FIELD_OBJECT_DISTINGUISHED_NAME], - ) - .await - .unwrap() - .success() - .unwrap() - .0 - .into_iter() - .map(|group| SearchEntry::construct(group).dn) - .collect::>() + Ok(ldap + .search( + base_dn, + Scope::Subtree, + &format!("(&(objectClass=group){groups_filter})"), + [LDAP_FIELD_OBJECT_DISTINGUISHED_NAME], + ) + .await + .context(RequestLdapSnafu)? + .success() + .context(FindUserGroupsLdapSnafu)? + .0 + .into_iter() + .map(|group| SearchEntry::construct(group).dn) + .collect::>()) } /// Escapes raw byte sequences for use in LDAP filter strings. @@ -247,6 +296,19 @@ fn ldap_escape_bytes(bytes: &[u8]) -> String { out } +#[derive(Snafu, Debug)] +#[snafu(module)] +pub enum ParseSecurityIdError { + #[snafu(display("read failed"), context(false))] + Read { source: std::io::Error }, + + #[snafu(display("unknown SID format revision {revision}"))] + InvalidRevision { revision: u8 }, + + #[snafu(display("SID is longer than expected"))] + TooLong, +} + /// An ActiveDirectory SID (Security ID) identifier for a user or group. #[derive(Debug, Clone)] struct SecurityId { @@ -257,25 +319,32 @@ struct SecurityId { impl SecurityId { /// Parses a SID from the binary SID--Packet representation. - fn from_bytes(bytes: &[u8]) -> Self { + fn from_bytes(bytes: &[u8]) -> Result { + use parse_security_id_error::*; let mut cursor = Cursor::new(bytes); // Format documented in https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/f992ad60-0fe4-4b87-9fed-beb478836861 - let revision = cursor.read_u8().unwrap(); - assert_eq!(revision, 1); - let subauthority_count = cursor.read_u8().unwrap(); - // From experimentation, yes this is a mix of big- and little endian values. Just roll with it... - let identifier_authority = cursor.read_u48::().unwrap(); - let subauthorities = (0..subauthority_count) - .map(|_| cursor.read_u32::()) - .collect::, _>>() - .unwrap(); - assert!(cursor.bytes().next().is_none()); - - Self { - revision, - identifier_authority, - subauthorities, + let revision = cursor.read_u8()?; + match revision { + 1 => { + assert_eq!(revision, 1); + let subauthority_count = cursor.read_u8()?; + // From experimentation, yes this is a mix of big- and little endian values. Just roll with it... + let identifier_authority = cursor.read_u48::()?; + let subauthorities = (0..subauthority_count) + .map(|_| cursor.read_u32::()) + .collect::, _>>()?; + if cursor.bytes().next().is_some() { + return TooLongSnafu.fail(); + } + + Ok(Self { + revision, + identifier_authority, + subauthorities, + }) + } + _ => InvalidRevisionSnafu { revision }.fail(), } } } From c8441e765adfb6ef7c20cff6e3f8d6472018f29d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Wed, 14 Aug 2024 01:53:26 +0200 Subject: [PATCH 08/18] Make base DN configurable --- rust/crd/src/user_info_fetcher.rs | 3 +++ .../src/backend/active_directory.rs | 12 +++++++++--- rust/user-info-fetcher/src/main.rs | 1 + 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/rust/crd/src/user_info_fetcher.rs b/rust/crd/src/user_info_fetcher.rs index adbaefd7..2a3eb4a3 100644 --- a/rust/crd/src/user_info_fetcher.rs +++ b/rust/crd/src/user_info_fetcher.rs @@ -99,6 +99,9 @@ pub struct ActiveDirectoryBackend { /// Hostname of the identity provider, e.g. `my.aas.corp`. pub ldap_server: String, + /// The root Distinguished Name (DN) where users and groups are located. + pub base_distinguished_name: String, + /// Custom attributes, and their LDAP attribute names. #[serde(default)] pub custom_attribute_mappings: BTreeMap, diff --git a/rust/user-info-fetcher/src/backend/active_directory.rs b/rust/user-info-fetcher/src/backend/active_directory.rs index 0d68aff3..1228e329 100644 --- a/rust/user-info-fetcher/src/backend/active_directory.rs +++ b/rust/user-info-fetcher/src/backend/active_directory.rs @@ -88,6 +88,7 @@ const LDAP_FIELD_GROUP_MEMBER: &str = "member"; pub(crate) async fn get_user_info( request: &UserInfoRequest, ldap_server: &str, + base_distinguished_name: &str, custom_attribute_mappings: &BTreeMap, ) -> Result where { let (ldap_conn, mut ldap) = @@ -115,7 +116,6 @@ pub(crate) async fn get_user_info( format!("{LDAP_FIELD_USER_NAME}={}", ldap_escape(&username.username)) } }; - let base_dn = "DC=sble,DC=test"; let requested_user_attrs = [ LDAP_FIELD_OBJECT_SECURITY_ID, LDAP_FIELD_OBJECT_ID, @@ -127,7 +127,7 @@ pub(crate) async fn get_user_info( .collect::>(); let user = ldap .search( - base_dn, + base_distinguished_name, Scope::Subtree, &format!("(&(objectClass=user)({user_filter}))"), requested_user_attrs, @@ -141,7 +141,13 @@ pub(crate) async fn get_user_info( .next() .context(UserNotFoundSnafu { request })?; let user = SearchEntry::construct(user); - user_attributes(&mut ldap, base_dn, &user, custom_attribute_mappings).await + user_attributes( + &mut ldap, + base_distinguished_name, + &user, + custom_attribute_mappings, + ) + .await } #[tracing::instrument(skip(ldap, base_dn, user, custom_attribute_mappings), fields(user.dn))] diff --git a/rust/user-info-fetcher/src/main.rs b/rust/user-info-fetcher/src/main.rs index d2ebb97a..3049a227 100644 --- a/rust/user-info-fetcher/src/main.rs +++ b/rust/user-info-fetcher/src/main.rs @@ -315,6 +315,7 @@ async fn get_user_info( crd::Backend::ActiveDirectory(ad) => backend::active_directory::get_user_info( &req, &ad.ldap_server, + &ad.base_distinguished_name, &ad.custom_attribute_mappings, ) .await From 5bb8bb85dccee3639dce68887bf734786124ed01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Wed, 14 Aug 2024 03:16:07 +0200 Subject: [PATCH 09/18] Authenticate to AD with Kerberos --- Cargo.lock | 149 ++ Cargo.nix | 1493 ++++++++++++++++- default.nix | 4 + rust/crd/src/user_info_fetcher.rs | 2 + rust/operator-binary/src/controller.rs | 38 +- rust/user-info-fetcher/Cargo.toml | 2 +- .../src/backend/active_directory.rs | 14 +- 7 files changed, 1603 insertions(+), 99 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ddb26153..368d31c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -276,6 +276,29 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bindgen" +version = "0.69.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +dependencies = [ + "bitflags 2.6.0", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.70", + "which", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -351,6 +374,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -370,6 +402,17 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.5.9" @@ -479,6 +522,19 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "cross-krb5" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5275b07d1df512cbde13ced63ee0645c2c036fa6b1452886b291c039c74aac42" +dependencies = [ + "anyhow", + "bitflags 2.6.0", + "bytes", + "libgssapi", + "windows", +] + [[package]] name = "crossbeam-channel" version = "0.5.13" @@ -1200,6 +1256,15 @@ version = "1.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -1402,6 +1467,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "lber" version = "0.4.2" @@ -1420,10 +1491,12 @@ checksum = "166199a8207874a275144c8a94ff6eed5fcbf5c52303e4d9b4d53a0c7ac76554" dependencies = [ "async-trait", "bytes", + "cross-krb5", "futures", "futures-util", "lazy_static", "lber", + "libgssapi", "log", "native-tls", "nom", @@ -1454,6 +1527,38 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libgssapi" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8663f3a3a93dd394b669dd9b213b457c5e0d2bc5a1b13a0950bd733c6fb6e37" +dependencies = [ + "bitflags 2.6.0", + "bytes", + "lazy_static", + "libgssapi-sys", +] + +[[package]] +name = "libgssapi-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b57d9a71c774ec53b1b9119dbbcc589b5209831f0ddc0ff4210640051f545372" +dependencies = [ + "bindgen", + "pkg-config", +] + +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets 0.48.5", +] + [[package]] name = "libz-sys" version = "1.1.18" @@ -1908,6 +2013,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "prettyplease" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +dependencies = [ + "proc-macro2", + "syn 2.0.70", +] + [[package]] name = "proc-macro2" version = "1.0.86" @@ -2122,6 +2237,12 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.4.0" @@ -2408,6 +2529,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -3277,6 +3404,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + [[package]] name = "winapi" version = "0.3.9" @@ -3299,6 +3438,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.52.0" diff --git a/Cargo.nix b/Cargo.nix index 871093cd..52d10fa4 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -943,6 +943,103 @@ rec { }; resolvedDefaultFeatures = [ "alloc" "default" "std" ]; }; + "bindgen" = rec { + crateName = "bindgen"; + version = "0.69.4"; + edition = "2018"; + sha256 = "18194611hn3k1dkxlha7a52sr8vmfhl9blc54xhj08cahd8wh3d0"; + libPath = "lib.rs"; + authors = [ + "Jyun-Yan You " + "Emilio Cobos Álvarez " + "Nick Fitzgerald " + "The Servo project developers" + ]; + dependencies = [ + { + name = "bitflags"; + packageId = "bitflags 2.6.0"; + } + { + name = "cexpr"; + packageId = "cexpr"; + } + { + name = "clang-sys"; + packageId = "clang-sys"; + features = [ "clang_6_0" ]; + } + { + name = "itertools"; + packageId = "itertools"; + usesDefaultFeatures = false; + } + { + name = "lazy_static"; + packageId = "lazy_static"; + } + { + name = "lazycell"; + packageId = "lazycell"; + } + { + name = "log"; + packageId = "log"; + optional = true; + } + { + name = "prettyplease"; + packageId = "prettyplease"; + optional = true; + features = [ "verbatim" ]; + } + { + name = "proc-macro2"; + packageId = "proc-macro2"; + usesDefaultFeatures = false; + } + { + name = "quote"; + packageId = "quote"; + usesDefaultFeatures = false; + } + { + name = "regex"; + packageId = "regex"; + usesDefaultFeatures = false; + features = [ "std" "unicode-perl" ]; + } + { + name = "rustc-hash"; + packageId = "rustc-hash"; + } + { + name = "shlex"; + packageId = "shlex"; + } + { + name = "syn"; + packageId = "syn 2.0.70"; + features = [ "full" "extra-traits" "visit-mut" ]; + } + { + name = "which"; + packageId = "which"; + optional = true; + usesDefaultFeatures = false; + } + ]; + features = { + "default" = [ "logging" "prettyplease" "runtime" "which-rustfmt" ]; + "experimental" = [ "dep:annotate-snippets" ]; + "logging" = [ "dep:log" ]; + "prettyplease" = [ "dep:prettyplease" ]; + "runtime" = [ "clang-sys/runtime" ]; + "static" = [ "clang-sys/static" ]; + "which-rustfmt" = [ "dep:which" ]; + }; + resolvedDefaultFeatures = [ "default" "logging" "prettyplease" "runtime" "which-rustfmt" ]; + }; "bit-set" = rec { crateName = "bit-set"; version = "0.5.3"; @@ -1139,6 +1236,24 @@ rec { }; resolvedDefaultFeatures = [ "parallel" ]; }; + "cexpr" = rec { + crateName = "cexpr"; + version = "0.6.0"; + edition = "2018"; + sha256 = "0rl77bwhs5p979ih4r0202cn5jrfsrbgrksp40lkfz5vk1x3ib3g"; + authors = [ + "Jethro Beekman " + ]; + dependencies = [ + { + name = "nom"; + packageId = "nom"; + usesDefaultFeatures = false; + features = [ "std" ]; + } + ]; + + }; "cfg-if" = rec { crateName = "cfg-if"; version = "1.0.0"; @@ -1215,6 +1330,69 @@ rec { }; resolvedDefaultFeatures = [ "alloc" "android-tzdata" "clock" "iana-time-zone" "now" "serde" "std" "winapi" "windows-targets" ]; }; + "clang-sys" = rec { + crateName = "clang-sys"; + version = "1.8.1"; + edition = "2021"; + links = "clang"; + sha256 = "1x1r9yqss76z8xwpdanw313ss6fniwc1r7dzb5ycjn0ph53kj0hb"; + libName = "clang_sys"; + authors = [ + "Kyle Mayes " + ]; + dependencies = [ + { + name = "glob"; + packageId = "glob"; + } + { + name = "libc"; + packageId = "libc"; + usesDefaultFeatures = false; + } + { + name = "libloading"; + packageId = "libloading"; + optional = true; + } + ]; + buildDependencies = [ + { + name = "glob"; + packageId = "glob"; + } + ]; + devDependencies = [ + { + name = "glob"; + packageId = "glob"; + } + ]; + features = { + "clang_10_0" = [ "clang_9_0" ]; + "clang_11_0" = [ "clang_10_0" ]; + "clang_12_0" = [ "clang_11_0" ]; + "clang_13_0" = [ "clang_12_0" ]; + "clang_14_0" = [ "clang_13_0" ]; + "clang_15_0" = [ "clang_14_0" ]; + "clang_16_0" = [ "clang_15_0" ]; + "clang_17_0" = [ "clang_16_0" ]; + "clang_18_0" = [ "clang_17_0" ]; + "clang_3_6" = [ "clang_3_5" ]; + "clang_3_7" = [ "clang_3_6" ]; + "clang_3_8" = [ "clang_3_7" ]; + "clang_3_9" = [ "clang_3_8" ]; + "clang_4_0" = [ "clang_3_9" ]; + "clang_5_0" = [ "clang_4_0" ]; + "clang_6_0" = [ "clang_5_0" ]; + "clang_7_0" = [ "clang_6_0" ]; + "clang_8_0" = [ "clang_7_0" ]; + "clang_9_0" = [ "clang_8_0" ]; + "libloading" = [ "dep:libloading" ]; + "runtime" = [ "libloading" ]; + }; + resolvedDefaultFeatures = [ "clang_3_5" "clang_3_6" "clang_3_7" "clang_3_8" "clang_3_9" "clang_4_0" "clang_5_0" "clang_6_0" "libloading" "runtime" ]; + }; "clap" = rec { crateName = "clap"; version = "4.5.9"; @@ -1525,6 +1703,47 @@ rec { }; resolvedDefaultFeatures = [ "default" "std" ]; }; + "cross-krb5" = rec { + crateName = "cross-krb5"; + version = "0.4.0"; + edition = "2021"; + sha256 = "0hmc9b3kkh4ina32hidilrph6b2wckh3xmnf2ggcn4pm3myv0xaj"; + libName = "cross_krb5"; + authors = [ + "Eric Stokes " + ]; + dependencies = [ + { + name = "anyhow"; + packageId = "anyhow"; + } + { + name = "bitflags"; + packageId = "bitflags 2.6.0"; + } + { + name = "bytes"; + packageId = "bytes"; + } + { + name = "libgssapi"; + packageId = "libgssapi"; + usesDefaultFeatures = false; + target = { target, features }: (target."unix" or false); + } + { + name = "windows"; + packageId = "windows"; + target = { target, features }: (target."windows" or false); + features = [ "Win32_Foundation" "Win32_Globalization" "Win32_Security_Authentication_Identity" "Win32_Security_Credentials" "Win32_System_Diagnostics_Debug" "Win32_System_SystemInformation" "Win32_System_SystemServices" "Win32_System_Time" ]; + } + ]; + features = { + "default" = [ "iov" ]; + "iov" = [ "libgssapi/iov" ]; + }; + resolvedDefaultFeatures = [ "default" "iov" ]; + }; "crossbeam-channel" = rec { crateName = "crossbeam-channel"; version = "0.5.13"; @@ -3700,6 +3919,26 @@ rec { }; resolvedDefaultFeatures = [ "default" ]; }; + "itertools" = rec { + crateName = "itertools"; + version = "0.12.1"; + edition = "2018"; + sha256 = "0s95jbb3ndj1lvfxyq5wanc0fm0r6hg6q4ngb92qlfdxvci10ads"; + authors = [ + "bluss" + ]; + dependencies = [ + { + name = "either"; + packageId = "either"; + usesDefaultFeatures = false; + } + ]; + features = { + "default" = [ "use_std" ]; + "use_std" = [ "use_alloc" "either/use_std" ]; + }; + }; "itoa" = rec { crateName = "itoa"; version = "1.0.11"; @@ -4498,117 +4737,342 @@ rec { "spin_no_std" = [ "spin" ]; }; }; - "libc" = rec { - crateName = "libc"; - version = "0.2.155"; + "lazycell" = rec { + crateName = "lazycell"; + version = "1.3.0"; edition = "2015"; - sha256 = "0z44c53z54znna8n322k5iwg80arxxpdzjj5260pxxzc9a58icwp"; + sha256 = "0m8gw7dn30i0zjjpjdyf6pc16c34nl71lpv461mix50x3p70h3c3"; authors = [ - "The Rust Project Developers" + "Alex Crichton " + "Nikita Pekin " ]; features = { - "default" = [ "std" ]; - "rustc-dep-of-std" = [ "align" "rustc-std-workspace-core" ]; - "rustc-std-workspace-core" = [ "dep:rustc-std-workspace-core" ]; - "use_std" = [ "std" ]; + "clippy" = [ "dep:clippy" ]; + "nightly-testing" = [ "clippy" "nightly" ]; + "serde" = [ "dep:serde" ]; }; - resolvedDefaultFeatures = [ "default" "extra_traits" "std" ]; }; - "libgit2-sys" = rec { - crateName = "libgit2-sys"; - version = "0.17.0+1.8.1"; - edition = "2018"; - links = "git2"; - sha256 = "093jxfl2i9vxdlgf7vk9d040sjwy0nq4fid640y7qix6m0k26iqh"; - libName = "libgit2_sys"; - libPath = "lib.rs"; + "lber" = rec { + crateName = "lber"; + version = "0.4.2"; + edition = "2015"; + sha256 = "02pghdykbsffimswayixinrsaxfmwwzpb854w5cqzkv4kzyzkxrd"; authors = [ - "Josh Triplett " - "Alex Crichton " + "Nadja Reitzenstein " + "Ivan Nejgebauer " ]; dependencies = [ { - name = "libc"; - packageId = "libc"; - } - { - name = "libz-sys"; - packageId = "libz-sys"; - usesDefaultFeatures = false; - features = [ "libc" ]; - } - ]; - buildDependencies = [ - { - name = "cc"; - packageId = "cc"; - features = [ "parallel" ]; + name = "bytes"; + packageId = "bytes"; } { - name = "pkg-config"; - packageId = "pkg-config"; + name = "nom"; + packageId = "nom"; } ]; - features = { - "https" = [ "openssl-sys" ]; - "libssh2-sys" = [ "dep:libssh2-sys" ]; - "openssl-sys" = [ "dep:openssl-sys" ]; - "ssh" = [ "libssh2-sys" ]; - "vendored-openssl" = [ "openssl-sys/vendored" ]; - "zlib-ng-compat" = [ "libz-sys/zlib-ng" "libssh2-sys?/zlib-ng-compat" ]; - }; + }; - "libz-sys" = rec { - crateName = "libz-sys"; - version = "1.1.18"; - edition = "2018"; - links = "z"; - sha256 = "0bpqmfzvijbrqs29vphnafjz834lpz6pabbsnf85rqppb9pa4pf1"; - libName = "libz_sys"; + "ldap3" = rec { + crateName = "ldap3"; + version = "0.11.5"; + edition = "2021"; + sha256 = "0m35qxx0qfnmnkcy80r3qpswnpzddvzr92jc2iss4x3q42l9jq8n"; authors = [ - "Alex Crichton " - "Josh Triplett " - "Sebastian Thiel " + "Ivan Nejgebauer " ]; dependencies = [ { - name = "libc"; - packageId = "libc"; + name = "async-trait"; + packageId = "async-trait"; + } + { + name = "bytes"; + packageId = "bytes"; + } + { + name = "cross-krb5"; + packageId = "cross-krb5"; optional = true; } - ]; - buildDependencies = [ { - name = "cc"; - packageId = "cc"; + name = "futures"; + packageId = "futures"; } { - name = "pkg-config"; - packageId = "pkg-config"; + name = "futures-util"; + packageId = "futures-util"; } { - name = "vcpkg"; - packageId = "vcpkg"; + name = "lazy_static"; + packageId = "lazy_static"; } - ]; - features = { - "cmake" = [ "dep:cmake" ]; - "default" = [ "libc" "stock-zlib" ]; - "libc" = [ "dep:libc" ]; - "zlib-ng" = [ "libc" "cmake" ]; - "zlib-ng-no-cmake-experimental-community-maintained" = [ "libc" ]; - }; - resolvedDefaultFeatures = [ "libc" ]; - }; - "linux-raw-sys" = rec { - crateName = "linux-raw-sys"; - version = "0.4.14"; - edition = "2021"; - sha256 = "12gsjgbhhjwywpqcrizv80vrp7p7grsz5laqq773i33wphjsxcvq"; - libName = "linux_raw_sys"; - authors = [ - "Dan Gohman " - ]; + { + name = "lber"; + packageId = "lber"; + } + { + name = "libgssapi"; + packageId = "libgssapi"; + optional = true; + } + { + name = "log"; + packageId = "log"; + } + { + name = "native-tls"; + packageId = "native-tls"; + optional = true; + } + { + name = "nom"; + packageId = "nom"; + } + { + name = "percent-encoding"; + packageId = "percent-encoding"; + } + { + name = "thiserror"; + packageId = "thiserror"; + } + { + name = "tokio"; + packageId = "tokio"; + features = [ "macros" "io-util" "sync" "time" "net" ]; + } + { + name = "tokio-native-tls"; + packageId = "tokio-native-tls"; + optional = true; + } + { + name = "tokio-stream"; + packageId = "tokio-stream"; + } + { + name = "tokio-util"; + packageId = "tokio-util"; + features = [ "codec" ]; + } + { + name = "url"; + packageId = "url"; + } + ]; + devDependencies = [ + { + name = "tokio"; + packageId = "tokio"; + features = [ "macros" "io-util" "sync" "time" "net" "rt-multi-thread" ]; + } + ]; + features = { + "cross-krb5" = [ "dep:cross-krb5" ]; + "default" = [ "sync" "tls" ]; + "gssapi" = [ "cross-krb5" "libgssapi?/default" ]; + "libgssapi" = [ "dep:libgssapi" ]; + "native-tls" = [ "dep:native-tls" ]; + "ring" = [ "dep:ring" ]; + "rustls" = [ "dep:rustls" ]; + "rustls-native-certs" = [ "dep:rustls-native-certs" ]; + "sync" = [ "tokio/rt" ]; + "tls" = [ "tls-native" ]; + "tls-native" = [ "native-tls" "tokio-native-tls" "tokio/rt" ]; + "tls-rustls" = [ "rustls" "tokio-rustls" "rustls-native-certs" "x509-parser" "ring" "tokio/rt" ]; + "tokio-native-tls" = [ "dep:tokio-native-tls" ]; + "tokio-rustls" = [ "dep:tokio-rustls" ]; + "x509-parser" = [ "dep:x509-parser" ]; + }; + resolvedDefaultFeatures = [ "cross-krb5" "default" "gssapi" "native-tls" "sync" "tls" "tls-native" "tokio-native-tls" ]; + }; + "libc" = rec { + crateName = "libc"; + version = "0.2.155"; + edition = "2015"; + sha256 = "0z44c53z54znna8n322k5iwg80arxxpdzjj5260pxxzc9a58icwp"; + authors = [ + "The Rust Project Developers" + ]; + features = { + "default" = [ "std" ]; + "rustc-dep-of-std" = [ "align" "rustc-std-workspace-core" ]; + "rustc-std-workspace-core" = [ "dep:rustc-std-workspace-core" ]; + "use_std" = [ "std" ]; + }; + resolvedDefaultFeatures = [ "default" "extra_traits" "std" ]; + }; + "libgit2-sys" = rec { + crateName = "libgit2-sys"; + version = "0.17.0+1.8.1"; + edition = "2018"; + links = "git2"; + sha256 = "093jxfl2i9vxdlgf7vk9d040sjwy0nq4fid640y7qix6m0k26iqh"; + libName = "libgit2_sys"; + libPath = "lib.rs"; + authors = [ + "Josh Triplett " + "Alex Crichton " + ]; + dependencies = [ + { + name = "libc"; + packageId = "libc"; + } + { + name = "libz-sys"; + packageId = "libz-sys"; + usesDefaultFeatures = false; + features = [ "libc" ]; + } + ]; + buildDependencies = [ + { + name = "cc"; + packageId = "cc"; + features = [ "parallel" ]; + } + { + name = "pkg-config"; + packageId = "pkg-config"; + } + ]; + features = { + "https" = [ "openssl-sys" ]; + "libssh2-sys" = [ "dep:libssh2-sys" ]; + "openssl-sys" = [ "dep:openssl-sys" ]; + "ssh" = [ "libssh2-sys" ]; + "vendored-openssl" = [ "openssl-sys/vendored" ]; + "zlib-ng-compat" = [ "libz-sys/zlib-ng" "libssh2-sys?/zlib-ng-compat" ]; + }; + }; + "libgssapi" = rec { + crateName = "libgssapi"; + version = "0.7.2"; + edition = "2018"; + sha256 = "0dvfzg337mqbjnh166sspk9f1iapnh9v5ncxcr5kkpck78x3yrp8"; + authors = [ + "Eric Stokes " + ]; + dependencies = [ + { + name = "bitflags"; + packageId = "bitflags 2.6.0"; + } + { + name = "bytes"; + packageId = "bytes"; + } + { + name = "lazy_static"; + packageId = "lazy_static"; + } + { + name = "libgssapi-sys"; + packageId = "libgssapi-sys"; + } + ]; + features = { + "default" = [ "iov" "localname" ]; + }; + resolvedDefaultFeatures = [ "default" "iov" "localname" ]; + }; + "libgssapi-sys" = rec { + crateName = "libgssapi-sys"; + version = "0.3.1"; + edition = "2018"; + links = "gssapi_krb5"; + sha256 = "0wjkahghah0647s0zp0d3y1hjllvb36bp78ip6qm7v3lqxqrlzdm"; + libName = "libgssapi_sys"; + authors = [ + "Eric Stokes " + ]; + buildDependencies = [ + { + name = "bindgen"; + packageId = "bindgen"; + } + { + name = "pkg-config"; + packageId = "pkg-config"; + } + ]; + + }; + "libloading" = rec { + crateName = "libloading"; + version = "0.8.5"; + edition = "2015"; + sha256 = "194dvczq4sifwkzllfmw0qkgvilpha7m5xy90gd6i446vcpz4ya9"; + authors = [ + "Simonas Kazlauskas " + ]; + dependencies = [ + { + name = "cfg-if"; + packageId = "cfg-if"; + target = { target, features }: (target."unix" or false); + } + { + name = "windows-targets"; + packageId = "windows-targets 0.48.5"; + target = { target, features }: (target."windows" or false); + } + ]; + + }; + "libz-sys" = rec { + crateName = "libz-sys"; + version = "1.1.18"; + edition = "2018"; + links = "z"; + sha256 = "0bpqmfzvijbrqs29vphnafjz834lpz6pabbsnf85rqppb9pa4pf1"; + libName = "libz_sys"; + authors = [ + "Alex Crichton " + "Josh Triplett " + "Sebastian Thiel " + ]; + dependencies = [ + { + name = "libc"; + packageId = "libc"; + optional = true; + } + ]; + buildDependencies = [ + { + name = "cc"; + packageId = "cc"; + } + { + name = "pkg-config"; + packageId = "pkg-config"; + } + { + name = "vcpkg"; + packageId = "vcpkg"; + } + ]; + features = { + "cmake" = [ "dep:cmake" ]; + "default" = [ "libc" "stock-zlib" ]; + "libc" = [ "dep:libc" ]; + "zlib-ng" = [ "libc" "cmake" ]; + "zlib-ng-no-cmake-experimental-community-maintained" = [ "libc" ]; + }; + resolvedDefaultFeatures = [ "libc" ]; + }; + "linux-raw-sys" = rec { + crateName = "linux-raw-sys"; + version = "0.4.14"; + edition = "2021"; + sha256 = "12gsjgbhhjwywpqcrizv80vrp7p7grsz5laqq773i33wphjsxcvq"; + libName = "linux_raw_sys"; + authors = [ + "Dan Gohman " + ]; features = { "compiler_builtins" = [ "dep:compiler_builtins" ]; "core" = [ "dep:core" ]; @@ -4726,6 +5190,20 @@ rec { ]; }; + "minimal-lexical" = rec { + crateName = "minimal-lexical"; + version = "0.2.1"; + edition = "2018"; + sha256 = "16ppc5g84aijpri4jzv14rvcnslvlpphbszc7zzp6vfkddf4qdb8"; + libName = "minimal_lexical"; + authors = [ + "Alex Huszagh " + ]; + features = { + "default" = [ "std" ]; + }; + resolvedDefaultFeatures = [ "std" ]; + }; "miniz_oxide" = rec { crateName = "miniz_oxide"; version = "0.7.4"; @@ -4955,6 +5433,32 @@ rec { "vendored" = [ "openssl/vendored" ]; }; }; + "nom" = rec { + crateName = "nom"; + version = "7.1.3"; + edition = "2018"; + sha256 = "0jha9901wxam390jcf5pfa0qqfrgh8li787jx2ip0yk5b8y9hwyj"; + authors = [ + "contact@geoffroycouprie.com" + ]; + dependencies = [ + { + name = "memchr"; + packageId = "memchr"; + usesDefaultFeatures = false; + } + { + name = "minimal-lexical"; + packageId = "minimal-lexical"; + usesDefaultFeatures = false; + } + ]; + features = { + "default" = [ "std" ]; + "std" = [ "alloc" "memchr/std" "minimal-lexical/std" ]; + }; + resolvedDefaultFeatures = [ "alloc" "default" "std" ]; + }; "nu-ansi-term" = rec { crateName = "nu-ansi-term"; version = "0.46.0"; @@ -5886,15 +6390,55 @@ rec { }; resolvedDefaultFeatures = [ "simd" "std" ]; }; - "proc-macro2" = rec { - crateName = "proc-macro2"; - version = "1.0.86"; + "prettyplease" = rec { + crateName = "prettyplease"; + version = "0.2.20"; edition = "2021"; - sha256 = "0xrv22p8lqlfdf1w0pj4si8n2ws4aw0kilmziwf0vpv5ys6rwway"; - libName = "proc_macro2"; + links = "prettyplease02"; + sha256 = "0pk4vm9fir1p0bl11p9fkgl9r1x9vi4avv8l7flb1wx2i1a364jz"; authors = [ "David Tolnay " - "Alex Crichton " + ]; + dependencies = [ + { + name = "proc-macro2"; + packageId = "proc-macro2"; + usesDefaultFeatures = false; + } + { + name = "syn"; + packageId = "syn 2.0.70"; + usesDefaultFeatures = false; + features = [ "full" ]; + } + ]; + devDependencies = [ + { + name = "proc-macro2"; + packageId = "proc-macro2"; + usesDefaultFeatures = false; + } + { + name = "syn"; + packageId = "syn 2.0.70"; + usesDefaultFeatures = false; + features = [ "parsing" ]; + } + ]; + features = { + "verbatim" = [ "syn/parsing" ]; + }; + resolvedDefaultFeatures = [ "verbatim" ]; + }; + "proc-macro2" = rec { + crateName = "proc-macro2"; + version = "1.0.86"; + edition = "2021"; + sha256 = "0xrv22p8lqlfdf1w0pj4si8n2ws4aw0kilmziwf0vpv5ys6rwway"; + libName = "proc_macro2"; + authors = [ + "David Tolnay " + "Alex Crichton " ]; dependencies = [ { @@ -6734,6 +7278,20 @@ rec { "rustc-dep-of-std" = [ "core" "compiler_builtins" ]; }; }; + "rustc-hash" = rec { + crateName = "rustc-hash"; + version = "1.1.0"; + edition = "2015"; + sha256 = "1qkc5khrmv5pqi5l5ca9p5nl5hs742cagrndhbrlk3dhlrx3zm08"; + libName = "rustc_hash"; + authors = [ + "The Rust Project Developers" + ]; + features = { + "default" = [ "std" ]; + }; + resolvedDefaultFeatures = [ "default" "std" ]; + }; "rustc_version" = rec { crateName = "rustc_version"; version = "0.4.0"; @@ -7646,6 +8204,24 @@ rec { "loom" = [ "dep:loom" ]; }; }; + "shlex" = rec { + crateName = "shlex"; + version = "1.3.0"; + edition = "2015"; + sha256 = "0r1y6bv26c1scpxvhg2cabimrmwgbp4p3wy6syj9n0c4s3q2znhg"; + authors = [ + "comex " + "Fenhl " + "Adrian Taylor " + "Alex Touchet " + "Daniel Parks " + "Garrett Berg " + ]; + features = { + "default" = [ "std" ]; + }; + resolvedDefaultFeatures = [ "default" "std" ]; + }; "signal-hook-registry" = rec { crateName = "signal-hook-registry"; version = "1.4.2"; @@ -8102,6 +8678,7 @@ rec { src = if ((lib.versionOlder builtins.nixVersion "2.4pre20211007") || (lib.versionOlder "2.5" builtins.nixVersion )) then lib.cleanSourceWith { filter = sourceFilter; src = ./rust/regorule-library; } else ./rust/regorule-library; + libName = "stackable_opa_regorule_library"; authors = [ "Stackable GmbH " ]; @@ -8131,6 +8708,14 @@ rec { name = "axum"; packageId = "axum"; } + { + name = "base64"; + packageId = "base64 0.22.1"; + } + { + name = "byteorder"; + packageId = "byteorder"; + } { name = "clap"; packageId = "clap"; @@ -8143,11 +8728,20 @@ rec { name = "hyper"; packageId = "hyper"; } + { + name = "ldap3"; + packageId = "ldap3"; + features = [ "gssapi" "tls" ]; + } { name = "moka"; packageId = "moka"; features = [ "future" ]; } + { + name = "native-tls"; + packageId = "native-tls"; + } { name = "pin-project"; packageId = "pin-project"; @@ -8195,6 +8789,10 @@ rec { name = "url"; packageId = "url"; } + { + name = "uuid"; + packageId = "uuid"; + } ]; }; @@ -10923,6 +11521,40 @@ rec { "serde" = [ "dep:serde" ]; }; }; + "which" = rec { + crateName = "which"; + version = "4.4.2"; + edition = "2021"; + sha256 = "1ixzmx3svsv5hbdvd8vdhd3qwvf6ns8jdpif1wmwsy10k90j9fl7"; + authors = [ + "Harry Fei " + ]; + dependencies = [ + { + name = "either"; + packageId = "either"; + } + { + name = "home"; + packageId = "home"; + target = { target, features }: ((target."windows" or false) || (target."unix" or false) || ("redox" == target."os" or null)); + } + { + name = "once_cell"; + packageId = "once_cell"; + target = { target, features }: (target."windows" or false); + } + { + name = "rustix"; + packageId = "rustix"; + usesDefaultFeatures = false; + features = [ "fs" "std" ]; + } + ]; + features = { + "regex" = [ "dep:regex" ]; + }; + }; "winapi" = rec { crateName = "winapi"; version = "0.3.9"; @@ -10970,6 +11602,687 @@ rec { ]; }; + "windows" = rec { + crateName = "windows"; + version = "0.52.0"; + edition = "2021"; + sha256 = "1gnh210qjlprpd1szaq04rjm1zqgdm9j7l9absg0kawi2rwm72p4"; + authors = [ + "Microsoft" + ]; + dependencies = [ + { + name = "windows-core"; + packageId = "windows-core"; + } + { + name = "windows-targets"; + packageId = "windows-targets 0.52.6"; + } + ]; + features = { + "AI_MachineLearning" = [ "AI" ]; + "ApplicationModel_Activation" = [ "ApplicationModel" ]; + "ApplicationModel_AppExtensions" = [ "ApplicationModel" ]; + "ApplicationModel_AppService" = [ "ApplicationModel" ]; + "ApplicationModel_Appointments" = [ "ApplicationModel" ]; + "ApplicationModel_Appointments_AppointmentsProvider" = [ "ApplicationModel_Appointments" ]; + "ApplicationModel_Appointments_DataProvider" = [ "ApplicationModel_Appointments" ]; + "ApplicationModel_Background" = [ "ApplicationModel" ]; + "ApplicationModel_Calls" = [ "ApplicationModel" ]; + "ApplicationModel_Calls_Background" = [ "ApplicationModel_Calls" ]; + "ApplicationModel_Calls_Provider" = [ "ApplicationModel_Calls" ]; + "ApplicationModel_Chat" = [ "ApplicationModel" ]; + "ApplicationModel_CommunicationBlocking" = [ "ApplicationModel" ]; + "ApplicationModel_Contacts" = [ "ApplicationModel" ]; + "ApplicationModel_Contacts_DataProvider" = [ "ApplicationModel_Contacts" ]; + "ApplicationModel_Contacts_Provider" = [ "ApplicationModel_Contacts" ]; + "ApplicationModel_ConversationalAgent" = [ "ApplicationModel" ]; + "ApplicationModel_Core" = [ "ApplicationModel" ]; + "ApplicationModel_DataTransfer" = [ "ApplicationModel" ]; + "ApplicationModel_DataTransfer_DragDrop" = [ "ApplicationModel_DataTransfer" ]; + "ApplicationModel_DataTransfer_DragDrop_Core" = [ "ApplicationModel_DataTransfer_DragDrop" ]; + "ApplicationModel_DataTransfer_ShareTarget" = [ "ApplicationModel_DataTransfer" ]; + "ApplicationModel_Email" = [ "ApplicationModel" ]; + "ApplicationModel_Email_DataProvider" = [ "ApplicationModel_Email" ]; + "ApplicationModel_ExtendedExecution" = [ "ApplicationModel" ]; + "ApplicationModel_ExtendedExecution_Foreground" = [ "ApplicationModel_ExtendedExecution" ]; + "ApplicationModel_Holographic" = [ "ApplicationModel" ]; + "ApplicationModel_LockScreen" = [ "ApplicationModel" ]; + "ApplicationModel_Payments" = [ "ApplicationModel" ]; + "ApplicationModel_Payments_Provider" = [ "ApplicationModel_Payments" ]; + "ApplicationModel_Preview" = [ "ApplicationModel" ]; + "ApplicationModel_Preview_Holographic" = [ "ApplicationModel_Preview" ]; + "ApplicationModel_Preview_InkWorkspace" = [ "ApplicationModel_Preview" ]; + "ApplicationModel_Preview_Notes" = [ "ApplicationModel_Preview" ]; + "ApplicationModel_Resources" = [ "ApplicationModel" ]; + "ApplicationModel_Resources_Core" = [ "ApplicationModel_Resources" ]; + "ApplicationModel_Resources_Management" = [ "ApplicationModel_Resources" ]; + "ApplicationModel_Search" = [ "ApplicationModel" ]; + "ApplicationModel_Search_Core" = [ "ApplicationModel_Search" ]; + "ApplicationModel_Store" = [ "ApplicationModel" ]; + "ApplicationModel_Store_LicenseManagement" = [ "ApplicationModel_Store" ]; + "ApplicationModel_Store_Preview" = [ "ApplicationModel_Store" ]; + "ApplicationModel_Store_Preview_InstallControl" = [ "ApplicationModel_Store_Preview" ]; + "ApplicationModel_UserActivities" = [ "ApplicationModel" ]; + "ApplicationModel_UserActivities_Core" = [ "ApplicationModel_UserActivities" ]; + "ApplicationModel_UserDataAccounts" = [ "ApplicationModel" ]; + "ApplicationModel_UserDataAccounts_Provider" = [ "ApplicationModel_UserDataAccounts" ]; + "ApplicationModel_UserDataAccounts_SystemAccess" = [ "ApplicationModel_UserDataAccounts" ]; + "ApplicationModel_UserDataTasks" = [ "ApplicationModel" ]; + "ApplicationModel_UserDataTasks_DataProvider" = [ "ApplicationModel_UserDataTasks" ]; + "ApplicationModel_VoiceCommands" = [ "ApplicationModel" ]; + "ApplicationModel_Wallet" = [ "ApplicationModel" ]; + "ApplicationModel_Wallet_System" = [ "ApplicationModel_Wallet" ]; + "Data_Html" = [ "Data" ]; + "Data_Json" = [ "Data" ]; + "Data_Pdf" = [ "Data" ]; + "Data_Text" = [ "Data" ]; + "Data_Xml" = [ "Data" ]; + "Data_Xml_Dom" = [ "Data_Xml" ]; + "Data_Xml_Xsl" = [ "Data_Xml" ]; + "Devices_Adc" = [ "Devices" ]; + "Devices_Adc_Provider" = [ "Devices_Adc" ]; + "Devices_Background" = [ "Devices" ]; + "Devices_Bluetooth" = [ "Devices" ]; + "Devices_Bluetooth_Advertisement" = [ "Devices_Bluetooth" ]; + "Devices_Bluetooth_Background" = [ "Devices_Bluetooth" ]; + "Devices_Bluetooth_GenericAttributeProfile" = [ "Devices_Bluetooth" ]; + "Devices_Bluetooth_Rfcomm" = [ "Devices_Bluetooth" ]; + "Devices_Custom" = [ "Devices" ]; + "Devices_Display" = [ "Devices" ]; + "Devices_Display_Core" = [ "Devices_Display" ]; + "Devices_Enumeration" = [ "Devices" ]; + "Devices_Enumeration_Pnp" = [ "Devices_Enumeration" ]; + "Devices_Geolocation" = [ "Devices" ]; + "Devices_Geolocation_Geofencing" = [ "Devices_Geolocation" ]; + "Devices_Geolocation_Provider" = [ "Devices_Geolocation" ]; + "Devices_Gpio" = [ "Devices" ]; + "Devices_Gpio_Provider" = [ "Devices_Gpio" ]; + "Devices_Haptics" = [ "Devices" ]; + "Devices_HumanInterfaceDevice" = [ "Devices" ]; + "Devices_I2c" = [ "Devices" ]; + "Devices_I2c_Provider" = [ "Devices_I2c" ]; + "Devices_Input" = [ "Devices" ]; + "Devices_Input_Preview" = [ "Devices_Input" ]; + "Devices_Lights" = [ "Devices" ]; + "Devices_Lights_Effects" = [ "Devices_Lights" ]; + "Devices_Midi" = [ "Devices" ]; + "Devices_PointOfService" = [ "Devices" ]; + "Devices_PointOfService_Provider" = [ "Devices_PointOfService" ]; + "Devices_Portable" = [ "Devices" ]; + "Devices_Power" = [ "Devices" ]; + "Devices_Printers" = [ "Devices" ]; + "Devices_Printers_Extensions" = [ "Devices_Printers" ]; + "Devices_Pwm" = [ "Devices" ]; + "Devices_Pwm_Provider" = [ "Devices_Pwm" ]; + "Devices_Radios" = [ "Devices" ]; + "Devices_Scanners" = [ "Devices" ]; + "Devices_Sensors" = [ "Devices" ]; + "Devices_Sensors_Custom" = [ "Devices_Sensors" ]; + "Devices_SerialCommunication" = [ "Devices" ]; + "Devices_SmartCards" = [ "Devices" ]; + "Devices_Sms" = [ "Devices" ]; + "Devices_Spi" = [ "Devices" ]; + "Devices_Spi_Provider" = [ "Devices_Spi" ]; + "Devices_Usb" = [ "Devices" ]; + "Devices_WiFi" = [ "Devices" ]; + "Devices_WiFiDirect" = [ "Devices" ]; + "Devices_WiFiDirect_Services" = [ "Devices_WiFiDirect" ]; + "Embedded_DeviceLockdown" = [ "Embedded" ]; + "Foundation_Collections" = [ "Foundation" ]; + "Foundation_Diagnostics" = [ "Foundation" ]; + "Foundation_Metadata" = [ "Foundation" ]; + "Foundation_Numerics" = [ "Foundation" ]; + "Gaming_Input" = [ "Gaming" ]; + "Gaming_Input_Custom" = [ "Gaming_Input" ]; + "Gaming_Input_ForceFeedback" = [ "Gaming_Input" ]; + "Gaming_Input_Preview" = [ "Gaming_Input" ]; + "Gaming_Preview" = [ "Gaming" ]; + "Gaming_Preview_GamesEnumeration" = [ "Gaming_Preview" ]; + "Gaming_UI" = [ "Gaming" ]; + "Gaming_XboxLive" = [ "Gaming" ]; + "Gaming_XboxLive_Storage" = [ "Gaming_XboxLive" ]; + "Globalization_Collation" = [ "Globalization" ]; + "Globalization_DateTimeFormatting" = [ "Globalization" ]; + "Globalization_Fonts" = [ "Globalization" ]; + "Globalization_NumberFormatting" = [ "Globalization" ]; + "Globalization_PhoneNumberFormatting" = [ "Globalization" ]; + "Graphics_Capture" = [ "Graphics" ]; + "Graphics_DirectX" = [ "Graphics" ]; + "Graphics_DirectX_Direct3D11" = [ "Graphics_DirectX" ]; + "Graphics_Display" = [ "Graphics" ]; + "Graphics_Display_Core" = [ "Graphics_Display" ]; + "Graphics_Effects" = [ "Graphics" ]; + "Graphics_Holographic" = [ "Graphics" ]; + "Graphics_Imaging" = [ "Graphics" ]; + "Graphics_Printing" = [ "Graphics" ]; + "Graphics_Printing3D" = [ "Graphics" ]; + "Graphics_Printing_OptionDetails" = [ "Graphics_Printing" ]; + "Graphics_Printing_PrintSupport" = [ "Graphics_Printing" ]; + "Graphics_Printing_PrintTicket" = [ "Graphics_Printing" ]; + "Graphics_Printing_Workflow" = [ "Graphics_Printing" ]; + "Management_Core" = [ "Management" ]; + "Management_Deployment" = [ "Management" ]; + "Management_Deployment_Preview" = [ "Management_Deployment" ]; + "Management_Policies" = [ "Management" ]; + "Management_Update" = [ "Management" ]; + "Management_Workplace" = [ "Management" ]; + "Media_AppBroadcasting" = [ "Media" ]; + "Media_AppRecording" = [ "Media" ]; + "Media_Audio" = [ "Media" ]; + "Media_Capture" = [ "Media" ]; + "Media_Capture_Core" = [ "Media_Capture" ]; + "Media_Capture_Frames" = [ "Media_Capture" ]; + "Media_Casting" = [ "Media" ]; + "Media_ClosedCaptioning" = [ "Media" ]; + "Media_ContentRestrictions" = [ "Media" ]; + "Media_Control" = [ "Media" ]; + "Media_Core" = [ "Media" ]; + "Media_Core_Preview" = [ "Media_Core" ]; + "Media_Devices" = [ "Media" ]; + "Media_Devices_Core" = [ "Media_Devices" ]; + "Media_DialProtocol" = [ "Media" ]; + "Media_Editing" = [ "Media" ]; + "Media_Effects" = [ "Media" ]; + "Media_FaceAnalysis" = [ "Media" ]; + "Media_Import" = [ "Media" ]; + "Media_MediaProperties" = [ "Media" ]; + "Media_Miracast" = [ "Media" ]; + "Media_Ocr" = [ "Media" ]; + "Media_PlayTo" = [ "Media" ]; + "Media_Playback" = [ "Media" ]; + "Media_Playlists" = [ "Media" ]; + "Media_Protection" = [ "Media" ]; + "Media_Protection_PlayReady" = [ "Media_Protection" ]; + "Media_Render" = [ "Media" ]; + "Media_SpeechRecognition" = [ "Media" ]; + "Media_SpeechSynthesis" = [ "Media" ]; + "Media_Streaming" = [ "Media" ]; + "Media_Streaming_Adaptive" = [ "Media_Streaming" ]; + "Media_Transcoding" = [ "Media" ]; + "Networking_BackgroundTransfer" = [ "Networking" ]; + "Networking_Connectivity" = [ "Networking" ]; + "Networking_NetworkOperators" = [ "Networking" ]; + "Networking_Proximity" = [ "Networking" ]; + "Networking_PushNotifications" = [ "Networking" ]; + "Networking_ServiceDiscovery" = [ "Networking" ]; + "Networking_ServiceDiscovery_Dnssd" = [ "Networking_ServiceDiscovery" ]; + "Networking_Sockets" = [ "Networking" ]; + "Networking_Vpn" = [ "Networking" ]; + "Networking_XboxLive" = [ "Networking" ]; + "Perception_Automation" = [ "Perception" ]; + "Perception_Automation_Core" = [ "Perception_Automation" ]; + "Perception_People" = [ "Perception" ]; + "Perception_Spatial" = [ "Perception" ]; + "Perception_Spatial_Preview" = [ "Perception_Spatial" ]; + "Perception_Spatial_Surfaces" = [ "Perception_Spatial" ]; + "Phone_ApplicationModel" = [ "Phone" ]; + "Phone_Devices" = [ "Phone" ]; + "Phone_Devices_Notification" = [ "Phone_Devices" ]; + "Phone_Devices_Power" = [ "Phone_Devices" ]; + "Phone_Management" = [ "Phone" ]; + "Phone_Management_Deployment" = [ "Phone_Management" ]; + "Phone_Media" = [ "Phone" ]; + "Phone_Media_Devices" = [ "Phone_Media" ]; + "Phone_Notification" = [ "Phone" ]; + "Phone_Notification_Management" = [ "Phone_Notification" ]; + "Phone_PersonalInformation" = [ "Phone" ]; + "Phone_PersonalInformation_Provisioning" = [ "Phone_PersonalInformation" ]; + "Phone_Speech" = [ "Phone" ]; + "Phone_Speech_Recognition" = [ "Phone_Speech" ]; + "Phone_StartScreen" = [ "Phone" ]; + "Phone_System" = [ "Phone" ]; + "Phone_System_Power" = [ "Phone_System" ]; + "Phone_System_Profile" = [ "Phone_System" ]; + "Phone_System_UserProfile" = [ "Phone_System" ]; + "Phone_System_UserProfile_GameServices" = [ "Phone_System_UserProfile" ]; + "Phone_System_UserProfile_GameServices_Core" = [ "Phone_System_UserProfile_GameServices" ]; + "Phone_UI" = [ "Phone" ]; + "Phone_UI_Input" = [ "Phone_UI" ]; + "Security_Authentication" = [ "Security" ]; + "Security_Authentication_Identity" = [ "Security_Authentication" ]; + "Security_Authentication_Identity_Core" = [ "Security_Authentication_Identity" ]; + "Security_Authentication_OnlineId" = [ "Security_Authentication" ]; + "Security_Authentication_Web" = [ "Security_Authentication" ]; + "Security_Authentication_Web_Core" = [ "Security_Authentication_Web" ]; + "Security_Authentication_Web_Provider" = [ "Security_Authentication_Web" ]; + "Security_Authorization" = [ "Security" ]; + "Security_Authorization_AppCapabilityAccess" = [ "Security_Authorization" ]; + "Security_Credentials" = [ "Security" ]; + "Security_Credentials_UI" = [ "Security_Credentials" ]; + "Security_Cryptography" = [ "Security" ]; + "Security_Cryptography_Certificates" = [ "Security_Cryptography" ]; + "Security_Cryptography_Core" = [ "Security_Cryptography" ]; + "Security_Cryptography_DataProtection" = [ "Security_Cryptography" ]; + "Security_DataProtection" = [ "Security" ]; + "Security_EnterpriseData" = [ "Security" ]; + "Security_ExchangeActiveSyncProvisioning" = [ "Security" ]; + "Security_Isolation" = [ "Security" ]; + "Services_Maps" = [ "Services" ]; + "Services_Maps_Guidance" = [ "Services_Maps" ]; + "Services_Maps_LocalSearch" = [ "Services_Maps" ]; + "Services_Maps_OfflineMaps" = [ "Services_Maps" ]; + "Services_Store" = [ "Services" ]; + "Services_TargetedContent" = [ "Services" ]; + "Storage_AccessCache" = [ "Storage" ]; + "Storage_BulkAccess" = [ "Storage" ]; + "Storage_Compression" = [ "Storage" ]; + "Storage_FileProperties" = [ "Storage" ]; + "Storage_Pickers" = [ "Storage" ]; + "Storage_Pickers_Provider" = [ "Storage_Pickers" ]; + "Storage_Provider" = [ "Storage" ]; + "Storage_Search" = [ "Storage" ]; + "Storage_Streams" = [ "Storage" ]; + "System_Diagnostics" = [ "System" ]; + "System_Diagnostics_DevicePortal" = [ "System_Diagnostics" ]; + "System_Diagnostics_Telemetry" = [ "System_Diagnostics" ]; + "System_Diagnostics_TraceReporting" = [ "System_Diagnostics" ]; + "System_Display" = [ "System" ]; + "System_Implementation" = [ "System" ]; + "System_Implementation_FileExplorer" = [ "System_Implementation" ]; + "System_Inventory" = [ "System" ]; + "System_Power" = [ "System" ]; + "System_Profile" = [ "System" ]; + "System_Profile_SystemManufacturers" = [ "System_Profile" ]; + "System_RemoteDesktop" = [ "System" ]; + "System_RemoteDesktop_Input" = [ "System_RemoteDesktop" ]; + "System_RemoteSystems" = [ "System" ]; + "System_Threading" = [ "System" ]; + "System_Threading_Core" = [ "System_Threading" ]; + "System_Update" = [ "System" ]; + "System_UserProfile" = [ "System" ]; + "UI_Accessibility" = [ "UI" ]; + "UI_ApplicationSettings" = [ "UI" ]; + "UI_Composition" = [ "UI" ]; + "UI_Composition_Core" = [ "UI_Composition" ]; + "UI_Composition_Desktop" = [ "UI_Composition" ]; + "UI_Composition_Diagnostics" = [ "UI_Composition" ]; + "UI_Composition_Effects" = [ "UI_Composition" ]; + "UI_Composition_Interactions" = [ "UI_Composition" ]; + "UI_Composition_Scenes" = [ "UI_Composition" ]; + "UI_Core" = [ "UI" ]; + "UI_Core_AnimationMetrics" = [ "UI_Core" ]; + "UI_Core_Preview" = [ "UI_Core" ]; + "UI_Input" = [ "UI" ]; + "UI_Input_Core" = [ "UI_Input" ]; + "UI_Input_Inking" = [ "UI_Input" ]; + "UI_Input_Inking_Analysis" = [ "UI_Input_Inking" ]; + "UI_Input_Inking_Core" = [ "UI_Input_Inking" ]; + "UI_Input_Inking_Preview" = [ "UI_Input_Inking" ]; + "UI_Input_Preview" = [ "UI_Input" ]; + "UI_Input_Preview_Injection" = [ "UI_Input_Preview" ]; + "UI_Input_Spatial" = [ "UI_Input" ]; + "UI_Notifications" = [ "UI" ]; + "UI_Notifications_Management" = [ "UI_Notifications" ]; + "UI_Popups" = [ "UI" ]; + "UI_Shell" = [ "UI" ]; + "UI_StartScreen" = [ "UI" ]; + "UI_Text" = [ "UI" ]; + "UI_Text_Core" = [ "UI_Text" ]; + "UI_UIAutomation" = [ "UI" ]; + "UI_UIAutomation_Core" = [ "UI_UIAutomation" ]; + "UI_ViewManagement" = [ "UI" ]; + "UI_ViewManagement_Core" = [ "UI_ViewManagement" ]; + "UI_WebUI" = [ "UI" ]; + "UI_WebUI_Core" = [ "UI_WebUI" ]; + "UI_WindowManagement" = [ "UI" ]; + "UI_WindowManagement_Preview" = [ "UI_WindowManagement" ]; + "Wdk_Foundation" = [ "Wdk" ]; + "Wdk_Graphics" = [ "Wdk" ]; + "Wdk_Graphics_Direct3D" = [ "Wdk_Graphics" ]; + "Wdk_Storage" = [ "Wdk" ]; + "Wdk_Storage_FileSystem" = [ "Wdk_Storage" ]; + "Wdk_Storage_FileSystem_Minifilters" = [ "Wdk_Storage_FileSystem" ]; + "Wdk_System" = [ "Wdk" ]; + "Wdk_System_IO" = [ "Wdk_System" ]; + "Wdk_System_OfflineRegistry" = [ "Wdk_System" ]; + "Wdk_System_Registry" = [ "Wdk_System" ]; + "Wdk_System_SystemInformation" = [ "Wdk_System" ]; + "Wdk_System_SystemServices" = [ "Wdk_System" ]; + "Wdk_System_Threading" = [ "Wdk_System" ]; + "Web_AtomPub" = [ "Web" ]; + "Web_Http" = [ "Web" ]; + "Web_Http_Diagnostics" = [ "Web_Http" ]; + "Web_Http_Filters" = [ "Web_Http" ]; + "Web_Http_Headers" = [ "Web_Http" ]; + "Web_Syndication" = [ "Web" ]; + "Web_UI" = [ "Web" ]; + "Web_UI_Interop" = [ "Web_UI" ]; + "Win32_AI" = [ "Win32" ]; + "Win32_AI_MachineLearning" = [ "Win32_AI" ]; + "Win32_AI_MachineLearning_DirectML" = [ "Win32_AI_MachineLearning" ]; + "Win32_AI_MachineLearning_WinML" = [ "Win32_AI_MachineLearning" ]; + "Win32_Data" = [ "Win32" ]; + "Win32_Data_HtmlHelp" = [ "Win32_Data" ]; + "Win32_Data_RightsManagement" = [ "Win32_Data" ]; + "Win32_Data_Xml" = [ "Win32_Data" ]; + "Win32_Data_Xml_MsXml" = [ "Win32_Data_Xml" ]; + "Win32_Data_Xml_XmlLite" = [ "Win32_Data_Xml" ]; + "Win32_Devices" = [ "Win32" ]; + "Win32_Devices_AllJoyn" = [ "Win32_Devices" ]; + "Win32_Devices_BiometricFramework" = [ "Win32_Devices" ]; + "Win32_Devices_Bluetooth" = [ "Win32_Devices" ]; + "Win32_Devices_Communication" = [ "Win32_Devices" ]; + "Win32_Devices_DeviceAccess" = [ "Win32_Devices" ]; + "Win32_Devices_DeviceAndDriverInstallation" = [ "Win32_Devices" ]; + "Win32_Devices_DeviceQuery" = [ "Win32_Devices" ]; + "Win32_Devices_Display" = [ "Win32_Devices" ]; + "Win32_Devices_Enumeration" = [ "Win32_Devices" ]; + "Win32_Devices_Enumeration_Pnp" = [ "Win32_Devices_Enumeration" ]; + "Win32_Devices_Fax" = [ "Win32_Devices" ]; + "Win32_Devices_FunctionDiscovery" = [ "Win32_Devices" ]; + "Win32_Devices_Geolocation" = [ "Win32_Devices" ]; + "Win32_Devices_HumanInterfaceDevice" = [ "Win32_Devices" ]; + "Win32_Devices_ImageAcquisition" = [ "Win32_Devices" ]; + "Win32_Devices_PortableDevices" = [ "Win32_Devices" ]; + "Win32_Devices_Properties" = [ "Win32_Devices" ]; + "Win32_Devices_Pwm" = [ "Win32_Devices" ]; + "Win32_Devices_Sensors" = [ "Win32_Devices" ]; + "Win32_Devices_SerialCommunication" = [ "Win32_Devices" ]; + "Win32_Devices_Tapi" = [ "Win32_Devices" ]; + "Win32_Devices_Usb" = [ "Win32_Devices" ]; + "Win32_Devices_WebServicesOnDevices" = [ "Win32_Devices" ]; + "Win32_Foundation" = [ "Win32" ]; + "Win32_Gaming" = [ "Win32" ]; + "Win32_Globalization" = [ "Win32" ]; + "Win32_Graphics" = [ "Win32" ]; + "Win32_Graphics_CompositionSwapchain" = [ "Win32_Graphics" ]; + "Win32_Graphics_DXCore" = [ "Win32_Graphics" ]; + "Win32_Graphics_Direct2D" = [ "Win32_Graphics" ]; + "Win32_Graphics_Direct2D_Common" = [ "Win32_Graphics_Direct2D" ]; + "Win32_Graphics_Direct3D" = [ "Win32_Graphics" ]; + "Win32_Graphics_Direct3D10" = [ "Win32_Graphics" ]; + "Win32_Graphics_Direct3D11" = [ "Win32_Graphics" ]; + "Win32_Graphics_Direct3D11on12" = [ "Win32_Graphics" ]; + "Win32_Graphics_Direct3D12" = [ "Win32_Graphics" ]; + "Win32_Graphics_Direct3D9" = [ "Win32_Graphics" ]; + "Win32_Graphics_Direct3D9on12" = [ "Win32_Graphics" ]; + "Win32_Graphics_Direct3D_Dxc" = [ "Win32_Graphics_Direct3D" ]; + "Win32_Graphics_Direct3D_Fxc" = [ "Win32_Graphics_Direct3D" ]; + "Win32_Graphics_DirectComposition" = [ "Win32_Graphics" ]; + "Win32_Graphics_DirectDraw" = [ "Win32_Graphics" ]; + "Win32_Graphics_DirectManipulation" = [ "Win32_Graphics" ]; + "Win32_Graphics_DirectWrite" = [ "Win32_Graphics" ]; + "Win32_Graphics_Dwm" = [ "Win32_Graphics" ]; + "Win32_Graphics_Dxgi" = [ "Win32_Graphics" ]; + "Win32_Graphics_Dxgi_Common" = [ "Win32_Graphics_Dxgi" ]; + "Win32_Graphics_Gdi" = [ "Win32_Graphics" ]; + "Win32_Graphics_GdiPlus" = [ "Win32_Graphics" ]; + "Win32_Graphics_Hlsl" = [ "Win32_Graphics" ]; + "Win32_Graphics_Imaging" = [ "Win32_Graphics" ]; + "Win32_Graphics_Imaging_D2D" = [ "Win32_Graphics_Imaging" ]; + "Win32_Graphics_OpenGL" = [ "Win32_Graphics" ]; + "Win32_Graphics_Printing" = [ "Win32_Graphics" ]; + "Win32_Graphics_Printing_PrintTicket" = [ "Win32_Graphics_Printing" ]; + "Win32_Management" = [ "Win32" ]; + "Win32_Management_MobileDeviceManagementRegistration" = [ "Win32_Management" ]; + "Win32_Media" = [ "Win32" ]; + "Win32_Media_Audio" = [ "Win32_Media" ]; + "Win32_Media_Audio_Apo" = [ "Win32_Media_Audio" ]; + "Win32_Media_Audio_DirectMusic" = [ "Win32_Media_Audio" ]; + "Win32_Media_Audio_DirectSound" = [ "Win32_Media_Audio" ]; + "Win32_Media_Audio_Endpoints" = [ "Win32_Media_Audio" ]; + "Win32_Media_Audio_XAudio2" = [ "Win32_Media_Audio" ]; + "Win32_Media_DeviceManager" = [ "Win32_Media" ]; + "Win32_Media_DirectShow" = [ "Win32_Media" ]; + "Win32_Media_DirectShow_Tv" = [ "Win32_Media_DirectShow" ]; + "Win32_Media_DirectShow_Xml" = [ "Win32_Media_DirectShow" ]; + "Win32_Media_DxMediaObjects" = [ "Win32_Media" ]; + "Win32_Media_KernelStreaming" = [ "Win32_Media" ]; + "Win32_Media_LibrarySharingServices" = [ "Win32_Media" ]; + "Win32_Media_MediaFoundation" = [ "Win32_Media" ]; + "Win32_Media_MediaPlayer" = [ "Win32_Media" ]; + "Win32_Media_Multimedia" = [ "Win32_Media" ]; + "Win32_Media_PictureAcquisition" = [ "Win32_Media" ]; + "Win32_Media_Speech" = [ "Win32_Media" ]; + "Win32_Media_Streaming" = [ "Win32_Media" ]; + "Win32_Media_WindowsMediaFormat" = [ "Win32_Media" ]; + "Win32_NetworkManagement" = [ "Win32" ]; + "Win32_NetworkManagement_Dhcp" = [ "Win32_NetworkManagement" ]; + "Win32_NetworkManagement_Dns" = [ "Win32_NetworkManagement" ]; + "Win32_NetworkManagement_InternetConnectionWizard" = [ "Win32_NetworkManagement" ]; + "Win32_NetworkManagement_IpHelper" = [ "Win32_NetworkManagement" ]; + "Win32_NetworkManagement_MobileBroadband" = [ "Win32_NetworkManagement" ]; + "Win32_NetworkManagement_Multicast" = [ "Win32_NetworkManagement" ]; + "Win32_NetworkManagement_Ndis" = [ "Win32_NetworkManagement" ]; + "Win32_NetworkManagement_NetBios" = [ "Win32_NetworkManagement" ]; + "Win32_NetworkManagement_NetManagement" = [ "Win32_NetworkManagement" ]; + "Win32_NetworkManagement_NetShell" = [ "Win32_NetworkManagement" ]; + "Win32_NetworkManagement_NetworkDiagnosticsFramework" = [ "Win32_NetworkManagement" ]; + "Win32_NetworkManagement_NetworkPolicyServer" = [ "Win32_NetworkManagement" ]; + "Win32_NetworkManagement_P2P" = [ "Win32_NetworkManagement" ]; + "Win32_NetworkManagement_QoS" = [ "Win32_NetworkManagement" ]; + "Win32_NetworkManagement_Rras" = [ "Win32_NetworkManagement" ]; + "Win32_NetworkManagement_Snmp" = [ "Win32_NetworkManagement" ]; + "Win32_NetworkManagement_WNet" = [ "Win32_NetworkManagement" ]; + "Win32_NetworkManagement_WebDav" = [ "Win32_NetworkManagement" ]; + "Win32_NetworkManagement_WiFi" = [ "Win32_NetworkManagement" ]; + "Win32_NetworkManagement_WindowsConnectNow" = [ "Win32_NetworkManagement" ]; + "Win32_NetworkManagement_WindowsConnectionManager" = [ "Win32_NetworkManagement" ]; + "Win32_NetworkManagement_WindowsFilteringPlatform" = [ "Win32_NetworkManagement" ]; + "Win32_NetworkManagement_WindowsFirewall" = [ "Win32_NetworkManagement" ]; + "Win32_NetworkManagement_WindowsNetworkVirtualization" = [ "Win32_NetworkManagement" ]; + "Win32_Networking" = [ "Win32" ]; + "Win32_Networking_ActiveDirectory" = [ "Win32_Networking" ]; + "Win32_Networking_BackgroundIntelligentTransferService" = [ "Win32_Networking" ]; + "Win32_Networking_Clustering" = [ "Win32_Networking" ]; + "Win32_Networking_HttpServer" = [ "Win32_Networking" ]; + "Win32_Networking_Ldap" = [ "Win32_Networking" ]; + "Win32_Networking_NetworkListManager" = [ "Win32_Networking" ]; + "Win32_Networking_RemoteDifferentialCompression" = [ "Win32_Networking" ]; + "Win32_Networking_WebSocket" = [ "Win32_Networking" ]; + "Win32_Networking_WinHttp" = [ "Win32_Networking" ]; + "Win32_Networking_WinInet" = [ "Win32_Networking" ]; + "Win32_Networking_WinSock" = [ "Win32_Networking" ]; + "Win32_Networking_WindowsWebServices" = [ "Win32_Networking" ]; + "Win32_Security" = [ "Win32" ]; + "Win32_Security_AppLocker" = [ "Win32_Security" ]; + "Win32_Security_Authentication" = [ "Win32_Security" ]; + "Win32_Security_Authentication_Identity" = [ "Win32_Security_Authentication" ]; + "Win32_Security_Authentication_Identity_Provider" = [ "Win32_Security_Authentication_Identity" ]; + "Win32_Security_Authorization" = [ "Win32_Security" ]; + "Win32_Security_Authorization_UI" = [ "Win32_Security_Authorization" ]; + "Win32_Security_ConfigurationSnapin" = [ "Win32_Security" ]; + "Win32_Security_Credentials" = [ "Win32_Security" ]; + "Win32_Security_Cryptography" = [ "Win32_Security" ]; + "Win32_Security_Cryptography_Catalog" = [ "Win32_Security_Cryptography" ]; + "Win32_Security_Cryptography_Certificates" = [ "Win32_Security_Cryptography" ]; + "Win32_Security_Cryptography_Sip" = [ "Win32_Security_Cryptography" ]; + "Win32_Security_Cryptography_UI" = [ "Win32_Security_Cryptography" ]; + "Win32_Security_DiagnosticDataQuery" = [ "Win32_Security" ]; + "Win32_Security_DirectoryServices" = [ "Win32_Security" ]; + "Win32_Security_EnterpriseData" = [ "Win32_Security" ]; + "Win32_Security_ExtensibleAuthenticationProtocol" = [ "Win32_Security" ]; + "Win32_Security_Isolation" = [ "Win32_Security" ]; + "Win32_Security_LicenseProtection" = [ "Win32_Security" ]; + "Win32_Security_NetworkAccessProtection" = [ "Win32_Security" ]; + "Win32_Security_Tpm" = [ "Win32_Security" ]; + "Win32_Security_WinTrust" = [ "Win32_Security" ]; + "Win32_Security_WinWlx" = [ "Win32_Security" ]; + "Win32_Storage" = [ "Win32" ]; + "Win32_Storage_Cabinets" = [ "Win32_Storage" ]; + "Win32_Storage_CloudFilters" = [ "Win32_Storage" ]; + "Win32_Storage_Compression" = [ "Win32_Storage" ]; + "Win32_Storage_DataDeduplication" = [ "Win32_Storage" ]; + "Win32_Storage_DistributedFileSystem" = [ "Win32_Storage" ]; + "Win32_Storage_EnhancedStorage" = [ "Win32_Storage" ]; + "Win32_Storage_FileHistory" = [ "Win32_Storage" ]; + "Win32_Storage_FileServerResourceManager" = [ "Win32_Storage" ]; + "Win32_Storage_FileSystem" = [ "Win32_Storage" ]; + "Win32_Storage_Imapi" = [ "Win32_Storage" ]; + "Win32_Storage_IndexServer" = [ "Win32_Storage" ]; + "Win32_Storage_InstallableFileSystems" = [ "Win32_Storage" ]; + "Win32_Storage_IscsiDisc" = [ "Win32_Storage" ]; + "Win32_Storage_Jet" = [ "Win32_Storage" ]; + "Win32_Storage_Nvme" = [ "Win32_Storage" ]; + "Win32_Storage_OfflineFiles" = [ "Win32_Storage" ]; + "Win32_Storage_OperationRecorder" = [ "Win32_Storage" ]; + "Win32_Storage_Packaging" = [ "Win32_Storage" ]; + "Win32_Storage_Packaging_Appx" = [ "Win32_Storage_Packaging" ]; + "Win32_Storage_Packaging_Opc" = [ "Win32_Storage_Packaging" ]; + "Win32_Storage_ProjectedFileSystem" = [ "Win32_Storage" ]; + "Win32_Storage_StructuredStorage" = [ "Win32_Storage" ]; + "Win32_Storage_Vhd" = [ "Win32_Storage" ]; + "Win32_Storage_VirtualDiskService" = [ "Win32_Storage" ]; + "Win32_Storage_Vss" = [ "Win32_Storage" ]; + "Win32_Storage_Xps" = [ "Win32_Storage" ]; + "Win32_Storage_Xps_Printing" = [ "Win32_Storage_Xps" ]; + "Win32_System" = [ "Win32" ]; + "Win32_System_AddressBook" = [ "Win32_System" ]; + "Win32_System_Antimalware" = [ "Win32_System" ]; + "Win32_System_ApplicationInstallationAndServicing" = [ "Win32_System" ]; + "Win32_System_ApplicationVerifier" = [ "Win32_System" ]; + "Win32_System_AssessmentTool" = [ "Win32_System" ]; + "Win32_System_ClrHosting" = [ "Win32_System" ]; + "Win32_System_Com" = [ "Win32_System" ]; + "Win32_System_Com_CallObj" = [ "Win32_System_Com" ]; + "Win32_System_Com_ChannelCredentials" = [ "Win32_System_Com" ]; + "Win32_System_Com_Events" = [ "Win32_System_Com" ]; + "Win32_System_Com_Marshal" = [ "Win32_System_Com" ]; + "Win32_System_Com_StructuredStorage" = [ "Win32_System_Com" ]; + "Win32_System_Com_UI" = [ "Win32_System_Com" ]; + "Win32_System_Com_Urlmon" = [ "Win32_System_Com" ]; + "Win32_System_ComponentServices" = [ "Win32_System" ]; + "Win32_System_Console" = [ "Win32_System" ]; + "Win32_System_Contacts" = [ "Win32_System" ]; + "Win32_System_CorrelationVector" = [ "Win32_System" ]; + "Win32_System_DataExchange" = [ "Win32_System" ]; + "Win32_System_DeploymentServices" = [ "Win32_System" ]; + "Win32_System_DesktopSharing" = [ "Win32_System" ]; + "Win32_System_DeveloperLicensing" = [ "Win32_System" ]; + "Win32_System_Diagnostics" = [ "Win32_System" ]; + "Win32_System_Diagnostics_Ceip" = [ "Win32_System_Diagnostics" ]; + "Win32_System_Diagnostics_ClrProfiling" = [ "Win32_System_Diagnostics" ]; + "Win32_System_Diagnostics_Debug" = [ "Win32_System_Diagnostics" ]; + "Win32_System_Diagnostics_Debug_ActiveScript" = [ "Win32_System_Diagnostics_Debug" ]; + "Win32_System_Diagnostics_Debug_Extensions" = [ "Win32_System_Diagnostics_Debug" ]; + "Win32_System_Diagnostics_Etw" = [ "Win32_System_Diagnostics" ]; + "Win32_System_Diagnostics_ProcessSnapshotting" = [ "Win32_System_Diagnostics" ]; + "Win32_System_Diagnostics_ToolHelp" = [ "Win32_System_Diagnostics" ]; + "Win32_System_DistributedTransactionCoordinator" = [ "Win32_System" ]; + "Win32_System_Environment" = [ "Win32_System" ]; + "Win32_System_ErrorReporting" = [ "Win32_System" ]; + "Win32_System_EventCollector" = [ "Win32_System" ]; + "Win32_System_EventLog" = [ "Win32_System" ]; + "Win32_System_EventNotificationService" = [ "Win32_System" ]; + "Win32_System_GroupPolicy" = [ "Win32_System" ]; + "Win32_System_HostCompute" = [ "Win32_System" ]; + "Win32_System_HostComputeNetwork" = [ "Win32_System" ]; + "Win32_System_HostComputeSystem" = [ "Win32_System" ]; + "Win32_System_Hypervisor" = [ "Win32_System" ]; + "Win32_System_IO" = [ "Win32_System" ]; + "Win32_System_Iis" = [ "Win32_System" ]; + "Win32_System_Ioctl" = [ "Win32_System" ]; + "Win32_System_JobObjects" = [ "Win32_System" ]; + "Win32_System_Js" = [ "Win32_System" ]; + "Win32_System_Kernel" = [ "Win32_System" ]; + "Win32_System_LibraryLoader" = [ "Win32_System" ]; + "Win32_System_Mailslots" = [ "Win32_System" ]; + "Win32_System_Mapi" = [ "Win32_System" ]; + "Win32_System_Memory" = [ "Win32_System" ]; + "Win32_System_Memory_NonVolatile" = [ "Win32_System_Memory" ]; + "Win32_System_MessageQueuing" = [ "Win32_System" ]; + "Win32_System_MixedReality" = [ "Win32_System" ]; + "Win32_System_Mmc" = [ "Win32_System" ]; + "Win32_System_Ole" = [ "Win32_System" ]; + "Win32_System_ParentalControls" = [ "Win32_System" ]; + "Win32_System_PasswordManagement" = [ "Win32_System" ]; + "Win32_System_Performance" = [ "Win32_System" ]; + "Win32_System_Performance_HardwareCounterProfiling" = [ "Win32_System_Performance" ]; + "Win32_System_Pipes" = [ "Win32_System" ]; + "Win32_System_Power" = [ "Win32_System" ]; + "Win32_System_ProcessStatus" = [ "Win32_System" ]; + "Win32_System_RealTimeCommunications" = [ "Win32_System" ]; + "Win32_System_Recovery" = [ "Win32_System" ]; + "Win32_System_Registry" = [ "Win32_System" ]; + "Win32_System_RemoteAssistance" = [ "Win32_System" ]; + "Win32_System_RemoteDesktop" = [ "Win32_System" ]; + "Win32_System_RemoteManagement" = [ "Win32_System" ]; + "Win32_System_RestartManager" = [ "Win32_System" ]; + "Win32_System_Restore" = [ "Win32_System" ]; + "Win32_System_Rpc" = [ "Win32_System" ]; + "Win32_System_Search" = [ "Win32_System" ]; + "Win32_System_Search_Common" = [ "Win32_System_Search" ]; + "Win32_System_SecurityCenter" = [ "Win32_System" ]; + "Win32_System_ServerBackup" = [ "Win32_System" ]; + "Win32_System_Services" = [ "Win32_System" ]; + "Win32_System_SettingsManagementInfrastructure" = [ "Win32_System" ]; + "Win32_System_SetupAndMigration" = [ "Win32_System" ]; + "Win32_System_Shutdown" = [ "Win32_System" ]; + "Win32_System_SideShow" = [ "Win32_System" ]; + "Win32_System_StationsAndDesktops" = [ "Win32_System" ]; + "Win32_System_SubsystemForLinux" = [ "Win32_System" ]; + "Win32_System_SystemInformation" = [ "Win32_System" ]; + "Win32_System_SystemServices" = [ "Win32_System" ]; + "Win32_System_TaskScheduler" = [ "Win32_System" ]; + "Win32_System_Threading" = [ "Win32_System" ]; + "Win32_System_Time" = [ "Win32_System" ]; + "Win32_System_TpmBaseServices" = [ "Win32_System" ]; + "Win32_System_TransactionServer" = [ "Win32_System" ]; + "Win32_System_UpdateAgent" = [ "Win32_System" ]; + "Win32_System_UpdateAssessment" = [ "Win32_System" ]; + "Win32_System_UserAccessLogging" = [ "Win32_System" ]; + "Win32_System_Variant" = [ "Win32_System" ]; + "Win32_System_VirtualDosMachines" = [ "Win32_System" ]; + "Win32_System_WinRT" = [ "Win32_System" ]; + "Win32_System_WinRT_AllJoyn" = [ "Win32_System_WinRT" ]; + "Win32_System_WinRT_Composition" = [ "Win32_System_WinRT" ]; + "Win32_System_WinRT_CoreInputView" = [ "Win32_System_WinRT" ]; + "Win32_System_WinRT_Direct3D11" = [ "Win32_System_WinRT" ]; + "Win32_System_WinRT_Display" = [ "Win32_System_WinRT" ]; + "Win32_System_WinRT_Graphics" = [ "Win32_System_WinRT" ]; + "Win32_System_WinRT_Graphics_Capture" = [ "Win32_System_WinRT_Graphics" ]; + "Win32_System_WinRT_Graphics_Direct2D" = [ "Win32_System_WinRT_Graphics" ]; + "Win32_System_WinRT_Graphics_Imaging" = [ "Win32_System_WinRT_Graphics" ]; + "Win32_System_WinRT_Holographic" = [ "Win32_System_WinRT" ]; + "Win32_System_WinRT_Isolation" = [ "Win32_System_WinRT" ]; + "Win32_System_WinRT_ML" = [ "Win32_System_WinRT" ]; + "Win32_System_WinRT_Media" = [ "Win32_System_WinRT" ]; + "Win32_System_WinRT_Metadata" = [ "Win32_System_WinRT" ]; + "Win32_System_WinRT_Pdf" = [ "Win32_System_WinRT" ]; + "Win32_System_WinRT_Printing" = [ "Win32_System_WinRT" ]; + "Win32_System_WinRT_Shell" = [ "Win32_System_WinRT" ]; + "Win32_System_WinRT_Storage" = [ "Win32_System_WinRT" ]; + "Win32_System_WindowsProgramming" = [ "Win32_System" ]; + "Win32_System_WindowsSync" = [ "Win32_System" ]; + "Win32_System_Wmi" = [ "Win32_System" ]; + "Win32_UI" = [ "Win32" ]; + "Win32_UI_Accessibility" = [ "Win32_UI" ]; + "Win32_UI_Animation" = [ "Win32_UI" ]; + "Win32_UI_ColorSystem" = [ "Win32_UI" ]; + "Win32_UI_Controls" = [ "Win32_UI" ]; + "Win32_UI_Controls_Dialogs" = [ "Win32_UI_Controls" ]; + "Win32_UI_Controls_RichEdit" = [ "Win32_UI_Controls" ]; + "Win32_UI_HiDpi" = [ "Win32_UI" ]; + "Win32_UI_Input" = [ "Win32_UI" ]; + "Win32_UI_Input_Ime" = [ "Win32_UI_Input" ]; + "Win32_UI_Input_Ink" = [ "Win32_UI_Input" ]; + "Win32_UI_Input_KeyboardAndMouse" = [ "Win32_UI_Input" ]; + "Win32_UI_Input_Pointer" = [ "Win32_UI_Input" ]; + "Win32_UI_Input_Radial" = [ "Win32_UI_Input" ]; + "Win32_UI_Input_Touch" = [ "Win32_UI_Input" ]; + "Win32_UI_Input_XboxController" = [ "Win32_UI_Input" ]; + "Win32_UI_InteractionContext" = [ "Win32_UI" ]; + "Win32_UI_LegacyWindowsEnvironmentFeatures" = [ "Win32_UI" ]; + "Win32_UI_Magnification" = [ "Win32_UI" ]; + "Win32_UI_Notifications" = [ "Win32_UI" ]; + "Win32_UI_Ribbon" = [ "Win32_UI" ]; + "Win32_UI_Shell" = [ "Win32_UI" ]; + "Win32_UI_Shell_Common" = [ "Win32_UI_Shell" ]; + "Win32_UI_Shell_PropertiesSystem" = [ "Win32_UI_Shell" ]; + "Win32_UI_TabletPC" = [ "Win32_UI" ]; + "Win32_UI_TextServices" = [ "Win32_UI" ]; + "Win32_UI_WindowsAndMessaging" = [ "Win32_UI" ]; + "Win32_UI_Wpf" = [ "Win32_UI" ]; + "Win32_Web" = [ "Win32" ]; + "Win32_Web_InternetExplorer" = [ "Win32_Web" ]; + "implement" = [ "windows-implement" "windows-interface" "windows-core/implement" ]; + "windows-implement" = [ "dep:windows-implement" ]; + "windows-interface" = [ "dep:windows-interface" ]; + }; + resolvedDefaultFeatures = [ "Win32" "Win32_Foundation" "Win32_Globalization" "Win32_Security" "Win32_Security_Authentication" "Win32_Security_Authentication_Identity" "Win32_Security_Credentials" "Win32_System" "Win32_System_Diagnostics" "Win32_System_Diagnostics_Debug" "Win32_System_SystemInformation" "Win32_System_SystemServices" "Win32_System_Time" "default" ]; + }; "windows-core" = rec { crateName = "windows-core"; version = "0.52.0"; diff --git a/default.nix b/default.nix index cea676d7..4932cb85 100644 --- a/default.nix +++ b/default.nix @@ -16,6 +16,10 @@ stackable-secret-operator = attrs: { buildInputs = [ pkgs.protobuf pkgs.rustfmt ]; }; + stackable-opa-user-info-fetcher = attrs: { + # TODO: why is this not pulled in via libgssapi-sys? + buildInputs = [ pkgs.krb5 ]; + }; krb5-sys = attrs: { nativeBuildInputs = [ pkgs.pkg-config ]; buildInputs = [ pkgs.krb5 ]; diff --git a/rust/crd/src/user_info_fetcher.rs b/rust/crd/src/user_info_fetcher.rs index 2a3eb4a3..ec5f41b9 100644 --- a/rust/crd/src/user_info_fetcher.rs +++ b/rust/crd/src/user_info_fetcher.rs @@ -102,6 +102,8 @@ pub struct ActiveDirectoryBackend { /// The root Distinguished Name (DN) where users and groups are located. pub base_distinguished_name: String, + pub kerberos_secret_class_name: String, + /// Custom attributes, and their LDAP attribute names. #[serde(default)] pub custom_attribute_mappings: BTreeMap, diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index d4574097..02d1f2f5 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -27,8 +27,10 @@ use stackable_operator::{ }, cluster_resources::{ClusterResourceApplyStrategy, ClusterResources}, commons::{ - authentication::tls::TlsClientDetailsError, product_image_selection::ResolvedProductImage, + authentication::tls::TlsClientDetailsError, + product_image_selection::ResolvedProductImage, rbac::build_rbac_resources, + secret_class::{SecretClassVolume, SecretClassVolumeScope}, }, k8s_openapi::{ api::{ @@ -94,6 +96,8 @@ const BUNDLES_VOLUME_NAME: &str = "bundles"; const BUNDLES_DIR: &str = "/bundles"; const USER_INFO_FETCHER_CREDENTIALS_VOLUME_NAME: &str = "credentials"; const USER_INFO_FETCHER_CREDENTIALS_DIR: &str = "/stackable/credentials"; +const USER_INFO_FETCHER_KERBEROS_VOLUME_NAME: &str = "kerberos"; +const USER_INFO_FETCHER_KERBEROS_DIR: &str = "/stackable/kerberos"; const DOCKER_IMAGE_BASE_NAME: &str = "opa"; @@ -914,7 +918,37 @@ fn build_server_rolegroup_daemonset( match &user_info.backend { user_info_fetcher::Backend::None {} => {} user_info_fetcher::Backend::ExperimentalXfscAas(_) => {} - user_info_fetcher::Backend::ActiveDirectory(_) => {} + user_info_fetcher::Backend::ActiveDirectory(ad) => { + pb.add_volume( + SecretClassVolume::new( + ad.kerberos_secret_class_name.clone(), + Some(SecretClassVolumeScope { + pod: true, + node: true, + services: Vec::new(), + }), + ) + .to_volume(USER_INFO_FETCHER_KERBEROS_VOLUME_NAME) + .unwrap(), + ); + cb_user_info_fetcher.add_volume_mount( + USER_INFO_FETCHER_KERBEROS_VOLUME_NAME, + USER_INFO_FETCHER_KERBEROS_DIR, + ); + cb_user_info_fetcher.add_env_var( + "KRB5_CONFIG", + format!("{USER_INFO_FETCHER_KERBEROS_DIR}/krb5.conf"), + ); + cb_user_info_fetcher.add_env_var( + "KRB5_CLIENT_KTNAME", + format!("{USER_INFO_FETCHER_KERBEROS_DIR}/keytab"), + ); + cb_user_info_fetcher.add_env_var("KRB5CCNAME", "MEMORY:".to_string()); + // keycloak + // .tls + // .add_volumes_and_mounts(&mut pb, vec![&mut cb_user_info_fetcher]) + // .context(VolumeAndMountsSnafu)?; + } user_info_fetcher::Backend::Keycloak(keycloak) => { pb.add_volume( VolumeBuilder::new(USER_INFO_FETCHER_CREDENTIALS_VOLUME_NAME) diff --git a/rust/user-info-fetcher/Cargo.toml b/rust/user-info-fetcher/Cargo.toml index df3ec35b..64bdbdb0 100644 --- a/rust/user-info-fetcher/Cargo.toml +++ b/rust/user-info-fetcher/Cargo.toml @@ -26,7 +26,7 @@ stackable-operator.workspace = true tokio.workspace = true tracing.workspace = true url.workspace = true -ldap3 = "0.11.5" +ldap3 = { version = "0.11.5", features = ["gssapi", "tls"] } native-tls = "0.2.12" uuid = "1.10.0" base64 = "0.22.1" diff --git a/rust/user-info-fetcher/src/backend/active_directory.rs b/rust/user-info-fetcher/src/backend/active_directory.rs index 1228e329..08762030 100644 --- a/rust/user-info-fetcher/src/backend/active_directory.rs +++ b/rust/user-info-fetcher/src/backend/active_directory.rs @@ -90,13 +90,15 @@ pub(crate) async fn get_user_info( ldap_server: &str, base_distinguished_name: &str, custom_attribute_mappings: &BTreeMap, -) -> Result where { - let (ldap_conn, mut ldap) = - LdapConnAsync::with_settings(LdapConnSettings::new().set_no_tls_verify(true), ldap_server) - .await - .context(ConnectLdapSnafu)?; +) -> Result { + let (ldap_conn, mut ldap) = LdapConnAsync::with_settings( + LdapConnSettings::new().set_no_tls_verify(true), + &format!("ldaps://{ldap_server}"), + ) + .await + .context(ConnectLdapSnafu)?; ldap3::drive!(ldap_conn); - ldap.simple_bind("asdf@sble.test", "Qwer1234") + ldap.sasl_gssapi_bind(ldap_server) .await .context(RequestLdapSnafu)? .success() From 4b709ea2bd0ef8c3a51fdaae2124e4a50f7d05e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Wed, 14 Aug 2024 13:16:43 +0200 Subject: [PATCH 10/18] Encrypt LDAP connections with TLS --- rust/crd/src/user_info_fetcher.rs | 4 +++ rust/operator-binary/src/controller.rs | 7 ++-- .../src/backend/active_directory.rs | 32 +++++++++++++++++-- rust/user-info-fetcher/src/main.rs | 2 ++ 4 files changed, 39 insertions(+), 6 deletions(-) diff --git a/rust/crd/src/user_info_fetcher.rs b/rust/crd/src/user_info_fetcher.rs index ec5f41b9..cefa112d 100644 --- a/rust/crd/src/user_info_fetcher.rs +++ b/rust/crd/src/user_info_fetcher.rs @@ -104,6 +104,10 @@ pub struct ActiveDirectoryBackend { pub kerberos_secret_class_name: String, + /// Use a TLS connection. If not specified no TLS will be used. + #[serde(flatten)] + pub tls: TlsClientDetails, + /// Custom attributes, and their LDAP attribute names. #[serde(default)] pub custom_attribute_mappings: BTreeMap, diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index 02d1f2f5..b3e8837d 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -944,10 +944,9 @@ fn build_server_rolegroup_daemonset( format!("{USER_INFO_FETCHER_KERBEROS_DIR}/keytab"), ); cb_user_info_fetcher.add_env_var("KRB5CCNAME", "MEMORY:".to_string()); - // keycloak - // .tls - // .add_volumes_and_mounts(&mut pb, vec![&mut cb_user_info_fetcher]) - // .context(VolumeAndMountsSnafu)?; + ad.tls + .add_volumes_and_mounts(&mut pb, vec![&mut cb_user_info_fetcher]) + .context(VolumeAndMountsSnafu)?; } user_info_fetcher::Backend::Keycloak(keycloak) => { pb.add_volume( diff --git a/rust/user-info-fetcher/src/backend/active_directory.rs b/rust/user-info-fetcher/src/backend/active_directory.rs index 08762030..f5da84c3 100644 --- a/rust/user-info-fetcher/src/backend/active_directory.rs +++ b/rust/user-info-fetcher/src/backend/active_directory.rs @@ -10,6 +10,8 @@ use byteorder::{BigEndian, LittleEndian, ReadBytesExt}; use hyper::StatusCode; use ldap3::{ldap_escape, Ldap, LdapConnAsync, LdapConnSettings, LdapError, Scope, SearchEntry}; use snafu::{OptionExt, ResultExt, Snafu}; +use stackable_operator::commons::authentication::tls::TlsClientDetails; +use tokio::fs::File; use uuid::Uuid; use crate::{http_error, ErrorRenderUserInfoRequest, UserInfo, UserInfoRequest}; @@ -88,12 +90,38 @@ const LDAP_FIELD_GROUP_MEMBER: &str = "member"; pub(crate) async fn get_user_info( request: &UserInfoRequest, ldap_server: &str, + tls: &TlsClientDetails, base_distinguished_name: &str, custom_attribute_mappings: &BTreeMap, ) -> Result { + use tokio::io::AsyncReadExt as _; + + let mut ldap_tls = native_tls::TlsConnector::builder(); + if tls.uses_tls() && !tls.uses_tls_verification() { + ldap_tls.danger_accept_invalid_certs(true); + } + if let Some(tls_ca_cert_mount_path) = tls.tls_ca_cert_mount_path() { + let mut buf = Vec::new(); + File::open(tls_ca_cert_mount_path) + .await + // .context(OpenCaCertSnafu)? + .unwrap() + .read_to_end(&mut buf) + .await + .unwrap(); + // .context(ReadCaCertSnafu)?; + let ca_cert = native_tls::Certificate::from_pem(&buf).unwrap(); //.context(ParseCaCertSnafu)?; + + ldap_tls + .disable_built_in_roots(true) + .add_root_certificate(ca_cert); + } let (ldap_conn, mut ldap) = LdapConnAsync::with_settings( - LdapConnSettings::new().set_no_tls_verify(true), - &format!("ldaps://{ldap_server}"), + LdapConnSettings::new().set_connector(ldap_tls.build().unwrap()), + &format!( + "{protocol}://{ldap_server}", + protocol = if tls.uses_tls() { "ldaps" } else { "ldap" } + ), ) .await .context(ConnectLdapSnafu)?; diff --git a/rust/user-info-fetcher/src/main.rs b/rust/user-info-fetcher/src/main.rs index 3049a227..a488b5ef 100644 --- a/rust/user-info-fetcher/src/main.rs +++ b/rust/user-info-fetcher/src/main.rs @@ -13,6 +13,7 @@ use reqwest::ClientBuilder; use serde::{Deserialize, Serialize}; use snafu::{ResultExt, Snafu}; use stackable_opa_crd::user_info_fetcher as crd; +use stackable_operator::config; use tokio::{fs::File, io::AsyncReadExt, net::TcpListener}; mod backend; @@ -315,6 +316,7 @@ async fn get_user_info( crd::Backend::ActiveDirectory(ad) => backend::active_directory::get_user_info( &req, &ad.ldap_server, + &ad.tls, &ad.base_distinguished_name, &ad.custom_attribute_mappings, ) From e5d4f9c920d25e874f005ae0745362a1b4c6946a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Wed, 14 Aug 2024 14:34:35 +0200 Subject: [PATCH 11/18] move util -> utils::http --- rust/user-info-fetcher/src/backend/keycloak.rs | 10 +++++----- rust/user-info-fetcher/src/backend/xfsc_aas.rs | 4 ++-- rust/user-info-fetcher/src/main.rs | 3 +-- rust/user-info-fetcher/src/{util.rs => utils/http.rs} | 0 rust/user-info-fetcher/src/utils/mod.rs | 1 + 5 files changed, 9 insertions(+), 9 deletions(-) rename rust/user-info-fetcher/src/{util.rs => utils/http.rs} (100%) create mode 100644 rust/user-info-fetcher/src/utils/mod.rs diff --git a/rust/user-info-fetcher/src/backend/keycloak.rs b/rust/user-info-fetcher/src/backend/keycloak.rs index f17e7239..9bd76f6e 100644 --- a/rust/user-info-fetcher/src/backend/keycloak.rs +++ b/rust/user-info-fetcher/src/backend/keycloak.rs @@ -6,19 +6,19 @@ use snafu::{OptionExt, ResultExt, Snafu}; use stackable_opa_crd::user_info_fetcher as crd; use stackable_operator::commons::authentication::oidc; -use crate::{http_error, util::send_json_request, Credentials, UserInfo, UserInfoRequest}; +use crate::{http_error, utils::http::send_json_request, Credentials, UserInfo, UserInfoRequest}; #[derive(Snafu, Debug)] pub enum Error { #[snafu(display("failed to get access_token"))] - AccessToken { source: crate::util::Error }, + AccessToken { source: crate::utils::http::Error }, #[snafu(display("failed to search for user"))] - SearchForUser { source: crate::util::Error }, + SearchForUser { source: crate::utils::http::Error }, #[snafu(display("unable to find user with id {user_id:?}"))] UserNotFoundById { - source: crate::util::Error, + source: crate::utils::http::Error, user_id: String, }, @@ -32,7 +32,7 @@ pub enum Error { "failed to request groups for user with username {username:?} (user_id: {user_id:?})" ))] RequestUserGroups { - source: crate::util::Error, + source: crate::utils::http::Error, username: String, user_id: String, }, diff --git a/rust/user-info-fetcher/src/backend/xfsc_aas.rs b/rust/user-info-fetcher/src/backend/xfsc_aas.rs index 208d2e6e..40e05628 100644 --- a/rust/user-info-fetcher/src/backend/xfsc_aas.rs +++ b/rust/user-info-fetcher/src/backend/xfsc_aas.rs @@ -18,7 +18,7 @@ use snafu::{ResultExt, Snafu}; use stackable_opa_crd::user_info_fetcher as crd; use url::Url; -use crate::{http_error, util::send_json_request, UserInfo, UserInfoRequest}; +use crate::{http_error, utils::http::send_json_request, UserInfo, UserInfoRequest}; static API_PATH: &str = "/cip/claims"; static SUB_CLAIM: &str = "sub"; @@ -34,7 +34,7 @@ pub enum Error { }, #[snafu(display("request failed"))] - Request { source: crate::util::Error }, + Request { source: crate::utils::http::Error }, #[snafu(display("the XFSC AAS does not support querying by username, only by user ID"))] UserInfoByUsernameNotSupported {}, diff --git a/rust/user-info-fetcher/src/main.rs b/rust/user-info-fetcher/src/main.rs index a488b5ef..f05c362c 100644 --- a/rust/user-info-fetcher/src/main.rs +++ b/rust/user-info-fetcher/src/main.rs @@ -13,12 +13,11 @@ use reqwest::ClientBuilder; use serde::{Deserialize, Serialize}; use snafu::{ResultExt, Snafu}; use stackable_opa_crd::user_info_fetcher as crd; -use stackable_operator::config; use tokio::{fs::File, io::AsyncReadExt, net::TcpListener}; mod backend; mod http_error; -mod util; +mod utils; pub const APP_NAME: &str = "opa-user-info-fetcher"; diff --git a/rust/user-info-fetcher/src/util.rs b/rust/user-info-fetcher/src/utils/http.rs similarity index 100% rename from rust/user-info-fetcher/src/util.rs rename to rust/user-info-fetcher/src/utils/http.rs diff --git a/rust/user-info-fetcher/src/utils/mod.rs b/rust/user-info-fetcher/src/utils/mod.rs new file mode 100644 index 00000000..3883215f --- /dev/null +++ b/rust/user-info-fetcher/src/utils/mod.rs @@ -0,0 +1 @@ +pub mod http; From d38931e0b183acb7cc715da80fda9544ba53899f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Wed, 14 Aug 2024 15:55:41 +0200 Subject: [PATCH 12/18] Unify TLS configuration (to the extent possible), allow multi-CA bundles --- Cargo.lock | 7 +- Cargo.nix | 10 ++- rust/user-info-fetcher/Cargo.toml | 1 + .../src/backend/active_directory.rs | 34 +++----- rust/user-info-fetcher/src/main.rs | 32 ++------ rust/user-info-fetcher/src/utils/mod.rs | 1 + rust/user-info-fetcher/src/utils/tls.rs | 79 +++++++++++++++++++ 7 files changed, 107 insertions(+), 57 deletions(-) create mode 100644 rust/user-info-fetcher/src/utils/tls.rs diff --git a/Cargo.lock b/Cargo.lock index 368d31c7..b7b828db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1556,7 +1556,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -2295,9 +2295,9 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" dependencies = [ "base64 0.22.1", "rustls-pki-types", @@ -2690,6 +2690,7 @@ dependencies = [ "native-tls", "pin-project", "reqwest", + "rustls-pemfile", "semver", "serde", "serde_json", diff --git a/Cargo.nix b/Cargo.nix index 52d10fa4..6ebee6a5 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -5016,7 +5016,7 @@ rec { } { name = "windows-targets"; - packageId = "windows-targets 0.48.5"; + packageId = "windows-targets 0.52.6"; target = { target, features }: (target."windows" or false); } ]; @@ -7521,9 +7521,9 @@ rec { }; "rustls-pemfile" = rec { crateName = "rustls-pemfile"; - version = "2.1.2"; + version = "2.1.3"; edition = "2018"; - sha256 = "0ggpmk5n7p096nim2hn57facx6rwf76l55qqsj4fny37d0jkm699"; + sha256 = "09bl873pkibmb2da49kkbm9jlagscjvzrv257q6k01p101my2vqr"; libName = "rustls_pemfile"; dependencies = [ { @@ -8751,6 +8751,10 @@ rec { packageId = "reqwest"; features = [ "json" ]; } + { + name = "rustls-pemfile"; + packageId = "rustls-pemfile"; + } { name = "semver"; packageId = "semver"; diff --git a/rust/user-info-fetcher/Cargo.toml b/rust/user-info-fetcher/Cargo.toml index 64bdbdb0..16928125 100644 --- a/rust/user-info-fetcher/Cargo.toml +++ b/rust/user-info-fetcher/Cargo.toml @@ -31,3 +31,4 @@ native-tls = "0.2.12" uuid = "1.10.0" base64 = "0.22.1" byteorder = "1.5.0" +rustls-pemfile = "2.1.3" diff --git a/rust/user-info-fetcher/src/backend/active_directory.rs b/rust/user-info-fetcher/src/backend/active_directory.rs index f5da84c3..5cf5d009 100644 --- a/rust/user-info-fetcher/src/backend/active_directory.rs +++ b/rust/user-info-fetcher/src/backend/active_directory.rs @@ -11,13 +11,15 @@ use hyper::StatusCode; use ldap3::{ldap_escape, Ldap, LdapConnAsync, LdapConnSettings, LdapError, Scope, SearchEntry}; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::commons::authentication::tls::TlsClientDetails; -use tokio::fs::File; use uuid::Uuid; -use crate::{http_error, ErrorRenderUserInfoRequest, UserInfo, UserInfoRequest}; +use crate::{http_error, utils, ErrorRenderUserInfoRequest, UserInfo, UserInfoRequest}; #[derive(Snafu, Debug)] pub enum Error { + #[snafu(display("failed to configure TLS"))] + ConfigureTls { source: utils::tls::Error }, + #[snafu(display("failed to connect to LDAP"))] ConnectLdap { source: LdapError }, @@ -61,6 +63,7 @@ pub enum Error { impl http_error::Error for Error { fn status_code(&self) -> StatusCode { match *self { + Error::ConfigureTls { .. } => StatusCode::SERVICE_UNAVAILABLE, Error::ConnectLdap { .. } => StatusCode::SERVICE_UNAVAILABLE, Error::RequestLdap { .. } => StatusCode::SERVICE_UNAVAILABLE, Error::BindLdap { .. } => StatusCode::SERVICE_UNAVAILABLE, @@ -94,30 +97,11 @@ pub(crate) async fn get_user_info( base_distinguished_name: &str, custom_attribute_mappings: &BTreeMap, ) -> Result { - use tokio::io::AsyncReadExt as _; - - let mut ldap_tls = native_tls::TlsConnector::builder(); - if tls.uses_tls() && !tls.uses_tls_verification() { - ldap_tls.danger_accept_invalid_certs(true); - } - if let Some(tls_ca_cert_mount_path) = tls.tls_ca_cert_mount_path() { - let mut buf = Vec::new(); - File::open(tls_ca_cert_mount_path) - .await - // .context(OpenCaCertSnafu)? - .unwrap() - .read_to_end(&mut buf) - .await - .unwrap(); - // .context(ReadCaCertSnafu)?; - let ca_cert = native_tls::Certificate::from_pem(&buf).unwrap(); //.context(ParseCaCertSnafu)?; - - ldap_tls - .disable_built_in_roots(true) - .add_root_certificate(ca_cert); - } + let ldap_tls = utils::tls::configure_native_tls(tls) + .await + .context(ConfigureTlsSnafu)?; let (ldap_conn, mut ldap) = LdapConnAsync::with_settings( - LdapConnSettings::new().set_connector(ldap_tls.build().unwrap()), + LdapConnSettings::new().set_connector(ldap_tls), &format!( "{protocol}://{ldap_server}", protocol = if tls.uses_tls() { "ldaps" } else { "ldap" } diff --git a/rust/user-info-fetcher/src/main.rs b/rust/user-info-fetcher/src/main.rs index f05c362c..aedd197f 100644 --- a/rust/user-info-fetcher/src/main.rs +++ b/rust/user-info-fetcher/src/main.rs @@ -13,7 +13,7 @@ use reqwest::ClientBuilder; use serde::{Deserialize, Serialize}; use snafu::{ResultExt, Snafu}; use stackable_opa_crd::user_info_fetcher as crd; -use tokio::{fs::File, io::AsyncReadExt, net::TcpListener}; +use tokio::net::TcpListener; mod backend; mod http_error; @@ -68,14 +68,8 @@ enum StartupError { #[snafu(display("failed to construct http client"))] ConstructHttpClient { source: reqwest::Error }, - #[snafu(display("failed to open ca certificate"))] - OpenCaCert { source: std::io::Error }, - - #[snafu(display("failed to read ca certificate"))] - ReadCaCert { source: std::io::Error }, - - #[snafu(display("failed to parse ca certificate"))] - ParseCaCert { source: reqwest::Error }, + #[snafu(display("failed to configure TLS"))] + ConfigureTls { source: utils::tls::Error }, } async fn read_config_file(path: &Path) -> Result { @@ -136,23 +130,9 @@ async fn main() -> Result<(), StartupError> { // I know it is for setting up the client, but an idea: make a trait for implementing backends // The trait can do all this for a genric client using an implementation on the trait (eg: get_http_client() which will call self.uses_tls()) if let crd::Backend::Keycloak(keycloak) = &config.backend { - if keycloak.tls.uses_tls() && !keycloak.tls.uses_tls_verification() { - client_builder = client_builder.danger_accept_invalid_certs(true); - } - if let Some(tls_ca_cert_mount_path) = keycloak.tls.tls_ca_cert_mount_path() { - let mut buf = Vec::new(); - File::open(tls_ca_cert_mount_path) - .await - .context(OpenCaCertSnafu)? - .read_to_end(&mut buf) - .await - .context(ReadCaCertSnafu)?; - let ca_cert = reqwest::Certificate::from_pem(&buf).context(ParseCaCertSnafu)?; - - client_builder = client_builder - .tls_built_in_root_certs(false) - .add_root_certificate(ca_cert); - } + client_builder = utils::tls::configure_reqwest(&keycloak.tls, client_builder) + .await + .context(ConfigureTlsSnafu)?; } let http = client_builder.build().context(ConstructHttpClientSnafu)?; diff --git a/rust/user-info-fetcher/src/utils/mod.rs b/rust/user-info-fetcher/src/utils/mod.rs index 3883215f..cb1f965d 100644 --- a/rust/user-info-fetcher/src/utils/mod.rs +++ b/rust/user-info-fetcher/src/utils/mod.rs @@ -1 +1,2 @@ pub mod http; +pub mod tls; diff --git a/rust/user-info-fetcher/src/utils/tls.rs b/rust/user-info-fetcher/src/utils/tls.rs new file mode 100644 index 00000000..ea08b8e4 --- /dev/null +++ b/rust/user-info-fetcher/src/utils/tls.rs @@ -0,0 +1,79 @@ +use std::{io::Cursor, path::Path}; + +use snafu::{ResultExt as _, Snafu}; +use stackable_operator::commons::authentication::tls::TlsClientDetails; +use tokio::{fs::File, io::AsyncReadExt}; + +#[derive(Debug, Snafu)] +pub enum Error { + #[snafu(display("failed to read ca certificates"))] + ReadCaBundle { source: std::io::Error }, + + #[snafu(display("failed to parse ca certificates (via reqwest)"))] + ParseCaBundleReqwest { source: reqwest::Error }, + + #[snafu(display("failed to split ca certificate bundle"))] + SplitCaBundle { source: std::io::Error }, + + #[snafu(display("failed to parse ca certificate (via native_tls)"))] + ParseCaCertNativeTls { source: native_tls::Error }, + + #[snafu(display("failed to build native_tls connector"))] + BuildNativeTlsConnector { source: native_tls::Error }, +} + +/// Configures a [`reqwest`] client according to the specified TLS configuration +// NOTE: MUST be kept in sync with all configure_* functions +pub async fn configure_reqwest( + tls: &TlsClientDetails, + builder: reqwest::ClientBuilder, +) -> Result { + Ok(if tls.uses_tls() && !tls.uses_tls_verification() { + builder.danger_accept_invalid_certs(true) + } else if let Some(tls_ca_cert_mount_path) = tls.tls_ca_cert_mount_path() { + reqwest::Certificate::from_pem_bundle( + &read_file(&tls_ca_cert_mount_path) + .await + .context(ReadCaBundleSnafu)?, + ) + .context(ParseCaBundleReqwestSnafu)? + .into_iter() + .fold( + builder.tls_built_in_root_certs(false), + reqwest::ClientBuilder::add_root_certificate, + ) + } else { + builder + }) +} + +/// Configures a [`native_tls`] connector according to the specified TLS configuration +// NOTE: MUST be kept in sync with all configure_* functions +pub async fn configure_native_tls( + tls: &TlsClientDetails, +) -> Result { + let mut builder = native_tls::TlsConnector::builder(); + if tls.uses_tls() && !tls.uses_tls_verification() { + builder.danger_accept_invalid_certs(true); + } else if let Some(tls_ca_cert_mount_path) = tls.tls_ca_cert_mount_path() { + builder.disable_built_in_roots(true); + // native-tls doesn't support parsing CA *bundles*, so split them using rustls first + for ca_cert in rustls_pemfile::certs(&mut Cursor::new( + read_file(&tls_ca_cert_mount_path) + .await + .context(ReadCaBundleSnafu)?, + )) { + builder.add_root_certificate( + native_tls::Certificate::from_der(&ca_cert.context(SplitCaBundleSnafu)?) + .context(ParseCaCertNativeTlsSnafu)?, + ); + } + } + builder.build().context(BuildNativeTlsConnectorSnafu) +} + +async fn read_file(path: &impl AsRef) -> Result, std::io::Error> { + let mut buf = Vec::::new(); + File::open(path).await?.read_to_end(&mut buf).await?; + Ok(buf) +} From d4c63759b4dbb7840fe7c03055ab6223c2934153 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Wed, 14 Aug 2024 17:04:16 +0200 Subject: [PATCH 13/18] Documentation --- deploy/helm/opa-operator/crds/crds.yaml | 65 +++++++++++++++++++ .../pages/usage-guide/user-info-fetcher.adoc | 41 ++++++++++-- rust/crd/src/user_info_fetcher.rs | 8 ++- rust/user-info-fetcher/src/main.rs | 2 +- 4 files changed, 108 insertions(+), 8 deletions(-) diff --git a/deploy/helm/opa-operator/crds/crds.yaml b/deploy/helm/opa-operator/crds/crds.yaml index 8dd282fc..e33a4c57 100644 --- a/deploy/helm/opa-operator/crds/crds.yaml +++ b/deploy/helm/opa-operator/crds/crds.yaml @@ -62,7 +62,72 @@ spec: - keycloak - required: - experimentalXfscAas + - required: + - experimentalActiveDirectory properties: + experimentalActiveDirectory: + description: Backend that fetches user information from Active Directory + properties: + baseDistinguishedName: + description: The root Distinguished Name (DN) where users and groups are located. + type: string + customAttributeMappings: + additionalProperties: + type: string + default: {} + description: Custom attributes, and their LDAP attribute names. + type: object + kerberosSecretClassName: + description: The name of the Kerberos SecretClass. + type: string + ldapHostname: + description: Hostname of the domain controller, e.g. `ad-ds-1.contoso.com`. + type: string + tls: + description: Use a TLS connection. If not specified no TLS will be used. + nullable: true + properties: + verification: + description: The verification method used to verify the certificates of the server and/or the client. + oneOf: + - required: + - none + - required: + - server + properties: + none: + description: Use TLS but don't verify certificates. + type: object + server: + description: Use TLS and a CA certificate to verify the server. + properties: + caCert: + description: CA cert to verify the server. + oneOf: + - required: + - webPki + - required: + - secretClass + properties: + secretClass: + description: Name of the [SecretClass](https://docs.stackable.tech/home/nightly/secret-operator/secretclass) which will provide the CA certificate. Note that a SecretClass does not need to have a key but can also work with just a CA certificate, so if you got provided with a CA cert but don't have access to the key you can still use this method. + type: string + webPki: + description: Use TLS and the CA certificates trusted by the common web browsers to verify the server. This can be useful when you e.g. use public AWS S3 or other public available services. + type: object + type: object + required: + - caCert + type: object + type: object + required: + - verification + type: object + required: + - baseDistinguishedName + - kerberosSecretClassName + - ldapHostname + type: object experimentalXfscAas: description: Backend that fetches user information from the Gaia-X Cross Federation Services Components (XFSC) Authentication & Authorization Service. properties: diff --git a/docs/modules/opa/pages/usage-guide/user-info-fetcher.adoc b/docs/modules/opa/pages/usage-guide/user-info-fetcher.adoc index 9830b97d..711db975 100644 --- a/docs/modules/opa/pages/usage-guide/user-info-fetcher.adoc +++ b/docs/modules/opa/pages/usage-guide/user-info-fetcher.adoc @@ -30,8 +30,8 @@ spec: clientCredentialsSecret: user-info-fetcher-client-credentials # <3> adminRealm: master # <4> userRealm: master # <4> - cache: # optional, enabled by default - entryTimeToLive: 60s # optional, defaults to 60s + cache: # optional, enabled by default + entryTimeToLive: 60s # optional, defaults to 60s servers: roleGroups: default: {} @@ -46,14 +46,16 @@ stringData: ---- <1> Enable the `user-info-fetcher` sidecar -<2> Enable TLS verification using the CA from the `tls` secretClass. +<2> Enable TLS verification using the CA from the `tls` SecretClass. <3> Obtain Keycloak API credentials from the specified secret. The Secret must have `clientId` and `clientSecret` entries. <4> Refer to the applicable realm in your Keycloak server. Currently the following backends are supported: -* <<_keycloak>> +* xref:#backend-keycloak[] +* xref:#backend-activedirectory[] +[#backend-keycloak] == Keycloak // todo: maybe this section should be under a Tutorial? @@ -61,6 +63,37 @@ Fetch groups and extra credentials, but not roles. NOTE: The OAuth2 Client in Keycloak must be given the `view-users` _Service Account Role_ for the realm that the users are in. +[#backend-activedirectory] +== Active Directory + +WARNING: The Active Directory backend is experimental, and subject to change. + +Fetches user attributes and groups over LDAP. + +[source,yaml] +---- +spec: + clusterConfig: + userInfo: + backend: + experimentalActiveDirectory: # <1> + ldapHostname: sble-adds.sble.test # <2> + baseDistinguishedName: DC=sble,DC=test # <3> + kerberosSecretClassName: kerberos-ad # <4> + tls: + verification: + server: + caCert: + secretClass: tls-ad # <5> + cache: # optional, enabled by default + entryTimeToLive: 60s # optional, defaults to 60s +---- +<1> Enables the Active Directory backend +<2> The hostname of the domain controller +<3> The distinguished name to search, users and groups outside of this will not be seen +<4> The name of the SecretClass that knows how to create Kerberos keytabs trusted by Active Directory +<5> The name of the SecretClass that contains the Active Directory's root CA certificate(s) + == User info fetcher API User information can be retrieved from regorules using the functions `userInfoByUsername(username)` and `userInfoById(id)` in `data.stackable.opa.userinfo.v1`. diff --git a/rust/crd/src/user_info_fetcher.rs b/rust/crd/src/user_info_fetcher.rs index cefa112d..6d444aef 100644 --- a/rust/crd/src/user_info_fetcher.rs +++ b/rust/crd/src/user_info_fetcher.rs @@ -33,6 +33,7 @@ pub enum Backend { /// Cross Federation Services Components (XFSC) Authentication & Authorization Service. ExperimentalXfscAas(AasBackend), + /// Backend that fetches user information from Active Directory #[serde(rename = "experimentalActiveDirectory")] ActiveDirectory(ActiveDirectoryBackend), } @@ -96,15 +97,16 @@ fn aas_default_port() -> u16 { #[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ActiveDirectoryBackend { - /// Hostname of the identity provider, e.g. `my.aas.corp`. - pub ldap_server: String, + /// Hostname of the domain controller, e.g. `ad-ds-1.contoso.com`. + pub ldap_hostname: String, /// The root Distinguished Name (DN) where users and groups are located. pub base_distinguished_name: String, + /// The name of the Kerberos SecretClass. pub kerberos_secret_class_name: String, - /// Use a TLS connection. If not specified no TLS will be used. + /// Use a TLS connection. If not specified then no TLS will be used. #[serde(flatten)] pub tls: TlsClientDetails, diff --git a/rust/user-info-fetcher/src/main.rs b/rust/user-info-fetcher/src/main.rs index aedd197f..3c3d1fcb 100644 --- a/rust/user-info-fetcher/src/main.rs +++ b/rust/user-info-fetcher/src/main.rs @@ -294,7 +294,7 @@ async fn get_user_info( } crd::Backend::ActiveDirectory(ad) => backend::active_directory::get_user_info( &req, - &ad.ldap_server, + &ad.ldap_hostname, &ad.tls, &ad.base_distinguished_name, &ad.custom_attribute_mappings, From 220f6b47b7926d1ef3e58ebcc8e542615be623a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Wed, 14 Aug 2024 17:13:15 +0200 Subject: [PATCH 14/18] Move new UIF dependencies into workspace toml --- Cargo.toml | 6 ++++++ rust/user-info-fetcher/Cargo.toml | 14 +++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f5c9dab4..0e049772 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,9 @@ repository = "https://github.com/stackabletech/opa-operator" [workspace.dependencies] anyhow = "1.0" axum = "0.7" +base64 = "0.22" built = { version ="0.7", features = ["chrono", "git2"] } +byteorder = "1.5" clap = "4.5" derivative = "2.2" flate2 = "1.0" @@ -20,10 +22,13 @@ fnv = "1.0" futures = { version = "0.3" } hyper = "1.4" indoc = "2.0" +ldap3 = { version = "0.11", features = ["gssapi", "tls"] } moka = { version = "0.12", features = ["future"] } +native-tls = "0.2.12" pin-project = "1.1" product-config = { git = "https://github.com/stackabletech/product-config.git", tag = "0.7.0" } reqwest = { version ="0.12", features = ["json"] } +rustls-pemfile = "2.1" semver = "1.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" @@ -34,6 +39,7 @@ tar = "0.4" tokio = { version = "1.39", features = ["full"] } tracing = "0.1" url = "2.5" +uuid = "1.10" # [patch."https://github.com/stackabletech/operator-rs.git"] # stackable-operator = { git = "https://github.com/stackabletech//operator-rs.git", branch = "main" } diff --git a/rust/user-info-fetcher/Cargo.toml b/rust/user-info-fetcher/Cargo.toml index 16928125..be013f73 100644 --- a/rust/user-info-fetcher/Cargo.toml +++ b/rust/user-info-fetcher/Cargo.toml @@ -12,23 +12,23 @@ publish = false stackable-opa-crd = { path = "../crd" } axum.workspace = true +base64.workspace = true +byteorder.workspace = true clap.workspace = true futures.workspace = true hyper.workspace = true +ldap3.workspace = true moka.workspace = true +native-tls.workspace = true pin-project.workspace = true reqwest.workspace = true +rustls-pemfile.workspace = true semver.workspace = true -serde_json.workspace = true serde.workspace = true +serde_json.workspace = true snafu.workspace = true stackable-operator.workspace = true tokio.workspace = true tracing.workspace = true url.workspace = true -ldap3 = { version = "0.11.5", features = ["gssapi", "tls"] } -native-tls = "0.2.12" -uuid = "1.10.0" -base64 = "0.22.1" -byteorder = "1.5.0" -rustls-pemfile = "2.1.3" +uuid.workspace = true From e83e58932fd995ab763bec7f51e12e34382aef72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Wed, 14 Aug 2024 17:14:33 +0200 Subject: [PATCH 15/18] Changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a374a6d3..2db60152 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file. ### Added - Added regorule library for accessing user-info-fetcher ([#580]). +- Added Active Directory backend for user-info-fetcher ([#622]). ### Changed @@ -22,6 +23,7 @@ All notable changes to this project will be documented in this file. [#578]: https://github.com/stackabletech/opa-operator/pull/578 [#580]: https://github.com/stackabletech/opa-operator/pull/580 [#621]: https://github.com/stackabletech/opa-operator/pull/621 +[#622]: https://github.com/stackabletech/opa-operator/pull/622 ## [24.7.0] - 2024-07-24 From 31bad46a21617780de0c5c2cdf0374badb58adc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Wed, 28 Aug 2024 13:37:16 +0200 Subject: [PATCH 16/18] Update docs/modules/opa/pages/usage-guide/user-info-fetcher.adoc Co-authored-by: Sebastian Bernauer --- docs/modules/opa/pages/usage-guide/user-info-fetcher.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/modules/opa/pages/usage-guide/user-info-fetcher.adoc b/docs/modules/opa/pages/usage-guide/user-info-fetcher.adoc index 711db975..731a239a 100644 --- a/docs/modules/opa/pages/usage-guide/user-info-fetcher.adoc +++ b/docs/modules/opa/pages/usage-guide/user-info-fetcher.adoc @@ -70,6 +70,8 @@ WARNING: The Active Directory backend is experimental, and subject to change. Fetches user attributes and groups over LDAP. +For this to work user-info-fetcher needs to be provided with a Kerberos keytab that enables it to access Active Directory. +This is provided by a configurable SecretClass. [source,yaml] ---- spec: From db442967e6b29d320c8fd66997b615c7ab8551dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Wed, 28 Aug 2024 13:41:42 +0200 Subject: [PATCH 17/18] Group up backends, clarify that response details will depend on the active backend --- .../opa/pages/usage-guide/user-info-fetcher.adoc | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/modules/opa/pages/usage-guide/user-info-fetcher.adoc b/docs/modules/opa/pages/usage-guide/user-info-fetcher.adoc index 731a239a..8fc604b7 100644 --- a/docs/modules/opa/pages/usage-guide/user-info-fetcher.adoc +++ b/docs/modules/opa/pages/usage-guide/user-info-fetcher.adoc @@ -55,8 +55,13 @@ Currently the following backends are supported: * xref:#backend-keycloak[] * xref:#backend-activedirectory[] +[#backends] +== Backends + +The user info fetcher can fetch data from a few different backends. We currently recommend the xref:#backend-keycloak[] backend. + [#backend-keycloak] -== Keycloak +=== Keycloak // todo: maybe this section should be under a Tutorial? Fetch groups and extra credentials, but not roles. @@ -64,7 +69,7 @@ Fetch groups and extra credentials, but not roles. NOTE: The OAuth2 Client in Keycloak must be given the `view-users` _Service Account Role_ for the realm that the users are in. [#backend-activedirectory] -== Active Directory +=== Active Directory WARNING: The Active Directory backend is experimental, and subject to change. @@ -114,6 +119,8 @@ An example of the returned structure: } ---- +NOTE: The exact formats of `id` and `groups` will vary depending on the xref:#backends[backend] in use. This example is using the xref:#backend-keycloak[] backend. + For example, the following rule will allow access for users in the `/admin` group: [source,rego] From 94febb5bcec447bdcd80041585743e20b7cb4883 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Fri, 30 Aug 2024 14:30:59 +0200 Subject: [PATCH 18/18] s/ldapHostname/ldapServer --- deploy/helm/opa-operator/crds/crds.yaml | 4 ++-- docs/modules/opa/pages/usage-guide/user-info-fetcher.adoc | 2 +- rust/crd/src/user_info_fetcher.rs | 2 +- rust/user-info-fetcher/src/main.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/deploy/helm/opa-operator/crds/crds.yaml b/deploy/helm/opa-operator/crds/crds.yaml index 4338bb8e..25280bd7 100644 --- a/deploy/helm/opa-operator/crds/crds.yaml +++ b/deploy/helm/opa-operator/crds/crds.yaml @@ -80,7 +80,7 @@ spec: kerberosSecretClassName: description: The name of the Kerberos SecretClass. type: string - ldapHostname: + ldapServer: description: Hostname of the domain controller, e.g. `ad-ds-1.contoso.com`. type: string tls: @@ -126,7 +126,7 @@ spec: required: - baseDistinguishedName - kerberosSecretClassName - - ldapHostname + - ldapServer type: object experimentalXfscAas: description: Backend that fetches user information from the Gaia-X Cross Federation Services Components (XFSC) Authentication & Authorization Service. diff --git a/docs/modules/opa/pages/usage-guide/user-info-fetcher.adoc b/docs/modules/opa/pages/usage-guide/user-info-fetcher.adoc index 8fc604b7..5cdb0e8a 100644 --- a/docs/modules/opa/pages/usage-guide/user-info-fetcher.adoc +++ b/docs/modules/opa/pages/usage-guide/user-info-fetcher.adoc @@ -84,7 +84,7 @@ spec: userInfo: backend: experimentalActiveDirectory: # <1> - ldapHostname: sble-adds.sble.test # <2> + ldapServer: sble-adds.sble.test # <2> baseDistinguishedName: DC=sble,DC=test # <3> kerberosSecretClassName: kerberos-ad # <4> tls: diff --git a/rust/crd/src/user_info_fetcher.rs b/rust/crd/src/user_info_fetcher.rs index 6d444aef..d8e40c1a 100644 --- a/rust/crd/src/user_info_fetcher.rs +++ b/rust/crd/src/user_info_fetcher.rs @@ -98,7 +98,7 @@ fn aas_default_port() -> u16 { #[serde(rename_all = "camelCase")] pub struct ActiveDirectoryBackend { /// Hostname of the domain controller, e.g. `ad-ds-1.contoso.com`. - pub ldap_hostname: String, + pub ldap_server: String, /// The root Distinguished Name (DN) where users and groups are located. pub base_distinguished_name: String, diff --git a/rust/user-info-fetcher/src/main.rs b/rust/user-info-fetcher/src/main.rs index 3c3d1fcb..aedd197f 100644 --- a/rust/user-info-fetcher/src/main.rs +++ b/rust/user-info-fetcher/src/main.rs @@ -294,7 +294,7 @@ async fn get_user_info( } crd::Backend::ActiveDirectory(ad) => backend::active_directory::get_user_info( &req, - &ad.ldap_hostname, + &ad.ldap_server, &ad.tls, &ad.base_distinguished_name, &ad.custom_attribute_mappings,