mirror of https://github.com/apache/cloudstack.git
Address comments
This commit is contained in:
parent
7740c5a381
commit
473983491f
|
|
@ -1038,6 +1038,7 @@ public class EventTypes {
|
|||
entityEventDetails.put(EVENT_KMS_KEY_WRAP, KMSKey.class);
|
||||
entityEventDetails.put(EVENT_KMS_KEY_DELETE, KMSKey.class);
|
||||
entityEventDetails.put(EVENT_KMS_KEY_ROTATE, KMSKey.class);
|
||||
entityEventDetails.put(EVENT_VOLUME_MIGRATE_TO_KMS, KMSKey.class);
|
||||
|
||||
// HSM Profile Events
|
||||
entityEventDetails.put(EVENT_HSM_PROFILE_CREATE, HSMProfile.class);
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
|
||||
import org.apache.cloudstack.api.response.ConsoleSessionResponse;
|
||||
import org.apache.cloudstack.api.response.KMSKeyResponse;
|
||||
import org.apache.cloudstack.consoleproxy.ConsoleSession;
|
||||
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
|
||||
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
|
||||
|
|
@ -157,7 +156,6 @@ import org.apache.cloudstack.direct.download.DirectDownloadCertificate;
|
|||
import org.apache.cloudstack.direct.download.DirectDownloadCertificateHostMap;
|
||||
import org.apache.cloudstack.direct.download.DirectDownloadManager;
|
||||
import org.apache.cloudstack.gui.theme.GuiThemeJoin;
|
||||
import org.apache.cloudstack.kms.KMSKey;
|
||||
import org.apache.cloudstack.management.ManagementServerHost;
|
||||
import org.apache.cloudstack.network.lb.ApplicationLoadBalancerRule;
|
||||
import org.apache.cloudstack.region.PortableIp;
|
||||
|
|
|
|||
|
|
@ -39,14 +39,13 @@ import javax.inject.Inject;
|
|||
import java.util.List;
|
||||
|
||||
@APICommand(name = "migrateVolumesToKMS",
|
||||
description = "Migrates passphrase-based volumes to KMS (admin only)",
|
||||
description = "Migrates encrypted volumes to KMS",
|
||||
responseObject = AsyncJobResponse.class,
|
||||
since = "4.23.0",
|
||||
authorized = {RoleType.Admin},
|
||||
requestHasSensitiveInfo = false,
|
||||
responseHasSensitiveInfo = false)
|
||||
public class MigrateVolumesToKMSCmd extends BaseAsyncCmd {
|
||||
private static final String s_name = "migratevolumestokmsresponse";
|
||||
|
||||
@Inject
|
||||
private KMSManager kmsManager;
|
||||
|
|
@ -112,11 +111,6 @@ public class MigrateVolumesToKMSCmd extends BaseAsyncCmd {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandName() {
|
||||
return s_name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
KMSKey key = _entityMgr.findById(KMSKey.class, kmsKeyId);
|
||||
|
|
@ -138,11 +132,11 @@ public class MigrateVolumesToKMSCmd extends BaseAsyncCmd {
|
|||
|
||||
@Override
|
||||
public ApiCommandResourceType getApiResourceType() {
|
||||
return ApiCommandResourceType.Zone;
|
||||
return ApiCommandResourceType.KmsKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getApiResourceId() {
|
||||
return zoneId;
|
||||
return kmsKeyId;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,6 +83,11 @@ public class DeleteKMSKeyCmd extends BaseAsyncCmd implements UserCmd {
|
|||
return ApiCommandResourceType.KmsKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getApiResourceId() {
|
||||
return getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventType() {
|
||||
return EventTypes.EVENT_KMS_KEY_DELETE;
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ import org.apache.cloudstack.kms.KMSManager;
|
|||
import javax.inject.Inject;
|
||||
|
||||
@APICommand(name = "rotateKMSKey",
|
||||
description = "Rotates KEK by creating new version and scheduling gradual re-encryption",
|
||||
description = "Rotates KMS key (KEK) by creating new version and scheduling gradual re-encryption",
|
||||
responseObject = AsyncJobResponse.class,
|
||||
since = "4.23.0",
|
||||
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User},
|
||||
|
|
@ -123,6 +123,6 @@ public class RotateKMSKeyCmd extends BaseAsyncCmd {
|
|||
|
||||
@Override
|
||||
public Long getApiResourceId() {
|
||||
return id;
|
||||
return getId();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,6 +104,11 @@ public class UpdateKMSKeyCmd extends BaseAsyncCmd implements UserCmd {
|
|||
return ApiCommandResourceType.KmsKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getApiResourceId() {
|
||||
return getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventType() {
|
||||
return EventTypes.EVENT_KMS_KEY_UPDATE;
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import com.cloud.exception.ResourceUnavailableException;
|
|||
import com.cloud.utils.StringUtils;
|
||||
import org.apache.cloudstack.acl.RoleType;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
|
|
@ -45,10 +46,10 @@ import java.util.Collection;
|
|||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@APICommand(name = "addHSMProfile", description = "Adds a new HSM profile", responseObject = HSMProfileResponse.class,
|
||||
@APICommand(name = "createHSMProfile", description = "Creates a new HSM profile", responseObject = HSMProfileResponse.class,
|
||||
requestHasSensitiveInfo = true, responseHasSensitiveInfo = true, since = "4.23.0",
|
||||
authorized = { RoleType.Admin })
|
||||
public class AddHSMProfileCmd extends BaseCmd {
|
||||
public class CreateHSMProfileCmd extends BaseCmd {
|
||||
|
||||
@Inject
|
||||
private KMSManager kmsManager;
|
||||
|
|
@ -77,10 +78,10 @@ public class AddHSMProfileCmd extends BaseCmd {
|
|||
description = "the ID of the project to add the HSM profile for")
|
||||
private Long projectId;
|
||||
|
||||
@Parameter(name = "system", type = CommandType.BOOLEAN,
|
||||
description = "whether this is a system HSM profile available to all users globally (root admin only). "
|
||||
@Parameter(name = ApiConstants.IS_PUBLIC, type = CommandType.BOOLEAN,
|
||||
description = "whether this is a public HSM profile available to all users globally (root admin only). "
|
||||
+ "Default is false")
|
||||
private Boolean system;
|
||||
private Boolean isPublic;
|
||||
|
||||
@Parameter(name = ApiConstants.VENDOR_NAME, type = CommandType.STRING, description = "the vendor name of the HSM")
|
||||
private String vendorName;
|
||||
|
|
@ -116,8 +117,8 @@ public class AddHSMProfileCmd extends BaseCmd {
|
|||
return projectId;
|
||||
}
|
||||
|
||||
public Boolean isSystem() {
|
||||
return system != null && system;
|
||||
public Boolean getIsPublic() {
|
||||
return isPublic != null && isPublic;
|
||||
}
|
||||
|
||||
public String getVendorName() {
|
||||
|
|
@ -159,4 +160,9 @@ public class AddHSMProfileCmd extends BaseCmd {
|
|||
}
|
||||
return CallContext.current().getCallingAccount().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCommandResourceType getApiResourceType() {
|
||||
return ApiCommandResourceType.HsmProfile;
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ import com.cloud.exception.ResourceAllocationException;
|
|||
import com.cloud.exception.ResourceUnavailableException;
|
||||
import org.apache.cloudstack.acl.RoleType;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
|
|
@ -78,4 +79,15 @@ public class DeleteHSMProfileCmd extends BaseCmd {
|
|||
}
|
||||
return CallContext.current().getCallingAccount().getId();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ApiCommandResourceType getApiResourceType() {
|
||||
return ApiCommandResourceType.HsmProfile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getApiResourceId() {
|
||||
return getId();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import com.cloud.exception.ResourceAllocationException;
|
|||
import com.cloud.exception.ResourceUnavailableException;
|
||||
import org.apache.cloudstack.acl.RoleType;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
|
|
@ -90,4 +91,14 @@ public class UpdateHSMProfileCmd extends BaseCmd {
|
|||
}
|
||||
return CallContext.current().getCallingAccount().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCommandResourceType getApiResourceType() {
|
||||
return ApiCommandResourceType.HsmProfile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getApiResourceId() {
|
||||
return getId();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,9 +90,9 @@ public class HSMProfileResponse extends BaseResponse implements ControlledViewEn
|
|||
@Param(description = "whether the HSM profile is enabled")
|
||||
private Boolean enabled;
|
||||
|
||||
@SerializedName("system")
|
||||
@SerializedName(ApiConstants.IS_PUBLIC)
|
||||
@Param(description = "whether this is a system HSM profile available to all users globally")
|
||||
private Boolean system;
|
||||
private Boolean isPublic;
|
||||
|
||||
@SerializedName(ApiConstants.CREATED)
|
||||
@Param(description = "the date the HSM profile was created")
|
||||
|
|
@ -168,8 +168,8 @@ public class HSMProfileResponse extends BaseResponse implements ControlledViewEn
|
|||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public void setSystem(Boolean system) {
|
||||
this.system = system;
|
||||
public void setIsPublic(Boolean isPublic) {
|
||||
this.isPublic = isPublic;
|
||||
}
|
||||
|
||||
public void setCreated(Date created) {
|
||||
|
|
|
|||
|
|
@ -310,7 +310,7 @@ public class VolumeResponse extends BaseResponseWithTagInformation implements Co
|
|||
private String encryptionFormat;
|
||||
|
||||
@SerializedName(ApiConstants.KMS_KEY)
|
||||
@Param(description = "KMS key id of the volume", since = "4.23.0")
|
||||
@Param(description = "KMS key name of the volume", since = "4.23.0")
|
||||
private String kmsKey;
|
||||
|
||||
@SerializedName(ApiConstants.KMS_KEY_ID)
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ public interface HSMProfile extends Identity, InternalIdentity, ControlledEntity
|
|||
|
||||
boolean isEnabled();
|
||||
|
||||
boolean isSystem();
|
||||
boolean getIsPublic();
|
||||
|
||||
Date getCreated();
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import org.apache.cloudstack.api.command.user.kms.CreateKMSKeyCmd;
|
|||
import org.apache.cloudstack.api.command.user.kms.DeleteKMSKeyCmd;
|
||||
import org.apache.cloudstack.api.command.user.kms.ListKMSKeysCmd;
|
||||
import org.apache.cloudstack.api.command.user.kms.UpdateKMSKeyCmd;
|
||||
import org.apache.cloudstack.api.command.user.kms.hsm.AddHSMProfileCmd;
|
||||
import org.apache.cloudstack.api.command.user.kms.hsm.CreateHSMProfileCmd;
|
||||
import org.apache.cloudstack.api.command.user.kms.hsm.DeleteHSMProfileCmd;
|
||||
import org.apache.cloudstack.api.command.user.kms.hsm.ListHSMProfilesCmd;
|
||||
import org.apache.cloudstack.api.command.user.kms.hsm.UpdateHSMProfileCmd;
|
||||
|
|
@ -231,7 +231,7 @@ public interface KMSManager extends Manager, Configurable {
|
|||
* @return the created HSM profile
|
||||
* @throws KMSException if addition fails
|
||||
*/
|
||||
HSMProfile addHSMProfile(AddHSMProfileCmd cmd) throws KMSException;
|
||||
HSMProfile addHSMProfile(CreateHSMProfileCmd cmd) throws KMSException;
|
||||
|
||||
/**
|
||||
* List HSM profiles
|
||||
|
|
|
|||
|
|
@ -1937,61 +1937,62 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
|||
return kmsKeyDao.findById(volume.getKmsKeyId());
|
||||
}
|
||||
|
||||
private VolumeVO setPassphraseForVolumeEncryption(VolumeVO volume) {
|
||||
return setPassphraseForVolumeEncryption(volume, null, null);
|
||||
}
|
||||
private VolumeVO setKmsKeyForVolumeEncryption(VolumeVO volume, KMSKeyVO kmsKey, Long callerAccountId) {
|
||||
// Determine caller account ID if not provided
|
||||
if (callerAccountId == null) {
|
||||
callerAccountId = volume.getAccountId();
|
||||
}
|
||||
|
||||
// Validate permission
|
||||
if (!kmsManager.hasPermission(callerAccountId, kmsKey)) {
|
||||
throw new CloudRuntimeException("No permission to use KMS key: " + kmsKey);
|
||||
}
|
||||
|
||||
try {
|
||||
logger.debug("Generating and wrapping DEK for volume {} using KMS key {}", volume.getName(), kmsKey.getUuid());
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
// Generate and wrap DEK using active KEK version
|
||||
WrappedKey wrappedKey = kmsManager.generateVolumeKeyWithKek(kmsKey, callerAccountId);
|
||||
|
||||
// The wrapped key is already persisted by generateVolumeKeyWithKek, get its ID
|
||||
KMSWrappedKeyVO wrappedKeyVO = kmsWrappedKeyDao.findByUuid(wrappedKey.getUuid());
|
||||
if (wrappedKeyVO == null) {
|
||||
throw new CloudRuntimeException("Failed to find persisted wrapped key: " + wrappedKey.getUuid());
|
||||
}
|
||||
|
||||
// Set the wrapped key ID on the volume
|
||||
volume.setKmsWrappedKeyId(wrappedKeyVO.getId());
|
||||
|
||||
long finishTime = System.currentTimeMillis();
|
||||
logger.debug("Generating and persisting wrapped key took {} ms for volume: {}",
|
||||
(finishTime - startTime), volume.getName());
|
||||
|
||||
return _volsDao.persist(volume);
|
||||
|
||||
} catch (KMSException e) {
|
||||
throw new CloudRuntimeException("KMS failure while setting up volume encryption: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
private VolumeVO setPassphraseForVolumeEncryption(VolumeVO volume, KMSKeyVO kmsKey, Long callerAccountId) {
|
||||
// If volume already has encryption set up, return it
|
||||
if (volume.getKmsWrappedKeyId() != null || volume.getPassphraseId() != null) {
|
||||
return volume;
|
||||
}
|
||||
|
||||
if (kmsKey != null) {
|
||||
// Determine caller account ID if not provided
|
||||
if (callerAccountId == null) {
|
||||
callerAccountId = volume.getAccountId();
|
||||
}
|
||||
|
||||
// Validate permission
|
||||
if (!kmsManager.hasPermission(callerAccountId, kmsKey)) {
|
||||
throw new CloudRuntimeException("No permission to use KMS key: " + kmsKey);
|
||||
}
|
||||
|
||||
try {
|
||||
logger.debug("Generating and wrapping DEK for volume {} using KMS key {}", volume.getName(), kmsKey.getUuid());
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
// Generate and wrap DEK using active KEK version
|
||||
WrappedKey wrappedKey = kmsManager.generateVolumeKeyWithKek(kmsKey, callerAccountId);
|
||||
|
||||
// The wrapped key is already persisted by generateVolumeKeyWithKek, get its ID
|
||||
KMSWrappedKeyVO wrappedKeyVO = kmsWrappedKeyDao.findByUuid(wrappedKey.getUuid());
|
||||
if (wrappedKeyVO == null) {
|
||||
throw new CloudRuntimeException("Failed to find persisted wrapped key: " + wrappedKey.getUuid());
|
||||
}
|
||||
|
||||
// Set the wrapped key ID on the volume
|
||||
volume.setKmsWrappedKeyId(wrappedKeyVO.getId());
|
||||
|
||||
long finishTime = System.currentTimeMillis();
|
||||
logger.debug("Generating and persisting wrapped key took {} ms for volume: {}",
|
||||
(finishTime - startTime), volume.getName());
|
||||
|
||||
return _volsDao.persist(volume);
|
||||
|
||||
} catch (KMSException e) {
|
||||
throw new CloudRuntimeException("KMS failure while setting up volume encryption: " + e.getMessage(), e);
|
||||
}
|
||||
return setKmsKeyForVolumeEncryption(volume, kmsKey, callerAccountId);
|
||||
}
|
||||
|
||||
// Legacy: passphrase-based encryption (fallback when KMS not enabled or KMS key not specified)
|
||||
logger.debug("Creating passphrase for the volume: " + volume.getName());
|
||||
return setPassphraseForVolumeEncryption(volume);
|
||||
}
|
||||
|
||||
private VolumeVO setPassphraseForVolumeEncryption(VolumeVO volume) {
|
||||
logger.debug("Creating passphrase for the volume: {}", volume.getName());
|
||||
long startTime = System.currentTimeMillis();
|
||||
PassphraseVO passphrase = passphraseDao.persist(new PassphraseVO(true));
|
||||
volume.setPassphraseId(passphrase.getId());
|
||||
long finishTime = System.currentTimeMillis();
|
||||
logger.debug("Creating and persisting passphrase took: " + (finishTime - startTime) + " ms for the volume: " + volume.toString());
|
||||
logger.debug("Creating and persisting passphrase took: {} ms for the volume: {}", finishTime - startTime, volume.toString());
|
||||
return _volsDao.persist(volume);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -61,8 +61,8 @@ public class HSMProfileVO implements HSMProfile {
|
|||
@Column(name = "enabled")
|
||||
private boolean enabled;
|
||||
|
||||
@Column(name = "system")
|
||||
private boolean system;
|
||||
@Column(name = "is_public")
|
||||
private boolean isPublic;
|
||||
|
||||
@Column(name = "created")
|
||||
private Date created;
|
||||
|
|
@ -73,7 +73,7 @@ public class HSMProfileVO implements HSMProfile {
|
|||
public HSMProfileVO() {
|
||||
this.uuid = UUID.randomUUID().toString();
|
||||
this.created = new Date();
|
||||
this.system = false;
|
||||
this.isPublic = false;
|
||||
}
|
||||
|
||||
public HSMProfileVO(String name, String protocol, Long accountId, Long domainId, Long zoneId, String vendorName) {
|
||||
|
|
@ -85,7 +85,7 @@ public class HSMProfileVO implements HSMProfile {
|
|||
this.zoneId = zoneId;
|
||||
this.vendorName = vendorName;
|
||||
this.enabled = true;
|
||||
this.system = false;
|
||||
this.isPublic = false;
|
||||
this.created = new Date();
|
||||
}
|
||||
|
||||
|
|
@ -173,11 +173,11 @@ public class HSMProfileVO implements HSMProfile {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isSystem() {
|
||||
return system;
|
||||
public boolean getIsPublic() {
|
||||
return isPublic;
|
||||
}
|
||||
|
||||
public void setSystem(boolean system) {
|
||||
this.system = system;
|
||||
public void setIsPublic(boolean isPublic) {
|
||||
this.isPublic = isPublic;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ CREATE TABLE IF NOT EXISTS `cloud`.`kms_hsm_profiles` (
|
|||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`uuid` VARCHAR(40) NOT NULL,
|
||||
`name` VARCHAR(255) NOT NULL,
|
||||
`protocol` VARCHAR(32) NOT NULL COMMENT 'PKCS11, KMIP, AWS_KMS, etc.',
|
||||
`protocol` VARCHAR(32) NOT NULL COMMENT 'Protocol for the HSM Profile',
|
||||
|
||||
-- Scoping
|
||||
`account_id` BIGINT UNSIGNED COMMENT 'null = admin-provided (available to all accounts)',
|
||||
|
|
@ -129,9 +129,9 @@ CREATE TABLE IF NOT EXISTS `cloud`.`kms_hsm_profiles` (
|
|||
`zone_id` BIGINT UNSIGNED COMMENT 'null = global scope',
|
||||
|
||||
-- Metadata
|
||||
`vendor_name` VARCHAR(64) COMMENT 'HSM vendor (Thales, AWS, SoftHSM, etc.)',
|
||||
`vendor_name` VARCHAR(64) COMMENT 'HSM vendor',
|
||||
`enabled` BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
`system` BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'System profile (globally available, root admin only)',
|
||||
`is_public` BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'Public profile. Available to all accounts',
|
||||
`created` DATETIME NOT NULL,
|
||||
`removed` DATETIME,
|
||||
|
||||
|
|
@ -159,7 +159,7 @@ CREATE TABLE IF NOT EXISTS `cloud`.`kms_hsm_profile_details` (
|
|||
-- KMS Keys (Key Encryption Key Metadata)
|
||||
-- Account-scoped KEKs for envelope encryption
|
||||
CREATE TABLE IF NOT EXISTS `cloud`.`kms_keys` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'Unique ID',
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`uuid` VARCHAR(40) NOT NULL COMMENT 'UUID - user-facing identifier',
|
||||
`name` VARCHAR(255) NOT NULL COMMENT 'User-friendly name',
|
||||
`description` VARCHAR(1024) COMMENT 'User description',
|
||||
|
|
@ -182,13 +182,13 @@ CREATE TABLE IF NOT EXISTS `cloud`.`kms_keys` (
|
|||
CONSTRAINT `fk_kms_keys__account_id` FOREIGN KEY (`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `fk_kms_keys__domain_id` FOREIGN KEY (`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `fk_kms_keys__zone_id` FOREIGN KEY (`zone_id`) REFERENCES `data_center`(`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `fk_kms_keys__hsm_profile_id` FOREIGN KEY (`hsm_profile_id`) REFERENCES `kms_hsm_profiles`(`id`)
|
||||
CONSTRAINT `fk_kms_keys__hsm_profile_id` FOREIGN KEY (`hsm_profile_id`) REFERENCES `kms_hsm_profiles`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='KMS Key (KEK) metadata - account-scoped keys for envelope encryption';
|
||||
|
||||
-- KMS KEK Versions (multiple KEKs per KMS key for gradual rotation)
|
||||
-- Supports multiple KEK versions per logical KMS key during rotation
|
||||
CREATE TABLE IF NOT EXISTS `cloud`.`kms_kek_versions` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'Unique ID',
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`uuid` VARCHAR(40) NOT NULL COMMENT 'UUID',
|
||||
`kms_key_id` BIGINT UNSIGNED NOT NULL COMMENT 'Reference to kms_keys table',
|
||||
`version_number` INT NOT NULL COMMENT 'Version number (1, 2, 3, ...)',
|
||||
|
|
@ -203,13 +203,13 @@ CREATE TABLE IF NOT EXISTS `cloud`.`kms_kek_versions` (
|
|||
INDEX `idx_kms_key_status` (`kms_key_id`, `status`, `removed`),
|
||||
INDEX `idx_kek_label` (`kek_label`),
|
||||
CONSTRAINT `fk_kms_kek_versions__kms_key_id` FOREIGN KEY (`kms_key_id`) REFERENCES `kms_keys`(`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `fk_kms_kek_versions__hsm_profile_id` FOREIGN KEY (`hsm_profile_id`) REFERENCES `kms_hsm_profiles`(`id`)
|
||||
CONSTRAINT `fk_kms_kek_versions__hsm_profile_id` FOREIGN KEY (`hsm_profile_id`) REFERENCES `kms_hsm_profiles`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='KEK versions for a KMS key - supports gradual rotation';
|
||||
|
||||
-- KMS Wrapped Keys (Data Encryption Keys)
|
||||
-- Generic table for wrapped DEKs - references kms_keys for metadata and kek_versions for specific KEK version
|
||||
CREATE TABLE IF NOT EXISTS `cloud`.`kms_wrapped_key` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'Unique ID',
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`uuid` VARCHAR(40) NOT NULL COMMENT 'UUID',
|
||||
`kms_key_id` BIGINT UNSIGNED COMMENT 'Reference to kms_keys table',
|
||||
`kek_version_id` BIGINT UNSIGNED COMMENT 'Reference to kms_kek_versions table',
|
||||
|
|
|
|||
|
|
@ -351,7 +351,7 @@ public class DatabaseKMSProvider extends AdapterBase implements KMSProvider {
|
|||
try {
|
||||
SearchBuilder<HSMProfileVO> sb = hsmProfileDao.createSearchBuilder();
|
||||
sb.and("name", sb.entity().getName(), SearchCriteria.Op.EQ);
|
||||
sb.and("system", sb.entity().isSystem(), SearchCriteria.Op.EQ);
|
||||
sb.and("system", sb.entity().getIsPublic(), SearchCriteria.Op.EQ);
|
||||
sb.and("protocol", sb.entity().getProtocol(), SearchCriteria.Op.EQ);
|
||||
sb.done();
|
||||
|
||||
|
|
@ -369,7 +369,7 @@ public class DatabaseKMSProvider extends AdapterBase implements KMSProvider {
|
|||
HSMProfileVO profile = new HSMProfileVO(DEFAULT_PROFILE_NAME, PROVIDER_NAME,
|
||||
SYSTEM_ACCOUNT_ID, ROOT_DOMAIN_ID, null, null);
|
||||
profile.setEnabled(false);
|
||||
profile.setSystem(true);
|
||||
profile.setIsPublic(true);
|
||||
hsmProfileDao.persist(profile);
|
||||
logger.info("Seeded default database HSM profile (id={}, uuid={})", profile.getId(), profile.getUuid());
|
||||
} catch (Exception e) {
|
||||
|
|
|
|||
|
|
@ -46,17 +46,17 @@ import com.cloud.utils.db.SearchCriteria;
|
|||
import com.cloud.utils.db.Transaction;
|
||||
import com.cloud.utils.db.TransactionCallbackWithException;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.api.command.admin.kms.MigrateVolumesToKMSCmd;
|
||||
import org.apache.cloudstack.api.command.user.kms.CreateKMSKeyCmd;
|
||||
import org.apache.cloudstack.api.command.user.kms.DeleteKMSKeyCmd;
|
||||
import org.apache.cloudstack.api.command.user.kms.ListKMSKeysCmd;
|
||||
import org.apache.cloudstack.api.command.user.kms.RotateKMSKeyCmd;
|
||||
import org.apache.cloudstack.api.command.user.kms.UpdateKMSKeyCmd;
|
||||
import org.apache.cloudstack.api.command.user.kms.hsm.AddHSMProfileCmd;
|
||||
import org.apache.cloudstack.api.command.user.kms.hsm.CreateHSMProfileCmd;
|
||||
import org.apache.cloudstack.api.command.user.kms.hsm.DeleteHSMProfileCmd;
|
||||
import org.apache.cloudstack.api.command.user.kms.hsm.ListHSMProfilesCmd;
|
||||
import org.apache.cloudstack.api.command.user.kms.hsm.UpdateHSMProfileCmd;
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.api.response.HSMProfileResponse;
|
||||
import org.apache.cloudstack.api.response.KMSKeyResponse;
|
||||
import org.apache.cloudstack.api.response.ListResponse;
|
||||
|
|
@ -196,6 +196,7 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
}
|
||||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_KMS_KEY_UNWRAP, eventDescription = "unwrapping key")
|
||||
public byte[] unwrapKey(Long wrappedKeyId) throws KMSException {
|
||||
KMSWrappedKeyVO wrappedVO = kmsWrappedKeyDao.findById(wrappedKeyId);
|
||||
if (wrappedVO == null) {
|
||||
|
|
@ -212,8 +213,14 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
if (version != null && version.getStatus() != KMSKekVersionVO.Status.Archived) {
|
||||
try {
|
||||
byte[] dek = getUnwrappedKey(wrappedVO, kmsKey, version);
|
||||
logger.debug("Successfully unwrapped key {} with KEK version {}", wrappedKeyId,
|
||||
version.getVersionNumber());
|
||||
|
||||
CallContext.current().setEventResourceId(kmsKey.getId());
|
||||
CallContext.current().setEventResourceType(ApiCommandResourceType.KmsKey);
|
||||
CallContext.current().setEventDetails(String.format(
|
||||
"Unwrapped %s key (wrapped key uuid: %s) using KMS key: %s (uuid: %s) with KEK version %d",
|
||||
kmsKey.getPurpose().getName(), wrappedVO.getUuid(), kmsKey.getName(), kmsKey.getUuid(), version.getVersionNumber()));
|
||||
|
||||
logger.debug("Successfully unwrapped key {} with KEK version {}", wrappedKeyId, version.getVersionNumber());
|
||||
return dek;
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to unwrap with version {}: {}", version.getVersionNumber(), e.getMessage());
|
||||
|
|
@ -226,8 +233,15 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
for (KMSKekVersionVO version : versions) {
|
||||
try {
|
||||
byte[] dek = getUnwrappedKey(wrappedVO, kmsKey, version);
|
||||
logger.info("Successfully unwrapped key {} with KEK version {} (fallback)", wrappedKeyId,
|
||||
version.getVersionNumber());
|
||||
|
||||
CallContext.current().setEventResourceId(kmsKey.getId());
|
||||
CallContext.current().setEventResourceType(ApiCommandResourceType.KmsKey);
|
||||
CallContext.current().setEventDetails(String.format(
|
||||
"Unwrapped %s key (wrapped key uuid: %s) using KMS key: %s (uuid: %s) with KEK version %d (fallback)",
|
||||
kmsKey.getPurpose().getName(), wrappedVO.getUuid(), kmsKey.getName(), kmsKey.getUuid(), version.getVersionNumber()));
|
||||
|
||||
logger.info("Successfully unwrapped key {} with KEK version {} (fallback)",
|
||||
wrappedKeyId, version.getVersionNumber());
|
||||
return dek;
|
||||
} catch (Exception e) {
|
||||
logger.debug("Failed to unwrap with version {}: {}", version.getVersionNumber(), e.getMessage());
|
||||
|
|
@ -249,8 +263,7 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
}
|
||||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_KMS_KEY_WRAP,
|
||||
eventDescription = "generating volume key with specified KEK")
|
||||
@ActionEvent(eventType = EventTypes.EVENT_KMS_KEY_WRAP, eventDescription = "generating key with specified KEK")
|
||||
public WrappedKey generateVolumeKeyWithKek(KMSKey kmsKey, Long callerAccountId) throws KMSException {
|
||||
if (kmsKey == null) {
|
||||
throw KMSException.kekNotFound("KMS key not found");
|
||||
|
|
@ -299,8 +312,14 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
throw handleKmsException(e);
|
||||
}
|
||||
|
||||
logger.debug("Generated volume key using KMS key {} with KEK version {}, wrapped key UUID: {}",
|
||||
kmsKey, activeVersion.getVersionNumber(), wrappedKey.getUuid());
|
||||
CallContext.current().setEventResourceId(kmsKey.getId());
|
||||
CallContext.current().setEventResourceType(ApiCommandResourceType.KmsKey);
|
||||
CallContext.current().setEventDetails(String.format(
|
||||
"Generated %s key (wrapped key uuid: %s) using KMS key: %s (uuid: %s) with KEK version %d",
|
||||
kmsKey.getPurpose().getName(), wrappedKey.getUuid(), kmsKey.getName(), kmsKey.getUuid(), activeVersion.getVersionNumber()));
|
||||
|
||||
logger.debug("Generated {} key using KMS key {} with KEK version {}, wrapped key UUID: {}",
|
||||
kmsKey.getPurpose().getName(), kmsKey, activeVersion.getVersionNumber(), wrappedKey.getUuid());
|
||||
return wrappedKey;
|
||||
}
|
||||
|
||||
|
|
@ -313,6 +332,7 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
}
|
||||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_KMS_KEY_CREATE, eventDescription = "creating user KMS key")
|
||||
public KMSKeyResponse createKMSKey(CreateKMSKeyCmd cmd) throws KMSException {
|
||||
Account caller = CallContext.current().getCallingAccount();
|
||||
Account targetAccount = accountManager.finalizeOwner(caller, cmd.getAccountName(), cmd.getDomainId(),
|
||||
|
|
@ -341,6 +361,10 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
bits,
|
||||
cmd.getHsmProfileId());
|
||||
|
||||
CallContext.current().setEventResourceId(kmsKey.getId());
|
||||
CallContext.current().setEventResourceType(ApiCommandResourceType.KmsKey);
|
||||
CallContext.current().setEventDetails(String.format("Created KMS key: %s (uuid: %s)", kmsKey.getName(), kmsKey.getUuid()));
|
||||
|
||||
return createKMSKeyResponse(kmsKey);
|
||||
}
|
||||
|
||||
|
|
@ -383,7 +407,6 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
return response;
|
||||
}
|
||||
|
||||
@ActionEvent(eventType = EventTypes.EVENT_KMS_KEY_CREATE, eventDescription = "creating user KMS key")
|
||||
KMSKey createUserKMSKey(Long accountId, Long domainId, Long zoneId,
|
||||
String name, String description, KeyPurpose purpose,
|
||||
Integer keyBits, long hsmProfileId) throws KMSException {
|
||||
|
|
@ -419,10 +442,6 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
|
||||
logger.info("Created KMS key ({}) with initial KEK version {} for account {} in zone {} (profile: {})",
|
||||
kmsKey, initialVersion.getVersionNumber(), accountId, zoneId, finalProfileId);
|
||||
ActionEventUtils.onCompletedActionEvent(CallContext.current().getCallingUserId(), kmsKey.getAccountId(),
|
||||
EventVO.LEVEL_INFO, EventTypes.EVENT_KMS_KEY_CREATE,
|
||||
String.format("Created KMS key: %s", kmsKey.getUuid()),
|
||||
kmsKey.getId(), ApiCommandResourceType.KmsKey.toString(), CallContext.current().getStartEventId());
|
||||
return kmsKey;
|
||||
}
|
||||
|
||||
|
|
@ -505,6 +524,7 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
Account caller = CallContext.current().getCallingAccount();
|
||||
KMSKeyVO key = findKMSKeyAndCheckAccess(cmd.getId(), caller);
|
||||
KMSKey updatedKey = updateUserKMSKey(key, cmd.getName(), cmd.getDescription(), cmd.getEnabled());
|
||||
CallContext.current().setEventDetails(String.format("Updated KMS key: %s (uuid: %s)", updatedKey.getName(), updatedKey.getUuid()));
|
||||
return createKMSKeyResponse(updatedKey);
|
||||
}
|
||||
|
||||
|
|
@ -536,7 +556,9 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
Account caller = CallContext.current().getCallingAccount();
|
||||
|
||||
KMSKeyVO key = findKMSKeyAndCheckAccess(cmd.getId(), caller);
|
||||
|
||||
CallContext.current().setEventResourceId(key.getId());
|
||||
CallContext.current().setEventResourceType(ApiCommandResourceType.KmsKey);
|
||||
CallContext.current().setEventDetails(String.format("Deleted KMS key: %s (uuid: %s)", key.getName(), key.getUuid()));
|
||||
deleteUserKMSKey(key);
|
||||
return new SuccessResponse();
|
||||
}
|
||||
|
|
@ -566,6 +588,10 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
} catch (Exception e) {
|
||||
logger.warn("Failed to delete KEK {} (v{}) from provider during KMS key deletion: {}",
|
||||
kekVersion.getKekLabel(), kekVersion.getVersionNumber(), e.getMessage());
|
||||
ActionEventUtils.onCompletedActionEvent(CallContext.current().getCallingUserId(),
|
||||
key.getAccountId(), EventVO.LEVEL_WARN, EventTypes.EVENT_KMS_KEY_DELETE, true,
|
||||
String.format("Failed to delete KEK %s from provider during KMS key deletion", kekVersion.getKekLabel()),
|
||||
key.getId(), ApiCommandResourceType.KmsKey.toString(), CallContext.current().getStartEventId());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -574,7 +600,6 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
}
|
||||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_KMS_KEY_ROTATE, eventDescription = "rotating KMS key", async = true)
|
||||
public String rotateKMSKey(RotateKMSKeyCmd cmd) throws KMSException {
|
||||
Account caller = CallContext.current().getCallingAccount();
|
||||
Integer keyBits = cmd.getKeyBits();
|
||||
|
|
@ -609,18 +634,13 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
profile);
|
||||
|
||||
KMSKekVersionVO newVersion = getActiveKekVersion(kmsKey.getId());
|
||||
CallContext.current().setEventDetails(String.format("Rotated KMS key: %s (uuid: %s) from version %d to %d", kmsKey.getName(), kmsKey.getUuid(), currentActive.getVersionNumber(), newVersion.getVersionNumber()));
|
||||
|
||||
logger.info("KMS key rotation initiated: {} -> new KEK version {} (UUID: {}). " +
|
||||
"Background job will gradually rewrap {} wrapped key(s)",
|
||||
kmsKey, newVersion.getVersionNumber(), newVersion.getUuid(),
|
||||
kmsWrappedKeyDao.countByKmsKeyId(kmsKey.getId()));
|
||||
|
||||
ActionEventUtils.onCompletedActionEvent(CallContext.current().getCallingUserId(), kmsKey.getAccountId(),
|
||||
EventVO.LEVEL_INFO, EventTypes.EVENT_KMS_KEY_ROTATE,
|
||||
String.format("KMS key rotation completed for KMS key from version %d to version %d",
|
||||
currentActive.getVersionNumber(), newVersion.getVersionNumber()),
|
||||
kmsKey.getId(), ApiCommandResourceType.KmsKey.toString(), CallContext.current().getStartEventId());
|
||||
|
||||
return newVersion.getUuid();
|
||||
}
|
||||
|
||||
|
|
@ -686,6 +706,10 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
} catch (KMSException ex) {
|
||||
logger.error("Failed to delete orphaned KEK {} from provider {} after DB failure: {}",
|
||||
newKekId, provider.getProviderName(), ex.getMessage());
|
||||
ActionEventUtils.onCompletedActionEvent(CallContext.current().getCallingUserId(),
|
||||
kmsKey.getAccountId(), EventVO.LEVEL_WARN, EventTypes.EVENT_KMS_KEY_ROTATE, true,
|
||||
String.format("Failed to delete orphaned KEK %s from provider after DB rollback failure", newKekId),
|
||||
kmsKey.getId(), ApiCommandResourceType.KmsKey.toString(), CallContext.current().getStartEventId());
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
|
@ -717,9 +741,6 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
}
|
||||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_MIGRATE_TO_KMS,
|
||||
eventDescription = "migrating volumes to KMS",
|
||||
async = true)
|
||||
public int migrateVolumesToKMS(MigrateVolumesToKMSCmd cmd) throws KMSException {
|
||||
Account caller = CallContext.current().getCallingAccount();
|
||||
Long zoneId = cmd.getZoneId();
|
||||
|
|
@ -826,6 +847,8 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
logger.info("Migration operation completed: {} total volumes processed, {} success, {} failures",
|
||||
successCount + failureCount, successCount, failureCount);
|
||||
|
||||
CallContext.current().setEventDetails(String.format("Migrated %d volumes to KMS key: %s (uuid: %s). Success: %d, Failures: %d", successCount + failureCount, kmsKey.getName(), kmsKey.getUuid(), successCount, failureCount));
|
||||
|
||||
return successCount;
|
||||
}
|
||||
|
||||
|
|
@ -861,6 +884,11 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
volume.setKmsKeyId(kmsKey.getId());
|
||||
volume.setPassphraseId(null);
|
||||
volumeDao.update(volume.getId(), volume);
|
||||
|
||||
ActionEventUtils.onCompletedActionEvent(CallContext.current().getCallingUserId(),
|
||||
kmsKey.getAccountId(), EventVO.LEVEL_INFO, EventTypes.EVENT_VOLUME_MIGRATE_TO_KMS, true,
|
||||
String.format("Successfully migrated volume encryption key to KMS key %s (v%d)", kmsKey.getUuid(), activeVersion.getVersionNumber()),
|
||||
volume.getId(), ApiCommandResourceType.Volume.toString(), CallContext.current().getStartEventId());
|
||||
return true;
|
||||
} finally {
|
||||
if (passphraseBytes != null) {
|
||||
|
|
@ -903,6 +931,10 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
} catch (Exception e) {
|
||||
logger.warn("Failed to delete KEK {} from provider: {}",
|
||||
kekVersion.getKekLabel(), e.getMessage());
|
||||
ActionEventUtils.onCompletedActionEvent(CallContext.current().getCallingUserId(),
|
||||
key.getAccountId(), EventVO.LEVEL_WARN, EventTypes.EVENT_KMS_KEY_DELETE, true,
|
||||
String.format("Failed to delete KEK %s from provider during account KMS cleanup", kekVersion.getKekLabel()),
|
||||
key.getId(), ApiCommandResourceType.KmsKey.toString(), CallContext.current().getStartEventId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -938,7 +970,7 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_HSM_PROFILE_CREATE, eventDescription = "Adding HSM profile")
|
||||
public HSMProfile addHSMProfile(AddHSMProfileCmd cmd) throws KMSException {
|
||||
public HSMProfile addHSMProfile(CreateHSMProfileCmd cmd) throws KMSException {
|
||||
Account caller = CallContext.current().getCallingAccount();
|
||||
|
||||
String protocol = cmd.getProtocol();
|
||||
|
|
@ -956,8 +988,8 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
Map<String, String> details = cmd.getDetails() != null ? cmd.getDetails() : new HashMap<>();
|
||||
provider.validateProfileConfig(details);
|
||||
|
||||
boolean isSystem = cmd.isSystem();
|
||||
if (isSystem && !accountManager.isRootAdmin(caller.getId())) {
|
||||
boolean isPublic = cmd.getIsPublic();
|
||||
if (isPublic && !accountManager.isRootAdmin(caller.getId())) {
|
||||
throw new PermissionDeniedException("Only root admins can create system HSM profiles");
|
||||
}
|
||||
|
||||
|
|
@ -974,7 +1006,7 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
domainId,
|
||||
cmd.getZoneId(),
|
||||
cmd.getVendorName());
|
||||
profile.setSystem(isSystem);
|
||||
profile.setIsPublic(isPublic);
|
||||
profile = hsmProfileDao.persist(profile);
|
||||
|
||||
if (cmd.getDetails() != null) {
|
||||
|
|
@ -990,10 +1022,9 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
}
|
||||
}
|
||||
|
||||
ActionEventUtils.onCompletedActionEvent(CallContext.current().getCallingUserId(), profile.getAccountId(),
|
||||
EventVO.LEVEL_INFO, EventTypes.EVENT_HSM_PROFILE_CREATE,
|
||||
String.format("created HSM profile with id: %s, name: %s", profile.getUuid(), profile.getName()),
|
||||
profile.getId(), ApiCommandResourceType.HsmProfile.toString(), CallContext.current().getStartEventId());
|
||||
CallContext.current().setEventResourceId(profile.getId());
|
||||
CallContext.current().setEventResourceType(ApiCommandResourceType.HsmProfile);
|
||||
CallContext.current().setEventDetails(String.format("Created HSM profile: %s (uuid: %s)", profile.getName(), profile.getUuid()));
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
|
@ -1035,7 +1066,7 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
// profiles
|
||||
// If the profile is owned by the user, they should see full details even if it
|
||||
// is a system profile
|
||||
boolean limited = profile.isSystem() && !isRootAdmin && !(cmd.getIsSystem() || cmd.listAll())
|
||||
boolean limited = profile.getIsPublic() && !isRootAdmin && !(cmd.getIsSystem() || cmd.listAll())
|
||||
&& profile.getAccountId() != caller.getId();
|
||||
responses.add(createHSMProfileResponse(profile, limited));
|
||||
}
|
||||
|
|
@ -1055,7 +1086,7 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
sb.and("zoneId", sb.entity().getZoneId(), SearchCriteria.Op.EQ);
|
||||
sb.and("protocol", sb.entity().getProtocol(), SearchCriteria.Op.EQ);
|
||||
sb.and("enabled", sb.entity().isEnabled(), SearchCriteria.Op.EQ);
|
||||
sb.and("system", sb.entity().isSystem(), SearchCriteria.Op.EQ);
|
||||
sb.and("system", sb.entity().getIsPublic(), SearchCriteria.Op.EQ);
|
||||
sb.done();
|
||||
return sb;
|
||||
}
|
||||
|
|
@ -1148,12 +1179,12 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
|
||||
getKMSProvider(profile.getProtocol()).invalidateProfileCache(profile.getId());
|
||||
hsmProfileDetailsDao.deleteDetails(profile.getId());
|
||||
|
||||
CallContext.current().setEventResourceId(profile.getId());
|
||||
CallContext.current().setEventResourceType(ApiCommandResourceType.HsmProfile);
|
||||
CallContext.current().setEventDetails(String.format("Deleted HSM profile: %s (uuid: %s)", profile.getName(), profile.getUuid()));
|
||||
|
||||
if (hsmProfileDao.remove(profile.getId())) {
|
||||
ActionEventUtils.onCompletedActionEvent(CallContext.current().getCallingUserId(), profile.getAccountId(),
|
||||
EventVO.LEVEL_INFO, EventTypes.EVENT_HSM_PROFILE_DELETE,
|
||||
String.format("Deleted HSM profile with id: %s, name: %s", profile.getUuid(), profile.getName()),
|
||||
profile.getId(), ApiCommandResourceType.HsmProfile.toString(),
|
||||
CallContext.current().getStartEventId());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -1175,10 +1206,10 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
|
||||
hsmProfileDao.update(profile.getId(), profile);
|
||||
|
||||
ActionEventUtils.onCompletedActionEvent(CallContext.current().getCallingUserId(), profile.getAccountId(),
|
||||
EventVO.LEVEL_INFO, EventTypes.EVENT_HSM_PROFILE_UPDATE,
|
||||
String.format("Updated HSM profile with id: %s, name: %s", profile.getUuid(), profile.getName()),
|
||||
profile.getId(), ApiCommandResourceType.HsmProfile.toString(), CallContext.current().getStartEventId());
|
||||
CallContext.current().setEventResourceId(profile.getId());
|
||||
CallContext.current().setEventResourceType(ApiCommandResourceType.HsmProfile);
|
||||
CallContext.current().setEventDetails(String.format("Updated HSM profile: %s (uuid: %s)", profile.getName(), profile.getUuid()));
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
|
|
@ -1192,7 +1223,7 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
response.setId(profile.getUuid());
|
||||
response.setName(profile.getName());
|
||||
response.setVendorName(profile.getVendorName());
|
||||
response.setSystem(profile.isSystem());
|
||||
response.setIsPublic(profile.getIsPublic());
|
||||
|
||||
if (profile.getZoneId() != null) {
|
||||
DataCenterVO zone = dataCenterDao.findById(profile.getZoneId());
|
||||
|
|
@ -1245,7 +1276,7 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
* For owned profiles: delegates to ACL checkAccess.
|
||||
*/
|
||||
void checkHSMProfileAccess(Account caller, HSMProfileVO profile, boolean requireModifyAccess) {
|
||||
if (profile.isSystem()) {
|
||||
if (profile.getIsPublic()) {
|
||||
if (requireModifyAccess && !accountManager.isRootAdmin(caller.getId())) {
|
||||
throw new PermissionDeniedException("Only root admins can modify system HSM profiles");
|
||||
}
|
||||
|
|
@ -1502,10 +1533,19 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
provider.deleteKek(oldVersion.getKekLabel());
|
||||
logger.info("Deleted archived KEK {} (v{}) from provider {}",
|
||||
oldVersion.getKekLabel(), oldVersion.getVersionNumber(), provider.getProviderName());
|
||||
|
||||
ActionEventUtils.onCompletedActionEvent(CallContext.current().getCallingUserId(),
|
||||
kmsKey.getAccountId(), EventVO.LEVEL_INFO, EventTypes.EVENT_KMS_KEY_DELETE, true,
|
||||
String.format("Deleted archived KEK %s from provider after all wrapped keys were rewrapped", oldVersion.getKekLabel()),
|
||||
kmsKey.getId(), ApiCommandResourceType.KmsKey.toString(), CallContext.current().getStartEventId());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to delete archived KEK {} (v{}) from provider: {}",
|
||||
oldVersion.getKekLabel(), oldVersion.getVersionNumber(), e.getMessage());
|
||||
ActionEventUtils.onCompletedActionEvent(CallContext.current().getCallingUserId(),
|
||||
kmsKey.getAccountId(), EventVO.LEVEL_WARN, EventTypes.EVENT_KMS_KEY_DELETE, true,
|
||||
String.format("Failed to delete archived KEK %s from provider: %s", oldVersion.getKekLabel(), e.getMessage()),
|
||||
kmsKey.getId(), ApiCommandResourceType.KmsKey.toString(), CallContext.current().getStartEventId());
|
||||
}
|
||||
|
||||
return;
|
||||
|
|
@ -1549,6 +1589,11 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
wrappedKeyVO.setKekVersionId(newVersion.getId());
|
||||
wrappedKeyVO.setWrappedBlob(newWrapped.getWrappedKeyMaterial());
|
||||
kmsWrappedKeyDao.update(wrappedKeyVO.getId(), wrappedKeyVO);
|
||||
|
||||
ActionEventUtils.onCompletedActionEvent(CallContext.current().getCallingUserId(), kmsKey.getAccountId(),
|
||||
EventVO.LEVEL_INFO, EventTypes.EVENT_KMS_KEY_WRAP, true,
|
||||
String.format("Rewrapped %s key (wrapped key uuid: %s) with new KEK version %d", kmsKey.getPurpose().getName(), wrappedKeyVO.getUuid(), newVersion.getVersionNumber()),
|
||||
kmsKey.getId(), ApiCommandResourceType.KmsKey.toString(), CallContext.current().getStartEventId());
|
||||
} finally {
|
||||
if (dek != null) {
|
||||
Arrays.fill(dek, (byte) 0);
|
||||
|
|
@ -1592,7 +1637,7 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
cmdList.add(DeleteKMSKeyCmd.class);
|
||||
cmdList.add(RotateKMSKeyCmd.class);
|
||||
cmdList.add(MigrateVolumesToKMSCmd.class);
|
||||
cmdList.add(AddHSMProfileCmd.class);
|
||||
cmdList.add(CreateHSMProfileCmd.class);
|
||||
cmdList.add(ListHSMProfilesCmd.class);
|
||||
cmdList.add(UpdateHSMProfileCmd.class);
|
||||
cmdList.add(DeleteHSMProfileCmd.class);
|
||||
|
|
|
|||
|
|
@ -220,7 +220,7 @@ public class KMSManagerImplAccessTest {
|
|||
@Test(expected = PermissionDeniedException.class)
|
||||
public void testCheckHSMProfileAccess_DeniesNonRootModifyOfSystemProfile() {
|
||||
HSMProfileVO profile = mock(HSMProfileVO.class);
|
||||
when(profile.isSystem()).thenReturn(true);
|
||||
when(profile.getIsPublic()).thenReturn(true);
|
||||
|
||||
Account caller = mock(Account.class);
|
||||
when(caller.getId()).thenReturn(1L);
|
||||
|
|
@ -232,7 +232,7 @@ public class KMSManagerImplAccessTest {
|
|||
@Test
|
||||
public void testCheckHSMProfileAccess_AllowsRootModifyOfSystemProfile() {
|
||||
HSMProfileVO profile = mock(HSMProfileVO.class);
|
||||
when(profile.isSystem()).thenReturn(true);
|
||||
when(profile.getIsPublic()).thenReturn(true);
|
||||
|
||||
Account caller = mock(Account.class);
|
||||
when(caller.getId()).thenReturn(1L);
|
||||
|
|
@ -244,7 +244,7 @@ public class KMSManagerImplAccessTest {
|
|||
@Test
|
||||
public void testCheckHSMProfileAccess_AllowsReadAccessToSystemProfileForAllUsers() {
|
||||
HSMProfileVO profile = mock(HSMProfileVO.class);
|
||||
when(profile.isSystem()).thenReturn(true);
|
||||
when(profile.getIsPublic()).thenReturn(true);
|
||||
|
||||
kmsManager.checkHSMProfileAccess(mock(Account.class), profile, false);
|
||||
}
|
||||
|
|
@ -252,7 +252,7 @@ public class KMSManagerImplAccessTest {
|
|||
@Test
|
||||
public void testCheckHSMProfileAccess_DelegatesToAclForOwnedProfile() {
|
||||
HSMProfileVO profile = mock(HSMProfileVO.class);
|
||||
when(profile.isSystem()).thenReturn(false);
|
||||
when(profile.getIsPublic()).thenReturn(false);
|
||||
|
||||
kmsManager.checkHSMProfileAccess(mock(Account.class), profile, true);
|
||||
}
|
||||
|
|
@ -260,7 +260,7 @@ public class KMSManagerImplAccessTest {
|
|||
@Test(expected = PermissionDeniedException.class)
|
||||
public void testCheckHSMProfileAccess_ThrowsWhenAclDeniesOwnedProfile() {
|
||||
HSMProfileVO profile = mock(HSMProfileVO.class);
|
||||
when(profile.isSystem()).thenReturn(false);
|
||||
when(profile.getIsPublic()).thenReturn(false);
|
||||
|
||||
Account caller = mock(Account.class);
|
||||
doThrow(new PermissionDeniedException("denied"))
|
||||
|
|
|
|||
|
|
@ -56,8 +56,8 @@ known_categories = {
|
|||
'HypervisorGuestOsNames': 'Guest OS',
|
||||
'Domain': 'Domain',
|
||||
'Template': 'Template',
|
||||
'KMS': 'KMS',
|
||||
'HSM': 'KMS',
|
||||
'KMS': 'Key Management System',
|
||||
'HSM': 'Key Management System',
|
||||
'Iso': 'ISO',
|
||||
'Volume': 'Volume',
|
||||
'Vlan': 'VLAN',
|
||||
|
|
|
|||
|
|
@ -2515,7 +2515,6 @@
|
|||
"label.suspend.project": "Suspend Project",
|
||||
"label.switch.type": "Switch type",
|
||||
"label.sync.storage": "Sync Storage Pool",
|
||||
"label.system": "System",
|
||||
"label.system.ip.pool": "System Pool",
|
||||
"label.system.offering": "System Offering",
|
||||
"label.system.offerings": "System Offerings",
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ export default {
|
|||
api: 'createKMSKey',
|
||||
icon: 'plus-outlined',
|
||||
label: 'label.create.kms.key',
|
||||
docHelp: 'adminguide/kms.html#creating-a-kms-key',
|
||||
listView: true,
|
||||
popup: true,
|
||||
dataView: false,
|
||||
|
|
@ -89,7 +90,6 @@ export default {
|
|||
{
|
||||
api: 'updateKMSKey',
|
||||
icon: 'edit-outlined',
|
||||
docHelp: 'adminguide/storage.html#lifecycle-operations',
|
||||
label: 'label.update.kms.key',
|
||||
dataView: true,
|
||||
popup: true,
|
||||
|
|
@ -103,7 +103,7 @@ export default {
|
|||
{
|
||||
api: 'rotateKMSKey',
|
||||
icon: 'sync-outlined',
|
||||
docHelp: 'adminguide/storage.html#lifecycle-operations',
|
||||
docHelp: 'adminguide/kms.html#rotating-a-kms-key',
|
||||
label: 'label.rotate.kms.key',
|
||||
dataView: true,
|
||||
popup: true,
|
||||
|
|
@ -117,7 +117,7 @@ export default {
|
|||
{
|
||||
api: 'migrateVolumesToKMS',
|
||||
icon: 'swap-outlined',
|
||||
docHelp: 'adminguide/storage.html#lifecycle-operations',
|
||||
docHelp: 'adminguide/kms.html#migrating-existing-volumes-to-kms',
|
||||
label: 'label.migrate.volumes.to.kms',
|
||||
message: 'message.action.migrate.volumes.to.kms',
|
||||
dataView: true,
|
||||
|
|
@ -141,7 +141,6 @@ export default {
|
|||
{
|
||||
api: 'deleteKMSKey',
|
||||
icon: 'delete-outlined',
|
||||
docHelp: 'adminguide/storage.html#lifecycle-operations',
|
||||
label: 'label.delete.kms.key',
|
||||
message: 'message.action.delete.kms.key',
|
||||
dataView: true,
|
||||
|
|
@ -204,8 +203,9 @@ export default {
|
|||
},
|
||||
actions: [
|
||||
{
|
||||
api: 'addHSMProfile',
|
||||
api: 'createHSMProfile',
|
||||
icon: 'plus-outlined',
|
||||
docHelp: 'adminguide/kms.html#adding-an-hsm-profile',
|
||||
label: 'label.create.hsmprofile',
|
||||
listView: true,
|
||||
popup: true,
|
||||
|
|
@ -227,7 +227,6 @@ export default {
|
|||
{
|
||||
api: 'updateHSMProfile',
|
||||
icon: 'edit-outlined',
|
||||
docHelp: 'adminguide/storage.html#lifecycle-operations',
|
||||
label: 'label.update.hsm.profile',
|
||||
dataView: true,
|
||||
popup: true,
|
||||
|
|
@ -244,7 +243,6 @@ export default {
|
|||
{
|
||||
api: 'deleteHSMProfile',
|
||||
icon: 'delete-outlined',
|
||||
docHelp: 'adminguide/storage.html#lifecycle-operations',
|
||||
label: 'label.delete.hsm.profile',
|
||||
message: 'message.action.delete.hsm.profile',
|
||||
dataView: true,
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ export default {
|
|||
{
|
||||
api: 'migrateVolumesToKMS',
|
||||
icon: 'lock-outlined',
|
||||
docHelp: 'adminguide/storage.html#lifecycle-operations',
|
||||
docHelp: 'adminguide/kms.html#migrating-existing-volumes-to-kms',
|
||||
label: 'label.migrate.volume.to.kms',
|
||||
message: 'message.action.migrate.volume.to.kms',
|
||||
dataView: true,
|
||||
|
|
|
|||
Loading…
Reference in New Issue