mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-24 04:57: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,
|
MG0003ServerPhaseInvalidForMigration,
|
||||||
MG0004DomainLevelInDevelopment,
|
MG0004DomainLevelInDevelopment,
|
||||||
MG0005GidConstraintsNotMet,
|
MG0005GidConstraintsNotMet,
|
||||||
|
MG0006SKConstraintsNotMet,
|
||||||
//
|
//
|
||||||
KP0001KeyProviderNotLoaded,
|
KP0001KeyProviderNotLoaded,
|
||||||
KP0002KeyProviderInvalidClass,
|
KP0002KeyProviderInvalidClass,
|
||||||
|
@ -302,6 +303,7 @@ impl OperationError {
|
||||||
Self::DB0002MismatchedRestoreVersion => None,
|
Self::DB0002MismatchedRestoreVersion => None,
|
||||||
Self::MG0004DomainLevelInDevelopment => None,
|
Self::MG0004DomainLevelInDevelopment => None,
|
||||||
Self::MG0005GidConstraintsNotMet => None,
|
Self::MG0005GidConstraintsNotMet => None,
|
||||||
|
Self::MG0006SKConstraintsNotMet => Some("Migration Constraints Not Met - Security Keys should not be present."),
|
||||||
Self::KP0001KeyProviderNotLoaded => None,
|
Self::KP0001KeyProviderNotLoaded => None,
|
||||||
Self::KP0002KeyProviderInvalidClass => None,
|
Self::KP0002KeyProviderInvalidClass => None,
|
||||||
Self::KP0003KeyProviderInvalidType => None,
|
Self::KP0003KeyProviderInvalidType => None,
|
||||||
|
|
|
@ -242,6 +242,9 @@ pub struct DomainUpgradeCheckReport {
|
||||||
pub enum DomainUpgradeCheckStatus {
|
pub enum DomainUpgradeCheckStatus {
|
||||||
Pass6To7Gidnumber,
|
Pass6To7Gidnumber,
|
||||||
Fail6To7Gidnumber,
|
Fail6To7Gidnumber,
|
||||||
|
|
||||||
|
Pass7To8SecurityKeys,
|
||||||
|
Fail7To8SecurityKeys,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[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);
|
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.
|
/// Get a reference to the contained webuthn credentials, if any.
|
||||||
pub fn securitykey_ref(&self) -> Result<&Map<String, SecurityKey>, OperationError> {
|
pub fn securitykey_ref(&self) -> Result<&Map<String, SecurityKey>, OperationError> {
|
||||||
match &self.type_ {
|
match &self.type_ {
|
||||||
|
|
|
@ -797,6 +797,39 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
||||||
return Err(OperationError::MG0004DomainLevelInDevelopment);
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1095,6 +1128,19 @@ impl<'a> QueryServerReadTransaction<'a> {
|
||||||
report_items.push(item);
|
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 {
|
Ok(ProtoDomainUpgradeCheckReport {
|
||||||
name,
|
name,
|
||||||
uuid,
|
uuid,
|
||||||
|
@ -1191,6 +1237,45 @@ impl<'a> QueryServerReadTransaction<'a> {
|
||||||
affected_entries,
|
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)]
|
#[cfg(test)]
|
||||||
|
|
Loading…
Reference in a new issue