Add tools for remigration and domain level raising (#2481)

This commit is contained in:
Firstyear 2024-02-06 20:01:06 +10:00 committed by GitHub
parent a1fbde9f2f
commit 9050188b29
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 257 additions and 10 deletions

View file

@ -173,6 +173,14 @@ pub enum Oauth2ClaimMapJoin {
Array, Array,
} }
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct DomainInfo {
pub name: String,
pub displayname: String,
pub uuid: Uuid,
pub level: u32,
}
#[test] #[test]
fn test_fstype_deser() { fn test_fstype_deser() {
assert_eq!(FsType::try_from("zfs"), Ok(FsType::Zfs)); assert_eq!(FsType::try_from("zfs"), Ok(FsType::Zfs));

View file

@ -296,7 +296,11 @@ pub enum OperationError {
VS0001IncomingReplSshPublicKey, VS0001IncomingReplSshPublicKey,
// Value Errors // Value Errors
VL0001ValueSshPublicKeyString, VL0001ValueSshPublicKeyString,
// SCIM
SC0001IncomingSshPublicKey, SC0001IncomingSshPublicKey,
// Migration
MG0001InvalidReMigrationLevel,
MG0002RaiseDomainLevelExceedsMaximum,
} }
impl PartialEq for OperationError { impl PartialEq for OperationError {

View file

@ -1,4 +1,6 @@
ARG BASE_IMAGE=opensuse/tumbleweed:latest ARG BASE_IMAGE=opensuse/tumbleweed:latest
# ARG BASE_IMAGE=opensuse/leap:15.5
# FROM ${BASE_IMAGE} as repos # FROM ${BASE_IMAGE} as repos
FROM ${BASE_IMAGE} FROM ${BASE_IMAGE}

View file

@ -1,5 +1,6 @@
# Build the main Kanidmd server # Build the main Kanidmd server
ARG BASE_IMAGE=opensuse/tumbleweed:latest ARG BASE_IMAGE=opensuse/tumbleweed:latest
# ARG BASE_IMAGE=opensuse/leap:15.5
FROM ${BASE_IMAGE} AS repos FROM ${BASE_IMAGE} AS repos
ADD scripts/zypper_fixing.sh /zypper_fixing.sh ADD scripts/zypper_fixing.sh /zypper_fixing.sh

View file

@ -1,5 +1,6 @@
use std::{iter, sync::Arc}; use std::{iter, sync::Arc};
use kanidm_proto::internal::DomainInfo as ProtoDomainInfo;
use kanidm_proto::internal::ImageValue; use kanidm_proto::internal::ImageValue;
use kanidm_proto::internal::Oauth2ClaimMapJoin as ProtoOauth2ClaimMapJoin; use kanidm_proto::internal::Oauth2ClaimMapJoin as ProtoOauth2ClaimMapJoin;
use kanidm_proto::v1::{ use kanidm_proto::v1::{
@ -1820,4 +1821,57 @@ impl QueryServerWriteV1 {
idms_prox_write.commit().map(|()| pw) 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<ProtoDomainInfo, OperationError> {
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<u32, OperationError> {
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<u32>,
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()
}
} }

View file

@ -17,18 +17,25 @@ use tokio_util::codec::{Decoder, Encoder, Framed};
use tracing::{span, Instrument, Level}; use tracing::{span, Instrument, Level};
use uuid::Uuid; use uuid::Uuid;
pub use kanidm_proto::internal::DomainInfo as ProtoDomainInfo;
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub enum AdminTaskRequest { pub enum AdminTaskRequest {
RecoverAccount { name: String }, RecoverAccount { name: String },
ShowReplicationCertificate, ShowReplicationCertificate,
RenewReplicationCertificate, RenewReplicationCertificate,
RefreshReplicationConsumer, RefreshReplicationConsumer,
DomainShow,
DomainRaise,
DomainRemigrate { level: Option<u32> },
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub enum AdminTaskResponse { pub enum AdminTaskResponse {
RecoverAccount { password: String }, RecoverAccount { password: String },
ShowReplicationCertificate { cert: String }, ShowReplicationCertificate { cert: String },
DomainRaise { level: u32 },
DomainShow { domain_info: ProtoDomainInfo },
Success, Success,
Error, Error,
} }
@ -315,6 +322,30 @@ async fn handle_client(
AdminTaskResponse::Error 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) .instrument(nspan)

View file

@ -30,7 +30,7 @@ use clap::{Args, Parser, Subcommand};
use futures::{SinkExt, StreamExt}; use futures::{SinkExt, StreamExt};
#[cfg(not(target_family = "windows"))] // not needed for windows builds #[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 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::config::{Configuration, ServerConfig};
use kanidmd_core::{ use kanidmd_core::{
backup_server_core, cert_generate_core, create_server_core, dbscan_get_id2entry_core, backup_server_core, cert_generate_core, create_server_core, dbscan_get_id2entry_core,
@ -89,8 +89,17 @@ impl KanidmdOpt {
commands: DbScanOpt::GetId2Entry(dopt), commands: DbScanOpt::GetId2Entry(dopt),
} => &dopt.commonopts, } => &dopt.commonopts,
KanidmdOpt::DomainSettings { KanidmdOpt::DomainSettings {
commands: DomainSettingsCmds::DomainChange(sopt), commands: DomainSettingsCmds::Show { commonopts },
} => sopt, }
| KanidmdOpt::DomainSettings {
commands: DomainSettingsCmds::Change { commonopts },
}
| KanidmdOpt::DomainSettings {
commands: DomainSettingsCmds::Raise { commonopts },
}
| KanidmdOpt::DomainSettings {
commands: DomainSettingsCmds::Remigrate { commonopts, .. },
} => commonopts,
KanidmdOpt::Database { KanidmdOpt::Database {
commands: DbCommands::Verify(sopt), commands: DbCommands::Verify(sopt),
} }
@ -161,6 +170,36 @@ async fn submit_admin_req(path: &str, req: AdminTaskRequest, output_mode: Consol
info!(certificate = ?cert) 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 { Some(Ok(AdminTaskResponse::Success)) => match output_mode {
ConsoleOutputMode::JSON => { ConsoleOutputMode::JSON => {
eprintln!("\"success\"") eprintln!("\"success\"")
@ -717,11 +756,49 @@ async fn kanidm_main() -> ExitCode {
} }
KanidmdOpt::DomainSettings { KanidmdOpt::DomainSettings {
commands: DomainSettingsCmds::DomainChange(_dopt), commands: DomainSettingsCmds::Change { .. },
} => { } => {
info!("Running in domain name change mode ... this may take a long time ..."); info!("Running in domain name change mode ... this may take a long time ...");
domain_rename_core(&config).await; 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 { KanidmdOpt::Database {
commands: DbCommands::Vacuum(_copt), commands: DbCommands::Vacuum(_copt),
} => { } => {

View file

@ -28,9 +28,32 @@ struct RestoreOpt {
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
enum DomainSettingsCmds { enum DomainSettingsCmds {
#[clap(name = "show")]
/// Show the current domain
Show {
#[clap(flatten)]
commonopts: CommonOpt,
},
#[clap(name = "rename")] #[clap(name = "rename")]
/// Change the IDM domain name /// Change the IDM domain name based on the values in the configuration
DomainChange(CommonOpt), 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<u32>,
},
} }
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]

View file

@ -49,6 +49,8 @@ pub const DOMAIN_LEVEL_2: DomainVersion = 2;
pub const DOMAIN_LEVEL_3: DomainVersion = 3; pub const DOMAIN_LEVEL_3: DomainVersion = 3;
pub const DOMAIN_LEVEL_4: DomainVersion = 4; pub const DOMAIN_LEVEL_4: DomainVersion = 4;
pub const DOMAIN_LEVEL_5: DomainVersion = 5; 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 // The minimum supported domain functional level
pub const DOMAIN_MIN_LEVEL: DomainVersion = DOMAIN_LEVEL_5; pub const DOMAIN_MIN_LEVEL: DomainVersion = DOMAIN_LEVEL_5;
// The target supported domain functional level // The target supported domain functional level

View file

@ -781,10 +781,10 @@ impl<'a> QueryServerWriteTransaction<'a> {
self.internal_modify(&filter, &modlist)?; self.internal_modify(&filter, &modlist)?;
// Now move all oauth2 rs name. // Now move all oauth2 rs name.
let filter = filter!(f_eq( let filter = filter!(f_and!([
Attribute::Class, f_eq(Attribute::Class, EntryClass::OAuth2ResourceServer.into()),
EntryClass::OAuth2ResourceServer.into() f_pres(Attribute::OAuth2RsName),
)); ]));
let pre_candidates = self.internal_search(filter).map_err(|err| { let pre_candidates = self.internal_search(filter).map_err(|err| {
admin_error!(?err, "migrate_domain_4_to_5 internal search failure"); admin_error!(?err, "migrate_domain_4_to_5 internal search failure");

View file

@ -11,6 +11,7 @@ use std::collections::BTreeSet;
use tokio::sync::{Semaphore, SemaphorePermit}; use tokio::sync::{Semaphore, SemaphorePermit};
use tracing::trace; use tracing::trace;
use kanidm_proto::internal::DomainInfo as ProtoDomainInfo;
use kanidm_proto::v1::{ConsistencyError, UiHint}; use kanidm_proto::v1::{ConsistencyError, UiHint};
use crate::be::{Backend, BackendReadTransaction, BackendTransaction, BackendWriteTransaction}; use crate::be::{Backend, BackendReadTransaction, BackendTransaction, BackendWriteTransaction};
@ -1385,6 +1386,48 @@ impl<'a> QueryServerWriteTransaction<'a> {
&mut self.dyngroup_cache &mut self.dyngroup_cache
} }
pub fn domain_info(&mut self) -> Result<ProtoDomainInfo, OperationError> {
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)] #[instrument(level = "debug", skip_all)]
pub(crate) fn reload_schema(&mut self) -> Result<(), OperationError> { pub(crate) fn reload_schema(&mut self) -> Result<(), OperationError> {
// supply entries to the writable schema to reload from. // supply entries to the writable schema to reload from.

View file

@ -1,5 +1,7 @@
# This builds the kanidm CLI tools # This builds the kanidm CLI tools
ARG BASE_IMAGE=opensuse/tumbleweed:latest ARG BASE_IMAGE=opensuse/tumbleweed:latest
# ARG BASE_IMAGE=opensuse/leap:15.5
FROM ${BASE_IMAGE} AS repos FROM ${BASE_IMAGE} AS repos
ADD ../scripts/zypper_fixing.sh /zypper_fixing.sh ADD ../scripts/zypper_fixing.sh /zypper_fixing.sh
RUN --mount=type=cache,id=zypp,target=/var/cache/zypp /zypper_fixing.sh RUN --mount=type=cache,id=zypp,target=/var/cache/zypp /zypper_fixing.sh