From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 3A106CD7E for ; Wed, 16 Aug 2023 16:49:10 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 22E571820F for ; Wed, 16 Aug 2023 16:49:08 +0200 (CEST) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [94.136.29.106]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS for ; Wed, 16 Aug 2023 16:49:05 +0200 (CEST) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 04F0A41025 for ; Wed, 16 Aug 2023 16:49:05 +0200 (CEST) From: Christoph Heiss To: pbs-devel@lists.proxmox.com Date: Wed, 16 Aug 2023 16:47:39 +0200 Message-ID: <20230816144746.1265108-10-c.heiss@proxmox.com> X-Mailer: git-send-email 2.41.0 In-Reply-To: <20230816144746.1265108-1-c.heiss@proxmox.com> References: <20230816144746.1265108-1-c.heiss@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL -0.040 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: [pbs-devel] [PATCH proxmox-backup v2 09/15] realm sync: add sync job for AD realms X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Wed, 16 Aug 2023 14:49:10 -0000 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 --- Changes v1 -> v2: * Implement `Display` + `FromStr` for `RealmType` using the serde_plain crate by deriving them. Previously a separate patch, but now folded into this as it was reduced to a two-liner due to this change. * Fix clippy deref warning 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 d2bce842..b1ac3c9d 100644 --- a/pbs-api-types/src/lib.rs +++ b/pbs-api-types/src/lib.rs @@ -509,8 +509,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 5a7bcf0b..de8124d7 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 + } +} + /// Implemenation 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() -- 2.41.0