From a8636bbb660787cdd87458f0b5b499a21bb51cd2 Mon Sep 17 00:00:00 2001 From: Christoph Heiss Date: Fri, 12 Jan 2024 17:16:04 +0100 Subject: [PATCH] realm sync: add sync job for AD realms Basically just a thin wrapper over the existing LDAP-based realm sync job, which retrieves the appropriate config and sets the correct user attributes. Signed-off-by: Christoph Heiss Reviewed-by: Lukas Wagner Tested-by: Lukas Wagner --- pbs-api-types/src/lib.rs | 5 +++ src/api2/access/domain.rs | 18 ++++++++-- src/server/realm_sync_job.rs | 69 ++++++++++++++++++++++++++++++++---- 3 files changed, 84 insertions(+), 8 deletions(-) diff --git a/pbs-api-types/src/lib.rs b/pbs-api-types/src/lib.rs index 72cddf38..a3ad185b 100644 --- a/pbs-api-types/src/lib.rs +++ b/pbs-api-types/src/lib.rs @@ -381,8 +381,13 @@ pub enum RealmType { OpenId, /// An LDAP realm Ldap, + /// An Active Directory (AD) realm + Ad, } +serde_plain::derive_display_from_serialize!(RealmType); +serde_plain::derive_fromstr_from_deserialize!(RealmType); + #[api( properties: { realm: { diff --git a/src/api2/access/domain.rs b/src/api2/access/domain.rs index 31aa62bc..8f8eebda 100644 --- a/src/api2/access/domain.rs +++ b/src/api2/access/domain.rs @@ -1,13 +1,14 @@ //! List Authentication domains/realms -use anyhow::{format_err, Error}; +use anyhow::{bail, format_err, Error}; use serde_json::{json, Value}; use proxmox_router::{Permission, Router, RpcEnvironment, RpcEnvironmentType, SubdirMap}; use proxmox_schema::api; use pbs_api_types::{ - Authid, BasicRealmInfo, Realm, PRIV_PERMISSIONS_MODIFY, REMOVE_VANISHED_SCHEMA, UPID_SCHEMA, + Authid, BasicRealmInfo, Realm, RealmRef, RealmType, PRIV_PERMISSIONS_MODIFY, + REMOVE_VANISHED_SCHEMA, UPID_SCHEMA, }; use crate::server::jobstate::Job; @@ -102,6 +103,7 @@ pub fn sync_realm( let upid_str = crate::server::do_realm_sync_job( job, realm.clone(), + realm_type_from_name(&realm)?, &auth_id, None, to_stdout, @@ -120,6 +122,18 @@ pub fn sync_realm( Ok(json!(upid_str)) } +fn realm_type_from_name(realm: &RealmRef) -> Result { + let config = pbs_config::domains::config()?.0; + + for (name, (section_type, _)) in config.sections.iter() { + if name == realm.as_str() { + return Ok(section_type.parse()?); + } + } + + bail!("unable to find realm {realm}") +} + const SYNC_ROUTER: Router = Router::new().post(&API_METHOD_SYNC_REALM); const SYNC_SUBDIRS: SubdirMap = &[("sync", &SYNC_ROUTER)]; diff --git a/src/server/realm_sync_job.rs b/src/server/realm_sync_job.rs index 972e9a6b..31412be8 100644 --- a/src/server/realm_sync_job.rs +++ b/src/server/realm_sync_job.rs @@ -10,9 +10,9 @@ use proxmox_sys::{task_log, task_warn}; use std::{collections::HashSet, sync::Arc}; use pbs_api_types::{ - ApiToken, Authid, LdapRealmConfig, Realm, RemoveVanished, SyncAttributes as LdapSyncAttributes, - SyncDefaultsOptions, User, Userid, EMAIL_SCHEMA, FIRST_NAME_SCHEMA, LAST_NAME_SCHEMA, - REMOVE_VANISHED_ARRAY, USER_CLASSES_ARRAY, + AdRealmConfig, ApiToken, Authid, LdapRealmConfig, Realm, RealmType, RemoveVanished, + SyncAttributes as LdapSyncAttributes, SyncDefaultsOptions, User, Userid, EMAIL_SCHEMA, + FIRST_NAME_SCHEMA, LAST_NAME_SCHEMA, REMOVE_VANISHED_ARRAY, USER_CLASSES_ARRAY, }; use crate::{auth, server::jobstate::Job}; @@ -22,6 +22,7 @@ use crate::{auth, server::jobstate::Job}; pub fn do_realm_sync_job( mut job: Job, realm: Realm, + realm_type: RealmType, auth_id: &Authid, _schedule: Option, to_stdout: bool, @@ -46,8 +47,19 @@ pub fn do_realm_sync_job( }; async move { - let sync_job = LdapRealmSyncJob::new(worker, realm, &override_settings, dry_run)?; - sync_job.sync().await + match realm_type { + RealmType::Ldap => { + LdapRealmSyncJob::new(worker, realm, &override_settings, dry_run)? + .sync() + .await + } + RealmType::Ad => { + AdRealmSyncJob::new(worker, realm, &override_settings, dry_run)? + .sync() + .await + } + _ => bail!("cannot sync realm {realm} of type {realm_type}"), + } } }, )?; @@ -55,6 +67,51 @@ pub fn do_realm_sync_job( Ok(upid_str) } +/// Implementation for syncing Active Directory realms. Merely a thin wrapper over +/// `LdapRealmSyncJob`, as AD is just LDAP with some special requirements. +struct AdRealmSyncJob(LdapRealmSyncJob); + +impl AdRealmSyncJob { + fn new( + worker: Arc, + realm: Realm, + override_settings: &GeneralSyncSettingsOverride, + dry_run: bool, + ) -> Result { + let (domains, _digest) = pbs_config::domains::config()?; + let config = if let Ok(config) = domains.lookup::("ad", realm.as_str()) { + config + } else { + bail!("unknown Active Directory realm '{}'", realm.as_str()); + }; + + let sync_settings = GeneralSyncSettings::default() + .apply_config(config.sync_defaults_options.as_deref())? + .apply_override(override_settings)?; + let sync_attributes = LdapSyncSettings::new( + "sAMAccountName", + config.sync_attributes.as_deref(), + config.user_classes.as_deref(), + config.filter.as_deref(), + )?; + + let ldap_config = auth::AdAuthenticator::api_type_to_config(&config)?; + + Ok(Self(LdapRealmSyncJob { + worker, + realm, + general_sync_settings: sync_settings, + ldap_sync_settings: sync_attributes, + ldap_config, + dry_run, + })) + } + + async fn sync(&self) -> Result<(), Error> { + self.0.sync().await + } +} + /// Implementation for syncing LDAP realms struct LdapRealmSyncJob { worker: Arc, @@ -77,7 +134,7 @@ impl LdapRealmSyncJob { let config = if let Ok(config) = domains.lookup::("ldap", realm.as_str()) { config } else { - bail!("unknown realm '{}'", realm.as_str()); + bail!("unknown LDAP realm '{}'", realm.as_str()); }; let sync_settings = GeneralSyncSettings::default()