From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id BAC8E1FF16F for ; Tue, 16 Sep 2025 16:48:30 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 2859F17D6C; Tue, 16 Sep 2025 16:48:38 +0200 (CEST) From: Shannon Sterz To: pdm-devel@lists.proxmox.com Date: Tue, 16 Sep 2025 16:48:26 +0200 Message-ID: <20250916144827.551806-11-s.sterz@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20250916144827.551806-1-s.sterz@proxmox.com> References: <20250916144827.551806-1-s.sterz@proxmox.com> MIME-Version: 1.0 X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1758034104119 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.049 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: [pdm-devel] [PATCH datacenter-manager 4/5] api/auth: add endpoint to start ldap sync jobs X-BeenThere: pdm-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Datacenter Manager development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Proxmox Datacenter Manager development discussion Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pdm-devel-bounces@lists.proxmox.com Sender: "pdm-devel" Signed-off-by: Shannon Sterz --- server/src/api/access/domains.rs | 90 +++++++++++++++++++++++++++++--- server/src/auth/ldap.rs | 87 +++++++++++++++++++++++++++++- 2 files changed, 169 insertions(+), 8 deletions(-) diff --git a/server/src/api/access/domains.rs b/server/src/api/access/domains.rs index cdfbee1..2aef6d9 100644 --- a/server/src/api/access/domains.rs +++ b/server/src/api/access/domains.rs @@ -1,13 +1,16 @@ //! List Authentication domains/realms. -use anyhow::Error; -use serde_json::Value; +use anyhow::{bail, format_err, Error}; +use serde_json::{json, Value}; -use pdm_api_types::{BasicRealmInfo, RealmType}; - -use proxmox_router::{Permission, Router, RpcEnvironment}; +use proxmox_auth_api::types::Realm; +use proxmox_ldap::types::REMOVE_VANISHED_SCHEMA; +use proxmox_router::{Permission, Router, RpcEnvironment, RpcEnvironmentType, SubdirMap}; use proxmox_schema::api; +use pbs_api_types::PRIV_PERMISSIONS_MODIFY; +use pdm_api_types::{Authid, BasicRealmInfo, RealmRef, RealmType, UPID_SCHEMA}; + #[api( returns: { description: "List of realms with basic info.", @@ -52,4 +55,79 @@ fn list_domains(rpcenv: &mut dyn RpcEnvironment) -> Result, Ok(list) } -pub const ROUTER: Router = Router::new().get(&API_METHOD_LIST_DOMAINS); +#[api( + protected: true, + input: { + properties: { + realm: { + type: Realm, + }, + "dry-run": { + type: bool, + description: "If set, do not create/delete anything", + default: false, + optional: true, + }, + "remove-vanished": { + optional: true, + schema: REMOVE_VANISHED_SCHEMA, + }, + "enable-new": { + description: "Enable newly synced users immediately", + optional: true, + } + }, + }, + returns: { + schema: UPID_SCHEMA, + }, + access: { + permission: &Permission::Privilege(&["access", "users"], PRIV_PERMISSIONS_MODIFY, false), + }, +)] +/// Synchronize users of a given realm +pub fn sync_realm( + realm: Realm, + dry_run: bool, + remove_vanished: Option, + enable_new: Option, + rpcenv: &mut dyn RpcEnvironment, +) -> Result { + let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; + + let to_stdout = rpcenv.env_type() == RpcEnvironmentType::CLI; + + let upid_str = crate::auth::ldap::do_realm_sync_job( + realm.clone(), + realm_type_from_name(&realm)?, + &auth_id, + to_stdout, + dry_run, + remove_vanished, + enable_new, + ) + .map_err(|err| format_err!("unable to start realm sync job on realm {realm} - {err:#}"))?; + + Ok(json!(upid_str)) +} + +fn realm_type_from_name(realm: &RealmRef) -> Result { + let config = pdm_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)]; + +const REALM_ROUTER: Router = Router::new().subdirs(SYNC_SUBDIRS); + +pub const ROUTER: Router = Router::new() + .get(&API_METHOD_LIST_DOMAINS) + .match_all("realm", &REALM_ROUTER); diff --git a/server/src/auth/ldap.rs b/server/src/auth/ldap.rs index fddb3f9..b42a81e 100644 --- a/server/src/auth/ldap.rs +++ b/server/src/auth/ldap.rs @@ -3,16 +3,19 @@ use std::net::IpAddr; use std::path::PathBuf; use std::pin::Pin; -use anyhow::Error; +use anyhow::{bail, Error}; use pdm_buildcfg::configdir; use proxmox_auth_api::api::Authenticator; +use proxmox_ldap::sync::{AdRealmSyncJob, GeneralSyncSettingsOverride, LdapRealmSyncJob}; use proxmox_ldap::types::{AdRealmConfig, LdapMode, LdapRealmConfig}; use proxmox_ldap::{Config, Connection, ConnectionMode}; use proxmox_product_config::ApiLockGuard; +use proxmox_rest_server::WorkerTask; use proxmox_router::http_bail; use serde_json::json; -use pdm_api_types::UsernameRef; +use pdm_api_types::{Authid, Realm, RealmType, UsernameRef}; +use pdm_config::domains; const LDAP_PASSWORDS_FILENAME: &str = configdir!("/ldap_passwords.json"); @@ -230,3 +233,83 @@ pub(super) fn get_ldap_bind_password(realm: &str) -> Result, Erro Ok(password) } + +/// Runs a realm sync job +#[allow(clippy::too_many_arguments)] +pub fn do_realm_sync_job( + realm: Realm, + realm_type: RealmType, + auth_id: &Authid, + to_stdout: bool, + dry_run: bool, + remove_vanished: Option, + enable_new: Option, +) -> Result { + let upid_str = WorkerTask::spawn( + "realm-sync", + Some(realm.as_str().to_owned()), + auth_id.to_string(), + to_stdout, + move |_worker| { + log::info!("starting realm sync for {realm}"); + + let override_settings = GeneralSyncSettingsOverride { + remove_vanished, + enable_new, + }; + + async move { + match realm_type { + RealmType::Ldap => { + let (domains, _digest) = domains::config()?; + let config = if let Ok(config) = + domains.lookup::("ldap", realm.as_str()) + { + config + } else { + bail!("unknown LDAP realm '{realm}'"); + }; + + let ldap_config = LdapAuthenticator::api_type_to_config(&config)?; + + LdapRealmSyncJob::new( + realm, + config, + ldap_config, + &override_settings, + dry_run, + )? + .sync() + .await + } + RealmType::Ad => { + let (domains, _digest) = domains::config()?; + let config = if let Ok(config) = + domains.lookup::("ad", realm.as_str()) + { + config + } else { + bail!("unknown Active Directory realm '{realm}'"); + }; + + let ldap_config = AdAuthenticator::api_type_to_config(&config)?; + + AdRealmSyncJob::new( + realm, + config, + ldap_config, + &override_settings, + dry_run, + )? + .sync() + .await + } + + _ => bail!("cannot sync realm {realm} of type {realm_type}"), + } + } + }, + )?; + + Ok(upid_str) +} -- 2.47.3 _______________________________________________ pdm-devel mailing list pdm-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel