mirror of https://github.com/apache/cloudstack.git
fixups and some ui changes
This commit is contained in:
parent
0d39a7b0be
commit
f354da4436
|
|
@ -868,6 +868,7 @@ public class ApiConstants {
|
|||
public static final String SORT_BY = "sortby";
|
||||
public static final String CHANGE_CIDR = "changecidr";
|
||||
public static final String HSM_PROFILE = "hsmprofile";
|
||||
public static final String HSM_PROFILE_ID = "hsmprofileid";
|
||||
public static final String PURPOSE = "purpose";
|
||||
public static final String KMS_KEY_ID = "kmskeyid";
|
||||
public static final String KMS_KEY_VERSION = "kmskeyversion";
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import org.apache.cloudstack.api.Parameter;
|
|||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.response.AsyncJobResponse;
|
||||
import org.apache.cloudstack.api.response.DomainResponse;
|
||||
import org.apache.cloudstack.api.response.KMSKeyResponse;
|
||||
import org.apache.cloudstack.api.response.ZoneResponse;
|
||||
import org.apache.cloudstack.framework.kms.KMSException;
|
||||
import org.apache.cloudstack.kms.KMSManager;
|
||||
|
|
@ -68,6 +69,13 @@ public class MigrateVolumesToKMSCmd extends BaseAsyncCmd {
|
|||
description = "Domain ID")
|
||||
private Long domainId;
|
||||
|
||||
@Parameter(name = ApiConstants.ID,
|
||||
required = true,
|
||||
type = CommandType.UUID,
|
||||
entityType = KMSKeyResponse.class,
|
||||
description = "KMS Key ID to use for migrating volumes")
|
||||
private Long kmsKeyId;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
@ -84,6 +92,10 @@ public class MigrateVolumesToKMSCmd extends BaseAsyncCmd {
|
|||
return domainId;
|
||||
}
|
||||
|
||||
public Long getKmsKeyId() {
|
||||
return kmsKeyId;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import org.apache.cloudstack.api.BaseAsyncCmd;
|
|||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.response.AsyncJobResponse;
|
||||
import org.apache.cloudstack.api.response.HSMProfileResponse;
|
||||
import org.apache.cloudstack.api.response.KMSKeyResponse;
|
||||
import org.apache.cloudstack.framework.kms.KMSException;
|
||||
import org.apache.cloudstack.kms.KMSManager;
|
||||
|
|
@ -45,10 +46,6 @@ public class RotateKMSKeyCmd extends BaseAsyncCmd {
|
|||
@Inject
|
||||
private KMSManager kmsManager;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
//////////////// API parameters /////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Parameter(name = ApiConstants.ID,
|
||||
required = true,
|
||||
type = CommandType.UUID,
|
||||
|
|
@ -61,15 +58,12 @@ public class RotateKMSKeyCmd extends BaseAsyncCmd {
|
|||
description = "Key size for new KEK (default: same as current)")
|
||||
private Integer keyBits;
|
||||
|
||||
@Parameter(name = ApiConstants.HSM_PROFILE,
|
||||
type = CommandType.STRING,
|
||||
description = "The target HSM profile name for the new KEK version. If provided, migrates the key to this HSM.")
|
||||
@Parameter(name = ApiConstants.HSM_PROFILE_ID,
|
||||
type = CommandType.UUID,
|
||||
entityType = HSMProfileResponse.class,
|
||||
description = "The target HSM profile ID for the new KEK version. If provided, migrates the key to this HSM.")
|
||||
private String hsmProfile;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
|
@ -82,10 +76,6 @@ public class RotateKMSKeyCmd extends BaseAsyncCmd {
|
|||
return hsmProfile;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import org.apache.cloudstack.api.Parameter;
|
|||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.command.user.UserCmd;
|
||||
import org.apache.cloudstack.api.response.DomainResponse;
|
||||
import org.apache.cloudstack.api.response.HSMProfileResponse;
|
||||
import org.apache.cloudstack.api.response.KMSKeyResponse;
|
||||
import org.apache.cloudstack.api.response.ZoneResponse;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
|
|
@ -51,10 +52,6 @@ public class CreateKMSKeyCmd extends BaseCmd implements UserCmd {
|
|||
@Inject
|
||||
private KMSManager kmsManager;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
//////////////// API parameters /////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Parameter(name = ApiConstants.NAME,
|
||||
required = true,
|
||||
type = CommandType.STRING,
|
||||
|
|
@ -95,14 +92,12 @@ public class CreateKMSKeyCmd extends BaseCmd implements UserCmd {
|
|||
description = "Key size in bits: 128, 192, or 256 (default: 256)")
|
||||
private Integer keyBits;
|
||||
|
||||
@Parameter(name = ApiConstants.HSM_PROFILE,
|
||||
type = CommandType.STRING,
|
||||
description = "Name of HSM profile to create key in")
|
||||
private String hsmProfile;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
@Parameter(name = ApiConstants.HSM_PROFILE_ID,
|
||||
type = CommandType.UUID,
|
||||
entityType = HSMProfileResponse.class,
|
||||
required = true,
|
||||
description = "ID of HSM profile to create key in")
|
||||
private Long hsmProfileId;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
|
|
@ -132,14 +127,10 @@ public class CreateKMSKeyCmd extends BaseCmd implements UserCmd {
|
|||
return keyBits != null ? keyBits : 256; // Default to 256 bits
|
||||
}
|
||||
|
||||
public String getHsmProfile() {
|
||||
return hsmProfile;
|
||||
public Long getHsmProfileId() {
|
||||
return hsmProfileId;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void execute() throws ResourceAllocationException {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -49,10 +49,6 @@ public class DeleteKMSKeyCmd extends BaseAsyncCmd implements UserCmd {
|
|||
@Inject
|
||||
private KMSManager kmsManager;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
//////////////// API parameters /////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Parameter(name = ApiConstants.ID,
|
||||
required = true,
|
||||
type = CommandType.UUID,
|
||||
|
|
@ -60,18 +56,10 @@ public class DeleteKMSKeyCmd extends BaseAsyncCmd implements UserCmd {
|
|||
description = "The UUID of the KMS key to delete")
|
||||
private Long id;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -47,10 +47,6 @@ public class ListKMSKeysCmd extends BaseListAccountResourcesCmd implements UserC
|
|||
@Inject
|
||||
private KMSManager kmsManager;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
//////////////// API parameters /////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Parameter(name = ApiConstants.ID,
|
||||
type = CommandType.UUID,
|
||||
entityType = KMSKeyResponse.class,
|
||||
|
|
@ -73,10 +69,6 @@ public class ListKMSKeysCmd extends BaseListAccountResourcesCmd implements UserC
|
|||
description = "Filter by state: Enabled, Disabled")
|
||||
private String state;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,10 +48,6 @@ public class UpdateKMSKeyCmd extends BaseAsyncCmd implements UserCmd {
|
|||
@Inject
|
||||
private KMSManager kmsManager;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
//////////////// API parameters /////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Parameter(name = ApiConstants.ID,
|
||||
required = true,
|
||||
type = CommandType.UUID,
|
||||
|
|
@ -74,10 +70,6 @@ public class UpdateKMSKeyCmd extends BaseAsyncCmd implements UserCmd {
|
|||
description = "New state: Enabled or Disabled")
|
||||
private String state;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
|
@ -94,10 +86,6 @@ public class UpdateKMSKeyCmd extends BaseAsyncCmd implements UserCmd {
|
|||
return state;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -17,16 +17,18 @@
|
|||
|
||||
package org.apache.cloudstack.api.command.user.kms.hsm;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import com.cloud.exception.ConcurrentOperationException;
|
||||
import com.cloud.exception.InsufficientCapacityException;
|
||||
import com.cloud.exception.NetworkRuleConflictException;
|
||||
import com.cloud.exception.ResourceAllocationException;
|
||||
import com.cloud.exception.ResourceUnavailableException;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.response.AccountResponse;
|
||||
import org.apache.cloudstack.api.response.DomainResponse;
|
||||
import org.apache.cloudstack.api.response.HSMProfileResponse;
|
||||
import org.apache.cloudstack.api.response.ZoneResponse;
|
||||
|
|
@ -34,55 +36,56 @@ import org.apache.cloudstack.context.CallContext;
|
|||
import org.apache.cloudstack.framework.kms.KMSException;
|
||||
import org.apache.cloudstack.kms.HSMProfile;
|
||||
import org.apache.cloudstack.kms.KMSManager;
|
||||
import org.apache.commons.collections.MapUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import com.cloud.exception.ConcurrentOperationException;
|
||||
import com.cloud.exception.InsufficientCapacityException;
|
||||
import com.cloud.exception.NetworkRuleConflictException;
|
||||
import com.cloud.exception.ResourceAllocationException;
|
||||
import com.cloud.exception.ResourceUnavailableException;
|
||||
import com.cloud.user.Account;
|
||||
import javax.inject.Inject;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@APICommand(name = "addHSMProfile", description = "Adds a new HSM profile", responseObject = HSMProfileResponse.class,
|
||||
requestHasSensitiveInfo = true, responseHasSensitiveInfo = true, since = "4.21.0")
|
||||
requestHasSensitiveInfo = true, responseHasSensitiveInfo = true, since = "4.23.0")
|
||||
public class AddHSMProfileCmd extends BaseCmd {
|
||||
|
||||
@Inject
|
||||
private KMSManager kmsManager;
|
||||
|
||||
////////////////////////////////////////////////=====
|
||||
// API parameters
|
||||
////////////////////////////////////////////////=====
|
||||
|
||||
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "the name of the HSM profile")
|
||||
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true,
|
||||
description = "the name of the HSM profile")
|
||||
private String name;
|
||||
|
||||
@Parameter(name = ApiConstants.PROTOCOL, type = CommandType.STRING, required = true, description = "the protocol of the HSM profile (PKCS11, KMIP, etc.)")
|
||||
@Parameter(name = ApiConstants.PROTOCOL, type = CommandType.STRING,
|
||||
description = "the protocol of the HSM profile (PKCS11, KMIP, etc.). Default is 'pkcs11'")
|
||||
private String protocol;
|
||||
|
||||
@Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, description = "the zone ID where the HSM profile is available. If null, global scope (for admin only)")
|
||||
@Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class,
|
||||
description = "the zone ID where the HSM profile is available. If null, global scope (for admin only)")
|
||||
private Long zoneId;
|
||||
|
||||
@Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, description = "the domain ID where the HSM profile is available")
|
||||
@Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class,
|
||||
description = "the domain ID where the HSM profile is available")
|
||||
private Long domainId;
|
||||
|
||||
@Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = DomainResponse.class, description = "the account ID of the HSM profile owner. If null, admin-provided (available to all accounts)")
|
||||
@Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class,
|
||||
description = "the account ID of the HSM profile owner. If null, admin-provided (available to all "
|
||||
+ "accounts)")
|
||||
private Long accountId;
|
||||
|
||||
@Parameter(name = ApiConstants.VENDOR_NAME, type = CommandType.STRING, description = "the vendor name of the HSM")
|
||||
private String vendorName;
|
||||
|
||||
@Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, required = true, description = "HSM configuration details (protocol specific)")
|
||||
@Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, description = "HSM configuration details (protocol specific)")
|
||||
private Map<String, String> details;
|
||||
|
||||
////////////////////////////////////////////////=====
|
||||
// Accessors
|
||||
////////////////////////////////////////////////=====
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getProtocol() {
|
||||
if (StringUtils.isBlank(protocol)) {
|
||||
return "pkcs11";
|
||||
}
|
||||
return protocol;
|
||||
}
|
||||
|
||||
|
|
@ -103,15 +106,22 @@ public class AddHSMProfileCmd extends BaseCmd {
|
|||
}
|
||||
|
||||
public Map<String, String> getDetails() {
|
||||
return details;
|
||||
Map<String, String> detailsMap = new HashMap<>();
|
||||
if (MapUtils.isNotEmpty(details)) {
|
||||
Collection<?> props = details.values();
|
||||
for (Object prop : props) {
|
||||
HashMap<String, String> detail = (HashMap<String, String>) prop;
|
||||
for (Map.Entry<String, String> entry: detail.entrySet()) {
|
||||
detailsMap.put(entry.getKey(),entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
return detailsMap;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////=====
|
||||
// Implementation
|
||||
////////////////////////////////////////////////=====
|
||||
|
||||
@Override
|
||||
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
|
||||
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException,
|
||||
ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
|
||||
try {
|
||||
// Default to caller account if not admin and accountId not specified
|
||||
// But wait, the plan says: "No accountId parameter means account_id = NULL (admin-provided)"
|
||||
|
|
|
|||
|
|
@ -39,31 +39,19 @@ import com.cloud.exception.ResourceAllocationException;
|
|||
import com.cloud.exception.ResourceUnavailableException;
|
||||
|
||||
@APICommand(name = "deleteHSMProfile", description = "Deletes an HSM profile", responseObject = SuccessResponse.class,
|
||||
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.21.0")
|
||||
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.23.0")
|
||||
public class DeleteHSMProfileCmd extends BaseCmd {
|
||||
|
||||
@Inject
|
||||
private KMSManager kmsManager;
|
||||
|
||||
////////////////////////////////////////////////=====
|
||||
// API parameters
|
||||
////////////////////////////////////////////////=====
|
||||
|
||||
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = HSMProfileResponse.class, required = true, description = "the ID of the HSM profile")
|
||||
private Long id;
|
||||
|
||||
////////////////////////////////////////////////=====
|
||||
// Accessors
|
||||
////////////////////////////////////////////////=====
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////=====
|
||||
// Implementation
|
||||
////////////////////////////////////////////////=====
|
||||
|
||||
@Override
|
||||
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -33,15 +33,14 @@ import org.apache.cloudstack.kms.HSMProfile;
|
|||
import org.apache.cloudstack.kms.KMSManager;
|
||||
|
||||
@APICommand(name = "listHSMProfiles", description = "Lists HSM profiles", responseObject = HSMProfileResponse.class,
|
||||
requestHasSensitiveInfo = false, responseHasSensitiveInfo = true, since = "4.21.0")
|
||||
requestHasSensitiveInfo = false, responseHasSensitiveInfo = true, since = "4.23.0")
|
||||
public class ListHSMProfilesCmd extends BaseListCmd {
|
||||
|
||||
@Inject
|
||||
private KMSManager kmsManager;
|
||||
|
||||
////////////////////////////////////////////////=====
|
||||
// API parameters
|
||||
////////////////////////////////////////////////=====
|
||||
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = HSMProfileResponse.class, description = "the HSM profile ID")
|
||||
private Long id;
|
||||
|
||||
@Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, description = "the zone ID")
|
||||
private Long zoneId;
|
||||
|
|
@ -52,9 +51,9 @@ public class ListHSMProfilesCmd extends BaseListCmd {
|
|||
@Parameter(name = ApiConstants.ENABLED, type = CommandType.BOOLEAN, description = "list only enabled profiles")
|
||||
private Boolean enabled;
|
||||
|
||||
////////////////////////////////////////////////=====
|
||||
// Accessors
|
||||
////////////////////////////////////////////////=====
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Long getZoneId() {
|
||||
return zoneId;
|
||||
|
|
@ -68,16 +67,12 @@ public class ListHSMProfilesCmd extends BaseListCmd {
|
|||
return enabled;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////=====
|
||||
// Implementation
|
||||
////////////////////////////////////////////////=====
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
List<HSMProfile> profiles = kmsManager.listHSMProfiles(this);
|
||||
ListResponse<HSMProfileResponse> response = new ListResponse<>();
|
||||
List<HSMProfileResponse> profileResponses = new ArrayList<>();
|
||||
|
||||
|
||||
for (HSMProfile profile : profiles) {
|
||||
HSMProfileResponse profileResponse = kmsManager.createHSMProfileResponse(profile);
|
||||
profileResponses.add(profileResponse);
|
||||
|
|
|
|||
|
|
@ -40,16 +40,12 @@ import com.cloud.exception.ResourceAllocationException;
|
|||
import com.cloud.exception.ResourceUnavailableException;
|
||||
|
||||
@APICommand(name = "updateHSMProfile", description = "Updates an HSM profile", responseObject = HSMProfileResponse.class,
|
||||
requestHasSensitiveInfo = true, responseHasSensitiveInfo = true, since = "4.21.0")
|
||||
requestHasSensitiveInfo = true, responseHasSensitiveInfo = true, since = "4.23.0")
|
||||
public class UpdateHSMProfileCmd extends BaseCmd {
|
||||
|
||||
@Inject
|
||||
private KMSManager kmsManager;
|
||||
|
||||
////////////////////////////////////////////////=====
|
||||
// API parameters
|
||||
////////////////////////////////////////////////=====
|
||||
|
||||
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = HSMProfileResponse.class, required = true, description = "the ID of the HSM profile")
|
||||
private Long id;
|
||||
|
||||
|
|
@ -62,10 +58,6 @@ public class UpdateHSMProfileCmd extends BaseCmd {
|
|||
@Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, description = "HSM configuration details to update (protocol specific)")
|
||||
private Map<String, String> details;
|
||||
|
||||
////////////////////////////////////////////////=====
|
||||
// Accessors
|
||||
////////////////////////////////////////////////=====
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
|
@ -82,10 +74,6 @@ public class UpdateHSMProfileCmd extends BaseCmd {
|
|||
return details;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////=====
|
||||
// Implementation
|
||||
////////////////////////////////////////////////=====
|
||||
|
||||
@Override
|
||||
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -100,4 +100,6 @@ public interface KMSKey extends Identity, InternalIdentity, ControlledEntity {
|
|||
/** Key is soft-deleted */
|
||||
Deleted
|
||||
}
|
||||
|
||||
Long getHsmProfileId();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,20 +50,6 @@ public interface KMSManager extends Manager, Configurable {
|
|||
|
||||
// ==================== Configuration Keys ====================
|
||||
|
||||
/**
|
||||
* Global: which KMS provider plugin to use by default
|
||||
* Supported values: "database" (default), "pkcs11", or custom provider names
|
||||
*/
|
||||
ConfigKey<String> KMSProviderPlugin = new ConfigKey<>(
|
||||
"Advanced",
|
||||
String.class,
|
||||
"kms.provider.plugin",
|
||||
"database",
|
||||
"The KMS provider plugin to use for cryptographic operations (database, pkcs11, etc.)",
|
||||
true,
|
||||
ConfigKey.Scope.Global
|
||||
);
|
||||
|
||||
/**
|
||||
* Zone-scoped: enable KMS for a specific zone
|
||||
* When false (default), new volumes use legacy passphrase encryption
|
||||
|
|
@ -209,23 +195,6 @@ public interface KMSManager extends Manager, Configurable {
|
|||
|
||||
// ==================== User KEK Management ====================
|
||||
|
||||
/**
|
||||
* Create a new KMS key (KEK) for a user account
|
||||
*
|
||||
* @param accountId the account ID
|
||||
* @param domainId the domain ID
|
||||
* @param zoneId the zone ID
|
||||
* @param name user-friendly name
|
||||
* @param description optional description
|
||||
* @param purpose key purpose
|
||||
* @param keyBits key size in bits
|
||||
* @return the created KMS key
|
||||
* @throws KMSException if creation fails
|
||||
*/
|
||||
KMSKey createUserKMSKey(Long accountId, Long domainId, Long zoneId,
|
||||
String name, String description, KeyPurpose purpose,
|
||||
Integer keyBits) throws KMSException;
|
||||
|
||||
/**
|
||||
* List KMS keys accessible to a user account
|
||||
*
|
||||
|
|
@ -341,7 +310,7 @@ public interface KMSManager extends Manager, Configurable {
|
|||
|
||||
/**
|
||||
* Add a new HSM profile
|
||||
*
|
||||
*
|
||||
* @param cmd the add command
|
||||
* @return the created HSM profile
|
||||
* @throws KMSException if addition fails
|
||||
|
|
@ -350,7 +319,7 @@ public interface KMSManager extends Manager, Configurable {
|
|||
|
||||
/**
|
||||
* List HSM profiles
|
||||
*
|
||||
*
|
||||
* @param cmd the list command
|
||||
* @return list of HSM profiles
|
||||
*/
|
||||
|
|
@ -358,7 +327,7 @@ public interface KMSManager extends Manager, Configurable {
|
|||
|
||||
/**
|
||||
* Delete an HSM profile
|
||||
*
|
||||
*
|
||||
* @param cmd the delete command
|
||||
* @return true if deletion was successful
|
||||
* @throws KMSException if deletion fails
|
||||
|
|
@ -367,7 +336,7 @@ public interface KMSManager extends Manager, Configurable {
|
|||
|
||||
/**
|
||||
* Update an HSM profile
|
||||
*
|
||||
*
|
||||
* @param cmd the update command
|
||||
* @return the updated HSM profile
|
||||
* @throws KMSException if update fails
|
||||
|
|
@ -376,7 +345,7 @@ public interface KMSManager extends Manager, Configurable {
|
|||
|
||||
/**
|
||||
* Create a response object for an HSM profile
|
||||
*
|
||||
*
|
||||
* @param profile the HSM profile
|
||||
* @return the response object
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -256,6 +256,11 @@
|
|||
<artifactId>cloud-plugin-kms-database</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-plugin-kms-pkcs11</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-plugin-network-nvp</artifactId>
|
||||
|
|
|
|||
|
|
@ -252,6 +252,7 @@ public class KMSKeyVO implements KMSKey {
|
|||
this.state = state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getHsmProfileId() {
|
||||
return hsmProfileId;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,10 @@ public class KMSException extends CloudRuntimeException {
|
|||
*/
|
||||
public enum ErrorType {
|
||||
CONNECTION_FAILED(true),
|
||||
/**
|
||||
* Authentication failed (e.g., incorrect PIN)
|
||||
*/
|
||||
AUTHENTICATION_FAILED(false),
|
||||
/**
|
||||
* Provider not initialized or unavailable
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -21,8 +21,6 @@
|
|||
xmlns="http://www.springframework.org/schema/beans"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
|
||||
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context.xsd"
|
||||
>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -16,14 +16,17 @@
|
|||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns="http://www.springframework.org/schema/beans"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context.xsd">
|
||||
|
||||
<context:component-scan base-package="org.apache.cloudstack.kms.provider.pkcs11" />
|
||||
<context:component-scan base-package="org.apache.cloudstack.kms.provider.pkcs11"/>
|
||||
<bean id="pkcs11HSMProvider" class="org.apache.cloudstack.kms.provider.pkcs11.PKCS11HSMProvider">
|
||||
<property name="name" value="PKCS11HSMProvider"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ public class ManagementServerMaintenanceManagerImplTest {
|
|||
Mockito.doReturn(expectedCount).when(jobManagerMock).countPendingNonPseudoJobs(1L);
|
||||
return expectedCount;
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void countPendingJobs() {
|
||||
long expectedCount = prepareCountPendingJobs();
|
||||
|
|
|
|||
|
|
@ -111,8 +111,9 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
|
||||
@Override
|
||||
public KMSProvider getKMSProvider(String name) {
|
||||
// Default to database provider if no name specified
|
||||
if (StringUtils.isEmpty(name)) {
|
||||
return getConfiguredKmsProvider();
|
||||
name = "database";
|
||||
}
|
||||
|
||||
String providerName = name.toLowerCase();
|
||||
|
|
@ -130,9 +131,9 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
|
||||
@Override
|
||||
public KMSProvider getKMSProviderForZone(Long zoneId) throws KMSException {
|
||||
// For now, use global provider
|
||||
// In the future, could support zone-specific providers via zone-scoped config
|
||||
return getConfiguredKmsProvider();
|
||||
// Default to database provider for backward compatibility
|
||||
// HSM-based keys will use provider from HSM profile's protocol field
|
||||
return getKMSProvider("database");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -225,40 +226,26 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_KMS_KEK_CREATE, eventDescription = "creating user KMS key")
|
||||
public KMSKey createUserKMSKey(Long accountId, Long domainId, Long zoneId,
|
||||
String name, String description, KeyPurpose purpose,
|
||||
Integer keyBits) throws KMSException {
|
||||
// Delegate to method with profileId
|
||||
return createUserKMSKey(accountId, domainId, zoneId, name, description, purpose, keyBits, null);
|
||||
}
|
||||
|
||||
KMSKey createUserKMSKey(Long accountId, Long domainId, Long zoneId,
|
||||
String name, String description, KeyPurpose purpose,
|
||||
Integer keyBits, String hsmProfileName) throws KMSException {
|
||||
Integer keyBits, long hsmProfileId) throws KMSException {
|
||||
validateKmsEnabled(zoneId);
|
||||
|
||||
KMSProvider provider = getKMSProviderForZone(zoneId);
|
||||
|
||||
// Resolve HSM Profile
|
||||
Long hsmProfileId = null;
|
||||
if (hsmProfileName != null) {
|
||||
HSMProfileVO profile = hsmProfileDao.findByName(hsmProfileName);
|
||||
if (profile == null) {
|
||||
throw KMSException.invalidParameter("HSM Profile not found: " + hsmProfileName);
|
||||
}
|
||||
// Validate access
|
||||
if (profile.getAccountId() != null && !profile.getAccountId().equals(accountId)) {
|
||||
// Check if admin
|
||||
// For simplicity, strict check for now. Ideally should check if user is admin.
|
||||
// Assuming caller check happened upstream in createKMSKey(CreateKMSKeyCmd)
|
||||
}
|
||||
hsmProfileId = profile.getId();
|
||||
} else {
|
||||
// Auto-resolve based on hierarchy
|
||||
hsmProfileId = resolveHSMProfile(accountId, zoneId, provider.getProviderName());
|
||||
HSMProfileVO profile = hsmProfileDao.findById(hsmProfileId);
|
||||
if (profile == null) {
|
||||
throw KMSException.invalidParameter("HSM Profile not found");
|
||||
}
|
||||
// Validate access
|
||||
if (profile.getAccountId() != null && !profile.getAccountId().equals(accountId)) {
|
||||
// Check if admin
|
||||
// For simplicity, strict check for now. Ideally should check if user is admin.
|
||||
// Assuming caller check happened upstream in createKMSKey(CreateKMSKeyCmd)
|
||||
throw KMSException.invalidParameter("HSM Profile not found");
|
||||
}
|
||||
|
||||
// Determine provider from HSM profile or default to database
|
||||
KMSProvider provider = getKMSProvider(profile.getProtocol());
|
||||
|
||||
// Generate unique KEK label
|
||||
String kekLabel = purpose.getName() + "-kek-" + UUID.randomUUID().toString().substring(0, 8);
|
||||
|
|
@ -290,46 +277,6 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
return kmsKey;
|
||||
}
|
||||
|
||||
Long resolveHSMProfile(Long accountId, Long zoneId, String providerName) {
|
||||
// Only applicable for providers that use profiles (pkcs11, kmip)
|
||||
if ("database".equalsIgnoreCase(providerName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 1. User-provided profile
|
||||
List<HSMProfileVO> userProfiles = hsmProfileDao.listByAccountId(accountId);
|
||||
if (CollectionUtils.isNotEmpty(userProfiles)) {
|
||||
// Filter by protocol/provider match if needed, for now pick first enabled
|
||||
for (HSMProfileVO p : userProfiles) {
|
||||
if (p.isEnabled() && isProviderMatch(p, providerName)) return p.getId();
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Zone-scoped admin profile
|
||||
List<HSMProfileVO> zoneProfiles = hsmProfileDao.listAdminProfiles(zoneId);
|
||||
if (CollectionUtils.isNotEmpty(zoneProfiles)) {
|
||||
for (HSMProfileVO p : zoneProfiles) {
|
||||
if (p.isEnabled() && isProviderMatch(p, providerName)) return p.getId();
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Global admin profile
|
||||
List<HSMProfileVO> globalProfiles = hsmProfileDao.listAdminProfiles();
|
||||
if (CollectionUtils.isNotEmpty(globalProfiles)) {
|
||||
for (HSMProfileVO p : globalProfiles) {
|
||||
if (p.isEnabled() && isProviderMatch(p, providerName)) return p.getId();
|
||||
}
|
||||
}
|
||||
|
||||
// If provider is not database, we must have a profile
|
||||
throw new CloudRuntimeException("No suitable HSM profile found for provider " + providerName + " for account " + accountId);
|
||||
}
|
||||
|
||||
boolean isProviderMatch(HSMProfileVO profile, String providerName) {
|
||||
// Simple mapping: PKCS11 -> pkcs11, KMIP -> kmip
|
||||
return profile.getProtocol().equalsIgnoreCase(providerName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends KMSKey> listUserKMSKeys(Long accountId, Long domainId, Long zoneId,
|
||||
KeyPurpose purpose, KMSKey.State state) {
|
||||
|
|
@ -490,7 +437,12 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
throw KMSException.invalidParameter("KMS key purpose is not VOLUME_ENCRYPTION: " + kmsKey);
|
||||
}
|
||||
|
||||
KMSProvider provider = getKMSProviderForZone(kmsKey.getZoneId());
|
||||
HSMProfileVO hsmProfile = hsmProfileDao.findById(kmsKey.getHsmProfileId());
|
||||
if (hsmProfile == null) {
|
||||
throw KMSException.invalidParameter("HSM profile not found: " + kmsKey.getHsmProfileId());
|
||||
}
|
||||
|
||||
KMSProvider provider = getKMSProvider(hsmProfile.getProtocol());
|
||||
|
||||
// Get active KEK version
|
||||
KMSKekVersionVO activeVersion = getActiveKekVersion(kmsKey.getId());
|
||||
|
|
@ -588,7 +540,8 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
cmd.getName(),
|
||||
cmd.getDescription(),
|
||||
keyPurpose,
|
||||
bits
|
||||
bits,
|
||||
cmd.getHsmProfileId()
|
||||
);
|
||||
|
||||
return responseGenerator.createKMSKeyResponse(kmsKey);
|
||||
|
|
@ -823,13 +776,47 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
Long zoneId = cmd.getZoneId();
|
||||
String accountName = cmd.getAccountName();
|
||||
Long domainId = cmd.getDomainId();
|
||||
Long kmsKeyId = cmd.getKmsKeyId();
|
||||
|
||||
if (zoneId == null) {
|
||||
throw KMSException.invalidParameter("zoneId must be specified");
|
||||
}
|
||||
|
||||
if (kmsKeyId == null) {
|
||||
throw KMSException.invalidParameter("kmsKeyId must be specified");
|
||||
}
|
||||
|
||||
validateKmsEnabled(zoneId);
|
||||
|
||||
// Get and validate KMS key
|
||||
KMSKeyVO kmsKey = kmsKeyDao.findById(kmsKeyId);
|
||||
if (kmsKey == null) {
|
||||
throw KMSException.kekNotFound("KMS key not found: " + kmsKeyId);
|
||||
}
|
||||
|
||||
if (kmsKey.getState() != KMSKey.State.Enabled) {
|
||||
throw KMSException.invalidParameter("KMS key is not enabled: " + kmsKey.getUuid());
|
||||
}
|
||||
|
||||
if (kmsKey.getPurpose() != KeyPurpose.VOLUME_ENCRYPTION) {
|
||||
throw KMSException.invalidParameter("KMS key purpose must be VOLUME_ENCRYPTION");
|
||||
}
|
||||
|
||||
// Get provider from KMS key's HSM profile
|
||||
KMSProvider provider;
|
||||
if (kmsKey.getHsmProfileId() != null) {
|
||||
HSMProfileVO profile = hsmProfileDao.findById(kmsKey.getHsmProfileId());
|
||||
if (profile == null) {
|
||||
throw KMSException.invalidParameter("HSM Profile not found for KMS key");
|
||||
}
|
||||
provider = getKMSProvider(profile.getProtocol());
|
||||
} else {
|
||||
provider = getKMSProvider("database");
|
||||
}
|
||||
|
||||
// Get active KEK version
|
||||
KMSKekVersionVO activeVersion = getActiveKekVersion(kmsKey.getId());
|
||||
|
||||
Long accountId = null;
|
||||
if (accountName != null) {
|
||||
accountId = accountManager.finalyzeAccountId(accountName, domainId, null, true);
|
||||
|
|
@ -837,9 +824,6 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
|
||||
int pageSize = 100; // Process 100 volumes per page to avoid OutOfMemoryError
|
||||
|
||||
// Get provider
|
||||
KMSProvider provider = getKMSProviderForZone(zoneId);
|
||||
|
||||
int successCount = 0;
|
||||
int failureCount = 0;
|
||||
logger.info("Starting migration of volumes to KMS (zone: {}, account: {}, domain: {})",
|
||||
|
|
@ -871,41 +855,13 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
// The KMS will store the same format, maintaining compatibility
|
||||
byte[] passphraseBytes = passphrase.getPassphrase();
|
||||
|
||||
// Get or create KMS key for account
|
||||
KMSKeyVO kmsKey;
|
||||
List<? extends KMSKey> accountKeys = listUserKMSKeys(
|
||||
volume.getAccountId(),
|
||||
volume.getDomainId(),
|
||||
zoneId,
|
||||
KeyPurpose.VOLUME_ENCRYPTION,
|
||||
KMSKey.State.Enabled
|
||||
);
|
||||
|
||||
if (!accountKeys.isEmpty()) {
|
||||
kmsKey = (KMSKeyVO) accountKeys.get(0); // Use first available key
|
||||
} else {
|
||||
// Create new KMS key for account
|
||||
String keyName = "Volume-Encryption-Key-" + volume.getAccountId();
|
||||
kmsKey = (KMSKeyVO) createUserKMSKey(
|
||||
volume.getAccountId(),
|
||||
volume.getDomainId(),
|
||||
zoneId,
|
||||
keyName,
|
||||
"Auto-created for volume migration",
|
||||
KeyPurpose.VOLUME_ENCRYPTION,
|
||||
256 // Default to 256 bits
|
||||
);
|
||||
logger.info("Created KMS key {} for account {} during migration", kmsKey, volume.getAccountId());
|
||||
}
|
||||
|
||||
// Get active KEK version
|
||||
KMSKekVersionVO activeVersion = getActiveKekVersion(kmsKey.getId());
|
||||
|
||||
// Wrap existing passphrase bytes as DEK (don't generate new DEK)
|
||||
// Wrap existing passphrase bytes as DEK using the specified KMS key
|
||||
// Pass the HSM profile ID from the active version
|
||||
WrappedKey wrappedKey = provider.wrapKey(
|
||||
passphraseBytes,
|
||||
KeyPurpose.VOLUME_ENCRYPTION,
|
||||
activeVersion.getKekLabel()
|
||||
activeVersion.getKekLabel(),
|
||||
activeVersion.getHsmProfileId()
|
||||
);
|
||||
|
||||
// Store wrapped key
|
||||
|
|
@ -1011,16 +967,6 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
return KMSException.transientError("KMS operation failed: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
private KMSProvider getConfiguredKmsProvider() {
|
||||
String providerName = KMSProviderPlugin.value();
|
||||
String providerKey = providerName != null ? providerName.toLowerCase() : null;
|
||||
if (providerKey != null && kmsProviderMap.containsKey(providerKey) && kmsProviderMap.get(providerKey) != null) {
|
||||
return kmsProviderMap.get(providerKey);
|
||||
}
|
||||
|
||||
throw new CloudRuntimeException("Failed to find default configured KMS provider plugin: " + providerName);
|
||||
}
|
||||
|
||||
public void setKmsProviders(List<KMSProvider> kmsProviders) {
|
||||
this.kmsProviders = kmsProviders;
|
||||
initializeKmsProviderMap();
|
||||
|
|
@ -1055,30 +1001,20 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
super.start();
|
||||
initializeKmsProviderMap();
|
||||
|
||||
String configuredProviderName = KMSProviderPlugin.value();
|
||||
String providerKey = configuredProviderName != null ? configuredProviderName.toLowerCase() : null;
|
||||
KMSProvider provider = null;
|
||||
if (providerKey != null && kmsProviderMap.containsKey(providerKey)) {
|
||||
provider = kmsProviderMap.get(providerKey);
|
||||
logger.info("Configured KMS provider: {}", provider.getProviderName());
|
||||
}
|
||||
|
||||
if (provider == null) {
|
||||
logger.warn("No valid configured KMS provider found. KMS functionality will be unavailable.");
|
||||
// Don't fail - KMS is optional
|
||||
return true;
|
||||
}
|
||||
|
||||
// Run health check on startup
|
||||
try {
|
||||
boolean healthy = provider.healthCheck();
|
||||
if (healthy) {
|
||||
logger.info("KMS provider {} health check passed", provider.getProviderName());
|
||||
} else {
|
||||
logger.warn("KMS provider {} health check failed", provider.getProviderName());
|
||||
// Run health check on all registered providers
|
||||
for (KMSProvider provider : kmsProviderMap.values()) {
|
||||
if (provider != null) {
|
||||
try {
|
||||
boolean healthy = provider.healthCheck();
|
||||
if (healthy) {
|
||||
logger.info("KMS provider {} health check passed", provider.getProviderName());
|
||||
} else {
|
||||
logger.warn("KMS provider {} health check failed", provider.getProviderName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("KMS provider {} health check error: {}", provider.getProviderName(), e.getMessage());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("KMS provider health check error: {}", e.getMessage());
|
||||
}
|
||||
|
||||
// Schedule background rewrap worker
|
||||
|
|
@ -1276,7 +1212,6 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
@Override
|
||||
public ConfigKey<?>[] getConfigKeys() {
|
||||
return new ConfigKey<?>[]{
|
||||
KMSProviderPlugin,
|
||||
KMSEnabled,
|
||||
KMSDekSizeBits,
|
||||
KMSRetryCount,
|
||||
|
|
@ -1296,7 +1231,6 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
cmdList.add(DeleteKMSKeyCmd.class);
|
||||
cmdList.add(RotateKMSKeyCmd.class);
|
||||
cmdList.add(MigrateVolumesToKMSCmd.class);
|
||||
cmdList.add(MigrateVolumesToKMSCmd.class);
|
||||
cmdList.add(AddHSMProfileCmd.class);
|
||||
cmdList.add(ListHSMProfilesCmd.class);
|
||||
cmdList.add(UpdateHSMProfileCmd.class);
|
||||
|
|
@ -1359,6 +1293,22 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
|
||||
List<HSMProfile> result = new ArrayList<>();
|
||||
|
||||
if (cmd.getId() != null) {
|
||||
HSMProfileVO key = hsmProfileDao.findById(cmd.getId());
|
||||
if (key == null) {
|
||||
return result;
|
||||
}
|
||||
// Validate the caller can list this profile
|
||||
if (!isAdmin) {
|
||||
Account caller = CallContext.current().getCallingAccount();
|
||||
Account owner = accountManager.getAccount(key.getAccountId());
|
||||
|
||||
accountManager.checkAccess(caller, null, true, owner);
|
||||
}
|
||||
result.add(key);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 1. User's own profiles
|
||||
result.addAll(hsmProfileDao.listByAccountId(accountId));
|
||||
|
||||
|
|
|
|||
|
|
@ -66,8 +66,6 @@ public class KMSManagerImplHSMTest {
|
|||
private AccountManager accountManager;
|
||||
|
||||
private Long testAccountId = 100L;
|
||||
private Long testZoneId = 1L;
|
||||
private String testProviderName = "pkcs11";
|
||||
|
||||
/**
|
||||
* Test: isSensitiveKey correctly identifies "pin" as sensitive
|
||||
|
|
@ -126,144 +124,6 @@ public class KMSManagerImplHSMTest {
|
|||
assertTrue("'Password' (mixed case) should be detected as sensitive", resultMixed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: resolveHSMProfile selects user profile when available
|
||||
*/
|
||||
@Test
|
||||
public void testResolveHSMProfile_SelectsUserProfile() {
|
||||
// Setup: User has a profile
|
||||
HSMProfileVO userProfile = mock(HSMProfileVO.class);
|
||||
when(userProfile.getId()).thenReturn(1L);
|
||||
when(userProfile.isEnabled()).thenReturn(true);
|
||||
when(userProfile.getProtocol()).thenReturn(testProviderName);
|
||||
when(hsmProfileDao.listByAccountId(testAccountId)).thenReturn(Arrays.asList(userProfile));
|
||||
|
||||
Long result = kmsManager.resolveHSMProfile(testAccountId, testZoneId, testProviderName);
|
||||
|
||||
assertNotNull("Should return user profile ID", result);
|
||||
assertEquals("Should select user profile", userProfile.getId(), result.longValue());
|
||||
verify(hsmProfileDao).listByAccountId(testAccountId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: resolveHSMProfile falls back to zone admin profile when no user profile
|
||||
*/
|
||||
@Test
|
||||
public void testResolveHSMProfile_FallbackToZoneAdmin() {
|
||||
// Setup: No user profile, but zone admin profile exists
|
||||
HSMProfileVO zoneProfile = mock(HSMProfileVO.class);
|
||||
when(zoneProfile.getId()).thenReturn(2L);
|
||||
when(zoneProfile.isEnabled()).thenReturn(true);
|
||||
when(zoneProfile.getProtocol()).thenReturn(testProviderName);
|
||||
when(hsmProfileDao.listByAccountId(testAccountId)).thenReturn(new ArrayList<>());
|
||||
when(hsmProfileDao.listAdminProfiles(testZoneId)).thenReturn(Arrays.asList(zoneProfile));
|
||||
|
||||
Long result = kmsManager.resolveHSMProfile(testAccountId, testZoneId, testProviderName);
|
||||
|
||||
assertNotNull("Should return zone admin profile ID", result);
|
||||
assertEquals("Should select zone admin profile", zoneProfile.getId(), result.longValue());
|
||||
verify(hsmProfileDao).listByAccountId(testAccountId);
|
||||
verify(hsmProfileDao).listAdminProfiles(testZoneId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: resolveHSMProfile falls back to global admin profile when no user or zone profile
|
||||
*/
|
||||
@Test
|
||||
public void testResolveHSMProfile_FallbackToGlobal() {
|
||||
// Setup: No user or zone profile, but global admin profile exists
|
||||
HSMProfileVO globalProfile = mock(HSMProfileVO.class);
|
||||
when(globalProfile.getId()).thenReturn(3L);
|
||||
when(globalProfile.isEnabled()).thenReturn(true);
|
||||
when(globalProfile.getProtocol()).thenReturn(testProviderName);
|
||||
when(hsmProfileDao.listByAccountId(testAccountId)).thenReturn(new ArrayList<>());
|
||||
when(hsmProfileDao.listAdminProfiles(testZoneId)).thenReturn(new ArrayList<>());
|
||||
when(hsmProfileDao.listAdminProfiles()).thenReturn(Arrays.asList(globalProfile));
|
||||
|
||||
Long result = kmsManager.resolveHSMProfile(testAccountId, testZoneId, testProviderName);
|
||||
|
||||
assertNotNull("Should return global admin profile ID", result);
|
||||
assertEquals("Should select global admin profile", globalProfile.getId(), result.longValue());
|
||||
verify(hsmProfileDao).listByAccountId(testAccountId);
|
||||
verify(hsmProfileDao).listAdminProfiles(testZoneId);
|
||||
verify(hsmProfileDao).listAdminProfiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: resolveHSMProfile throws exception when no profile found
|
||||
*/
|
||||
@Test(expected = CloudRuntimeException.class)
|
||||
public void testResolveHSMProfile_ThrowsExceptionWhenNoneFound() {
|
||||
// Setup: No profiles at any level
|
||||
when(hsmProfileDao.listByAccountId(testAccountId)).thenReturn(new ArrayList<>());
|
||||
when(hsmProfileDao.listAdminProfiles(testZoneId)).thenReturn(new ArrayList<>());
|
||||
when(hsmProfileDao.listAdminProfiles()).thenReturn(new ArrayList<>());
|
||||
|
||||
kmsManager.resolveHSMProfile(testAccountId, testZoneId, testProviderName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: resolveHSMProfile skips disabled profiles
|
||||
*/
|
||||
@Test
|
||||
public void testResolveHSMProfile_SkipsDisabledProfiles() {
|
||||
// Setup: User has disabled profile, zone has enabled profile
|
||||
HSMProfileVO disabledProfile = mock(HSMProfileVO.class);
|
||||
when(disabledProfile.isEnabled()).thenReturn(false);
|
||||
|
||||
HSMProfileVO enabledZoneProfile = mock(HSMProfileVO.class);
|
||||
when(enabledZoneProfile.getId()).thenReturn(5L);
|
||||
when(enabledZoneProfile.isEnabled()).thenReturn(true);
|
||||
when(enabledZoneProfile.getProtocol()).thenReturn(testProviderName);
|
||||
|
||||
when(hsmProfileDao.listByAccountId(testAccountId)).thenReturn(Arrays.asList(disabledProfile));
|
||||
when(hsmProfileDao.listAdminProfiles(testZoneId)).thenReturn(Arrays.asList(enabledZoneProfile));
|
||||
|
||||
Long result = kmsManager.resolveHSMProfile(testAccountId, testZoneId, testProviderName);
|
||||
|
||||
assertNotNull("Should return zone profile ID (skip disabled)", result);
|
||||
assertEquals("Should select zone profile (not disabled user profile)", enabledZoneProfile.getId(), result.longValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: resolveHSMProfile returns null for database provider
|
||||
*/
|
||||
@Test
|
||||
public void testResolveHSMProfile_ReturnsNullForDatabaseProvider() {
|
||||
Long result = kmsManager.resolveHSMProfile(testAccountId, testZoneId, "database");
|
||||
|
||||
assertNull("Should return null for database provider", result);
|
||||
verify(hsmProfileDao, never()).listByAccountId(anyLong());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: isProviderMatch correctly matches PKCS11 protocol
|
||||
*/
|
||||
@Test
|
||||
public void testIsProviderMatch_MatchesPKCS11() {
|
||||
HSMProfileVO profile = mock(HSMProfileVO.class);
|
||||
when(profile.getProtocol()).thenReturn("PKCS11");
|
||||
|
||||
boolean result = kmsManager.isProviderMatch(profile, "pkcs11");
|
||||
|
||||
assertTrue("Should match PKCS11 (case-insensitive)", result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: isProviderMatch is case-insensitive
|
||||
*/
|
||||
@Test
|
||||
public void testIsProviderMatch_MatchesDifferentCases() {
|
||||
HSMProfileVO profile = mock(HSMProfileVO.class);
|
||||
when(profile.getProtocol()).thenReturn("pkcs11");
|
||||
|
||||
boolean resultUpper = kmsManager.isProviderMatch(profile, "PKCS11");
|
||||
boolean resultMixed = kmsManager.isProviderMatch(profile, "Pkcs11");
|
||||
|
||||
assertTrue("Should match PKCS11 (uppercase)", resultUpper);
|
||||
assertTrue("Should match Pkcs11 (mixed case)", resultMixed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: createHSMProfileResponse populates details correctly
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ public class KMSManagerImplKeyCreationTest {
|
|||
doReturn(true).when(kmsManager).isKmsEnabled(testZoneId);
|
||||
|
||||
KMSKey result = kmsManager.createUserKMSKey(testAccountId, testDomainId,
|
||||
testZoneId, "test-key", "Test key", KeyPurpose.VOLUME_ENCRYPTION, 256, hsmProfileName);
|
||||
testZoneId, "test-key", "Test key", KeyPurpose.VOLUME_ENCRYPTION, 256, hsmProfileId);
|
||||
|
||||
// Verify explicit profile was used
|
||||
assertNotNull(result);
|
||||
|
|
@ -125,52 +125,6 @@ public class KMSManagerImplKeyCreationTest {
|
|||
assertEquals(hsmProfileId, createdKey.getHsmProfileId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: createUserKMSKey auto-resolves profile when not provided
|
||||
*/
|
||||
@Test
|
||||
public void testCreateUserKMSKey_AutoResolvesProfile() throws Exception {
|
||||
// Setup: No explicit profile name, should auto-resolve
|
||||
Long autoResolvedProfileId = 20L;
|
||||
|
||||
// Mock profile resolution hierarchy - user has a profile
|
||||
HSMProfileVO userProfile = mock(HSMProfileVO.class);
|
||||
when(userProfile.getId()).thenReturn(autoResolvedProfileId);
|
||||
when(userProfile.isEnabled()).thenReturn(true);
|
||||
when(userProfile.getProtocol()).thenReturn(testProviderName);
|
||||
when(hsmProfileDao.listByAccountId(testAccountId)).thenReturn(Arrays.asList(userProfile));
|
||||
|
||||
// Mock provider KEK creation
|
||||
when(kmsProvider.createKek(any(KeyPurpose.class), anyString(), anyInt(), eq(autoResolvedProfileId)))
|
||||
.thenReturn("test-kek-label");
|
||||
|
||||
// Mock DAO persist operations
|
||||
KMSKeyVO mockKey = mock(KMSKeyVO.class);
|
||||
when(mockKey.getId()).thenReturn(1L);
|
||||
when(kmsKeyDao.persist(any(KMSKeyVO.class))).thenReturn(mockKey);
|
||||
|
||||
KMSKekVersionVO mockVersion = mock(KMSKekVersionVO.class);
|
||||
when(kmsKekVersionDao.persist(any(KMSKekVersionVO.class))).thenReturn(mockVersion);
|
||||
|
||||
// Mock getKMSProviderForZone
|
||||
doReturn(kmsProvider).when(kmsManager).getKMSProviderForZone(testZoneId);
|
||||
doReturn(true).when(kmsManager).isKmsEnabled(testZoneId);
|
||||
|
||||
KMSKey result = kmsManager.createUserKMSKey(testAccountId, testDomainId,
|
||||
testZoneId, "test-key", "Test key", KeyPurpose.VOLUME_ENCRYPTION, 256, null);
|
||||
|
||||
// Verify profile was auto-resolved
|
||||
assertNotNull(result);
|
||||
verify(hsmProfileDao).listByAccountId(testAccountId);
|
||||
verify(kmsProvider).createKek(any(KeyPurpose.class), anyString(), eq(256), eq(autoResolvedProfileId));
|
||||
|
||||
// Verify KMSKeyVO was created with auto-resolved profile ID
|
||||
ArgumentCaptor<KMSKeyVO> keyCaptor = ArgumentCaptor.forClass(KMSKeyVO.class);
|
||||
verify(kmsKeyDao).persist(keyCaptor.capture());
|
||||
KMSKeyVO createdKey = keyCaptor.getValue();
|
||||
assertEquals(autoResolvedProfileId, createdKey.getHsmProfileId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: createUserKMSKey throws exception when explicit profile not found
|
||||
*/
|
||||
|
|
@ -178,59 +132,14 @@ public class KMSManagerImplKeyCreationTest {
|
|||
public void testCreateUserKMSKey_ThrowsExceptionWhenProfileNotFound() throws KMSException {
|
||||
// Setup: Profile name provided but doesn't exist
|
||||
String invalidProfileName = "non-existent-profile";
|
||||
when(hsmProfileDao.findByName(invalidProfileName)).thenReturn(null);
|
||||
long hsmProfileId = 1L;
|
||||
when(hsmProfileDao.findById(hsmProfileId)).thenReturn(null);
|
||||
|
||||
doReturn(kmsProvider).when(kmsManager).getKMSProviderForZone(testZoneId);
|
||||
doReturn(true).when(kmsManager).isKmsEnabled(testZoneId);
|
||||
|
||||
kmsManager.createUserKMSKey(testAccountId, testDomainId, testZoneId,
|
||||
"test-key", "Test key", KeyPurpose.VOLUME_ENCRYPTION, 256, invalidProfileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: createUserKMSKey auto-resolves to zone admin profile when no user profile
|
||||
*/
|
||||
@Test
|
||||
public void testCreateUserKMSKey_AutoResolvesToZoneAdmin() throws Exception {
|
||||
// Setup: No user profile, but zone admin profile exists
|
||||
Long zoneAdminProfileId = 30L;
|
||||
|
||||
HSMProfileVO zoneProfile = mock(HSMProfileVO.class);
|
||||
when(zoneProfile.getId()).thenReturn(zoneAdminProfileId);
|
||||
when(zoneProfile.isEnabled()).thenReturn(true);
|
||||
when(zoneProfile.getProtocol()).thenReturn(testProviderName);
|
||||
|
||||
when(hsmProfileDao.listByAccountId(testAccountId)).thenReturn(new ArrayList<>());
|
||||
when(hsmProfileDao.listAdminProfiles(testZoneId)).thenReturn(Arrays.asList(zoneProfile));
|
||||
|
||||
// Mock provider KEK creation
|
||||
when(kmsProvider.createKek(any(KeyPurpose.class), anyString(), anyInt(), eq(zoneAdminProfileId)))
|
||||
.thenReturn("test-kek-label");
|
||||
|
||||
// Mock DAO persist operations
|
||||
KMSKeyVO mockKey = mock(KMSKeyVO.class);
|
||||
when(mockKey.getId()).thenReturn(1L);
|
||||
when(kmsKeyDao.persist(any(KMSKeyVO.class))).thenReturn(mockKey);
|
||||
|
||||
KMSKekVersionVO mockVersion = mock(KMSKekVersionVO.class);
|
||||
when(kmsKekVersionDao.persist(any(KMSKekVersionVO.class))).thenReturn(mockVersion);
|
||||
|
||||
doReturn(kmsProvider).when(kmsManager).getKMSProviderForZone(testZoneId);
|
||||
doReturn(true).when(kmsManager).isKmsEnabled(testZoneId);
|
||||
|
||||
KMSKey result = kmsManager.createUserKMSKey(testAccountId, testDomainId,
|
||||
testZoneId, "test-key", "Test key", KeyPurpose.VOLUME_ENCRYPTION, 256, null);
|
||||
|
||||
// Verify zone admin profile was used
|
||||
assertNotNull(result);
|
||||
verify(hsmProfileDao).listByAccountId(testAccountId);
|
||||
verify(hsmProfileDao).listAdminProfiles(testZoneId);
|
||||
verify(kmsProvider).createKek(any(KeyPurpose.class), anyString(), eq(256), eq(zoneAdminProfileId));
|
||||
|
||||
// Verify KMSKeyVO was created with zone admin profile ID
|
||||
ArgumentCaptor<KMSKeyVO> keyCaptor = ArgumentCaptor.forClass(KMSKeyVO.class);
|
||||
verify(kmsKeyDao).persist(keyCaptor.capture());
|
||||
assertEquals(zoneAdminProfileId, keyCaptor.getValue().getHsmProfileId());
|
||||
"test-key", "Test key", KeyPurpose.VOLUME_ENCRYPTION, 256, hsmProfileId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -261,7 +170,7 @@ public class KMSManagerImplKeyCreationTest {
|
|||
doReturn(true).when(kmsManager).isKmsEnabled(testZoneId);
|
||||
|
||||
kmsManager.createUserKMSKey(testAccountId, testDomainId, testZoneId,
|
||||
"test-key", "Test key", KeyPurpose.VOLUME_ENCRYPTION, 256, null);
|
||||
"test-key", "Test key", KeyPurpose.VOLUME_ENCRYPTION, 256, hsmProfileId);
|
||||
|
||||
// Verify KEK version was created with correct profile ID
|
||||
ArgumentCaptor<KMSKekVersionVO> versionCaptor = ArgumentCaptor.forClass(KMSKekVersionVO.class);
|
||||
|
|
@ -271,37 +180,4 @@ public class KMSManagerImplKeyCreationTest {
|
|||
assertEquals(Integer.valueOf(1), Integer.valueOf(createdVersion.getVersionNumber()));
|
||||
assertEquals("test-kek-label", createdVersion.getKekLabel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: createUserKMSKey returns null profile ID for database provider
|
||||
*/
|
||||
@Test
|
||||
public void testCreateUserKMSKey_NullProfileIdForDatabaseProvider() throws Exception {
|
||||
// Setup: Database provider doesn't use profiles
|
||||
KMSProvider databaseProvider = mock(KMSProvider.class);
|
||||
when(databaseProvider.getProviderName()).thenReturn("database");
|
||||
when(databaseProvider.createKek(any(KeyPurpose.class), anyString(), anyInt(), eq(null)))
|
||||
.thenReturn("test-kek-label");
|
||||
|
||||
KMSKeyVO mockKey = mock(KMSKeyVO.class);
|
||||
when(mockKey.getId()).thenReturn(1L);
|
||||
when(kmsKeyDao.persist(any(KMSKeyVO.class))).thenReturn(mockKey);
|
||||
|
||||
KMSKekVersionVO mockVersion = mock(KMSKekVersionVO.class);
|
||||
when(kmsKekVersionDao.persist(any(KMSKekVersionVO.class))).thenReturn(mockVersion);
|
||||
|
||||
doReturn(databaseProvider).when(kmsManager).getKMSProviderForZone(testZoneId);
|
||||
doReturn(true).when(kmsManager).isKmsEnabled(testZoneId);
|
||||
|
||||
kmsManager.createUserKMSKey(testAccountId, testDomainId, testZoneId,
|
||||
"test-key", "Test key", KeyPurpose.VOLUME_ENCRYPTION, 256, null);
|
||||
|
||||
// Verify KEK was created with null profile ID
|
||||
verify(databaseProvider).createKek(any(KeyPurpose.class), anyString(), eq(256), eq(null));
|
||||
|
||||
// Verify KMSKeyVO has null profile ID
|
||||
ArgumentCaptor<KMSKeyVO> keyCaptor = ArgumentCaptor.forClass(KMSKeyVO.class);
|
||||
verify(kmsKeyDao).persist(keyCaptor.capture());
|
||||
assertEquals(null, keyCaptor.getValue().getHsmProfileId());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1434,6 +1434,8 @@
|
|||
"label.keyboardtype": "Keyboard type",
|
||||
"label.keypair": "SSH key pair",
|
||||
"label.keypairs": "SSH key pair(s)",
|
||||
"label.kms.key": "KMS Key",
|
||||
"label.select.kms.key.optional": "Select KMS Key (optional)",
|
||||
"label.kubeconfig.cluster": "Kubernetes Cluster config",
|
||||
"label.kubernetes": "Kubernetes",
|
||||
"label.kubernetes.access.details": "The kubernetes nodes can be accessed via ssh using: <br> <code><b> ssh -i [ssh_key] -p [port_number] cloud@[public_ip_address] </b></code> <br><br> where, <br> <code><b>ssh_key:</b></code> points to the ssh private key file corresponding to the key that was associated while creating the Kubernetes Cluster. If no ssh key was provided during Kubernetes cluster creation, use the ssh private key of the management server. <br> <code><b>port_number:</b></code> can be obtained from the Port Forwarding Tab (Public Port column)",
|
||||
|
|
@ -4224,5 +4226,6 @@
|
|||
"Compute*Month": "Compute * Month",
|
||||
"GB*Month": "GB * Month",
|
||||
"IP*Month": "IP * Month",
|
||||
"Policy*Month": "Policy * Month"
|
||||
"Policy*Month": "Policy * Month",
|
||||
"message.kms.key.optional": "Optional: Select a KMS key for encryption. If not selected, legacy passphrase encryption will be used."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import compute from '@/config/section/compute'
|
|||
import storage from '@/config/section/storage'
|
||||
import network from '@/config/section/network'
|
||||
import image from '@/config/section/image'
|
||||
import kms from '@/config/section/kms'
|
||||
import project from '@/config/section/project'
|
||||
import event from '@/config/section/event'
|
||||
import user from '@/config/section/user'
|
||||
|
|
@ -216,6 +217,7 @@ export function asyncRouterMap () {
|
|||
|
||||
generateRouterMap(compute),
|
||||
generateRouterMap(storage),
|
||||
generateRouterMap(kms),
|
||||
generateRouterMap(network),
|
||||
generateRouterMap(image),
|
||||
generateRouterMap(event),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,148 @@
|
|||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
import store from '@/store'
|
||||
|
||||
export default {
|
||||
name: 'kms',
|
||||
title: 'label.kms',
|
||||
icon: 'hdd-outlined',
|
||||
children: [
|
||||
{
|
||||
name: 'KMS key',
|
||||
title: 'label.kms.keys',
|
||||
icon: 'file-text-outlined',
|
||||
permission: ['listKMSKeys'],
|
||||
resourceType: 'KMSKey',
|
||||
columns: () => {
|
||||
const fields = ['name', 'state', 'account', 'domain', 'purpose']
|
||||
return fields
|
||||
},
|
||||
details: ['id', 'name', 'description', 'state', 'account', 'domain', 'created'],
|
||||
searchFilters: () => {
|
||||
var filters = ['zoneid']
|
||||
if (store.getters.userInfo.roletype === 'Admin') {
|
||||
filters.push('accountid', 'domainid')
|
||||
}
|
||||
return filters
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
api: 'createKMSKey',
|
||||
icon: 'plus-outlined',
|
||||
label: 'label.create.kms.key',
|
||||
listView: true,
|
||||
popup: true,
|
||||
dataView: true,
|
||||
args: (record, store, group) => {
|
||||
var fields = ['zoneid', 'name', 'description', 'purpose', 'hsmprofileid', 'keybits']
|
||||
return (['Admin'].includes(store.userInfo.roletype))
|
||||
? fields.concat(['domainid', 'account']) : fields
|
||||
}
|
||||
},
|
||||
{
|
||||
api: 'updateKMSKey',
|
||||
icon: 'edit-outlined',
|
||||
docHelp: 'adminguide/storage.html#lifecycle-operations',
|
||||
label: 'label.update.kms.ket',
|
||||
dataView: true,
|
||||
popup: true,
|
||||
args: ['id', 'name', 'description', 'state'],
|
||||
mapping: {
|
||||
id: {
|
||||
value: (record) => record.id
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
api: 'deleteKMSKey',
|
||||
icon: 'delete-outlined',
|
||||
docHelp: 'adminguide/storage.html#lifecycle-operations',
|
||||
label: 'label.delete.kms.key',
|
||||
message: 'message.action.delete.kms.key',
|
||||
dataView: true,
|
||||
popup: true,
|
||||
args: ['id'],
|
||||
mapping: {
|
||||
id: {
|
||||
value: (record) => record.id
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'hsmprofile',
|
||||
title: 'label.hsm.profile',
|
||||
icon: 'file-text-outlined',
|
||||
permission: ['listHSMProfiles'],
|
||||
resourceType: 'HSMProfile',
|
||||
columns: () => {
|
||||
const fields = ['name', 'state']
|
||||
return fields
|
||||
},
|
||||
details: ['id', 'name', 'description', 'state', 'account', 'domain', 'created'],
|
||||
searchFilters: () => {
|
||||
var filters = ['zoneid']
|
||||
return filters
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
api: 'addHSMProfile',
|
||||
icon: 'plus-outlined',
|
||||
label: 'label.create.hsmprofile',
|
||||
listView: true,
|
||||
popup: true,
|
||||
dataView: true,
|
||||
args: (record, store, group) => {
|
||||
return (['Admin'].includes(store.userInfo.roletype))
|
||||
? ['zoneid', 'name', 'vendorname', 'domainid', 'accountid', 'details', 'protocol'] : ['zoneid', 'name', 'vendorname', 'details', 'protocol']
|
||||
}
|
||||
},
|
||||
{
|
||||
api: 'updateHSMProfile',
|
||||
icon: 'edit-outlined',
|
||||
docHelp: 'adminguide/storage.html#lifecycle-operations',
|
||||
label: 'label.update.hsm.profile',
|
||||
dataView: true,
|
||||
popup: true,
|
||||
args: ['id', 'name', 'details', 'enabled'],
|
||||
mapping: {
|
||||
id: {
|
||||
value: (record) => record.id
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
api: 'deleteHSMProfile',
|
||||
icon: 'delete-outlined',
|
||||
docHelp: 'adminguide/storage.html#lifecycle-operations',
|
||||
label: 'label.delete.hsm.profile',
|
||||
message: 'message.action.delete.hsm.profile',
|
||||
dataView: true,
|
||||
popup: true,
|
||||
args: ['id'],
|
||||
mapping: {
|
||||
id: {
|
||||
value: (record) => record.id
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -341,15 +341,19 @@
|
|||
@handle-search-filter="($event) => handleSearchFilter('diskOfferings', $event)"
|
||||
></disk-offering-selection>
|
||||
<disk-size-selection
|
||||
v-if="overrideDiskOffering && (overrideDiskOffering.iscustomized || overrideDiskOffering.iscustomizediops)"
|
||||
v-if="overrideDiskOffering && (overrideDiskOffering.iscustomized || overrideDiskOffering.iscustomizediops || overrideDiskOffering.encrypt || (serviceOffering && serviceOffering.encryptroot))"
|
||||
input-decorator="rootdisksize"
|
||||
:preFillContent="dataPreFill"
|
||||
:minDiskSize="dataPreFill.minrootdisksize"
|
||||
:rootDiskSelected="overrideDiskOffering"
|
||||
:isCustomized="overrideDiskOffering.iscustomized"
|
||||
:kmsKeys="options.kmsKeys"
|
||||
:loadingKmsKeys="loading.kmsKeys"
|
||||
:computeOfferingEncryptRoot="serviceOffering && serviceOffering.encryptroot"
|
||||
@handler-error="handlerError"
|
||||
@update-disk-size="updateFieldValue"
|
||||
@update-root-disk-iops-value="updateIOPSValue"/>
|
||||
@update-root-disk-iops-value="updateIOPSValue"
|
||||
@update-root-kms-key="updateRootKmsKey"/>
|
||||
<a-form-item class="form-item-hidden">
|
||||
<a-input v-model:value="form.rootdisksize"/>
|
||||
</a-form-item>
|
||||
|
|
@ -394,14 +398,17 @@
|
|||
@handle-search-filter="($event) => handleSearchFilter('diskOfferings', $event)"
|
||||
></disk-offering-selection>
|
||||
<disk-size-selection
|
||||
v-if="diskOffering && (diskOffering.iscustomized || diskOffering.iscustomizediops)"
|
||||
v-if="diskOffering && (diskOffering.iscustomized || diskOffering.iscustomizediops || diskOffering.encrypt)"
|
||||
input-decorator="size"
|
||||
:preFillContent="dataPreFill"
|
||||
:diskSelected="diskSelected"
|
||||
:isCustomized="diskOffering.iscustomized"
|
||||
:kmsKeys="options.kmsKeys"
|
||||
:loadingKmsKeys="loading.kmsKeys"
|
||||
@handler-error="handlerError"
|
||||
@update-disk-size="updateFieldValue"
|
||||
@update-iops-value="updateIOPSValue"/>
|
||||
@update-iops-value="updateIOPSValue"
|
||||
@update-data-kms-key="updateDataKmsKey"/>
|
||||
<a-form-item class="form-item-hidden">
|
||||
<a-input v-model:value="form.size"/>
|
||||
</a-form-item>
|
||||
|
|
@ -1050,7 +1057,8 @@ export default {
|
|||
keyboards: [],
|
||||
bootTypes: [],
|
||||
bootModes: [],
|
||||
ioPolicyTypes: []
|
||||
ioPolicyTypes: [],
|
||||
kmsKeys: []
|
||||
},
|
||||
rowCount: {},
|
||||
loading: {
|
||||
|
|
@ -1071,7 +1079,8 @@ export default {
|
|||
pods: false,
|
||||
clusters: false,
|
||||
hosts: false,
|
||||
groups: false
|
||||
groups: false,
|
||||
kmsKeys: false
|
||||
},
|
||||
owner: {
|
||||
projectid: store.getters.project?.id,
|
||||
|
|
@ -1726,6 +1735,22 @@ export default {
|
|||
serviceOffering (oldValue, newValue) {
|
||||
if (oldValue && newValue && oldValue.id !== newValue.id) {
|
||||
this.dynamicscalingenabled = this.isDynamicallyScalable()
|
||||
// Fetch KMS keys if encryption is enabled
|
||||
if (newValue && newValue.encryptroot && this.zoneId) {
|
||||
this.fetchKmsKeys()
|
||||
}
|
||||
}
|
||||
},
|
||||
diskOffering (newValue) {
|
||||
// Fetch KMS keys if encryption is enabled
|
||||
if (newValue && newValue.encrypt && this.zoneId) {
|
||||
this.fetchKmsKeys()
|
||||
}
|
||||
},
|
||||
overrideDiskOffering (newValue) {
|
||||
// Fetch KMS keys if encryption is enabled
|
||||
if (newValue && newValue.encrypt && this.zoneId) {
|
||||
this.fetchKmsKeys()
|
||||
}
|
||||
},
|
||||
template (oldValue, newValue) {
|
||||
|
|
@ -1993,6 +2018,25 @@ export default {
|
|||
const param = this.params.networks
|
||||
this.fetchOptions(param, 'networks')
|
||||
},
|
||||
fetchKmsKeys () {
|
||||
if (!this.zoneId) {
|
||||
return
|
||||
}
|
||||
this.loading.kmsKeys = true
|
||||
this.options.kmsKeys = []
|
||||
getAPI('listKMSKeys', {
|
||||
zoneid: this.zoneId,
|
||||
account: this.owner.account,
|
||||
domainid: this.owner.domainid,
|
||||
projectid: this.owner.projectid
|
||||
}).then(response => {
|
||||
this.options.kmsKeys = response.listkmskeysresponse.kmskey || []
|
||||
}).catch(error => {
|
||||
this.$notifyError(error)
|
||||
}).finally(() => {
|
||||
this.loading.kmsKeys = false
|
||||
})
|
||||
},
|
||||
resetData () {
|
||||
this.vm = {
|
||||
name: null,
|
||||
|
|
@ -2017,6 +2061,12 @@ export default {
|
|||
this.formRef.value.resetFields()
|
||||
this.fetchData()
|
||||
},
|
||||
updateRootKmsKey (value) {
|
||||
this.form.rootkmskeyid = value
|
||||
},
|
||||
updateDataKmsKey (value) {
|
||||
this.form.datakmskeyid = value
|
||||
},
|
||||
updateFieldValue (name, value) {
|
||||
if (name === 'templateid') {
|
||||
this.imageType = 'templateid'
|
||||
|
|
@ -2380,6 +2430,10 @@ export default {
|
|||
deployVmData['details[0].memory'] = values.memory
|
||||
}
|
||||
}
|
||||
// Add root disk KMS key if selected (optional - falls back to legacy passphrase if not provided)
|
||||
if (values.rootkmskeyid) {
|
||||
deployVmData.rootdiskkmskeyid = values.rootkmskeyid
|
||||
}
|
||||
if (this.selectedTemplateConfiguration) {
|
||||
deployVmData['details[0].configurationId'] = this.selectedTemplateConfiguration.id
|
||||
}
|
||||
|
|
@ -2406,12 +2460,29 @@ export default {
|
|||
})
|
||||
}
|
||||
} else {
|
||||
deployVmData.diskofferingid = values.diskofferingid
|
||||
if (values.size) {
|
||||
deployVmData.size = values.size
|
||||
// When a KMS key is selected for data disk, we must use datadisksdetails format
|
||||
if (values.datakmskeyid) {
|
||||
deployVmData['datadisksdetails[0].diskofferingid'] = values.diskofferingid
|
||||
deployVmData['datadisksdetails[0].deviceid'] = 1 // Device ID 1 for first data disk (0=root, 3=CD-ROM reserved)
|
||||
if (values.size) {
|
||||
deployVmData['datadisksdetails[0].size'] = values.size
|
||||
}
|
||||
deployVmData['datadisksdetails[0].kmskeyid'] = values.datakmskeyid
|
||||
// Add IOPS if customized
|
||||
if (this.isCustomizedDiskIOPS) {
|
||||
deployVmData['datadisksdetails[0].miniops'] = this.diskIOpsMin
|
||||
deployVmData['datadisksdetails[0].maxiops'] = this.diskIOpsMax
|
||||
}
|
||||
} else {
|
||||
// Legacy format when no KMS key
|
||||
deployVmData.diskofferingid = values.diskofferingid
|
||||
if (values.size) {
|
||||
deployVmData.size = values.size
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.isCustomizedDiskIOPS) {
|
||||
// IOPS for non-KMS data disks (KMS data disks IOPS handled above in datadisksdetails)
|
||||
if (this.isCustomizedDiskIOPS && !values.datakmskeyid) {
|
||||
deployVmData['details[0].minIopsDo'] = this.diskIOpsMin
|
||||
deployVmData['details[0].maxIopsDo'] = this.diskIOpsMax
|
||||
}
|
||||
|
|
@ -3087,6 +3158,7 @@ export default {
|
|||
this.selectedBackupOffering = null
|
||||
this.fetchZoneOptions()
|
||||
this.updateZoneAllowsBackupOperations()
|
||||
this.fetchKmsKeys()
|
||||
},
|
||||
onSelectPodId (value) {
|
||||
this.podId = value
|
||||
|
|
|
|||
|
|
@ -16,35 +16,62 @@
|
|||
// under the License.
|
||||
|
||||
<template>
|
||||
<a-row :span="24" :style="{ marginTop: '20px' }">
|
||||
<a-col :span="isCustomizedDiskIOps || isCustomizedIOps ? 8 : 24" v-if="isCustomized">
|
||||
<a-form-item
|
||||
:label="inputDecorator === 'rootdisksize' ? $t('label.root.disk.size') : $t('label.disksize')"
|
||||
class="form-item">
|
||||
<span style="display: inline-flex">
|
||||
<a-input-number
|
||||
v-focus="true"
|
||||
v-model:value="inputValue"
|
||||
@change="($event) => updateDiskSize($event)"
|
||||
/>
|
||||
<span style="padding-top: 6px; margin-left: 5px">GB</span>
|
||||
</span>
|
||||
<p v-if="error" style="color: red"> {{ $t(error) }} </p>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8" v-if="isCustomizedDiskIOps || isCustomizedIOps">
|
||||
<a-form-item :label="$t('label.diskiopsmin')">
|
||||
<a-input-number v-model:value="minIOps" @change="updateDiskIOps" />
|
||||
<p v-if="errorMinIOps" style="color: red"> {{ $t(errorMinIOps) }} </p>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8" v-if="isCustomizedDiskIOps || isCustomizedIOps">
|
||||
<a-form-item :label="$t('label.diskiopsmax')">
|
||||
<a-input-number v-model:value="maxIOps" @change="updateDiskIOps" />
|
||||
<p v-if="errorMaxIOps" style="color: red"> {{ $t(errorMaxIOps) }} </p>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div>
|
||||
<a-row :span="24" :style="{ marginTop: '20px' }">
|
||||
<a-col :span="isCustomizedDiskIOps || isCustomizedIOps ? 8 : 24" v-if="isCustomized">
|
||||
<a-form-item
|
||||
:label="inputDecorator === 'rootdisksize' ? $t('label.root.disk.size') : $t('label.disksize')"
|
||||
class="form-item">
|
||||
<span style="display: inline-flex">
|
||||
<a-input-number
|
||||
v-focus="true"
|
||||
v-model:value="inputValue"
|
||||
@change="($event) => updateDiskSize($event)"
|
||||
/>
|
||||
<span style="padding-top: 6px; margin-left: 5px">GB</span>
|
||||
</span>
|
||||
<p v-if="error" style="color: red"> {{ $t(error) }} </p>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8" v-if="isCustomizedDiskIOps || isCustomizedIOps">
|
||||
<a-form-item :label="$t('label.diskiopsmin')">
|
||||
<a-input-number v-model:value="minIOps" @change="updateDiskIOps" />
|
||||
<p v-if="errorMinIOps" style="color: red"> {{ $t(errorMinIOps) }} </p>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8" v-if="isCustomizedDiskIOps || isCustomizedIOps">
|
||||
<a-form-item :label="$t('label.diskiopsmax')">
|
||||
<a-input-number v-model:value="maxIOps" @change="updateDiskIOps" />
|
||||
<p v-if="errorMaxIOps" style="color: red"> {{ $t(errorMaxIOps) }} </p>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :span="24" v-if="showKmsKeySelector">
|
||||
<a-col :span="24">
|
||||
<a-form-item :label="$t('label.kms.key')" class="form-item">
|
||||
<a-select
|
||||
v-model:value="selectedKmsKey"
|
||||
:loading="loadingKmsKeys"
|
||||
:placeholder="$t('label.select.kms.key.optional')"
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
allowClear
|
||||
@change="updateKmsKey">
|
||||
<a-select-option
|
||||
v-for="key in kmsKeys"
|
||||
:key="key.id"
|
||||
:value="key.id"
|
||||
:label="key.name">
|
||||
{{ key.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<p style="color: gray; font-size: 12px; margin-top: 5px">
|
||||
{{ $t('message.kms.key.optional') }}
|
||||
</p>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
@ -74,6 +101,18 @@ export default {
|
|||
isCustomized: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
kmsKeys: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
loadingKmsKeys: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
computeOfferingEncryptRoot: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
|
@ -90,6 +129,13 @@ export default {
|
|||
},
|
||||
isCustomizedIOps () {
|
||||
return this.rootDiskSelected?.iscustomizediops || false
|
||||
},
|
||||
showKmsKeySelector () {
|
||||
const isRootDisk = this.inputDecorator === 'rootdisksize'
|
||||
const isDataDisk = this.inputDecorator === 'size'
|
||||
|
||||
return (isRootDisk && (this.computeOfferingEncryptRoot || this.rootDiskSelected?.encrypt)) ||
|
||||
(isDataDisk && this.diskSelected?.encrypt)
|
||||
}
|
||||
},
|
||||
data () {
|
||||
|
|
@ -99,7 +145,8 @@ export default {
|
|||
minIOps: null,
|
||||
maxIOps: null,
|
||||
errorMinIOps: false,
|
||||
errorMaxIOps: false
|
||||
errorMaxIOps: false,
|
||||
selectedKmsKey: null
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
|
|
@ -153,6 +200,15 @@ export default {
|
|||
this.$emit('update-root-disk-iops-value', 'minIops', this.minIOps)
|
||||
this.$emit('update-root-disk-iops-value', 'maxIops', this.maxIOps)
|
||||
this.$emit('handler-error', false)
|
||||
},
|
||||
updateKmsKey (value) {
|
||||
// Emit the KMS key ID (or null if cleared)
|
||||
// Use different event names for root vs data disk
|
||||
if (this.inputDecorator === 'rootdisksize') {
|
||||
this.$emit('update-root-kms-key', value)
|
||||
} else if (this.inputDecorator === 'size') {
|
||||
this.$emit('update-data-kms-key', value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue