diff --git a/designs/access_profiles_and_security.rst b/designs/access_profiles_and_security.rst index 0ceab23d2..33f6a1cb5 100644 --- a/designs/access_profiles_and_security.rst +++ b/designs/access_profiles_and_security.rst @@ -13,19 +13,18 @@ will override even if applicable. They should only be created by system access p because we have certain requirements to deny certain changes. Access profiles are stored as entries and are dynamically loaded into a structure that is -more efficent for use at runtime. Schema and it's transactions are a similar implementation. +more efficent for use at runtime. Schema and its transactions are a similar implementation. Search Requirements ------------------- -A search access profile, must be able to limit the content of a search request and it's -scoping. +A search access profile must be able to limit: -A search access profile, must be able to limit the returned set of data from the objects -visible. +1. the content of a search request and its scoping. +2. the returned set of data from the objects visible. An example is that user Alice should only be able to search for objects where the class -is person, and where they are a memberOf "visible" group. Alice should only be able to +is person and the object is a memberOf "visible" group. Alice should only be able to see those users displayNames (not their legalName for example), and their public email. Worded a bit differently. You need permission over the scope of entries, you need to be able @@ -142,7 +141,7 @@ An example is that user Alice should only be able to delete objects where the me Create Requirements ------------------- -A create profile defines a filtering limit on what content can be created and it's requirements. +A create profile defines a filtering limit on what content can be created and its requirements. A create profile defines a limit on what attributes can be created in addition to the filtering requirements. @@ -152,7 +151,7 @@ only name the group - they can not add members to the group. A content requirement could be something such as the value an attribute can contain must conform to a regex, IE, you can create a group of any name, except where the name contains "admin" somewhere -in it's name. Arguable, this is partially possible with filtering. +in its name. Arguable, this is partially possible with filtering. For example, we want to be able to limit the classes that someone *could* create on something because classes often are used as a security type. @@ -329,7 +328,7 @@ user is collected. These then are added to the users requested search as: In this manner, the search security is easily applied, as if the targets to conform to one of the required search profile filters, the outer And condition is nullified and no results returned. -Once complete, in the translation of the entry -> proto_entry, each access control and it's allowed +Once complete, in the translation of the entry -> proto_entry, each access control and its allowed set of attrs has to be checked to determine what of that entry can be displayed. Consider there are three entries, A, B, C. An ACI that allows read of "name" on A, B exists, and a read of "mail" on B, C. The correct behaviour is then: @@ -395,7 +394,7 @@ filter_no_index to the entry to entry. If all of this passes, the create is allo A key point, is that there is no union of create aci's - the WHOLE aci must pass, not parts of multiple. This means if a control say "allows creating group with member" and "allows creating user with name", creating a gorup with name is not allowed - despite your ability to create -an entry with "name" it's classes don't match. This way, the admin of the service can define +an entry with "name", its classes don't match. This way, the admin of the service can define create controls with really specific intent to how they'll be used, without risk of two controls causing un-intended effects (users that are also groups, or allowing values that were not intended). diff --git a/designs/account_policy.rst b/designs/account_policy.rst index 8b014428f..d86746c80 100644 --- a/designs/account_policy.rst +++ b/designs/account_policy.rst @@ -46,14 +46,15 @@ Rate Limiting is the process of delaying authentication responses to slow the nu against an account to deter attackers. This is often used to prevent attackers from bruteforcing passwords at a high rate. -The best defence again these attacks is MFA. Due to the design of Kanidm, the second factor -(ie the webauthn token or the otp) is always checked *before* the password, meaning that the -attacker is unable to attack the password *unless* they also have the corresponding MFA token. +The best defence again these attacks is Multi-factor authentication (MFA). Due to the design of Kanidm, +the second factor (ie the webauthn token or the OTP) is always checked *before* the password, +meaning that the attacker is unable to attack the password *unless* they also have +the corresponding MFA token. However, not all accounts will have MFA enabled, which means that defences are still required to prevent these attacks for password-only accounts. Accounts protected with TOTP must also be rate -limited according to NIST sp800 63b. Webauthn does *not* require ratelimiting as a single factor -or multi factor device. +limited according to [NIST SP800-63B](https://pages.nist.gov/800-63-3/sp800-63b.html). +Webauthn does *not* require rate-limiting as a single factor or multi factor device. As an account can only have a single proceeding authentication session at a time, this provides serialisation and rate limiting per account of the service. However, as Kanidm will in the future @@ -78,7 +79,7 @@ For accounts with password-only: * If the attempts continue, the account is hard locked and signalled to an external system that this has occured. The value of X should be less than 100, so that the NIST guidelines can be met. This is beacuse when there are -many replicas, each replica maintains it's own locking state, so "eventually" as each replica is attempted to be +many replicas, each replica maintains its own locking state, so "eventually" as each replica is attempted to be bruteforced, then they will all eventually soft lock the account. In larger environments, we require external signalling to coordinate the locking of the account. @@ -101,7 +102,7 @@ It must be possible to expire an account so it no longer operates (IE temporary accounts that can only operate after a known point in time (Student enrollments and their course commencment date). -This expiry must exist at the account level, but also on issued token/api password levels. This allows revocation of +This expiry must exist at the account level, but also on issued token/API password levels. This allows revocation of individual tokens, but also the expiry of the account and all tokens as a whole. This expiry may be undone, allowing the credentials to become valid once again. @@ -111,7 +112,7 @@ are stored on the server in unix epoch to account for timezones and geographic d * Interaction with already issued tokens. * it prevents them from working? -Must prevent creation of radius auth tokens +Must prevent creation of RADIUS auth tokens Must prevent login via unix. diff --git a/kanidmd/src/lib/actors/v1_write.rs b/kanidmd/src/lib/actors/v1_write.rs index dcc905333..fd8f5326f 100644 --- a/kanidmd/src/lib/actors/v1_write.rs +++ b/kanidmd/src/lib/actors/v1_write.rs @@ -886,7 +886,7 @@ impl QueryServerWriteV1 { "class".into(), Value::new_class("person"), ))) - .filter_map(|v| v) + .flatten() .collect(); let ml = ModifyList::new_list(mods); @@ -939,7 +939,7 @@ impl QueryServerWriteV1 { .chain(iter::once(shell.map(|s| { Modify::Present("loginshell".into(), Value::new_iutf8(s.as_str())) }))) - .filter_map(|v| v) + .flatten() .collect(); let ml = ModifyList::new_list(mods); @@ -980,7 +980,7 @@ impl QueryServerWriteV1 { .chain(iter::once(gx.gidnumber.map(|n| { Modify::Present("gidnumber".into(), Value::new_uint32(n)) }))) - .filter_map(|v| v) + .flatten() .collect(); let ml = ModifyList::new_list(mods); diff --git a/kanidmd/src/lib/core/https.rs b/kanidmd/src/lib/core/https.rs index 73427771b..84cab9b52 100644 --- a/kanidmd/src/lib/core/https.rs +++ b/kanidmd/src/lib/core/https.rs @@ -1005,7 +1005,7 @@ pub async fn auth(mut req: tide::Request) -> tide::Result { Ok(ProtoAuthState::Denied(reason)) } } - .map(|state| AuthResponse { state, sessionid }) + .map(|state| AuthResponse { sessionid, state }) } Err(e) => Err(e), }; diff --git a/kanidmd/src/lib/schema.rs b/kanidmd/src/lib/schema.rs index bea2e5c58..1d3440f47 100644 --- a/kanidmd/src/lib/schema.rs +++ b/kanidmd/src/lib/schema.rs @@ -194,7 +194,7 @@ impl SchemaAttribute { SyntaxType::Credential => v.is_credential(), SyntaxType::RadiusUtf8String => v.is_radius_string(), SyntaxType::SshKey => v.is_sshkey(), - SyntaxType::ServicePrincipalName => v.is_spn(), + SyntaxType::SecurityPrincipalName => v.is_spn(), SyntaxType::UINT32 => v.is_uint32(), SyntaxType::Cid => v.is_cid(), SyntaxType::NsUniqueId => v.is_nsuniqueid(), @@ -338,7 +338,7 @@ impl SchemaAttribute { } }) }), - SyntaxType::ServicePrincipalName => ava.iter().fold(Ok(()), |acc, v| { + SyntaxType::SecurityPrincipalName => ava.iter().fold(Ok(()), |acc, v| { acc.and_then(|_| { if v.is_spn() { Ok(()) @@ -708,13 +708,13 @@ impl<'a> SchemaWriteTransaction<'a> { name: AttrString::from("spn"), uuid: *UUID_SCHEMA_ATTR_SPN, description: String::from( - "The service principle name of an object, unique across all domain trusts", + "The Security Principal Name of an object, unique across all domain trusts", ), multivalue: false, unique: true, phantom: false, index: vec![IndexType::Equality], - syntax: SyntaxType::ServicePrincipalName, + syntax: SyntaxType::SecurityPrincipalName, }, ); self.attributes.insert( @@ -1085,7 +1085,7 @@ impl<'a> SchemaWriteTransaction<'a> { unique: false, phantom: true, index: vec![], - syntax: SyntaxType::ServicePrincipalName, + syntax: SyntaxType::SecurityPrincipalName, }, ); self.attributes.insert( diff --git a/kanidmd/src/lib/server.rs b/kanidmd/src/lib/server.rs index 7df112e4b..4b99839d2 100644 --- a/kanidmd/src/lib/server.rs +++ b/kanidmd/src/lib/server.rs @@ -530,7 +530,7 @@ pub trait QueryServerTransaction<'a> { SyntaxType::Credential => Err(OperationError::InvalidAttribute("Credentials can not be supplied through modification - please use the IDM api".to_string())), SyntaxType::RadiusUtf8String => Err(OperationError::InvalidAttribute("Radius secrets can not be supplied through modification - please use the IDM api".to_string())), SyntaxType::SshKey => Err(OperationError::InvalidAttribute("SSH public keys can not be supplied through modification - please use the IDM api".to_string())), - SyntaxType::ServicePrincipalName => Err(OperationError::InvalidAttribute("SPNs are generated and not able to be set.".to_string())), + SyntaxType::SecurityPrincipalName => Err(OperationError::InvalidAttribute("SPNs are generated and not able to be set.".to_string())), SyntaxType::UINT32 => Value::new_uint32_str(value) .ok_or_else(|| OperationError::InvalidAttribute("Invalid uint32 syntax".to_string())), SyntaxType::Cid => Err(OperationError::InvalidAttribute("CIDs are generated and not able to be set.".to_string())), @@ -612,7 +612,7 @@ pub trait QueryServerTransaction<'a> { SyntaxType::Credential => Ok(PartialValue::new_credential_tag(value)), SyntaxType::RadiusUtf8String => Ok(PartialValue::new_radius_string()), SyntaxType::SshKey => Ok(PartialValue::new_sshkey_tag_s(value)), - SyntaxType::ServicePrincipalName => { + SyntaxType::SecurityPrincipalName => { PartialValue::new_spn_s(value).ok_or_else(|| { OperationError::InvalidAttribute("Invalid spn syntax".to_string()) }) diff --git a/kanidmd/src/lib/value.rs b/kanidmd/src/lib/value.rs index 35bb333ce..40cd1d09b 100644 --- a/kanidmd/src/lib/value.rs +++ b/kanidmd/src/lib/value.rs @@ -123,7 +123,7 @@ pub enum SyntaxType { Credential, RadiusUtf8String, SshKey, - ServicePrincipalName, + SecurityPrincipalName, UINT32, Cid, NsUniqueId, @@ -148,7 +148,7 @@ impl TryFrom<&str> for SyntaxType { "CREDENTIAL" => Ok(SyntaxType::Credential), "RADIUS_UTF8STRING" => Ok(SyntaxType::RadiusUtf8String), "SSHKEY" => Ok(SyntaxType::SshKey), - "SERVICE_PRINCIPLE_NAME" => Ok(SyntaxType::ServicePrincipalName), + "SECURITY_PRINCIPAL_NAME" => Ok(SyntaxType::SecurityPrincipalName), "UINT32" => Ok(SyntaxType::UINT32), "CID" => Ok(SyntaxType::Cid), "NSUNIQUEID" => Ok(SyntaxType::NsUniqueId), @@ -174,7 +174,7 @@ impl TryFrom for SyntaxType { 8 => Ok(SyntaxType::Credential), 9 => Ok(SyntaxType::RadiusUtf8String), 10 => Ok(SyntaxType::SshKey), - 11 => Ok(SyntaxType::ServicePrincipalName), + 11 => Ok(SyntaxType::SecurityPrincipalName), 12 => Ok(SyntaxType::UINT32), 13 => Ok(SyntaxType::Cid), 14 => Ok(SyntaxType::Utf8StringIname), @@ -199,7 +199,7 @@ impl SyntaxType { SyntaxType::Credential => 8, SyntaxType::RadiusUtf8String => 9, SyntaxType::SshKey => 10, - SyntaxType::ServicePrincipalName => 11, + SyntaxType::SecurityPrincipalName => 11, SyntaxType::UINT32 => 12, SyntaxType::Cid => 13, SyntaxType::Utf8StringIname => 14, @@ -227,7 +227,7 @@ impl fmt::Display for SyntaxType { SyntaxType::Credential => "CREDENTIAL", SyntaxType::RadiusUtf8String => "RADIUS_UTF8STRING", SyntaxType::SshKey => "SSHKEY", - SyntaxType::ServicePrincipalName => "SERVICE_PRINCIPLE_NAME", + SyntaxType::SecurityPrincipalName => "SECURITY_PRINCIPAL_NAME", SyntaxType::UINT32 => "UINT32", SyntaxType::Cid => "CID", SyntaxType::NsUniqueId => "NSUNIQUEID",