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 E2101B07A for ; Tue, 8 Aug 2023 14:23:25 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id A2E3F9B92 for ; Tue, 8 Aug 2023 14:22:53 +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 ; Tue, 8 Aug 2023 14:22:51 +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 1B4AF437A7 for ; Tue, 8 Aug 2023 14:22:51 +0200 (CEST) From: Christoph Heiss To: pbs-devel@lists.proxmox.com Date: Tue, 8 Aug 2023 14:22:11 +0200 Message-ID: <20230808122239.1025524-10-c.heiss@proxmox.com> X-Mailer: git-send-email 2.41.0 In-Reply-To: <20230808122239.1025524-1-c.heiss@proxmox.com> References: <20230808122239.1025524-1-c.heiss@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL -0.047 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 09/12] 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: Tue, 08 Aug 2023 12:23:25 -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 --- pbs-api-types/src/lib.rs | 4 +++ src/api2/access/domain.rs | 16 ++++++++- src/server/realm_sync_job.rs | 69 ++++++++++++++++++++++++++++++++---- 3 files changed, 82 insertions(+), 7 deletions(-) diff --git a/pbs-api-types/src/lib.rs b/pbs-api-types/src/lib.rs index c622484e..3f86803f 100644 --- a/pbs-api-types/src/lib.rs +++ b/pbs-api-types/src/lib.rs @@ -513,6 +513,8 @@ pub enum RealmType { OpenId, /// An LDAP realm Ldap, + /// An Active Directory (AD) realm + Ad, } impl fmt::Display for RealmType { @@ -523,6 +525,7 @@ impl fmt::Display for RealmType { Pbs => write!(f, "PBS"), OpenId => write!(f, "OpenID"), Ldap => write!(f, "LDAP"), + Ad => write!(f, "AD"), } } } @@ -537,6 +540,7 @@ impl std::str::FromStr for RealmType { "pbs" => Ok(Pbs), "openid" => Ok(OpenId), "ldap" => Ok(Ldap), + "ad" => Ok(Ad), _ => Err(format_err!("unknown realm type {realm_type}")), } } diff --git a/src/api2/access/domain.rs b/src/api2/access/domain.rs index 31aa62bc..027597fa 100644 --- a/src/api2/access/domain.rs +++ b/src/api2/access/domain.rs @@ -7,7 +7,8 @@ use proxmox_router::{Permission, Router, RpcEnvironment, RpcEnvironmentType, Sub 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 section_type.parse(); + } + } + + Err(format_err!("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