@@ -21,6 +21,7 @@ use crate::{
2121 algorithm_policy:: enforce_kmip_algorithm_policy_for_operation,
2222 attributes:: get_attribute_list, check, mac:: mac_verify, query:: query as query_op,
2323 } ,
24+ retrieve_object_utils:: user_has_permission,
2425 } ,
2526 error:: KmsError ,
2627 kms_bail,
@@ -83,7 +84,8 @@ macro_rules! op {
8384/// Map a TTLV operation tag string to a [`KmipOperation`] variant for role-based access control.
8485///
8586/// Operations not present in the [`KmipOperation`] enum (e.g. `CreateKeyPair`, `CreateSplitKey`,
86- /// `JoinSplitKey`, `Register`, `ReKeyKeyPair`) return `None` and are **not** role-gated.
87+ /// `JoinSplitKey`, `Register`, `ReKeyKeyPair`) return `None` here but may still be
88+ /// gated via [`LIFECYCLE_OPERATION_TAGS`].
8789fn operation_tag_to_kmip_operation ( tag : & str ) -> Option < KmipOperation > {
8890 match tag {
8991 "Activate" => Some ( KmipOperation :: Activate ) ,
@@ -113,17 +115,49 @@ fn operation_tag_to_kmip_operation(tag: &str) -> Option<KmipOperation> {
113115 }
114116}
115117
118+ /// Lifecycle operation tags that have no [`KmipOperation`] variant but must be restricted
119+ /// to `CryptoOfficer` (or Administrator) when role enforcement is active.
120+ ///
121+ /// `CreateKeyPair`, `Register`, and `ReKeyKeyPair` create or replace Managed Objects and
122+ /// are therefore lifecycle operations equivalent to `Create`/`Import`/`Rekey`.
123+ /// `CreateSplitKey` produces new `SplitKey` share objects and is likewise lifecycle-scoped.
124+ ///
125+ /// `JoinSplitKey` is intentionally omitted: it is needed by Administrator candidates (users
126+ /// in `administrator.users` with `require_ceremony = true`) to complete the split-key
127+ /// ceremony before they hold an active Administrator role.
128+ const LIFECYCLE_OPERATION_TAGS : & [ & str ] = & [
129+ "CreateKeyPair" ,
130+ "Register" ,
131+ "ReKeyKeyPair" ,
132+ "CreateSplitKey" ,
133+ ] ;
134+
116135/// Enforce role-based access control before dispatching a KMIP operation.
117136///
118- /// When role lists are configured on the server, a user assigned to `Operator` or `CryptoOfficer`
119- /// can only call the operations permitted by their role's [`Role::allowed_operations`] set.
120- /// `Administrator` users are always allowed through. Users not listed in any role default to
121- /// `Operator` when role enforcement is active (fail-secure per NIST SP 800-57 Part 2 §4.4.2).
137+ /// ## Design
138+ ///
139+ /// Operations with a [`KmipOperation`] variant (Create, Import, Certify, Rekey, Destroy, etc.)
140+ /// are **not** blocked at dispatch level — their individual handlers already enforce
141+ /// `crypto_officer_users` restrictions while respecting explicit per-user grants. Blocking them
142+ /// here would prevent a `CryptoOfficer` from delegating `Create` permission to an `Operator`.
143+ ///
144+ /// The dispatch check only gates lifecycle operations that have **no** [`KmipOperation`] mapping
145+ /// (i.e. entries in [`LIFECYCLE_OPERATION_TAGS`]). For those, if the user is `Operator` and has
146+ /// no explicit `Create` permission in the database, the request is denied.
147+ ///
148+ /// `Administrator` users are always allowed through.
149+ /// Users not listed in any role default to `Operator` when role enforcement is active
150+ /// (fail-secure per NIST SP 800-57 Part 2 §4.4.2).
122151///
123152/// # Errors
124153/// Returns [`KmsError::Unauthorized`] when the user's role does not permit the requested
125154/// operation.
126- fn check_role_permission ( user : & str , operation_tag : & str , role_config : & RoleConfig ) -> KResult < ( ) > {
155+ async fn check_role_permission (
156+ kms : & KMS ,
157+ user : & str ,
158+ operation_tag : & str ,
159+ role_config : & RoleConfig ,
160+ ) -> KResult < ( ) > {
127161 // If no role lists are configured, skip role enforcement entirely.
128162 if !role_config. is_configured ( ) {
129163 return Ok ( ( ) ) ;
@@ -137,12 +171,24 @@ fn check_role_permission(user: &str, operation_tag: &str, role_config: &RoleConf
137171 match effective_role {
138172 Role :: Administrator => Ok ( ( ) ) ,
139173 role => {
140- // Only gate operations that map to a KmipOperation enum variant.
141- if let Some ( op) = operation_tag_to_kmip_operation ( operation_tag) {
142- if !role. allowed_operations ( ) . contains ( & op) {
174+ // For operations that have a KmipOperation mapping, defer to the individual handler.
175+ // Each handler already checks crypto_officer_users + explicit grants, which correctly
176+ // allows delegated privileges (e.g. an Operator who was explicitly granted Create).
177+ if operation_tag_to_kmip_operation ( operation_tag) . is_some ( ) {
178+ return Ok ( ( ) ) ;
179+ }
180+
181+ // For lifecycle operations without a KmipOperation mapping (CreateKeyPair, Register,
182+ // ReKeyKeyPair, CreateSplitKey), block Operators unless they have an explicit Create
183+ // permission grant in the database.
184+ if LIFECYCLE_OPERATION_TAGS . contains ( & operation_tag) && matches ! ( role, Role :: Operator ) {
185+ let has_create =
186+ user_has_permission ( user, None , & KmipOperation :: Create , kms) . await ?;
187+ if !has_create {
143188 kms_bail ! ( KmsError :: Unauthorized ( format!(
144- "User `{user}` (role: {role:?}) is not authorized to perform \
145- operation `{operation_tag}`"
189+ "User `{user}` (role: Operator) is not authorized to perform \
190+ operation `{operation_tag}` (lifecycle operation requires CryptoOfficer \
191+ role or explicit Create grant)"
146192 ) ) )
147193 }
148194 }
@@ -180,7 +226,7 @@ async fn dispatch_inner(
180226 operation_tag : & str ,
181227) -> KResult < Operation > {
182228 // Enforce role-based access control before any other check.
183- check_role_permission ( user, operation_tag, & kms. params . role_config ) ?;
229+ check_role_permission ( kms , user, operation_tag, & kms. params . role_config ) . await ?;
184230
185231 // For operations where the request carries algorithm choices, validate them
186232 // before executing any cryptographic action.
0 commit comments