This commit is contained in:
vishesh92 2026-02-26 17:30:41 +05:30
parent d7c5e249b6
commit d8cdddb540
No known key found for this signature in database
GPG Key ID: 4E395186CBFA790B
9 changed files with 97 additions and 46 deletions

View File

@ -145,6 +145,10 @@ CREATE TABLE IF NOT EXISTS `cloud`.`kms_hsm_profiles` (
CONSTRAINT `fk_kms_hsm_profiles__zone_id` FOREIGN KEY (`zone_id`) REFERENCES `data_center`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='HSM profiles for KMS providers';
-- Add default database HSM profile (disabled by default)
INSERT INTO `cloud`.`kms_hsm_profiles` (`uuid`, `name`, `protocol`, `account_id`, `domain_id`, `enabled`, `system`, `created`)
VALUES (UUID(), 'default', 'database', 1, 1, 0, 1, NOW());
-- KMS HSM Profile Details (Protocol-specific configuration)
CREATE TABLE IF NOT EXISTS `cloud`.`kms_hsm_profile_details` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,

View File

@ -103,6 +103,18 @@ public interface KMSProvider extends Configurable, Adapter {
*/
void deleteKek(String kekId) throws KMSException;
/**
* Validates the configuration details for this provider before saving an HSM
* profile.
* Implementations should override this to perform provider-specific validation.
*
* @param details the configuration details to validate
* @throws KMSException if validation fails
*/
default void validateProfileConfig(java.util.Map<String, String> details) throws KMSException {
// default no-op
}
/**
* Check if a KEK exists and is accessible
*

View File

@ -296,30 +296,35 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
/**
* Validates HSM profile configuration for PKCS#11 provider.
*
* <p>Validates:
* <p>
* Validates:
* <ul>
* <li>{@code library}: Required, should point to PKCS#11 library</li>
* <li>{@code slot} or {@code token_label}: At least one required</li>
* <li>{@code pin}: Required for HSM authentication</li>
* <li>{@code max_sessions}: Optional, must be positive integer if provided</li>
* <li>{@code library}: Required, should point to PKCS#11 library</li>
* <li>{@code slot}, {@code slot_list_index}, or {@code token_label}: At least
* one required</li>
* <li>{@code pin}: Required for HSM authentication</li>
* <li>{@code max_sessions}: Optional, must be positive integer if provided</li>
* </ul>
*
* @param config Configuration map from HSM profile details
* @throws KMSException with {@code INVALID_PARAMETER} if validation fails
*/
void validateProfileConfig(Map<String, String> config) throws KMSException {
@Override
public void validateProfileConfig(Map<String, String> config) throws KMSException {
String libraryPath = config.get("library");
if (StringUtils.isEmpty(libraryPath)) {
if (StringUtils.isBlank(libraryPath)) {
throw KMSException.invalidParameter("library is required for PKCS#11 HSM profile");
}
String slot = config.get("slot");
String slotListIndex = config.get("slot_list_index");
String tokenLabel = config.get("token_label");
if (StringUtils.isEmpty(slot) && StringUtils.isEmpty(tokenLabel)) {
throw KMSException.invalidParameter("Either 'slot' or 'token_label' is required for PKCS#11 HSM profile");
if (StringUtils.isAllBlank(slot, slotListIndex, tokenLabel)) {
throw KMSException.invalidParameter(
"One of 'slot', 'slot_list_index', or 'token_label' is required for PKCS#11 HSM profile");
}
if (!StringUtils.isEmpty(slot)) {
if (StringUtils.isNotBlank(slot)) {
try {
Integer.parseInt(slot);
} catch (NumberFormatException e) {
@ -327,6 +332,17 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
}
}
if (StringUtils.isNotBlank(slotListIndex)) {
try {
int idx = Integer.parseInt(slotListIndex);
if (idx < 0) {
throw KMSException.invalidParameter("slot_list_index must be a non-negative integer");
}
} catch (NumberFormatException e) {
throw KMSException.invalidParameter("slot_list_index must be a valid integer: " + slotListIndex);
}
}
File libraryFile = new File(libraryPath);
if (!libraryFile.exists() && !libraryFile.isAbsolute()) {
// The HSM library might be in the system library path
@ -334,31 +350,16 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
libraryPath);
}
parsePositiveInteger(config, "max_sessions", "max_sessions");
}
/**
* Parses a positive integer from configuration.
*
* @param config Configuration map
* @param key Configuration key
* @param errorPrefix Prefix for error messages
* @return Parsed integer value, or -1 if not provided
* @throws KMSException if value is invalid or not positive
*/
private int parsePositiveInteger(Map<String, String> config, String key, String errorPrefix) throws KMSException {
String value = config.get(key);
if (StringUtils.isEmpty(value)) {
return -1; // Not provided
}
try {
int parsed = Integer.parseInt(value);
if (parsed <= 0) {
throw KMSException.invalidParameter(errorPrefix + " must be greater than 0");
String max_sessions = config.get("max_sessions");
if (StringUtils.isNotBlank(max_sessions)) {
try {
int idx = Integer.parseInt(max_sessions);
if (idx <= 0) {
throw KMSException.invalidParameter("max_sessions must be greater than 0");
}
} catch (NumberFormatException e) {
throw KMSException.invalidParameter("max_sessions must be a valid integer: " + max_sessions);
}
return parsed;
} catch (NumberFormatException e) {
throw KMSException.invalidParameter(errorPrefix + " must be a valid integer: " + value);
}
}
@ -615,7 +616,7 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
*/
private String buildSunPKCS11Config(Map<String, String> config, String nameSuffix) throws KMSException {
String libraryPath = config.get("library");
if (StringUtils.isEmpty(libraryPath)) {
if (StringUtils.isBlank(libraryPath)) {
throw KMSException.invalidParameter("library is required");
}
@ -627,14 +628,17 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
configBuilder.append("library=").append(libraryPath).append("\n");
String tokenLabel = config.get("token_label");
String slotListIndex = config.get("slot_list_index");
String slot = config.get("slot");
if (!StringUtils.isEmpty(tokenLabel)) {
if (StringUtils.isNotBlank(tokenLabel)) {
configBuilder.append("tokenLabel=").append(tokenLabel).append("\n");
} else if (!StringUtils.isEmpty(slot)) {
} else if (StringUtils.isNotBlank(slotListIndex)) {
configBuilder.append("slotListIndex=").append(slotListIndex).append("\n");
} else if (StringUtils.isNotBlank(slot)) {
configBuilder.append("slot=").append(slot).append("\n");
} else {
throw KMSException.invalidParameter("Either 'slot' or 'token_label' is required");
throw KMSException.invalidParameter("One of 'slot', 'slot_list_index', or 'token_label' is required");
}
return configBuilder.toString();

View File

@ -969,12 +969,16 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
throw new InvalidParameterValueException("Protocol cannot be empty");
}
KMSProvider provider;
try {
getKMSProvider(protocol);
provider = getKMSProvider(protocol);
} catch (CloudRuntimeException e) {
throw new InvalidParameterValueException("No provider found for protocol: " + protocol);
}
Map<String, String> details = cmd.getDetails() != null ? cmd.getDetails() : new HashMap<>();
provider.validateProfileConfig(details);
boolean isSystem = cmd.isSystem();
if (isSystem && !accountManager.isRootAdmin(caller.getId())) {
throw new PermissionDeniedException("Only root admins can create system HSM profiles");

View File

@ -22,6 +22,9 @@ export default {
name: 'kms',
title: 'label.kms',
icon: 'hdd-outlined',
show: (record, store) => {
return ['Admin'].includes(store.getters.userInfo.roletype) || store.getters.features.hashsmprofiles
},
children: [
{
name: 'kmskey',
@ -157,6 +160,7 @@ export default {
title: 'label.hsm.profile',
icon: 'safety-outlined',
permission: ['listHSMProfiles'],
show: (record, route, user) => { return ['Admin'].includes(user.roletype) },
resourceType: 'HSMProfile',
columns: () => {
const fields = ['name', 'enabled']

View File

@ -480,6 +480,16 @@ const user = {
commit('SET_CLOUDIAN', cloudian)
}).catch(ignored => {
})
if ('listHSMProfiles' in store.getters.apis) {
getAPI('listHSMProfiles', { listall: true }).then(response => {
const hasHsmProfiles = (response.listhsmprofilesresponse.count > 0)
const features = Object.assign({}, store.getters.features)
features.hashsmprofiles = hasHsmProfiles
commit('SET_FEATURES', features)
}).catch(ignored => {
})
}
}).catch(error => {
console.error(error)
})

View File

@ -2030,9 +2030,14 @@ export default {
domainid: this.owner.domainid,
projectid: this.owner.projectid
}).then(response => {
this.options.kmsKeys = response.listkmskeysresponse.kmskey || []
}).catch(error => {
this.$notifyError(error)
const kmskeyMap = response.listkmskeysresponse.kmskey || []
if (kmskeyMap.length > 0) {
this.options.kmsKeys = kmskeyMap
} else {
this.options.kmsKeys = null
}
}).catch(() => {
this.options.kmsKeys = null
}).finally(() => {
this.loading.kmsKeys = false
})

View File

@ -131,6 +131,9 @@ export default {
return this.rootDiskSelected?.iscustomizediops || false
},
showKmsKeySelector () {
if (this.kmsKeys === null) {
return false
}
const isRootDisk = this.inputDecorator === 'rootdisksize'
const isDataDisk = this.inputDecorator === 'size'

View File

@ -116,7 +116,7 @@
:placeholder="apiParams.maxiops.description"/>
</a-form-item>
</span>
<span v-if="diskOfferingSupportsEncryption">
<span v-if="diskOfferingSupportsEncryption && kmsKeys !== null">
<a-form-item ref="kmskeyid" name="kmskeyid">
<template #label>
<tooltip-label :title="$t('label.kms.key')" :tooltip="apiParams.kmskeyid.description"/>
@ -529,9 +529,14 @@ export default {
purpose: 'volume'
}
getAPI('listKMSKeys', params).then(response => {
this.kmsKeys = response.listkmskeysresponse.kmskey || []
}).catch(error => {
this.$notifyError(error)
const kmskeyMap = response.listkmskeysresponse.kmskey || []
if (kmskeyMap.length > 0) {
this.kmsKeys = kmskeyMap
} else {
this.kmsKeys = null
}
}).catch(() => {
this.kmsKeys = null
}).finally(() => {
this.loadingKmsKeys = false
})