Skip to content

Commit 943f19a

Browse files
committed
fixup
1 parent d629f87 commit 943f19a

File tree

9 files changed

+97
-46
lines changed

9 files changed

+97
-46
lines changed

engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ CREATE TABLE IF NOT EXISTS `cloud`.`kms_hsm_profiles` (
8080
CONSTRAINT `fk_kms_hsm_profiles__zone_id` FOREIGN KEY (`zone_id`) REFERENCES `data_center`(`id`) ON DELETE CASCADE
8181
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='HSM profiles for KMS providers';
8282

83+
-- Add default database HSM profile (disabled by default)
84+
INSERT INTO `cloud`.`kms_hsm_profiles` (`uuid`, `name`, `protocol`, `account_id`, `domain_id`, `enabled`, `system`, `created`)
85+
VALUES (UUID(), 'default', 'database', 1, 1, 0, 1, NOW());
86+
8387
-- KMS HSM Profile Details (Protocol-specific configuration)
8488
CREATE TABLE IF NOT EXISTS `cloud`.`kms_hsm_profile_details` (
8589
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,

framework/kms/src/main/java/org/apache/cloudstack/framework/kms/KMSProvider.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,18 @@ default String createKek(KeyPurpose purpose, String label, int keyBits) throws K
103103
*/
104104
void deleteKek(String kekId) throws KMSException;
105105

106+
/**
107+
* Validates the configuration details for this provider before saving an HSM
108+
* profile.
109+
* Implementations should override this to perform provider-specific validation.
110+
*
111+
* @param details the configuration details to validate
112+
* @throws KMSException if validation fails
113+
*/
114+
default void validateProfileConfig(java.util.Map<String, String> details) throws KMSException {
115+
// default no-op
116+
}
117+
106118
/**
107119
* Check if a KEK exists and is accessible
108120
*

plugins/kms/pkcs11/src/main/java/org/apache/cloudstack/kms/provider/pkcs11/PKCS11HSMProvider.java

Lines changed: 42 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -296,69 +296,70 @@ Map<String, String> loadProfileConfig(Long profileId) {
296296
/**
297297
* Validates HSM profile configuration for PKCS#11 provider.
298298
*
299-
* <p>Validates:
299+
* <p>
300+
* Validates:
300301
* <ul>
301-
* <li>{@code library}: Required, should point to PKCS#11 library</li>
302-
* <li>{@code slot} or {@code token_label}: At least one required</li>
303-
* <li>{@code pin}: Required for HSM authentication</li>
304-
* <li>{@code max_sessions}: Optional, must be positive integer if provided</li>
302+
* <li>{@code library}: Required, should point to PKCS#11 library</li>
303+
* <li>{@code slot}, {@code slot_list_index}, or {@code token_label}: At least
304+
* one required</li>
305+
* <li>{@code pin}: Required for HSM authentication</li>
306+
* <li>{@code max_sessions}: Optional, must be positive integer if provided</li>
305307
* </ul>
306308
*
307309
* @param config Configuration map from HSM profile details
308310
* @throws KMSException with {@code INVALID_PARAMETER} if validation fails
309311
*/
310-
void validateProfileConfig(Map<String, String> config) throws KMSException {
312+
@Override
313+
public void validateProfileConfig(Map<String, String> config) throws KMSException {
311314
String libraryPath = config.get("library");
312-
if (StringUtils.isEmpty(libraryPath)) {
315+
if (StringUtils.isBlank(libraryPath)) {
313316
throw KMSException.invalidParameter("library is required for PKCS#11 HSM profile");
314317
}
315318

316319
String slot = config.get("slot");
320+
String slotListIndex = config.get("slot_list_index");
317321
String tokenLabel = config.get("token_label");
318-
if (StringUtils.isEmpty(slot) && StringUtils.isEmpty(tokenLabel)) {
319-
throw KMSException.invalidParameter("Either 'slot' or 'token_label' is required for PKCS#11 HSM profile");
322+
if (StringUtils.isAllBlank(slot, slotListIndex, tokenLabel)) {
323+
throw KMSException.invalidParameter(
324+
"One of 'slot', 'slot_list_index', or 'token_label' is required for PKCS#11 HSM profile");
320325
}
321326

322-
if (!StringUtils.isEmpty(slot)) {
327+
if (StringUtils.isNotBlank(slot)) {
323328
try {
324329
Integer.parseInt(slot);
325330
} catch (NumberFormatException e) {
326331
throw KMSException.invalidParameter("slot must be a valid integer: " + slot);
327332
}
328333
}
329334

335+
if (StringUtils.isNotBlank(slotListIndex)) {
336+
try {
337+
int idx = Integer.parseInt(slotListIndex);
338+
if (idx < 0) {
339+
throw KMSException.invalidParameter("slot_list_index must be a non-negative integer");
340+
}
341+
} catch (NumberFormatException e) {
342+
throw KMSException.invalidParameter("slot_list_index must be a valid integer: " + slotListIndex);
343+
}
344+
}
345+
330346
File libraryFile = new File(libraryPath);
331347
if (!libraryFile.exists() && !libraryFile.isAbsolute()) {
332348
// The HSM library might be in the system library path
333349
logger.debug("Library path {} does not exist as absolute path, will rely on system library path",
334350
libraryPath);
335351
}
336352

337-
parsePositiveInteger(config, "max_sessions", "max_sessions");
338-
}
339-
340-
/**
341-
* Parses a positive integer from configuration.
342-
*
343-
* @param config Configuration map
344-
* @param key Configuration key
345-
* @param errorPrefix Prefix for error messages
346-
* @return Parsed integer value, or -1 if not provided
347-
* @throws KMSException if value is invalid or not positive
348-
*/
349-
private int parsePositiveInteger(Map<String, String> config, String key, String errorPrefix) throws KMSException {
350-
String value = config.get(key);
351-
if (StringUtils.isEmpty(value)) {
352-
return -1; // Not provided
353-
}
354-
try {
355-
int parsed = Integer.parseInt(value);
356-
if (parsed <= 0) {
357-
throw KMSException.invalidParameter(errorPrefix + " must be greater than 0");
353+
String max_sessions = config.get("max_sessions");
354+
if (StringUtils.isNotBlank(max_sessions)) {
355+
try {
356+
int idx = Integer.parseInt(max_sessions);
357+
if (idx <= 0) {
358+
throw KMSException.invalidParameter("max_sessions must be greater than 0");
359+
}
360+
} catch (NumberFormatException e) {
361+
throw KMSException.invalidParameter("max_sessions must be a valid integer: " + max_sessions);
358362
}
359-
return parsed;
360-
} catch (NumberFormatException e) {
361-
throw KMSException.invalidParameter(errorPrefix + " must be a valid integer: " + value);
362363
}
363364
}
364365

@@ -615,7 +616,7 @@ private void connect(Map<String, String> config) throws KMSException {
615616
*/
616617
private String buildSunPKCS11Config(Map<String, String> config, String nameSuffix) throws KMSException {
617618
String libraryPath = config.get("library");
618-
if (StringUtils.isEmpty(libraryPath)) {
619+
if (StringUtils.isBlank(libraryPath)) {
619620
throw KMSException.invalidParameter("library is required");
620621
}
621622

@@ -627,14 +628,17 @@ private String buildSunPKCS11Config(Map<String, String> config, String nameSuffi
627628
configBuilder.append("library=").append(libraryPath).append("\n");
628629

629630
String tokenLabel = config.get("token_label");
631+
String slotListIndex = config.get("slot_list_index");
630632
String slot = config.get("slot");
631633

632-
if (!StringUtils.isEmpty(tokenLabel)) {
634+
if (StringUtils.isNotBlank(tokenLabel)) {
633635
configBuilder.append("tokenLabel=").append(tokenLabel).append("\n");
634-
} else if (!StringUtils.isEmpty(slot)) {
636+
} else if (StringUtils.isNotBlank(slotListIndex)) {
637+
configBuilder.append("slotListIndex=").append(slotListIndex).append("\n");
638+
} else if (StringUtils.isNotBlank(slot)) {
635639
configBuilder.append("slot=").append(slot).append("\n");
636640
} else {
637-
throw KMSException.invalidParameter("Either 'slot' or 'token_label' is required");
641+
throw KMSException.invalidParameter("One of 'slot', 'slot_list_index', or 'token_label' is required");
638642
}
639643

640644
return configBuilder.toString();

server/src/main/java/org/apache/cloudstack/kms/KMSManagerImpl.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -969,12 +969,16 @@ public HSMProfile addHSMProfile(AddHSMProfileCmd cmd) throws KMSException {
969969
throw new InvalidParameterValueException("Protocol cannot be empty");
970970
}
971971

972+
KMSProvider provider;
972973
try {
973-
getKMSProvider(protocol);
974+
provider = getKMSProvider(protocol);
974975
} catch (CloudRuntimeException e) {
975976
throw new InvalidParameterValueException("No provider found for protocol: " + protocol);
976977
}
977978

979+
Map<String, String> details = cmd.getDetails() != null ? cmd.getDetails() : new HashMap<>();
980+
provider.validateProfileConfig(details);
981+
978982
boolean isSystem = cmd.isSystem();
979983
if (isSystem && !accountManager.isRootAdmin(caller.getId())) {
980984
throw new PermissionDeniedException("Only root admins can create system HSM profiles");

ui/src/config/section/kms.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ export default {
2222
name: 'kms',
2323
title: 'label.kms',
2424
icon: 'hdd-outlined',
25+
show: (record, store) => {
26+
return ['Admin'].includes(store.getters.userInfo.roletype) || store.getters.features.hashsmprofiles
27+
},
2528
children: [
2629
{
2730
name: 'kmskey',
@@ -157,6 +160,7 @@ export default {
157160
title: 'label.hsm.profile',
158161
icon: 'safety-outlined',
159162
permission: ['listHSMProfiles'],
163+
show: (record, route, user) => { return ['Admin'].includes(user.roletype) },
160164
resourceType: 'HSMProfile',
161165
columns: () => {
162166
const fields = ['name', 'enabled']

ui/src/store/modules/user.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,16 @@ const user = {
480480
commit('SET_CLOUDIAN', cloudian)
481481
}).catch(ignored => {
482482
})
483+
484+
if ('listHSMProfiles' in store.getters.apis) {
485+
getAPI('listHSMProfiles', { listall: true }).then(response => {
486+
const hasHsmProfiles = (response.listhsmprofilesresponse.count > 0)
487+
const features = Object.assign({}, store.getters.features)
488+
features.hashsmprofiles = hasHsmProfiles
489+
commit('SET_FEATURES', features)
490+
}).catch(ignored => {
491+
})
492+
}
483493
}).catch(error => {
484494
console.error(error)
485495
})

ui/src/views/compute/DeployVM.vue

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2030,9 +2030,14 @@ export default {
20302030
domainid: this.owner.domainid,
20312031
projectid: this.owner.projectid
20322032
}).then(response => {
2033-
this.options.kmsKeys = response.listkmskeysresponse.kmskey || []
2034-
}).catch(error => {
2035-
this.$notifyError(error)
2033+
const kmskeyMap = response.listkmskeysresponse.kmskey || []
2034+
if (kmskeyMap.length > 0) {
2035+
this.options.kmsKeys = kmskeyMap
2036+
} else {
2037+
this.options.kmsKeys = null
2038+
}
2039+
}).catch(() => {
2040+
this.options.kmsKeys = null
20362041
}).finally(() => {
20372042
this.loading.kmsKeys = false
20382043
})

ui/src/views/compute/wizard/DiskSizeSelection.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ export default {
131131
return this.rootDiskSelected?.iscustomizediops || false
132132
},
133133
showKmsKeySelector () {
134+
if (this.kmsKeys === null) {
135+
return false
136+
}
134137
const isRootDisk = this.inputDecorator === 'rootdisksize'
135138
const isDataDisk = this.inputDecorator === 'size'
136139

ui/src/views/storage/CreateVolume.vue

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@
116116
:placeholder="apiParams.maxiops.description"/>
117117
</a-form-item>
118118
</span>
119-
<span v-if="diskOfferingSupportsEncryption">
119+
<span v-if="diskOfferingSupportsEncryption && kmsKeys !== null">
120120
<a-form-item ref="kmskeyid" name="kmskeyid">
121121
<template #label>
122122
<tooltip-label :title="$t('label.kms.key')" :tooltip="apiParams.kmskeyid.description"/>
@@ -529,9 +529,14 @@ export default {
529529
purpose: 'volume'
530530
}
531531
getAPI('listKMSKeys', params).then(response => {
532-
this.kmsKeys = response.listkmskeysresponse.kmskey || []
533-
}).catch(error => {
534-
this.$notifyError(error)
532+
const kmskeyMap = response.listkmskeysresponse.kmskey || []
533+
if (kmskeyMap.length > 0) {
534+
this.kmsKeys = kmskeyMap
535+
} else {
536+
this.kmsKeys = null
537+
}
538+
}).catch(() => {
539+
this.kmsKeys = null
535540
}).finally(() => {
536541
this.loadingKmsKeys = false
537542
})

0 commit comments

Comments
 (0)