diff --git a/proto/src/internal.rs b/proto/src/internal.rs index d027dd18c..1bdd958dc 100644 --- a/proto/src/internal.rs +++ b/proto/src/internal.rs @@ -173,6 +173,14 @@ pub enum Oauth2ClaimMapJoin { Array, } +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct DomainInfo { + pub name: String, + pub displayname: String, + pub uuid: Uuid, + pub level: u32, +} + #[test] fn test_fstype_deser() { assert_eq!(FsType::try_from("zfs"), Ok(FsType::Zfs)); diff --git a/proto/src/v1.rs b/proto/src/v1.rs index 4effb71f6..e41332342 100644 --- a/proto/src/v1.rs +++ b/proto/src/v1.rs @@ -296,7 +296,11 @@ pub enum OperationError { VS0001IncomingReplSshPublicKey, // Value Errors VL0001ValueSshPublicKeyString, + // SCIM SC0001IncomingSshPublicKey, + // Migration + MG0001InvalidReMigrationLevel, + MG0002RaiseDomainLevelExceedsMaximum, } impl PartialEq for OperationError { diff --git a/rlm_python/Dockerfile b/rlm_python/Dockerfile index 3a620205a..25aa9fa3f 100644 --- a/rlm_python/Dockerfile +++ b/rlm_python/Dockerfile @@ -1,4 +1,6 @@ ARG BASE_IMAGE=opensuse/tumbleweed:latest +# ARG BASE_IMAGE=opensuse/leap:15.5 + # FROM ${BASE_IMAGE} as repos FROM ${BASE_IMAGE} diff --git a/server/Dockerfile b/server/Dockerfile index 98a1d5b81..7a21e0e65 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -1,5 +1,6 @@ # Build the main Kanidmd server ARG BASE_IMAGE=opensuse/tumbleweed:latest +# ARG BASE_IMAGE=opensuse/leap:15.5 FROM ${BASE_IMAGE} AS repos ADD scripts/zypper_fixing.sh /zypper_fixing.sh diff --git a/server/core/src/actors/v1_write.rs b/server/core/src/actors/v1_write.rs index bd27d6219..6ae107f16 100644 --- a/server/core/src/actors/v1_write.rs +++ b/server/core/src/actors/v1_write.rs @@ -1,5 +1,6 @@ use std::{iter, sync::Arc}; +use kanidm_proto::internal::DomainInfo as ProtoDomainInfo; use kanidm_proto::internal::ImageValue; use kanidm_proto::internal::Oauth2ClaimMapJoin as ProtoOauth2ClaimMapJoin; use kanidm_proto::v1::{ @@ -1820,4 +1821,57 @@ impl QueryServerWriteV1 { idms_prox_write.commit().map(|()| pw) } + + #[instrument( + level = "info", + skip_all, + fields(uuid = ?eventid) + )] + pub(crate) async fn handle_domain_show( + &self, + eventid: Uuid, + ) -> Result { + trace!("Begin domain show event"); + let ct = duration_from_epoch_now(); + let mut idms_prox_write = self.idms.proxy_write(ct).await; + + let domain_info = idms_prox_write.qs_write.domain_info()?; + + idms_prox_write.commit().map(|()| domain_info) + } + + #[instrument( + level = "info", + skip_all, + fields(uuid = ?eventid) + )] + pub(crate) async fn handle_domain_raise(&self, eventid: Uuid) -> Result { + trace!("Begin domain raise event"); + let ct = duration_from_epoch_now(); + let mut idms_prox_write = self.idms.proxy_write(ct).await; + + idms_prox_write.qs_write.domain_raise(DOMAIN_MAX_LEVEL)?; + + idms_prox_write.commit().map(|()| DOMAIN_MAX_LEVEL) + } + + #[instrument( + level = "info", + skip_all, + fields(uuid = ?eventid) + )] + pub(crate) async fn handle_domain_remigrate( + &self, + level: Option, + eventid: Uuid, + ) -> Result<(), OperationError> { + let level = level.unwrap_or(DOMAIN_MIN_REMIGRATION_LEVEL); + trace!(%level, "Begin domain remigrate event"); + let ct = duration_from_epoch_now(); + let mut idms_prox_write = self.idms.proxy_write(ct).await; + + idms_prox_write.qs_write.domain_remigrate(level)?; + + idms_prox_write.commit() + } } diff --git a/server/core/src/admin.rs b/server/core/src/admin.rs index e3f412f2f..65ade5392 100644 --- a/server/core/src/admin.rs +++ b/server/core/src/admin.rs @@ -17,18 +17,25 @@ use tokio_util::codec::{Decoder, Encoder, Framed}; use tracing::{span, Instrument, Level}; use uuid::Uuid; +pub use kanidm_proto::internal::DomainInfo as ProtoDomainInfo; + #[derive(Serialize, Deserialize, Debug)] pub enum AdminTaskRequest { RecoverAccount { name: String }, ShowReplicationCertificate, RenewReplicationCertificate, RefreshReplicationConsumer, + DomainShow, + DomainRaise, + DomainRemigrate { level: Option }, } #[derive(Serialize, Deserialize, Debug)] pub enum AdminTaskResponse { RecoverAccount { password: String }, ShowReplicationCertificate { cert: String }, + DomainRaise { level: u32 }, + DomainShow { domain_info: ProtoDomainInfo }, Success, Error, } @@ -315,6 +322,30 @@ async fn handle_client( AdminTaskResponse::Error } }, + + AdminTaskRequest::DomainShow => match server.handle_domain_show(eventid).await { + Ok(domain_info) => AdminTaskResponse::DomainShow { domain_info }, + Err(e) => { + error!(err = ?e, "error during domain show"); + AdminTaskResponse::Error + } + }, + AdminTaskRequest::DomainRaise => match server.handle_domain_raise(eventid).await { + Ok(level) => AdminTaskResponse::DomainRaise { level }, + Err(e) => { + error!(err = ?e, "error during domain raise"); + AdminTaskResponse::Error + } + }, + AdminTaskRequest::DomainRemigrate { level } => { + match server.handle_domain_remigrate(level, eventid).await { + Ok(()) => AdminTaskResponse::Success, + Err(e) => { + error!(err = ?e, "error during domain remigrate"); + AdminTaskResponse::Error + } + } + } } } .instrument(nspan) diff --git a/server/daemon/src/main.rs b/server/daemon/src/main.rs index 4152af981..5b2c14130 100644 --- a/server/daemon/src/main.rs +++ b/server/daemon/src/main.rs @@ -30,7 +30,7 @@ use clap::{Args, Parser, Subcommand}; use futures::{SinkExt, StreamExt}; #[cfg(not(target_family = "windows"))] // not needed for windows builds use kanidm_utils_users::{get_current_gid, get_current_uid, get_effective_gid, get_effective_uid}; -use kanidmd_core::admin::{AdminTaskRequest, AdminTaskResponse, ClientCodec}; +use kanidmd_core::admin::{AdminTaskRequest, AdminTaskResponse, ClientCodec, ProtoDomainInfo}; use kanidmd_core::config::{Configuration, ServerConfig}; use kanidmd_core::{ backup_server_core, cert_generate_core, create_server_core, dbscan_get_id2entry_core, @@ -89,8 +89,17 @@ impl KanidmdOpt { commands: DbScanOpt::GetId2Entry(dopt), } => &dopt.commonopts, KanidmdOpt::DomainSettings { - commands: DomainSettingsCmds::DomainChange(sopt), - } => sopt, + commands: DomainSettingsCmds::Show { commonopts }, + } + | KanidmdOpt::DomainSettings { + commands: DomainSettingsCmds::Change { commonopts }, + } + | KanidmdOpt::DomainSettings { + commands: DomainSettingsCmds::Raise { commonopts }, + } + | KanidmdOpt::DomainSettings { + commands: DomainSettingsCmds::Remigrate { commonopts, .. }, + } => commonopts, KanidmdOpt::Database { commands: DbCommands::Verify(sopt), } @@ -161,6 +170,36 @@ async fn submit_admin_req(path: &str, req: AdminTaskRequest, output_mode: Consol info!(certificate = ?cert) } }, + + Some(Ok(AdminTaskResponse::DomainRaise { level })) => match output_mode { + ConsoleOutputMode::JSON => { + eprintln!("{{\"success\":\"{}\"}}", level) + } + ConsoleOutputMode::Text => { + info!("success - raised domain level to {}", level) + } + }, + Some(Ok(AdminTaskResponse::DomainShow { domain_info })) => match output_mode { + ConsoleOutputMode::JSON => { + let json_output = serde_json::json!({ + "domain_info": domain_info + }); + println!("{}", json_output); + } + ConsoleOutputMode::Text => { + let ProtoDomainInfo { + name, + displayname, + uuid, + level, + } = domain_info; + + info!("domain_name : {}", name); + info!("domain_display: {}", displayname); + info!("domain_uuid : {}", uuid); + info!("domain_level : {}", level); + } + }, Some(Ok(AdminTaskResponse::Success)) => match output_mode { ConsoleOutputMode::JSON => { eprintln!("\"success\"") @@ -717,11 +756,49 @@ async fn kanidm_main() -> ExitCode { } KanidmdOpt::DomainSettings { - commands: DomainSettingsCmds::DomainChange(_dopt), + commands: DomainSettingsCmds::Change { .. }, } => { info!("Running in domain name change mode ... this may take a long time ..."); domain_rename_core(&config).await; } + + KanidmdOpt::DomainSettings { + commands: DomainSettingsCmds::Show { commonopts }, + } => { + info!("Running domain show ..."); + let output_mode: ConsoleOutputMode = commonopts.output_mode.to_owned().into(); + submit_admin_req( + config.adminbindpath.as_str(), + AdminTaskRequest::DomainShow, + output_mode, + ) + .await; + } + KanidmdOpt::DomainSettings { + commands: DomainSettingsCmds::Raise { commonopts }, + } => { + info!("Running domain raise ..."); + let output_mode: ConsoleOutputMode = commonopts.output_mode.to_owned().into(); + submit_admin_req( + config.adminbindpath.as_str(), + AdminTaskRequest::DomainRaise, + output_mode, + ) + .await; + } + KanidmdOpt::DomainSettings { + commands: DomainSettingsCmds::Remigrate { commonopts, level }, + } => { + info!("Running domain remigrate ..."); + let output_mode: ConsoleOutputMode = commonopts.output_mode.to_owned().into(); + submit_admin_req( + config.adminbindpath.as_str(), + AdminTaskRequest::DomainRemigrate { level: *level }, + output_mode, + ) + .await; + } + KanidmdOpt::Database { commands: DbCommands::Vacuum(_copt), } => { diff --git a/server/daemon/src/opt.rs b/server/daemon/src/opt.rs index 8d9e8b2e2..55088d9e6 100644 --- a/server/daemon/src/opt.rs +++ b/server/daemon/src/opt.rs @@ -28,9 +28,32 @@ struct RestoreOpt { #[derive(Debug, Subcommand)] enum DomainSettingsCmds { + #[clap(name = "show")] + /// Show the current domain + Show { + #[clap(flatten)] + commonopts: CommonOpt, + }, #[clap(name = "rename")] - /// Change the IDM domain name - DomainChange(CommonOpt), + /// Change the IDM domain name based on the values in the configuration + Change { + #[clap(flatten)] + commonopts: CommonOpt, + }, + #[clap(name = "raise")] + /// Raise the functional level of this domain to the maximum available. + Raise { + #[clap(flatten)] + commonopts: CommonOpt, + }, + #[clap(name = "remigrate")] + /// Rerun migrations of this domains database, optionally nominating the level + /// to start from. + Remigrate { + #[clap(flatten)] + commonopts: CommonOpt, + level: Option, + }, } #[derive(Debug, Subcommand)] diff --git a/server/lib/src/constants/mod.rs b/server/lib/src/constants/mod.rs index 7bac54793..85c3be200 100644 --- a/server/lib/src/constants/mod.rs +++ b/server/lib/src/constants/mod.rs @@ -49,6 +49,8 @@ pub const DOMAIN_LEVEL_2: DomainVersion = 2; pub const DOMAIN_LEVEL_3: DomainVersion = 3; pub const DOMAIN_LEVEL_4: DomainVersion = 4; pub const DOMAIN_LEVEL_5: DomainVersion = 5; +// The minimum level that we can re-migrate from +pub const DOMAIN_MIN_REMIGRATION_LEVEL: DomainVersion = DOMAIN_LEVEL_2; // The minimum supported domain functional level pub const DOMAIN_MIN_LEVEL: DomainVersion = DOMAIN_LEVEL_5; // The target supported domain functional level diff --git a/server/lib/src/server/migrations.rs b/server/lib/src/server/migrations.rs index 31597bc08..3978e050c 100644 --- a/server/lib/src/server/migrations.rs +++ b/server/lib/src/server/migrations.rs @@ -781,10 +781,10 @@ impl<'a> QueryServerWriteTransaction<'a> { self.internal_modify(&filter, &modlist)?; // Now move all oauth2 rs name. - let filter = filter!(f_eq( - Attribute::Class, - EntryClass::OAuth2ResourceServer.into() - )); + let filter = filter!(f_and!([ + f_eq(Attribute::Class, EntryClass::OAuth2ResourceServer.into()), + f_pres(Attribute::OAuth2RsName), + ])); let pre_candidates = self.internal_search(filter).map_err(|err| { admin_error!(?err, "migrate_domain_4_to_5 internal search failure"); diff --git a/server/lib/src/server/mod.rs b/server/lib/src/server/mod.rs index 6ca0d4c08..d515b3a50 100644 --- a/server/lib/src/server/mod.rs +++ b/server/lib/src/server/mod.rs @@ -11,6 +11,7 @@ use std::collections::BTreeSet; use tokio::sync::{Semaphore, SemaphorePermit}; use tracing::trace; +use kanidm_proto::internal::DomainInfo as ProtoDomainInfo; use kanidm_proto::v1::{ConsistencyError, UiHint}; use crate::be::{Backend, BackendReadTransaction, BackendTransaction, BackendWriteTransaction}; @@ -1385,6 +1386,48 @@ impl<'a> QueryServerWriteTransaction<'a> { &mut self.dyngroup_cache } + pub fn domain_info(&mut self) -> Result { + let d_info = &self.d_info; + + Ok(ProtoDomainInfo { + name: d_info.d_name.clone(), + displayname: d_info.d_display.clone(), + uuid: d_info.d_uuid, + level: d_info.d_vers, + }) + } + + pub fn domain_raise(&mut self, level: u32) -> Result<(), OperationError> { + if level > DOMAIN_MAX_LEVEL { + return Err(OperationError::MG0002RaiseDomainLevelExceedsMaximum); + } + + let modl = ModifyList::new_purge_and_set(Attribute::Version, Value::Uint32(level)); + let udi = PVUUID_DOMAIN_INFO.clone(); + let filt = filter_all!(f_eq(Attribute::Uuid, udi)); + self.internal_modify(&filt, &modl) + } + + pub fn domain_remigrate(&mut self, level: u32) -> Result<(), OperationError> { + let mut_d_info = self.d_info.get_mut(); + + if level > mut_d_info.d_vers { + // Nothing to do. + return Ok(()); + } else if level < DOMAIN_MIN_REMIGRATION_LEVEL { + return Err(OperationError::MG0001InvalidReMigrationLevel); + }; + + debug!( + "Prepare to re-migrate from {} -> {}", + level, mut_d_info.d_vers + ); + mut_d_info.d_vers = level; + self.changed_domain = true; + + Ok(()) + } + #[instrument(level = "debug", skip_all)] pub(crate) fn reload_schema(&mut self) -> Result<(), OperationError> { // supply entries to the writable schema to reload from. diff --git a/tools/Dockerfile b/tools/Dockerfile index b61cd3cea..c1a5c3f02 100644 --- a/tools/Dockerfile +++ b/tools/Dockerfile @@ -1,5 +1,7 @@ # This builds the kanidm CLI tools ARG BASE_IMAGE=opensuse/tumbleweed:latest +# ARG BASE_IMAGE=opensuse/leap:15.5 + FROM ${BASE_IMAGE} AS repos ADD ../scripts/zypper_fixing.sh /zypper_fixing.sh RUN --mount=type=cache,id=zypp,target=/var/cache/zypp /zypper_fixing.sh