Add a migration for future versions that will notify and warn about the removal of security keys. (#2885)

This commit is contained in:
Firstyear 2024-07-12 12:19:43 +10:00 committed by GitHub
parent 5af33ade0a
commit a4a06c1172
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 114 additions and 0 deletions

View file

@ -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,

View file

@ -242,6 +242,9 @@ pub struct DomainUpgradeCheckReport {
pub enum DomainUpgradeCheckStatus {
Pass6To7Gidnumber,
Fail6To7Gidnumber,
Pass7To8SecurityKeys,
Fail7To8SecurityKeys,
}
#[derive(Debug, Serialize, Deserialize, Clone)]

View file

@ -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);
}
}
}
}
}

View file

@ -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_ {

View file

@ -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)]