mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 12:37:00 +01:00
Add a migration for future versions that will notify and warn about the removal of security keys. (#2885)
This commit is contained in:
parent
5af33ade0a
commit
a4a06c1172
|
@ -151,6 +151,7 @@ pub enum OperationError {
|
|||
MG0003ServerPhaseInvalidForMigration,
|
||||
MG0004DomainLevelInDevelopment,
|
||||
MG0005GidConstraintsNotMet,
|
||||
MG0006SKConstraintsNotMet,
|
||||
//
|
||||
KP0001KeyProviderNotLoaded,
|
||||
KP0002KeyProviderInvalidClass,
|
||||
|
@ -302,6 +303,7 @@ impl OperationError {
|
|||
Self::DB0002MismatchedRestoreVersion => None,
|
||||
Self::MG0004DomainLevelInDevelopment => None,
|
||||
Self::MG0005GidConstraintsNotMet => None,
|
||||
Self::MG0006SKConstraintsNotMet => Some("Migration Constraints Not Met - Security Keys should not be present."),
|
||||
Self::KP0001KeyProviderNotLoaded => None,
|
||||
Self::KP0002KeyProviderInvalidClass => None,
|
||||
Self::KP0003KeyProviderInvalidType => None,
|
||||
|
|
|
@ -242,6 +242,9 @@ pub struct DomainUpgradeCheckReport {
|
|||
pub enum DomainUpgradeCheckStatus {
|
||||
Pass6To7Gidnumber,
|
||||
Fail6To7Gidnumber,
|
||||
|
||||
Pass7To8SecurityKeys,
|
||||
Fail7To8SecurityKeys,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
|
|
|
@ -221,6 +221,23 @@ async fn submit_admin_req(path: &str, req: AdminTaskRequest, output_mode: Consol
|
|||
info!("affected_entry : {}", entry_id);
|
||||
}
|
||||
}
|
||||
ProtoDomainUpgradeCheckStatus::Pass7To8SecurityKeys => {
|
||||
info!("upgrade_item : security key usage");
|
||||
debug!("from_level : {}", item.from_level);
|
||||
debug!("to_level : {}", item.to_level);
|
||||
info!("status : PASS");
|
||||
}
|
||||
ProtoDomainUpgradeCheckStatus::Fail7To8SecurityKeys => {
|
||||
info!("upgrade_item : security key usage");
|
||||
debug!("from_level : {}", item.from_level);
|
||||
debug!("to_level : {}", item.to_level);
|
||||
info!("status : FAIL");
|
||||
info!("description : Security keys no longer function as a second factor due to the introduction of CTAP2 and greater forcing PIN interactions.");
|
||||
info!("action : Modify the accounts in question to remove their security key and add it as a passkey or enable TOTP");
|
||||
for entry_id in item.affected_entries {
|
||||
info!("affected_entry : {}", entry_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -645,6 +645,13 @@ impl Credential {
|
|||
}))
|
||||
}
|
||||
|
||||
pub(crate) fn has_securitykey(&self) -> bool {
|
||||
match &self.type_ {
|
||||
CredentialType::PasswordMfa(_, _, map, _) => !map.is_empty(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to the contained webuthn credentials, if any.
|
||||
pub fn securitykey_ref(&self) -> Result<&Map<String, SecurityKey>, OperationError> {
|
||||
match &self.type_ {
|
||||
|
|
|
@ -797,6 +797,39 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
|||
return Err(OperationError::MG0004DomainLevelInDevelopment);
|
||||
}
|
||||
|
||||
// ============== Apply constraints ===============
|
||||
let filter = filter!(f_and!([
|
||||
f_eq(Attribute::Class, EntryClass::Account.into()),
|
||||
f_pres(Attribute::PrimaryCredential),
|
||||
]));
|
||||
|
||||
let results = self.internal_search(filter)?;
|
||||
|
||||
let affected_entries = results
|
||||
.into_iter()
|
||||
.filter_map(|entry| {
|
||||
if entry
|
||||
.get_ava_single_credential(Attribute::PrimaryCredential)
|
||||
.map(|cred| cred.has_securitykey())
|
||||
.unwrap_or_default()
|
||||
{
|
||||
Some(entry.get_display_id())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !affected_entries.is_empty() {
|
||||
error!("Unable to proceed. Not all entries meet gid/uid constraints.");
|
||||
for sk_present in affected_entries {
|
||||
error!(%sk_present);
|
||||
}
|
||||
return Err(OperationError::MG0006SKConstraintsNotMet);
|
||||
}
|
||||
|
||||
// =========== Apply changes ==============
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -1095,6 +1128,19 @@ impl<'a> QueryServerReadTransaction<'a> {
|
|||
report_items.push(item);
|
||||
}
|
||||
|
||||
if current_level <= DOMAIN_LEVEL_7 && upgrade_level >= DOMAIN_LEVEL_8 {
|
||||
let item = self
|
||||
.domain_upgrade_check_7_to_8_security_keys()
|
||||
.map_err(|err| {
|
||||
error!(
|
||||
?err,
|
||||
"Failed to perform domain upgrade check 7 to 8 - security-keys"
|
||||
);
|
||||
err
|
||||
})?;
|
||||
report_items.push(item);
|
||||
}
|
||||
|
||||
Ok(ProtoDomainUpgradeCheckReport {
|
||||
name,
|
||||
uuid,
|
||||
|
@ -1191,6 +1237,45 @@ impl<'a> QueryServerReadTransaction<'a> {
|
|||
affected_entries,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn domain_upgrade_check_7_to_8_security_keys(
|
||||
&mut self,
|
||||
) -> Result<ProtoDomainUpgradeCheckItem, OperationError> {
|
||||
let filter = filter!(f_and!([
|
||||
f_eq(Attribute::Class, EntryClass::Account.into()),
|
||||
f_pres(Attribute::PrimaryCredential),
|
||||
]));
|
||||
|
||||
let results = self.internal_search(filter)?;
|
||||
|
||||
let affected_entries = results
|
||||
.into_iter()
|
||||
.filter_map(|entry| {
|
||||
if entry
|
||||
.get_ava_single_credential(Attribute::PrimaryCredential)
|
||||
.map(|cred| cred.has_securitykey())
|
||||
.unwrap_or_default()
|
||||
{
|
||||
Some(entry.get_display_id())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let status = if affected_entries.is_empty() {
|
||||
ProtoDomainUpgradeCheckStatus::Pass7To8SecurityKeys
|
||||
} else {
|
||||
ProtoDomainUpgradeCheckStatus::Fail7To8SecurityKeys
|
||||
};
|
||||
|
||||
Ok(ProtoDomainUpgradeCheckItem {
|
||||
status,
|
||||
from_level: DOMAIN_LEVEL_7,
|
||||
to_level: DOMAIN_LEVEL_8,
|
||||
affected_entries,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
Loading…
Reference in a new issue