use crate::state::WebhookState;
use kaniop_operator::controller::kanidm::{KanidmClients, KanidmKey, KanidmUser};
use kaniop_operator::crd::KanidmRef;
use kaniop_operator::kanidm::crd::Kanidm;
use kube::runtime::reflector::Store;
use kube::{Api, Resource, ResourceExt};
#[derive(Clone, Copy, Debug)]
pub enum KanidmEntityKind {
Group,
Person,
OAuth2Client,
ServiceAccount,
}
pub fn normalize_kanidm_ref(kanidm_ref: &KanidmRef, object_namespace: &str) -> (String, String) {
let ref_name = kanidm_ref.name.clone();
let ref_namespace = kanidm_ref
.namespace
.clone()
.unwrap_or_else(|| object_namespace.to_string());
(ref_name, ref_namespace)
}
pub fn check_duplicate<T>(object: &T, object_name: &str, store: &Store<T>) -> Result<(), String>
where
T: Resource + ResourceExt + Clone + HasKanidmRef,
<T as Resource>::DynamicType: Eq + std::hash::Hash + Clone,
{
let obj_namespace = object.namespace().unwrap_or_else(|| "default".to_string());
let (ref_name, ref_namespace) = normalize_kanidm_ref(object.kanidm_ref_spec(), &obj_namespace);
let obj_kanidm_entity_name = object.kanidm_entity_name();
store
.state()
.into_iter()
.find(|resource| {
resource.meta().uid != object.meta().uid && {
let res_namespace = resource
.namespace()
.unwrap_or_else(|| "default".to_string());
let (res_ref_name, res_ref_namespace) =
normalize_kanidm_ref(resource.kanidm_ref_spec(), &res_namespace);
ref_name == res_ref_name
&& ref_namespace == res_ref_namespace
&& resource.kanidm_entity_name() == obj_kanidm_entity_name
}
})
.map(|r| {
Err(format!(
"{} with same kanidmRef and kanidmName already exists: {}/{}",
object_name,
r.namespace().unwrap_or_else(|| "default".to_string()),
r.name_any()
))
})
.unwrap_or(Ok(()))
}
pub async fn check_external_duplicate<T>(
state: &WebhookState,
object: &T,
object_name: &str,
entity_kind: KanidmEntityKind,
) -> Result<(), String>
where
T: Resource + ResourceExt + HasKanidmRef,
{
let object_namespace = object.namespace().unwrap_or_else(|| "default".to_string());
let (kanidm_name, kanidm_namespace) =
normalize_kanidm_ref(object.kanidm_ref_spec(), &object_namespace);
let kanidm_api = Api::<Kanidm>::namespaced(state.kube_client.clone(), &kanidm_namespace);
let kanidm = match kanidm_api.get(&kanidm_name).await {
Ok(kanidm) => kanidm,
Err(kube::Error::Api(response)) if response.code == 404 => return Ok(()),
Err(error) => {
return Err(format!(
"failed to verify {object_name} against Kanidm {kanidm_namespace}/{kanidm_name}: {error}"
));
}
};
if kanidm.spec.external_replication_nodes.is_empty() {
return Ok(());
}
let key = KanidmKey {
namespace: kanidm_namespace.clone(),
name: kanidm_name.clone(),
};
let cached_client = state.kanidm_clients.read().await.get(&key).cloned();
let client = match cached_client {
Some(client) if client.auth_valid().await.is_ok() => client,
_ => {
let client = KanidmClients::create_client(
&kanidm_namespace,
&kanidm_name,
KanidmUser::IdmAdmin,
state.kube_client.clone(),
)
.await
.map_err(|error| {
format!(
"failed to connect to externally replicated Kanidm {kanidm_namespace}/{kanidm_name} while validating {object_name}: {error:?}"
)
})?;
state
.kanidm_clients
.write()
.await
.insert(key, client.clone());
client
}
};
let entity_name = object.kanidm_entity_name();
let entity_exists = match entity_kind {
KanidmEntityKind::Group => client
.idm_group_get(&entity_name)
.await
.map(|entry| entry.is_some()),
KanidmEntityKind::Person => client
.idm_person_account_get(&entity_name)
.await
.map(|entry| entry.is_some()),
KanidmEntityKind::OAuth2Client => client
.idm_oauth2_rs_get(&entity_name)
.await
.map(|entry| entry.is_some()),
KanidmEntityKind::ServiceAccount => client
.idm_service_account_get(&entity_name)
.await
.map(|entry| entry.is_some()),
}
.map_err(|error| {
format!(
"failed to verify whether Kanidm entity '{entity_name}' exists in externally replicated Kanidm {kanidm_namespace}/{kanidm_name}: {error:?}"
)
})?;
if entity_exists {
Err(format!(
"{object_name} cannot manage Kanidm entity '{entity_name}' because it already exists in externally replicated Kanidm {kanidm_namespace}/{kanidm_name}"
))
} else {
Ok(())
}
}
pub trait HasKanidmRef {
fn kanidm_ref_spec(&self) -> &KanidmRef;
fn kanidm_entity_name(&self) -> String;
}
#[cfg(test)]
mod tests {
use super::*;
use kaniop_operator::crd::KanidmRef;
#[test]
fn test_normalize_kanidm_ref_with_namespace() {
let ref_obj = KanidmRef {
name: "my-kanidm".to_string(),
namespace: Some("other-ns".to_string()),
};
let (name, ns) = normalize_kanidm_ref(&ref_obj, "current-ns");
assert_eq!(name, "my-kanidm");
assert_eq!(ns, "other-ns");
}
#[test]
fn test_normalize_kanidm_ref_without_namespace() {
let ref_obj = KanidmRef {
name: "my-kanidm".to_string(),
namespace: None,
};
let (name, ns) = normalize_kanidm_ref(&ref_obj, "current-ns");
assert_eq!(name, "my-kanidm");
assert_eq!(ns, "current-ns");
}
}